-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Fix ICE for user-defined value type in external library function #16509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
cameel marked this conversation as resolved.
|
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In addition to a semantic test we also need one that shows what the new signatures look like so that we can evaluate how the change impacts the ABI. Especially how they differ between libraries and contracts and between memory and storage arguments. ABI has very strong backwards-compatibility guarantees, so if we get it wrong, we'll be stuck with mess that we won't be able to fix easily. Here's one I just used to investigate this manually. It should be added to contract C {}
interface I {}
type U is uint;
struct S { U x; E y; C c; I i; }
enum E {A, B, C}
library LMap {
function getUDVTMapping(mapping(U => U) storage) external {}
function getEnumMapping(mapping(E => E) storage) external {}
function getStructMapping(mapping(uint => S) storage) external {}
function getContractMapping(mapping(C => I) storage) external {}
}
library LStor {
function getUDVT(U[] storage) external {}
function getBytes(bytes[] storage) external {}
function getStruct(S[] storage) external {}
function getEnum(E[] storage) external {}
function getContract(C[] storage) external {}
function getInterface(I[] storage) external {}
function getFunction(function(U, S memory, E) external[] storage) external view {}
}
library LMem {
function getUDVT(U[] memory) external {}
function getBytes(bytes[] memory) external {}
function getStruct(S[] memory) external {}
function getEnum(E[] memory) external {}
function getContract(C[] memory) external {}
function getInterface(I[] memory) external {}
function getFunction(function(U, S memory, E) external[] memory) external view {}
}
contract CMem {
function getUDVT(U[] memory) external {}
function getBytes(bytes[] memory) external {}
function getStruct(S[] memory) external {}
function getEnum(E[] memory) external {}
function getContract(C[] memory) external {}
function getInterface(I[] memory) external {}
function getFunction(function(U, S memory, E) external[] memory) external view {}
}Here's the relevant part of the output of Would be good to also add a similar one for scalar types (without mappings/arrays).
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the output, there are some irregularities. It's odd that we decided to use the underlying type for UDVTs in libraries. We do not do this with any other user-defined type. Enums, contracts, interfaces, structs all use the user-provided name. I wonder if this was even intentional. It contradicts what the documentation says about value types (Function Signatures and Selectors in Libraries):
It looks like a bug to me, but it's too late to fix it now. We have to live with it. Could you correct that in the documentation? After you do that, it would be best to explicitly mention that UDVTs behave differently.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The output also shows that your fix is incomplete. The types are not being unwrapped for mappings. That's probably because the assumption that user-provided name is used is hard-coded in multiple places. I suspect we'll need to special-case it for mappings. We should also think if there are cases other than mappings where it could happen.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test names should be more specific so that you can find what you're looking for without having to look inside. We have so many of them that it's becoming unmanageable otherwise.
For completeness, the test should also cover enums, contracts and interfaces. Or it could be a separate test, but then please call this one |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // Used to cause ICE in signatureInExternalFunction. | ||
| type MyUint is uint; | ||
| type MyAddr is address; | ||
| type MyBytes1 is bytes1; | ||
|
|
||
| struct S { MyUint x; } | ||
|
|
||
| library L { | ||
| function getArray(MyUint[] storage _a, uint _i) external view returns (MyUint) { | ||
| return _a[_i]; | ||
| } | ||
| function getNestedArray(MyUint[][] storage _a, uint _i, uint _j) external view returns (MyUint) { | ||
| return _a[_i][_j]; | ||
| } | ||
| function getMappingByValue(mapping(uint => MyAddr) storage _m, uint _k) external view returns (MyAddr) { | ||
| return _m[_k]; | ||
| } | ||
| function getMappingByKey(mapping(MyUint => uint) storage _m, MyUint _k) external view returns (uint) { | ||
| return _m[_k]; | ||
| } | ||
| function getStruct(S[] storage _s, uint _i) external view returns (MyUint) { | ||
| return _s[_i].x; | ||
| } | ||
| function getBytes1Array(MyBytes1[] storage _a, uint _i) external view returns (MyBytes1) { | ||
| return _a[_i]; | ||
| } | ||
| } | ||
|
|
||
| contract C { | ||
| MyUint[] uintArr; | ||
| MyUint[][] nestedArr; | ||
| mapping(uint => MyAddr) addrMap; | ||
| mapping(MyUint => uint) keyMap; | ||
| S[] structArr; | ||
| MyBytes1[] bytes1Arr; | ||
|
|
||
| function testArray() public returns (uint) { | ||
| uintArr.push(MyUint.wrap(42)); | ||
| return MyUint.unwrap(L.getArray(uintArr, 0)); | ||
| } | ||
| function testNestedArray() public returns (uint) { | ||
| nestedArr.push(); | ||
| nestedArr[0].push(MyUint.wrap(7)); | ||
| return MyUint.unwrap(L.getNestedArray(nestedArr, 0, 0)); | ||
| } | ||
| function testMappingByValue() public returns (address) { | ||
| addrMap[1] = MyAddr.wrap(address(0xBEEF)); | ||
| return MyAddr.unwrap(L.getMappingByValue(addrMap, 1)); | ||
| } | ||
| function testMappingByKey() public returns (uint) { | ||
| keyMap[MyUint.wrap(5)] = 123; | ||
| return L.getMappingByKey(keyMap, MyUint.wrap(5)); | ||
| } | ||
| function testStruct() public returns (uint) { | ||
| structArr.push(S(MyUint.wrap(99))); | ||
| return MyUint.unwrap(L.getStruct(structArr, 0)); | ||
| } | ||
| function testBytes1Array() public returns (bytes1) { | ||
| bytes1Arr.push(MyBytes1.wrap(0xab)); | ||
| return MyBytes1.unwrap(L.getBytes1Array(bytes1Arr, 0)); | ||
| } | ||
| } | ||
| // ---- | ||
| // library: L | ||
| // testArray() -> 42 | ||
| // testNestedArray() -> 7 | ||
| // testMappingByValue() -> 0xbeef | ||
| // testMappingByKey() -> 123 | ||
| // testStruct() -> 99 | ||
| // testBytes1Array() -> 0xab00000000000000000000000000000000000000000000000000000000000000 |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return parameters were unaffected since they are not a part of the signature, right?