-
Notifications
You must be signed in to change notification settings - Fork 11.8k
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
Low level call and memory management #5091
base: master
Are you sure you want to change the base?
Conversation
|
@@ -84,10 +84,10 @@ abstract contract ERC4626 is ERC20, IERC4626 { | |||
* @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. | |||
*/ | |||
function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { |
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.
@ernestognw Can you remember why a try catch was not good enough here ? Is it related to truncating the result (and ignoring high bits from the returned value?)
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.
Isn't it because the return value may revert at decoding? A try IERC20Metadata(address(asset_)).decimals() returns (uint8 value)
would've failed if the token returned an uint256
. Nothing in the ERC20 restricts the return value to uint8
I tried using a library like this one https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/utils/Exec.sol. The result did not really look good to me. I found the use of assembly actually cleaner ... because we automatically copy X bytes of the returndata, without having to call an additional I found that the most valuable thing from the 4337 draft PR was the free memory management, to dealocate memory. |
immediate := gt(mload(0x00), 0) | ||
} | ||
if gt(returndatasize(), 0x3F) { | ||
delay := and(mload(0x20), 0xFFFFFFFF) |
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.
Should this revert if delay is too large ?
delay := and(mload(0x20), 0xFFFFFFFF) | |
delay := mload(0x20) | |
if gt(delay, 0xFFFFFFFF) { revert(0,0) } |
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.
abi.decode
reverts, so I guess we should revert too to keep backwards compatibility
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.
Left some comments, though I've been thinking about the set of "low level" functions we can offer, and I feel there are essentially 3 tricks people would do with this library:
- Calling and ignoring the return data
- Caling and saving the returndata in scratch space
- Calling and saving the returndata on top of another another memory pointer
So, I feel it'd make sense to offer a library with the following functions:
EDIT: Made a PoC at #5094
library LowLevelCall {
function callRaw(address target, uint256 value, bytes memory data) internal returns (bool success) {
assembly ("memory-safe") {
success := call(gas(), target, value, add(data, 0x20), mload(data), 0, 0)
}
}
function callReturnBytes32(
address target,
uint256 value,
bytes memory data
) internal returns (bool success, bytes32 result) {
assembly ("memory-safe") {
success := call(gas(), target, value, add(data, 0x20), mload(data), 0, 0x20)
result := mload(0)
}
}
function callReturnBytes32Tuple(
address target,
uint256 value,
bytes memory data
) internal returns (bool success, bytes32 result1, bytes32 result2) {
assembly ("memory-safe") {
success := call(gas(), target, value, add(data, 0x20), mload(data), 0, 0x40)
result1 := mload(0)
result2 := mload(0x20)
}
}
function callReturnOverride(
address target,
uint256 value,
bytes memory data,
bytes memory resultPtr
) internal returns (bool success) {
// WARNING: Memory safetiness depends on the caller to ensure that writing to `resultPtr` is safe
assembly ("memory-safe") {
let maxSize := mload(resultPtr)
success := call(gas(), target, value, add(data, 0x20), mload(data), resultPtr, maxSize)
}
}
}
The changes for this PR should be doable with these variants so far, I'll iterate this.
* @dev Helper library for deallocating memory reserved by abi.encode or low level calls. | ||
*/ | ||
library Memory { | ||
type FreePtr is bytes32; |
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.
I think this should be just Pointer
, it doesn't have anything to do with the free memory pointer strictly talking (e.g. the library may add functions for dealing with pointers)
type FreePtr is bytes32; | |
type Pointer is bytes32; |
library Memory { | ||
type FreePtr is bytes32; | ||
|
||
function save() internal pure returns (FreePtr ptr) { |
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.
Similarly, I think this should be very explicit, otherwise reading Memory.save()
doesn't tell much.
function save() internal pure returns (FreePtr ptr) { | |
function saveFreePointer() internal pure returns (Pointer ptr) { |
} | ||
} | ||
|
||
function load(FreePtr ptr) internal pure { |
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.
Accordingly
function load(FreePtr ptr) internal pure { | |
function loadFreePointer(Pointer ptr) internal pure { |
Fixes #5013
PR Checklist
npx changeset add
)