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 all 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/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.callNoReturn(msg.sender, missingAccountFunds, ""); // The entrypoint should validate the result.
}
}

Expand Down
14 changes: 5 additions & 9 deletions contracts/account/extensions/draft-AccountERC7579.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import {
} 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 {Bytes} from "../../utils/Bytes.sol";
import {Packing} from "../../utils/Packing.sol";
import {Address} from "../../utils/Address.sol";
import {Calldata} from "../../utils/Calldata.sol";
import {Account} from "../Account.sol";

Expand Down Expand Up @@ -314,14 +314,10 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
// From https://eips.ethereum.org/EIPS/eip-7579#fallback[ERC-7579 specifications]:
// - MUST utilize ERC-2771 to add the original msg.sender to the calldata sent to the fallback handler
// - MUST use call to invoke the fallback handler
(bool success, bytes memory returndata) = handler.call{value: msg.value}(
abi.encodePacked(msg.data, msg.sender)
);

if (success) return returndata;

assembly ("memory-safe") {
revert(add(returndata, 0x20), mload(returndata))
if (LowLevelCall.callNoReturn(handler, msg.value, abi.encodePacked(msg.data, msg.sender))) {
return LowLevelCall.returnData();
} else {
LowLevelCall.bubbleRevert();
}
}

Expand Down
42 changes: 35 additions & 7 deletions contracts/mocks/CallReceiverMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,51 @@ contract CallReceiverMock {

function mockFunction() public payable returns (string memory) {
emit MockFunctionCalled();
return "0x1234";
}

function mockFunctionWritesStorage(bytes32 slot, bytes32 value) public returns (string memory) {
assembly ("memory-safe") {
sstore(slot, value)
}
return "0x1234";
}

function mockFunctionEmptyReturn() public payable {
emit MockFunctionCalled();
}

function mockFunctionEmptyReturnWritesStorage(bytes32 slot, bytes32 value) public payable {
assembly ("memory-safe") {
sstore(slot, value)
}
emit MockFunctionCalled();
}

function mockFunctionWithArgs(uint256 a, uint256 b) public payable returns (string memory) {
emit MockFunctionCalledWithArgs(a, b);

return "0x1234";
}

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

function mockFunctionWithArgsReturnWritesStorage(
bytes32 slot,
bytes32 value,
uint256 a,
uint256 b
) public payable returns (uint256, uint256) {
assembly ("memory-safe") {
sstore(slot, value)
}
emit MockFunctionCalledWithArgs(a, b);
return (a, b);
}

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

Expand All @@ -35,6 +66,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 All @@ -53,13 +88,6 @@ contract CallReceiverMock {
}
}

function mockFunctionWritesStorage(bytes32 slot, bytes32 value) public returns (string memory) {
assembly {
sstore(slot, value)
}
return "0x1234";
}

function mockFunctionExtra() public payable {
emit MockFunctionCalledExtra(msg.sender, msg.value);
}
Expand Down
3 changes: 2 additions & 1 deletion contracts/mocks/Stateless.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ import {ERC7913P256Verifier} from "../utils/cryptography/verifiers/ERC7913P256Ve
import {ERC7913RSAVerifier} from "../utils/cryptography/verifiers/ERC7913RSAVerifier.sol";
import {Heap} from "../utils/structs/Heap.sol";
import {InteroperableAddress} from "../utils/draft-InteroperableAddress.sol";
import {LowLevelCall} from "../utils/LowLevelCall.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 All @@ -49,7 +51,6 @@ import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol";
import {SignedMath} from "../utils/math/SignedMath.sol";
import {StorageSlot} from "../utils/StorageSlot.sol";
import {Strings} from "../utils/Strings.sol";
import {Memory} from "../utils/Memory.sol";
import {Time} from "../utils/types/Time.sol";

contract Dummy1234 {}
19 changes: 11 additions & 8 deletions contracts/token/ERC20/extensions/ERC4626.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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 {Memory} from "../../../utils/Memory.sol";
import {Math} from "../../../utils/math/Math.sol";

/**
Expand Down Expand Up @@ -84,16 +86,17 @@ 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(
Memory.Pointer ptr = Memory.getFreeMemoryPointer();
(bool success, bytes32 returnedDecimals, ) = LowLevelCall.staticcallReturn64Bytes(
address(asset_),
abi.encodeCall(IERC20Metadata.decimals, ())
);
if (success && encodedDecimals.length >= 32) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (returnedDecimals <= type(uint8).max) {
return (true, uint8(returnedDecimals));
}
}
return (false, 0);
Memory.setFreeMemoryPointer(ptr);

return
(success && LowLevelCall.returnDataSize() >= 32 && uint256(returnedDecimals) <= type(uint8).max)
? (true, uint8(uint256(returnedDecimals)))
: (false, 0);
}

/**
Expand Down
53 changes: 39 additions & 14 deletions contracts/utils/Address.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pragma solidity ^0.8.20;

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

/**
* @dev Collection of functions related to the address type
Expand Down Expand Up @@ -34,10 +35,13 @@ library Address {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}

(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
if (LowLevelCall.callNoReturn(recipient, amount, "")) {
// call successful, nothing to do
return;
} else if (LowLevelCall.returnDataSize() == 0) {
revert Errors.FailedCall();
} else {
LowLevelCall.bubbleRevert();
}
}

Expand Down Expand Up @@ -76,26 +80,50 @@ library Address {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
bool success = LowLevelCall.callNoReturn(target, value, data);
if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
return LowLevelCall.returnData();
} else if (success) {
revert AddressEmptyCode(target);
} else if (LowLevelCall.returnDataSize() > 0) {
LowLevelCall.bubbleRevert();
} else {
revert Errors.FailedCall();
}
}

/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
bool success = LowLevelCall.staticcallNoReturn(target, data);
if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
return LowLevelCall.returnData();
} else if (success) {
revert AddressEmptyCode(target);
} else if (LowLevelCall.returnDataSize() > 0) {
LowLevelCall.bubbleRevert();
} else {
revert Errors.FailedCall();
}
}

/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
bool success = LowLevelCall.delegatecallNoReturn(target, data);
if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
return LowLevelCall.returnData();
} else if (success) {
revert AddressEmptyCode(target);
} else if (LowLevelCall.returnDataSize() > 0) {
LowLevelCall.bubbleRevert();
} else {
revert Errors.FailedCall();
}
}

/**
Expand Down Expand Up @@ -138,10 +166,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);
} else {
revert Errors.FailedCall();
}
Expand Down
13 changes: 6 additions & 7 deletions contracts/utils/Create2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pragma solidity ^0.8.20;

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

/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
Expand Down Expand Up @@ -43,15 +44,13 @@ 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) {
revert Errors.FailedDeployment();
} else {
LowLevelCall.bubbleRevert();
}
}
}

Expand Down
Loading