From 00a3d9f83c7f40b395a5c0e92a110563c274bdca Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 18 Jun 2024 11:07:27 +0200 Subject: [PATCH 1/5] Limit memory expension --- contracts/access/manager/AuthorityUtils.sol | 22 +++--- contracts/utils/LowLevelCalls.sol | 72 +++++++++++++++++++ contracts/utils/LowLevelMemory.sol | 22 ++++++ .../utils/cryptography/SignatureChecker.sol | 20 ++++-- 4 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 contracts/utils/LowLevelCalls.sol create mode 100644 contracts/utils/LowLevelMemory.sol diff --git a/contracts/access/manager/AuthorityUtils.sol b/contracts/access/manager/AuthorityUtils.sol index fb3018ca805..c6d9a9beaa5 100644 --- a/contracts/access/manager/AuthorityUtils.sol +++ b/contracts/access/manager/AuthorityUtils.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.20; import {IAuthority} from "./IAuthority.sol"; +import {LowLevelCalls} from "../../utils/LowLevelCalls.sol"; +import {LowLevelMemory} from "../../utils/LowLevelMemory.sol"; library AuthorityUtils { /** @@ -17,16 +19,20 @@ 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)) - ); - if (success) { - if (data.length >= 0x40) { - (immediate, delay) = abi.decode(data, (bool, uint32)); - } else if (data.length >= 0x20) { - immediate = abi.decode(data, (bool)); + // snapshot free memory pointer (moved by encodeCall and getReturnDataFixed) + LowLevelMemory.FreePtr ptr = LowLevelMemory.save(); + + if (LowLevelCalls.staticcall(authority, abi.encodeCall(IAuthority.canCall, (caller, target, selector)))) { + if (LowLevelCalls.getReturnDataSize() >= 0x40) { + (immediate, delay) = abi.decode(LowLevelCalls.getReturnDataFixed(0x40), (bool, uint32)); + } else if (LowLevelCalls.getReturnDataSize() >= 0x20) { + immediate = abi.decode(LowLevelCalls.getReturnDataFixed(0x20), (bool)); } } + + // restore free memory pointer to reduce memory leak + LowLevelMemory.load(ptr); + return (immediate, delay); } } diff --git a/contracts/utils/LowLevelCalls.sol b/contracts/utils/LowLevelCalls.sol new file mode 100644 index 00000000000..5757116c2aa --- /dev/null +++ b/contracts/utils/LowLevelCalls.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Math} from "./math/Math.sol"; + +/** + * Utility functions helpful when making different kinds of contract calls in Solidity. + */ +library LowLevelCalls { + function call(address to, uint256 value, bytes memory data) internal returns (bool success) { + return call(to, value, data, gasleft()); + } + + function call(address to, uint256 value, bytes memory data, uint256 txGas) internal returns (bool success) { + assembly ("memory-safe") { + success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) + } + } + + function staticcall(address to, bytes memory data) internal view returns (bool success) { + return staticcall(to, data, gasleft()); + } + + function staticcall(address to, bytes memory data, uint256 txGas) internal view returns (bool success) { + assembly ("memory-safe") { + success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0) + } + } + + function delegateCall(address to, bytes memory data) internal returns (bool success) { + return delegateCall(to, data, gasleft()); + } + + function delegateCall(address to, bytes memory data, uint256 txGas) internal returns (bool success) { + assembly ("memory-safe") { + success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) + } + } + + function getReturnDataSize() internal pure returns (uint256 returnDataSize) { + assembly ("memory-safe") { + returnDataSize := returndatasize() + } + } + + function getReturnData(uint256 maxLen) internal pure returns (bytes memory ptr) { + return getReturnDataFixed(Math.min(maxLen, getReturnDataSize())); + } + + function getReturnDataFixed(uint256 len) internal pure returns (bytes memory ptr) { + assembly ("memory-safe") { + ptr := mload(0x40) + mstore(0x40, add(ptr, add(len, 0x20))) + mstore(ptr, len) + returndatacopy(add(ptr, 0x20), 0, len) + } + } + + function revertWithData(bytes memory returnData) internal pure { + assembly ("memory-safe") { + revert(add(returnData, 0x20), mload(returnData)) + } + } + + function revertWithCode(bytes32 code) internal pure { + assembly ("memory-safe") { + mstore(0, code) + revert(0, 0x20) + } + } +} diff --git a/contracts/utils/LowLevelMemory.sol b/contracts/utils/LowLevelMemory.sol new file mode 100644 index 00000000000..edb752e91e8 --- /dev/null +++ b/contracts/utils/LowLevelMemory.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @dev Helper library packing and unpacking multiple values into bytes32 + */ +library LowLevelMemory { + type FreePtr is bytes32; + + function save() internal pure returns (FreePtr ptr) { + assembly ("memory-safe") { + ptr := mload(0x40) + } + } + + function load(FreePtr ptr) internal pure { + assembly ("memory-safe") { + mstore(0x40, ptr) + } + } +} diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 9aaa2e0716c..07458d04385 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -5,6 +5,8 @@ pragma solidity ^0.8.20; import {ECDSA} from "./ECDSA.sol"; import {IERC1271} from "../../interfaces/IERC1271.sol"; +import {LowLevelCalls} from "../LowLevelCalls.sol"; +import {LowLevelMemory} from "../LowLevelMemory.sol"; /** * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA @@ -40,11 +42,17 @@ 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)); + // snapshot free memory pointer (moved by encodeCall and getReturnDataFixed) + LowLevelMemory.FreePtr ptr = LowLevelMemory.save(); + + bool success = LowLevelCalls.staticcall(signer, abi.encodeCall(IERC1271.isValidSignature, (hash, signature))) && + LowLevelCalls.getReturnDataSize() >= 0x20 && + abi.decode(LowLevelCalls.getReturnDataFixed(0x20), (bytes32)) == + bytes32(IERC1271.isValidSignature.selector); + + // restore free memory pointer to reduce memory leak + LowLevelMemory.load(ptr); + + return success; } } From 9b8a084d4acc3884207d6ae7488cb7dcf3337008 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 18 Jun 2024 11:21:37 +0200 Subject: [PATCH 2/5] up --- contracts/token/ERC20/extensions/ERC4626.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index c71b14ad48c..4d04a54d9e4 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -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) { - (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( - abi.encodeCall(IERC20Metadata.decimals, ()) - ); - if (success && encodedDecimals.length >= 32) { + bool success = LowLevelCalls.staticcall(asset_, abi.encodeCall(IERC20Metadata.decimals, ())) && + LowLevelCalls.getReturnDataSize() >= 0x20; + + if (success) { uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); if (returnedDecimals <= type(uint8).max) { return (true, uint8(returnedDecimals)); From 19f38214389a7c4cea4410f41b3537ac6d71212b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 18 Jun 2024 11:31:27 +0200 Subject: [PATCH 3/5] fix --- contracts/token/ERC20/extensions/ERC4626.sol | 5 +++-- contracts/utils/LowLevelCalls.sol | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 4d04a54d9e4..e66c45f587a 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -7,6 +7,7 @@ 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 {LowLevelCalls} from "../../../utils/LowLevelCalls.sol"; /** * @dev Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in @@ -84,11 +85,11 @@ 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) { - bool success = LowLevelCalls.staticcall(asset_, abi.encodeCall(IERC20Metadata.decimals, ())) && + bool success = LowLevelCalls.staticcall(address(asset_), abi.encodeCall(IERC20Metadata.decimals, ())) && LowLevelCalls.getReturnDataSize() >= 0x20; if (success) { - uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + uint256 returnedDecimals = abi.decode(LowLevelCalls.getReturnDataFixed(0x20), (uint256)); if (returnedDecimals <= type(uint8).max) { return (true, uint8(returnedDecimals)); } diff --git a/contracts/utils/LowLevelCalls.sol b/contracts/utils/LowLevelCalls.sol index 5757116c2aa..945fba89276 100644 --- a/contracts/utils/LowLevelCalls.sol +++ b/contracts/utils/LowLevelCalls.sol @@ -9,7 +9,7 @@ import {Math} from "./math/Math.sol"; */ library LowLevelCalls { function call(address to, uint256 value, bytes memory data) internal returns (bool success) { - return call(to, value, data, gasleft()); + return call(to, value, data, type(uint256).max); } function call(address to, uint256 value, bytes memory data, uint256 txGas) internal returns (bool success) { @@ -19,7 +19,7 @@ library LowLevelCalls { } function staticcall(address to, bytes memory data) internal view returns (bool success) { - return staticcall(to, data, gasleft()); + return staticcall(to, data, type(uint256).max); } function staticcall(address to, bytes memory data, uint256 txGas) internal view returns (bool success) { @@ -29,7 +29,7 @@ library LowLevelCalls { } function delegateCall(address to, bytes memory data) internal returns (bool success) { - return delegateCall(to, data, gasleft()); + return delegateCall(to, data, type(uint256).max); } function delegateCall(address to, bytes memory data, uint256 txGas) internal returns (bool success) { From ddbb30d88017104ff84435d8b73b0a5857855520 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 18 Jun 2024 14:25:32 +0200 Subject: [PATCH 4/5] update --- contracts/access/manager/AuthorityUtils.sol | 26 +++---- contracts/token/ERC20/extensions/ERC4626.sol | 31 ++++---- contracts/utils/LowLevelCalls.sol | 72 ------------------- .../utils/{LowLevelMemory.sol => Memory.sol} | 4 +- .../utils/cryptography/SignatureChecker.sol | 23 +++--- 5 files changed, 44 insertions(+), 112 deletions(-) delete mode 100644 contracts/utils/LowLevelCalls.sol rename contracts/utils/{LowLevelMemory.sol => Memory.sol} (77%) diff --git a/contracts/access/manager/AuthorityUtils.sol b/contracts/access/manager/AuthorityUtils.sol index c6d9a9beaa5..c28cba8782f 100644 --- a/contracts/access/manager/AuthorityUtils.sol +++ b/contracts/access/manager/AuthorityUtils.sol @@ -4,8 +4,7 @@ pragma solidity ^0.8.20; import {IAuthority} from "./IAuthority.sol"; -import {LowLevelCalls} from "../../utils/LowLevelCalls.sol"; -import {LowLevelMemory} from "../../utils/LowLevelMemory.sol"; +import {Memory} from "../../utils/Memory.sol"; library AuthorityUtils { /** @@ -19,20 +18,21 @@ library AuthorityUtils { address target, bytes4 selector ) internal view returns (bool immediate, uint32 delay) { - // snapshot free memory pointer (moved by encodeCall and getReturnDataFixed) - LowLevelMemory.FreePtr ptr = LowLevelMemory.save(); + Memory.FreePtr ptr = Memory.save(); - if (LowLevelCalls.staticcall(authority, abi.encodeCall(IAuthority.canCall, (caller, target, selector)))) { - if (LowLevelCalls.getReturnDataSize() >= 0x40) { - (immediate, delay) = abi.decode(LowLevelCalls.getReturnDataFixed(0x40), (bool, uint32)); - } else if (LowLevelCalls.getReturnDataSize() >= 0x20) { - immediate = abi.decode(LowLevelCalls.getReturnDataFixed(0x20), (bool)); + bytes memory params = abi.encodeCall(IAuthority.canCall, (caller, target, selector)); + assembly ("memory-safe") { + let success := staticcall(not(0), authority, add(params, 0x20), mload(params), 0, 0x40) + if success { + if gt(returndatasize(), 0x1F) { + immediate := gt(mload(0x00), 0) + } + if gt(returndatasize(), 0x3F) { + delay := and(mload(0x20), 0xFFFFFFFF) + } } } - // restore free memory pointer to reduce memory leak - LowLevelMemory.load(ptr); - - return (immediate, delay); + Memory.load(ptr); } } diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index e66c45f587a..4336c67edf7 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -7,7 +7,7 @@ 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 {LowLevelCalls} from "../../../utils/LowLevelCalls.sol"; +import {Memory} from "../../../utils/Memory.sol"; /** * @dev Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in @@ -76,25 +76,30 @@ 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 = LowLevelCalls.staticcall(address(asset_), abi.encodeCall(IERC20Metadata.decimals, ())) && - LowLevelCalls.getReturnDataSize() >= 0x20; - - if (success) { - uint256 returnedDecimals = abi.decode(LowLevelCalls.getReturnDataFixed(0x20), (uint256)); - if (returnedDecimals <= type(uint8).max) { - return (true, uint8(returnedDecimals)); - } + function _tryGetAssetDecimalsWithFallback(IERC20 asset_, uint8 defaultValue) private view returns (uint8) { + Memory.FreePtr ptr = Memory.save(); + + bool success; + uint256 length; + uint256 value; + + bytes memory params = abi.encodeCall(IERC20Metadata.decimals, ()); + assembly ("memory-safe") { + success := staticcall(not(0), asset_, add(params, 0x20), mload(params), 0, 0x20) + length := returndatasize() + value := mload(0) } - return (false, 0); + + Memory.load(ptr); + + return uint8(Math.ternary(success && length >= 0x20 && value <= type(uint8).max, value, defaultValue)); } /** diff --git a/contracts/utils/LowLevelCalls.sol b/contracts/utils/LowLevelCalls.sol deleted file mode 100644 index 945fba89276..00000000000 --- a/contracts/utils/LowLevelCalls.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Math} from "./math/Math.sol"; - -/** - * Utility functions helpful when making different kinds of contract calls in Solidity. - */ -library LowLevelCalls { - function call(address to, uint256 value, bytes memory data) internal returns (bool success) { - return call(to, value, data, type(uint256).max); - } - - function call(address to, uint256 value, bytes memory data, uint256 txGas) internal returns (bool success) { - assembly ("memory-safe") { - success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) - } - } - - function staticcall(address to, bytes memory data) internal view returns (bool success) { - return staticcall(to, data, type(uint256).max); - } - - function staticcall(address to, bytes memory data, uint256 txGas) internal view returns (bool success) { - assembly ("memory-safe") { - success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0) - } - } - - function delegateCall(address to, bytes memory data) internal returns (bool success) { - return delegateCall(to, data, type(uint256).max); - } - - function delegateCall(address to, bytes memory data, uint256 txGas) internal returns (bool success) { - assembly ("memory-safe") { - success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) - } - } - - function getReturnDataSize() internal pure returns (uint256 returnDataSize) { - assembly ("memory-safe") { - returnDataSize := returndatasize() - } - } - - function getReturnData(uint256 maxLen) internal pure returns (bytes memory ptr) { - return getReturnDataFixed(Math.min(maxLen, getReturnDataSize())); - } - - function getReturnDataFixed(uint256 len) internal pure returns (bytes memory ptr) { - assembly ("memory-safe") { - ptr := mload(0x40) - mstore(0x40, add(ptr, add(len, 0x20))) - mstore(ptr, len) - returndatacopy(add(ptr, 0x20), 0, len) - } - } - - function revertWithData(bytes memory returnData) internal pure { - assembly ("memory-safe") { - revert(add(returnData, 0x20), mload(returnData)) - } - } - - function revertWithCode(bytes32 code) internal pure { - assembly ("memory-safe") { - mstore(0, code) - revert(0, 0x20) - } - } -} diff --git a/contracts/utils/LowLevelMemory.sol b/contracts/utils/Memory.sol similarity index 77% rename from contracts/utils/LowLevelMemory.sol rename to contracts/utils/Memory.sol index edb752e91e8..90b3d445bb7 100644 --- a/contracts/utils/LowLevelMemory.sol +++ b/contracts/utils/Memory.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.20; /** - * @dev Helper library packing and unpacking multiple values into bytes32 + * @dev Helper library for deallocating memory reserved by abi.encode or low level calls. */ -library LowLevelMemory { +library Memory { type FreePtr is bytes32; function save() internal pure returns (FreePtr ptr) { diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 07458d04385..c2acf3b2a7c 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -5,8 +5,7 @@ pragma solidity ^0.8.20; import {ECDSA} from "./ECDSA.sol"; import {IERC1271} from "../../interfaces/IERC1271.sol"; -import {LowLevelCalls} from "../LowLevelCalls.sol"; -import {LowLevelMemory} from "../LowLevelMemory.sol"; +import {Memory} from "../Memory.sol"; /** * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA @@ -41,18 +40,18 @@ library SignatureChecker { address signer, bytes32 hash, bytes memory signature - ) internal view returns (bool) { - // snapshot free memory pointer (moved by encodeCall and getReturnDataFixed) - LowLevelMemory.FreePtr ptr = LowLevelMemory.save(); + ) internal view returns (bool success) { + bytes4 magic = IERC1271.isValidSignature.selector; - bool success = LowLevelCalls.staticcall(signer, abi.encodeCall(IERC1271.isValidSignature, (hash, signature))) && - LowLevelCalls.getReturnDataSize() >= 0x20 && - abi.decode(LowLevelCalls.getReturnDataFixed(0x20), (bytes32)) == - bytes32(IERC1271.isValidSignature.selector); + Memory.FreePtr ptr = Memory.save(); - // restore free memory pointer to reduce memory leak - LowLevelMemory.load(ptr); + bytes memory params = abi.encodeCall(IERC1271.isValidSignature, (hash, signature)); + assembly ("memory-safe") { + if staticcall(not(0), signer, add(params, 0x20), mload(params), 0, 0x20) { + success := and(gt(returndatasize(), 0x1F), eq(mload(0), magic)) + } + } - return success; + Memory.load(ptr); } } From 9ff7d4d97415a17ad8ffd57e181fc48ee4eba532 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 19 Jun 2024 18:27:56 +0200 Subject: [PATCH 5/5] up --- contracts/token/ERC20/utils/SafeERC20.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 58f9fcf4d68..ce4b9b00303 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -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 @@ -34,7 +35,9 @@ library SafeERC20 { * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { + Memory.FreePtr ptr = Memory.save(); _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + Memory.load(ptr); } /** @@ -42,7 +45,9 @@ library SafeERC20 { * 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.FreePtr ptr = Memory.save(); _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + Memory.load(ptr); } /** @@ -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.FreePtr ptr = Memory.save(); 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.load(ptr); } /**