Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
66 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
d79627b
consistency
Amxx Aug 22, 2025
0227ab4
memory-safety
Amxx Aug 22, 2025
bd60fec
Merge branch 'master' into feature/low-level-call
Amxx Aug 27, 2025
6a74179
deprecate + unit test for verifyCallResultFromTarget
Amxx Aug 27, 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
25 changes: 16 additions & 9 deletions contracts/access/manager/AuthorityUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
pragma solidity ^0.8.20;

import {IAuthority} from "./IAuthority.sol";
import {Memory} from "../../utils/Memory.sol";
import {LowLevelCall} from "../../utils/LowLevelCall.sol";

library AuthorityUtils {
/**
Expand All @@ -17,16 +19,21 @@ library AuthorityUtils {
address target,
bytes4 selector
) internal view returns (bool immediate, uint32 delay) {
(bool success, bytes memory data) = authority.staticcall(
abi.encodeCall(IAuthority.canCall, (caller, target, selector))
Memory.Pointer ptr = Memory.saveFreePointer();
bytes memory params = abi.encodeCall(IAuthority.canCall, (caller, target, selector));
(bool success, bytes32 immediateWord, bytes32 delayWord) = LowLevelCall.staticcallReturnScratchBytes32Pair(
authority,
params
);
if (success) {
if (data.length >= 0x40) {
(immediate, delay) = abi.decode(data, (bool, uint32));
} else if (data.length >= 0x20) {
immediate = abi.decode(data, (bool));
}
Memory.loadFreePointer(ptr);

if (!success) {
return (false, 0);
}
return (immediate, delay);

return (
uint256(immediateWord) != 0,
uint32(uint256(delayWord)) // Intentional overflow to truncate the higher 224 bits
);
}
}
30 changes: 14 additions & 16 deletions contracts/token/ERC20/extensions/ERC4626.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol";
import {SafeERC20} from "../utils/SafeERC20.sol";
import {IERC4626} from "../../../interfaces/IERC4626.sol";
import {Math} from "../../../utils/math/Math.sol";
import {Memory} from "../../../utils/Memory.sol";
import {LowLevelCall} from "../../../utils/LowLevelCall.sol";

/**
* @dev Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in
Expand Down Expand Up @@ -75,25 +77,21 @@ abstract contract ERC4626 is ERC20, IERC4626 {
* @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC-20 or ERC-777).
*/
constructor(IERC20 asset_) {
(bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
_underlyingDecimals = success ? assetDecimals : 18;
_underlyingDecimals = _tryGetAssetDecimalsWithFallback(asset_, 18);
_asset = asset_;
}

/**
* @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) {
(bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
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);
function _tryGetAssetDecimalsWithFallback(IERC20 asset_, uint8 defaultValue) private view returns (uint8) {
Memory.Pointer ptr = Memory.saveFreePointer();
bytes memory params = abi.encodeCall(IERC20Metadata.decimals, ());

(bool success, bytes32 rawValue) = LowLevelCall.staticcallReturnScratchBytes32(address(asset_), params);
uint256 length = LowLevelCall.returnDataSize();
uint256 value = uint256(rawValue);

Memory.loadFreePointer(ptr);

return uint8(Math.ternary(success && length >= 0x20 && value <= type(uint8).max, value, defaultValue));
}

/**
Expand Down
8 changes: 7 additions & 1 deletion contracts/token/ERC20/utils/SafeERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
import {Address} from "../../../utils/Address.sol";
import {Memory} from "../../../utils/Memory.sol";

/**
* @title SafeERC20
Expand Down Expand Up @@ -34,15 +35,19 @@ library SafeERC20 {
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
Memory.Pointer ptr = Memory.saveFreePointer();
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
Memory.loadFreePointer(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.saveFreePointer();
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
Memory.loadFreePointer(ptr);
}

/**
Expand Down Expand Up @@ -74,12 +79,13 @@ library SafeERC20 {
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
Memory.Pointer ptr = Memory.saveFreePointer();
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.loadFreePointer(ptr);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion 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 @@ -35,7 +36,7 @@ library Address {
revert Errors.InsufficientBalance(address(this).balance, amount);
}

(bool success, ) = recipient.call{value: amount}("");
bool success = LowLevelCall.callRaw(recipient, amount, "");
if (!success) {
revert Errors.FailedCall();
}
Expand Down
99 changes: 99 additions & 0 deletions contracts/utils/LowLevelCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

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

/**
* @dev Library of low level call functions that implement different calling strategies to deal with the return data.
*/
library LowLevelCall {
/// === CALL ===

/// @dev Performs a Solidity function call using a low level `call` and ignoring the return data.
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)
}
}

