Skip to content

Commit

Permalink
Memory-Safe Proxy Code Without Allocation (#67)
Browse files Browse the repository at this point in the history
This PR modifies the account fallback code to no longer use allocations,
as they are not required for memory-safety. [From the
docs](https://docs.soliditylang.org/en/v0.8.22/assembly.html#memory-safety):

> In particular, a memory-safe assembly block may only access the
following memory ranges:
>
> * [...]
> * Temporary memory that is located after the value of the free memory
pointer at the beginning of the assembly block, i.e. memory that is
“allocated” at the free memory pointer **without updating the free
memory pointer**.
>
> [...]
>
> On the other hand, **the following code _is_ memory safe**, because
memory beyond the location pointed to by the free memory pointer can
safely be used as temporary scratch space:
>
> ```solidity
> assembly ("memory-safe") {
>   let p := mload(0x40)
>   returndatacopy(p, 0, returndatasize())
>   revert(p, returndatasize())
> }
> ```

I also [did some
investigation](safe-global/safe-smart-account#544 (comment))
and found that when variables are moved into memory, the space gets
reserved **before** user code starts, meaning that the free memory
pointer already accounts for the reserved space (i.e. setting variables
cannot write past the free memory pointer ever). With that in mind, we
do not need to update the free memory pointer when we write past it for
scratch space.

cc @mmv08
  • Loading branch information
nlordell authored Dec 14, 2023
1 parent ce2b76f commit 2fe7273
Showing 1 changed file with 7 additions and 14 deletions.
21 changes: 7 additions & 14 deletions accounts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,33 +61,26 @@ An example implementation of such a `fallback` function is:
bytes32 slot = SAFE_CORE_PROTOCOL_MANAGER_SLOT;
/// @solidity memory-safe-assembly
assembly {
function allocate(length) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, length))
}
let protocol := sload(slot)
if iszero(protocol) {
return(0, 0)
}
let calldataPtr := allocate(calldatasize())
calldatacopy(calldataPtr, 0, calldatasize())
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
// The msg.sender address is shifted to the left by 12 bytes to remove the padding
// Then the address without padding is stored right after the calldata
let senderPtr := allocate(20)
mstore(senderPtr, shl(96, caller()))
mstore(add(ptr, calldatasize()), shl(96, caller()))
// Add 20 bytes for the address appended add the end
let success := call(gas(), protocol, 0, calldataPtr, add(calldatasize(), 20), 0, 0)
let success := call(gas(), protocol, 0, ptr, add(calldatasize(), 20), 0, 0)
let returnDataPtr := allocate(returndatasize())
returndatacopy(returnDataPtr, 0, returndatasize())
returndatacopy(ptr, 0, returndatasize())
if iszero(success) {
revert(returnDataPtr, returndatasize())
revert(ptr, returndatasize())
}
return(returnDataPtr, returndatasize())
return(ptr, returndatasize())
}
}
```
Expand Down

0 comments on commit 2fe7273

Please sign in to comment.