Skip to content

Implement LowLevelCall library #5094

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

Open
wants to merge 62 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
5dfb2b3
Implement LowLevelCall library
ernestognw Jun 20, 2024
e8e8438
Add comments to Memory library
ernestognw Jun 20, 2024
8e1cc9c
Update names
ernestognw Jun 22, 2024
7c7be9a
Update name
ernestognw Jun 22, 2024
4ad1b23
Apply suggestions from code review
ernestognw Jun 25, 2024
dfe5cc1
Merge branch 'master' into feature/low-level-call
ernestognw Jun 25, 2024
5240323
Add LowLevelCall to SafeERC20
ernestognw Jun 25, 2024
58b4a96
Add value versions to call
ernestognw Jun 25, 2024
bbb6aa1
Add documentation
ernestognw Jun 26, 2024
044fbbe
Add changeset
ernestognw Jun 26, 2024
85ce078
Add LowLevelCall tests
ernestognw Jun 26, 2024
bb1a555
Add tests to memory
ernestognw Jun 26, 2024
cf31c38
Add missing check
ernestognw Jun 26, 2024
7d4196b
Add to stateless
ernestognw Jun 27, 2024
0323b38
Try to fix tests
ernestognw Jun 27, 2024
9331c0d
Merge branch 'master' into feature/low-level-call
ernestognw Jun 27, 2024
28889bd
Rollback
ernestognw Jun 27, 2024
39edc84
FV for Memory
ernestognw Jun 27, 2024
a5918de
Simplify
ernestognw Jun 27, 2024
29e0c7a
Simplify
ernestognw Jun 27, 2024
652df3f
Fix coverage
ernestognw Jun 28, 2024
59c2f87
Merge branch 'master' into feature/low-level-call
ernestognw Sep 2, 2024
9eb5f1c
Add memory utils
ernestognw Sep 4, 2024
60d33d4
Move memory tests
ernestognw Sep 4, 2024
2d397f4
Fix tests upgradeable
ernestognw Sep 4, 2024
2a0fb7e
Add docs
ernestognw Sep 5, 2024
a7e61c3
Make use of the library
ernestognw Sep 5, 2024
1aae8bb
Update docs/modules/ROOT/pages/utilities.adoc
ernestognw Oct 9, 2024
1b2679a
Merge branch 'master' into utils/memory
Amxx Mar 6, 2025
d514606
fix tests
Amxx Mar 6, 2025
14fa04e
Update contracts/utils/Memory.sol
ernestognw May 7, 2025
d0d55fc
Update contracts/utils/Memory.sol
arr00 May 7, 2025
608e3cd
Merge branch 'master' into utils/memory
ernestognw Jun 8, 2025
513f8be
up
ernestognw Jun 8, 2025
e38691d
Enhance LowLevelCall and Memory utils and usage
ernestognw Jun 8, 2025
ac92bb4
up
ernestognw Jun 8, 2025
6094bb7
Merge branch 'master' into utils/memory
ernestognw Jun 8, 2025
6bb96d5
WIP: Add more Memory functions
ernestognw Jun 8, 2025
860e5a8
up
ernestognw Jun 8, 2025
ecdb768
revert
ernestognw Jun 8, 2025
95907aa
Update docs
ernestognw Jun 8, 2025
124ccee
Nit
ernestognw Jun 8, 2025
c3237df
Finish fuzz tests and FV
ernestognw Jun 9, 2025
444ce03
Merge branch 'utils/memory' into feature/low-level-call
ernestognw Jun 9, 2025
27f0a9b
up
ernestognw Jun 9, 2025
e7f35cc
Merge branch 'utils/memory' into feature/low-level-call
ernestognw Jun 9, 2025
848fc06
up
ernestognw Jun 9, 2025
a213518
up
ernestognw Jun 9, 2025
4182f32
Use LowLevelCall more
ernestognw Jun 9, 2025
5230b2c
Merge branch 'master' into feature/low-level-call
Amxx Jul 9, 2025
ad16f66
Update following team discussion
Amxx Jul 9, 2025
d1d6412
update
Amxx Jul 9, 2025
716cd3f
update
Amxx Jul 9, 2025
cdd58c1
Merge branch 'master' into feature/low-level-call
Amxx Jul 10, 2025
b7ce6dd
cleanup and testing
Amxx Jul 10, 2025
2a4cc06
simplify
Amxx Jul 10, 2025
a05e964
Update Create2.sol
Amxx Jul 10, 2025
45e8d66
Update SignatureChecker.sol
Amxx Jul 10, 2025
0a3b2cc
Update utilities.adoc
Amxx Jul 10, 2025
b547cf5
fix testing logic
Amxx Jul 10, 2025
cb233b9
Merge branch 'master' into feature/low-level-call
Amxx Jul 14, 2025
51a3c50
cleanup
Amxx Jul 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dull-students-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Memory`: Add library with utilities to manipulate memory
5 changes: 5 additions & 0 deletions .changeset/sharp-scissors-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`LowLevelCall`: Add a library to perform low-level calls and deal with the `returndata` more granularly.
4 changes: 2 additions & 2 deletions contracts/account/Account.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.20;
import {PackedUserOperation, IAccount, IEntryPoint} from "../interfaces/draft-IERC4337.sol";
import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol";
import {AbstractSigner} from "../utils/cryptography/signers/AbstractSigner.sol";
import {LowLevelCall} from "../utils/LowLevelCall.sol";

/**
* @dev A simple ERC4337 account implementation. This base implementation only includes the minimal logic to process
Expand Down Expand Up @@ -112,8 +113,7 @@ abstract contract Account is AbstractSigner, IAccount {
*/
function _payPrefund(uint256 missingAccountFunds) internal virtual {
if (missingAccountFunds > 0) {
(bool success, ) = payable(msg.sender).call{value: missingAccountFunds}("");
success; // Silence warning. The entrypoint should validate the result.
LowLevelCall.callRaw(msg.sender, "", missingAccountFunds); // The entrypoint should validate the result.
}
}

Expand Down
8 changes: 5 additions & 3 deletions contracts/account/extensions/AccountERC7579.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {IERC1271} from "../../interfaces/IERC1271.sol";
import {IERC7579Module, IERC7579Validator, IERC7579Execution, IERC7579AccountConfig, IERC7579ModuleConfig, MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK} from "../../interfaces/draft-IERC7579.sol";
import {ERC7579Utils, Mode, CallType, ExecType} from "../../account/utils/draft-ERC7579Utils.sol";
import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol";
import {LowLevelCall} from "../../utils/LowLevelCall.sol";
import {Memory} from "../../utils/Memory.sol";
import {Bytes} from "../../utils/Bytes.sol";
import {Packing} from "../../utils/Packing.sol";
import {Address} from "../../utils/Address.sol";
Expand Down Expand Up @@ -50,6 +52,8 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
using ERC7579Utils for *;
using EnumerableSet for *;
using Packing for bytes32;
using LowLevelCall for *;
using Memory for *;

EnumerableSet.AddressSet private _validators;
EnumerableSet.AddressSet private _executors;
Expand Down Expand Up @@ -311,9 +315,7 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75

if (success) return returndata;

assembly ("memory-safe") {
revert(add(returndata, 0x20), mload(returndata))
}
returndata.asPointer().addOffset(0x20).asBytes().bubbleRevert();
}

/// @dev Returns the fallback handler for the given selector. Returns `address(0)` if not installed.
Expand Down
10 changes: 10 additions & 0 deletions contracts/mocks/CallReceiverMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ contract CallReceiverMock {
return "0x1234";
}

function mockFunctionWithArgsReturn(uint256 a, uint256 b) public payable returns (uint256, uint256) {
emit MockFunctionCalledWithArgs(a, b);

return (a, b);
}

function mockFunctionNonPayable() public returns (string memory) {
emit MockFunctionCalled();

Expand All @@ -35,6 +41,10 @@ contract CallReceiverMock {
return "0x1234";
}

function mockStaticFunctionWithArgsReturn(uint256 a, uint256 b) public pure returns (uint256, uint256) {
return (a, b);
}

function mockFunctionRevertsNoReason() public payable {
revert();
}
Expand Down
2 changes: 2 additions & 0 deletions contracts/mocks/Stateless.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ import {EnumerableSet} from "../utils/structs/EnumerableSet.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol";
import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";
import {LowLevelCall} from "../utils/LowLevelCall.sol";
import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol";
import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
import {ERC4337Utils} from "../account/utils/draft-ERC4337Utils.sol";
import {ERC7579Utils} from "../account/utils/draft-ERC7579Utils.sol";
import {Heap} from "../utils/structs/Heap.sol";
import {Math} from "../utils/math/Math.sol";
import {Memory} from "../utils/Memory.sol";
import {MerkleProof} from "../utils/cryptography/MerkleProof.sol";
import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol";
import {Nonces} from "../utils/Nonces.sol";
Expand Down
7 changes: 6 additions & 1 deletion contracts/proxy/transparent/ProxyAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.22;

import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol";
import {Ownable} from "../../access/Ownable.sol";
import {LowLevelCall} from "../../utils/LowLevelCall.sol";

/**
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
Expand Down Expand Up @@ -40,6 +41,10 @@ contract ProxyAdmin is Ownable {
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
LowLevelCall.callRaw(
address(proxy),
abi.encodeCall(ITransparentUpgradeableProxy.upgradeToAndCall, (implementation, data)),
msg.value
);
}
}
8 changes: 5 additions & 3 deletions contracts/token/ERC20/extensions/ERC4626.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.20;
import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol";
import {SafeERC20} from "../utils/SafeERC20.sol";
import {IERC4626} from "../../../interfaces/IERC4626.sol";
import {LowLevelCall} from "../../../utils/LowLevelCall.sol";
import {Math} from "../../../utils/math/Math.sol";

/**
Expand Down Expand Up @@ -84,11 +85,12 @@ 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 ok, uint8 assetDecimals) {
(bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
(bool success, bytes32 encodedDecimals) = LowLevelCall.staticcallReturnBytes32(
address(asset_),
abi.encodeCall(IERC20Metadata.decimals, ())
);
if (success && encodedDecimals.length >= 32) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (success && LowLevelCall.returnDataSize() >= 32) {
uint256 returnedDecimals = uint256(encodedDecimals);
if (returnedDecimals <= type(uint8).max) {
return (true, uint8(returnedDecimals));
}
Expand Down
41 changes: 18 additions & 23 deletions contracts/token/ERC20/utils/SafeERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
import {Address} from "../../../utils/Address.sol";
import {LowLevelCall} from "../../../utils/LowLevelCall.sol";
import {Memory} from "../../../utils/Memory.sol";

/**
* @title SafeERC20
Expand All @@ -31,15 +34,19 @@ library SafeERC20 {
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
Memory.Pointer ptr = Memory.getFreePointer();
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
Memory.setFreePointer(ptr);
}

/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
Memory.Pointer ptr = Memory.getFreePointer();
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
Memory.setFreePointer(ptr);
}

/**
Expand Down Expand Up @@ -99,12 +106,13 @@ library SafeERC20 {
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
Memory.Pointer ptr = Memory.getFreePointer();
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
Memory.setFreePointer(ptr);
}

/**
Expand Down Expand Up @@ -171,21 +179,14 @@ library SafeERC20 {
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
(bool success, bytes32 returnValue) = LowLevelCall.callReturnBytes32(address(token), data);
uint256 returnSize = LowLevelCall.returnDataSize();

if (!success) {
LowLevelCall.bubbleRevert(data);
}

if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
if (returnSize == 0 ? address(token).code.length == 0 : uint256(returnValue) != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
Expand All @@ -199,14 +200,8 @@ library SafeERC20 {
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
(bool success, bytes32 returnValue) = LowLevelCall.callReturnBytes32(address(token), data);
uint256 returnSize = LowLevelCall.returnDataSize();
return success && (returnSize == 0 ? address(token).code.length > 0 : uint256(returnValue) == 1);
}
}
12 changes: 7 additions & 5 deletions contracts/utils/Address.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";
import {LowLevelCall} from "./LowLevelCall.sol";
import {Memory} from "./Memory.sol";

/**
* @dev Collection of functions related to the address type
*/
library Address {
using Memory for *;

/**
* @dev There's no code at `target` (it is not a contract).
*/
Expand All @@ -35,7 +39,8 @@ library Address {
revert Errors.InsufficientBalance(address(this).balance, amount);
}

(bool success, bytes memory returndata) = recipient.call{value: amount}("");
bool success = LowLevelCall.callRaw(recipient, "", amount);
bytes memory returndata = LowLevelCall.returnData();
if (!success) {
_revert(returndata);
}
Expand Down Expand Up @@ -138,10 +143,7 @@ library Address {
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly ("memory-safe") {
revert(add(returndata, 0x20), mload(returndata))
}
LowLevelCall.bubbleRevert(returndata.asPointer().addOffset(0x20).asBytes32());
} else {
revert Errors.FailedCall();
}
Expand Down
11 changes: 4 additions & 7 deletions contracts/utils/Create2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";
import {LowLevelCall} from "./LowLevelCall.sol";
import {Memory} from "./Memory.sol";

/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
Expand Down Expand Up @@ -43,15 +45,10 @@ library Create2 {
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
if (LowLevelCall.returnDataSize() != 0) LowLevelCall.bubbleRevert(LowLevelCall.returnData());
else revert Errors.FailedDeployment();
}
}

Expand Down
Loading
Loading