/// @dev Performs a Solidity function call using a low level `call` and returns the first 32 bytes of the result
/// in the scratch space of memory. Useful for functions that return a single-word value.
///
/// WARNING: Do not assume that the result is zero if `success` is false. Memory can be already allocated
/// and this function doesn't zero it out.
function callReturnScratchBytes32(
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)
}
}

/// @dev Performs a Solidity function call using a low level `call` and returns the first 64 bytes of the result
/// in the scratch space of memory. Useful for functions that return a tuple of single-word values values.
///
/// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
/// and this function doesn't zero it out.
function callReturnScratchBytes32Pair(
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)
}
}

/// === STATICCALL ===

/// @dev Performs a Solidity function call using a low level `staticcall` and ignoring the return data.
function staticcallRaw(address target, bytes memory data) internal view returns (bool success) {
assembly ("memory-safe") {
success := staticcall(gas(), target, add(data, 0x20), mload(data), 0, 0)
}
}

/// @dev Performs a Solidity function call using a low level `staticcall` and returns the first 32 bytes of the result
/// in the scratch space of memory. Useful for functions that return a single-word value.
///
/// WARNING: Do not assume that the result is zero if `success` is false. Memory can be already allocated
/// and this function doesn't zero it out.
function staticcallReturnScratchBytes32(
address target,
bytes memory data
) internal view returns (bool success, bytes32 result) {
assembly ("memory-safe") {
success := staticcall(gas(), target, add(data, 0x20), mload(data), 0, 0x20)
result := mload(0)
}
}

/// @dev Performs a Solidity function call using a low level `staticcall` and returns the first 64 bytes of the result
/// in the scratch space of memory. Useful for functions that return a tuple of single-word values values.
///
/// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
/// and this function doesn't zero it out.
function staticcallReturnScratchBytes32Pair(
address target,
bytes memory data
) internal view returns (bool success, bytes32 result1, bytes32 result2) {
assembly ("memory-safe") {
success := staticcall(gas(), target, add(data, 0x20), mload(data), 0, 0x40)
result1 := mload(0)
result2 := mload(0x20)
}
}

/// @dev Returns the size of the return data buffer.
function returnDataSize() internal pure returns (uint256 size) {
assembly ("memory-safe") {
size := returndatasize()
}
}
}
24 changes: 24 additions & 0 deletions contracts/utils/Memory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

/// @dev Memory utility library.
library Memory {
type Pointer is bytes32;

/// @dev Returns a memory pointer to the current free memory pointer.
function saveFreePointer() internal pure returns (Pointer ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
}
}

/// @dev Sets the free memory pointer to a specific value.
///
/// WARNING: Everything after the pointer may be overwritten.
function loadFreePointer(Pointer ptr) internal pure {
assembly ("memory-safe") {
mstore(0x40, ptr)
}
}
}
17 changes: 11 additions & 6 deletions contracts/utils/cryptography/SignatureChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pragma solidity ^0.8.20;

import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
import {Memory} from "../Memory.sol";
import {LowLevelCall} from "../LowLevelCall.sol";

/**
* @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA
Expand Down Expand Up @@ -40,11 +42,14 @@ library SignatureChecker {
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
);
return (success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
bytes4 magic = IERC1271.isValidSignature.selector;

Memory.Pointer ptr = Memory.saveFreePointer();
bytes memory params = abi.encodeCall(IERC1271.isValidSignature, (hash, signature));
(bool success, bytes32 result) = LowLevelCall.staticcallReturnScratchBytes32(signer, params);
uint256 length = LowLevelCall.returnDataSize();
Memory.loadFreePointer(ptr);

return success && length >= 32 && result == bytes32(magic);
}
}