From 1d7539c3b6c0bec19de891656f8b5db3f9bd6849 Mon Sep 17 00:00:00 2001 From: Iaroslav Mazur Date: Wed, 28 Aug 2024 18:39:19 +0300 Subject: [PATCH] chore: move getCallValues() to non-constant functions --- .../native-tokens/NativeTokens.sol | 49 +++++++------- .../ContractToTransferAndCallTo.sol | 64 +++++++++++++++++++ src/test-utils/NaiveTokenTransferrerMock.sol | 49 ++++++++++++++ 3 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 src/test-utils/ContractToTransferAndCallTo.sol create mode 100644 src/test-utils/NaiveTokenTransferrerMock.sol diff --git a/src/precompiles/native-tokens/NativeTokens.sol b/src/precompiles/native-tokens/NativeTokens.sol index d68b3e0..22bb380 100644 --- a/src/precompiles/native-tokens/NativeTokens.sol +++ b/src/precompiles/native-tokens/NativeTokens.sol @@ -30,31 +30,6 @@ library NativeTokens { return abi.decode(returnData, (uint256)); } - /// @notice Returns the token ids and amounts of the Native Tokens transferred in the context of the current - /// contract call. - /// - /// Requirements: - /// - The caller of this function must be a contract. - /// - /// @return tokenIDs The IDs of the transferred Native Tokens. - /// @return amounts The amounts of the transferred Native Tokens. - function getCallValues(address notUsed) internal returns (uint256[] memory, uint256[] memory) { - notUsed; - // ABI encode the input parameters. - bytes memory callData = abi.encodeCall(INativeTokens.getCallValues, ()); - - // Call the precompile. - (bool success, bytes memory returnData) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData); - - // This is an unexpected error since the VM should have panicked if the call failed. - if (!success) { - revert StdLib_UnknownError("NativeTokens: getCallValues failed"); - } - - // Decode the return data. - return abi.decode(returnData, (uint256[], uint256[])); - } - /*////////////////////////////////////////////////////////////////////////// USER-FACING NON-CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ @@ -78,6 +53,30 @@ library NativeTokens { response; } + /// @notice Returns the token ids and amounts of the Native Tokens transferred in the context of the current + /// contract call. + /// + /// Requirements: + /// - The caller of this function must be a contract. + /// + /// @return tokenIDs The IDs of the transferred Native Tokens. + /// @return amounts The amounts of the transferred Native Tokens. + function getCallValues(address /*notUsed*/ ) internal returns (uint256[] memory, uint256[] memory) { + // ABI encode the input parameters. + bytes memory callData = abi.encodeCall(INativeTokens.getCallValues, ()); + + // Call the precompile. + (bool success, bytes memory returnData) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData); + + // This is an unexpected error since the VM should have panicked if the call failed. + if (!success) { + revert StdLib_UnknownError("NativeTokens: getCallValues failed"); + } + + // Decode the return data. + return abi.decode(returnData, (uint256[], uint256[])); + } + /// @notice Mints `amount` tokens with sub-identifier `subID` to the provided `recipient`. /// @dev Generates a Mint receipt. /// diff --git a/src/test-utils/ContractToTransferAndCallTo.sol b/src/test-utils/ContractToTransferAndCallTo.sol new file mode 100644 index 0000000..b1bc77b --- /dev/null +++ b/src/test-utils/ContractToTransferAndCallTo.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.12; + +import { NativeTokens } from "../precompiles/native-tokens/NativeTokens.sol"; + +/// @notice A mock contract playing the role of the "receiving" side in the single- +/// and multi-token transfer-and-call tests +contract ContractToTransferAndCallTo { + using NativeTokens for address; + + function transferTokenForAFee(address recipient, uint256 tokenID, uint256 amount, uint256 fee) external payable { + require(fee < amount, "Fee must be less than the amount to transfer"); + + // Make sure the caller has transferred enough tokenID tokens + (uint256[] memory tokenIds, uint256[] memory tokenAmounts) = address(this).getCallValues(); + require(tokenIds.length == 1 && tokenAmounts.length == 1, "Caller must have transferred exactly one token"); + require(tokenIds[0] == tokenID && tokenAmounts[0] == amount, "Caller must transfer the token to the callee"); + + // Transfer the token to the provided recipient. + recipient.transfer(tokenID, amount - fee); + } + + function transferMultipleTokensForAFee( + address recipient, + uint256[] calldata tokenIDs, + uint256[] calldata amounts, + uint256 fee + ) + external + payable + { + require(tokenIDs.length == amounts.length, "Token IDs and amounts must have the same length"); + + for (uint256 i = 0; i < tokenIDs.length; i++) { + require(fee < amounts[i], "Fee must be less than the amount to transfer"); + + // Make sure the caller has transferred amounts[i] worth of the tokenIDs[i] token + require( + enoughTokensTransferred(tokenIDs[i], amounts[i], tokenIDs, amounts), "Not enough tokens transferred" + ); + + // Transfer the token to the provided recipient. + recipient.transfer(tokenIDs[i], amounts[i] - fee); + } + } + + function enoughTokensTransferred( + uint256 tokenId, + uint256 tokenAmount, + uint256[] memory tokenIds, + uint256[] memory tokenAmounts + ) + internal + pure + returns (bool) + { + for (uint256 i = 0; i < tokenIds.length; i++) { + if (tokenIds[i] == tokenId && tokenAmounts[i] == tokenAmount) { + return true; + } + } + return false; + } +} diff --git a/src/test-utils/NaiveTokenTransferrerMock.sol b/src/test-utils/NaiveTokenTransferrerMock.sol new file mode 100644 index 0000000..1cb870e --- /dev/null +++ b/src/test-utils/NaiveTokenTransferrerMock.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.12; + +import { NativeTokens } from "../precompiles/native-tokens/NativeTokens.sol"; + +/// @notice Dummy mock contract for testing the individual- and multi-token transfer functionalities +contract NaiveTokenTransferrerMock { + using NativeTokens for address; + + function getBalanceOfToken(address account, uint256 tokenID) external view returns (uint256) { + return account.balanceOf(tokenID); + } + + function getCallValues() external payable returns (uint256[] memory, uint256[] memory) { + // TODO: make the getCallValues() function callable directly from the Precompile, w/o having to call it on an + // address? + return address(this).getCallValues(); + } + + function transfer(address to, uint256 tokenID, uint256 amount) external { + to.transfer(tokenID, amount); + } + + function transferAndCall( + address recipientAndCallee, + uint256 tokenID, + uint256 amount, + bytes calldata data + ) + external + { + recipientAndCallee.transferAndCall(tokenID, amount, data); + } + + function transferMultiple(address to, uint256[] calldata tokenIDs, uint256[] calldata amounts) external { + to.transferMultiple(tokenIDs, amounts); + } + + function transferMultipleAndCall( + address recipientAndCallee, + uint256[] calldata tokenIDs, + uint256[] calldata amounts, + bytes calldata data + ) + external + { + recipientAndCallee.transferMultipleAndCall(tokenIDs, amounts, data); + } +}