diff --git a/go.mod b/go.mod index aa3b117..3a56e91 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ replace ( ) require ( - github.com/datachainlab/ethereum-ibc-relay-chain v0.3.6 + github.com/datachainlab/ethereum-ibc-relay-chain v0.3.7 github.com/datachainlab/ibc-hd-signer v0.1.0 github.com/hyperledger-labs/yui-relayer v0.5.5 ) diff --git a/go.sum b/go.sum index 81022a7..47b7892 100644 --- a/go.sum +++ b/go.sum @@ -415,8 +415,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= -github.com/datachainlab/ethereum-ibc-relay-chain v0.3.6 h1:7OUPp16JgyGgmsPj18FbaXhfQX0j5e5kKXO0NYFIeAA= -github.com/datachainlab/ethereum-ibc-relay-chain v0.3.6/go.mod h1:9rJTxXmEFVw8VrHYbKSK7bIQk1PVJm8pzyAYknMNNqc= +github.com/datachainlab/ethereum-ibc-relay-chain v0.3.7 h1:rwBkbaqKiqAS8Xb+4hUXoOylvuEoCQLgO84D2zTneeU= +github.com/datachainlab/ethereum-ibc-relay-chain v0.3.7/go.mod h1:ehlg1jKBVAW+JnFQIqRyCFGuzf4g0ZOECg58kV3uY2w= github.com/datachainlab/ibc-hd-signer v0.1.0 h1:dmnFTAwFpl0m7Lx6+b+N/rrNpHQnXpyJAYnM25GhDi0= github.com/datachainlab/ibc-hd-signer v0.1.0/go.mod h1:wUbLb2EryMCY+GfEsziU0T032Gch04jmrN0D4XGAfOI= github.com/datachainlab/ibc-mock-client v0.4.1 h1:FQfyFOodgnchCIicpS7Vzji3yxXDe4Jl5hmE5Vz7M1s= diff --git a/tests/cases/eth2eth/Makefile b/tests/cases/eth2eth/Makefile index a2d2f9f..6e3c37f 100644 --- a/tests/cases/eth2eth/Makefile +++ b/tests/cases/eth2eth/Makefile @@ -9,7 +9,19 @@ network: .PHONY: test test: - ./scripts/fixture + $(MAKE) test-single + $(MAKE) test-multi + +.PHONY: test-multi +test-multi: + ./scripts/fixture multi + ./scripts/init-rly + ./scripts/handshake + ./scripts/test-tx + +.PHONY: test-single +test-single: + ./scripts/fixture single ./scripts/init-rly ./scripts/handshake ./scripts/test-tx diff --git a/tests/cases/eth2eth/configs/template/ibc-0.template.json b/tests/cases/eth2eth/configs/template/ibc-0.template.json index 4633800..fea9442 100644 --- a/tests/cases/eth2eth/configs/template/ibc-0.template.json +++ b/tests/cases/eth2eth/configs/template/ibc-0.template.json @@ -10,6 +10,7 @@ "path": "m/44'/60'/0'/0/0" }, "ibc_address": "PLACE_HOLDER", + "multicall3_address": "PLACE_HOLDER", "initial_send_checkpoint": 1, "initial_recv_checkpoint": 1, "enable_debug_trace": true, diff --git a/tests/cases/eth2eth/configs/template/ibc-1.template.json b/tests/cases/eth2eth/configs/template/ibc-1.template.json index 314a863..953b10c 100644 --- a/tests/cases/eth2eth/configs/template/ibc-1.template.json +++ b/tests/cases/eth2eth/configs/template/ibc-1.template.json @@ -10,6 +10,7 @@ "path": "m/44'/60'/0'/0/0" }, "ibc_address": "PLACE_HOLDER", + "multicall3_address": "PLACE_HOLDER", "initial_send_checkpoint": 1, "initial_recv_checkpoint": 1, "enable_debug_trace": true, diff --git a/tests/cases/eth2eth/scripts/fixture b/tests/cases/eth2eth/scripts/fixture index 60a73bb..57b3255 100755 --- a/tests/cases/eth2eth/scripts/fixture +++ b/tests/cases/eth2eth/scripts/fixture @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -eux +ARG1=$1 + DOCKER=docker SCRIPT_DIR=$(cd $(dirname $0); pwd) FIXTURES_DIR=${SCRIPT_DIR}/../fixtures @@ -22,8 +24,19 @@ ${DOCKER} cp ethereum-geth0:/root/addresses ${FIXTURES_DIR}/ethereum/ibc0/addres ${DOCKER} cp ethereum-geth1:/root/addresses ${FIXTURES_DIR}/ethereum/ibc1/addresses # assign the contract address to ChainConfig -IBC_HANDLER_ADDRESS_A=`cat ${FIXTURES_DIR}/ethereum/ibc0/addresses/IBCHandler` -IBC_HANDLER_ADDRESS_B=`cat ${FIXTURES_DIR}/ethereum/ibc1/addresses/IBCHandler` mkdir -p "$CONF_DIR/chains" -jq ".chain.ibc_address |= \"$IBC_HANDLER_ADDRESS_A\"" < "$CONF_DIR/template/ibc-0.template.json" > "$CONF_DIR/chains/ibc-0.json" -jq ".chain.ibc_address |= \"$IBC_HANDLER_ADDRESS_B\"" < "$CONF_DIR/template/ibc-1.template.json" > "$CONF_DIR/chains/ibc-1.json" +for id in 0 1; do + IBC_HANDLER_ADDRESS=`cat ${FIXTURES_DIR}/ethereum/ibc${id}/addresses/IBCHandler` + MULTICALL3_ADDRESS=`cat ${FIXTURES_DIR}/ethereum/ibc${id}/addresses/Multicall3` + if [ "$ARG1" = "multi" ]; then + cat "$CONF_DIR/template/ibc-${id}.template.json" \ + | jq ".chain.ibc_address |= \"$IBC_HANDLER_ADDRESS\"" \ + | jq ".chain.multicall3_address |= \"$MULTICALL3_ADDRESS\"" \ + > "$CONF_DIR/chains/ibc-${id}.json" + else + cat "$CONF_DIR/template/ibc-${id}.template.json" \ + | jq ".chain.ibc_address |= \"$IBC_HANDLER_ADDRESS\"" \ + | jq ".chain.multicall3_address |= null" \ + > "$CONF_DIR/chains/ibc-${id}.json" + fi +done diff --git a/tests/chains/ethereum/contracts/contracts/Dependencies.sol b/tests/chains/ethereum/contracts/contracts/Dependencies.sol index 589a1bf..2930902 100644 --- a/tests/chains/ethereum/contracts/contracts/Dependencies.sol +++ b/tests/chains/ethereum/contracts/contracts/Dependencies.sol @@ -21,3 +21,5 @@ import {MockClient} from "@hyperledger-labs/yui-ibc-solidity/contracts/clients/M import {ERC20Token} from "@hyperledger-labs/yui-ibc-solidity/contracts/apps/20-transfer/ERC20Token.sol"; import {ICS20Bank} from "@hyperledger-labs/yui-ibc-solidity/contracts/apps/20-transfer/ICS20Bank.sol"; import {ICS20TransferBank} from "@hyperledger-labs/yui-ibc-solidity/contracts/apps/20-transfer/ICS20TransferBank.sol"; + +import {Multicall3} from "./Multicall3.sol"; diff --git a/tests/chains/ethereum/contracts/contracts/Multicall3.sol b/tests/chains/ethereum/contracts/contracts/Multicall3.sol new file mode 100644 index 0000000..e26b4f2 --- /dev/null +++ b/tests/chains/ethereum/contracts/contracts/Multicall3.sol @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +/// @title Multicall3 +/// @notice Aggregate results from multiple function calls +/// @dev Multicall & Multicall2 backwards-compatible +/// @dev Aggregate methods are marked `payable` to save 24 gas per call +/// @author Michael Elliot +/// @author Joshua Levine +/// @author Nick Johnson +/// @author Andreas Bigger +/// @author Matt Solomon +contract Multicall3 { + struct Call { + address target; + bytes callData; + } + + struct Call3 { + address target; + bool allowFailure; + bytes callData; + } + + struct Call3Value { + address target; + bool allowFailure; + uint256 value; + bytes callData; + } + + struct Result { + bool success; + bytes returnData; + } + + /// @notice Backwards-compatible call aggregation with Multicall + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return returnData An array of bytes containing the responses + function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + uint256 length = calls.length; + returnData = new bytes[](length); + Call calldata call; + for (uint256 i = 0; i < length;) { + bool success; + call = calls[i]; + (success, returnData[i]) = call.target.call(call.callData); + require(success, "Multicall3: call failed"); + unchecked { ++i; } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls without requiring success + /// @param requireSuccess If true, require all calls to succeed + /// @param calls An array of Call structs + /// @return returnData An array of Result structs + function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata call; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + call = calls[i]; + (result.success, result.returnData) = call.target.call(call.callData); + if (requireSuccess) require(result.success, "Multicall3: call failed"); + unchecked { ++i; } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + blockNumber = block.number; + blockHash = blockhash(block.number); + returnData = tryAggregate(requireSuccess, calls); + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); + } + + /// @notice Aggregate calls, ensuring each returns success if required + /// @param calls An array of Call3 structs + /// @return returnData An array of Result structs + function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call3 calldata calli; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x64) + } + } + unchecked { ++i; } + } + } + + /// @notice Aggregate calls with a msg value + /// @notice Reverts if msg.value is less than the sum of the call values + /// @param calls An array of Call3Value structs + /// @return returnData An array of Result structs + function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 valAccumulator; + uint256 length = calls.length; + returnData = new Result[](length); + Call3Value calldata calli; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + calli = calls[i]; + uint256 val = calli.value; + // Humanity will be a Type V Kardashev Civilization before this overflows - andreas + // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 + unchecked { valAccumulator += val; } + (result.success, result.returnData) = calli.target.call{value: val}(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x84) + } + } + unchecked { ++i; } + } + // Finally, make sure the msg.value = SUM(call[0...i].value) + require(msg.value == valAccumulator, "Multicall3: value mismatch"); + } + + /// @notice Returns the block hash for the given block number + /// @param blockNumber The block number + function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { + blockHash = blockhash(blockNumber); + } + + /// @notice Returns the block number + function getBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; + } + + /// @notice Returns the block coinbase + function getCurrentBlockCoinbase() public view returns (address coinbase) { + coinbase = block.coinbase; + } + + /// @notice Returns the block difficulty + function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { + difficulty = block.difficulty; + } + + /// @notice Returns the block gas limit + function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { + gaslimit = block.gaslimit; + } + + /// @notice Returns the block timestamp + function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { + timestamp = block.timestamp; + } + + /// @notice Returns the (ETH) balance of a given address + function getEthBalance(address addr) public view returns (uint256 balance) { + balance = addr.balance; + } + + /// @notice Returns the block hash of the last block + function getLastBlockHash() public view returns (bytes32 blockHash) { + unchecked { + blockHash = blockhash(block.number - 1); + } + } + + /// @notice Gets the base fee of the given block + /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain + function getBasefee() public view returns (uint256 basefee) { + basefee = block.basefee; + } + + /// @notice Returns the chain id + function getChainId() public view returns (uint256 chainid) { + chainid = block.chainid; + } +} diff --git a/tests/chains/ethereum/contracts/scripts/deploy.js b/tests/chains/ethereum/contracts/scripts/deploy.js index 2461c79..d64ba10 100644 --- a/tests/chains/ethereum/contracts/scripts/deploy.js +++ b/tests/chains/ethereum/contracts/scripts/deploy.js @@ -28,7 +28,7 @@ async function deployIBC(deployer) { "IBCChannelPacketSendRecv", "IBCChannelPacketTimeout", "IBCChannelUpgradeInitTryAck", - "IBCChannelUpgradeConfirmTimeoutCancel" + "IBCChannelUpgradeConfirmTimeoutCancel", ]; const logics = []; for (const name of logicNames) { @@ -71,6 +71,9 @@ async function main() { const mockClient = await deploy(deployer, "MockClient", [ibcHandler.target]); saveAddress("MockClient", mockClient); + const multicall3 = await deploy(deployer, "Multicall3", []); + saveAddress("Multicall3", multicall3); + await ibcHandler.bindPort("transfer", ics20transferbank.target); await ibcHandler.registerClient("mock-client", mockClient.target); await ics20bank.setOperator(ics20transferbank.target);