diff --git a/Cargo.lock b/Cargo.lock index 87ba68d6f..4e72542b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5594,12 +5594,9 @@ dependencies = [ "async-trait", "futures", "hex", + "hex-literal", "reqwest", "rosetta-client", - "rosetta-ethereum-backend", - "rosetta-ethereum-types", - "rosetta-server", - "rosetta-server-ethereum", "serde", "serde_json", "sha3", @@ -14158,7 +14155,7 @@ dependencies = [ [[package]] name = "rosetta-client" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "async-trait", @@ -14174,6 +14171,9 @@ dependencies = [ "num-traits", "rosetta-config-ethereum", "rosetta-core", + "rosetta-ethereum-backend", + "rosetta-ethereum-types", + "rosetta-server", "rosetta-server-astar", "rosetta-server-ethereum", "rosetta-server-polkadot", @@ -14189,7 +14189,7 @@ dependencies = [ [[package]] name = "rosetta-config-astar" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "rosetta-core", @@ -14199,7 +14199,7 @@ dependencies = [ [[package]] name = "rosetta-config-ethereum" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "const-hex", @@ -14219,7 +14219,7 @@ dependencies = [ [[package]] name = "rosetta-config-polkadot" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "rosetta-core", @@ -14230,7 +14230,7 @@ dependencies = [ [[package]] name = "rosetta-core" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "async-trait", @@ -14247,7 +14247,7 @@ dependencies = [ [[package]] name = "rosetta-crypto" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "bech32", @@ -14273,7 +14273,7 @@ dependencies = [ [[package]] name = "rosetta-ethereum-backend" version = "0.1.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "async-trait", "auto_impl", @@ -14289,7 +14289,7 @@ dependencies = [ [[package]] name = "rosetta-ethereum-types" version = "0.2.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "bytes", "const-hex", @@ -14323,7 +14323,7 @@ dependencies = [ [[package]] name = "rosetta-server" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "async-trait", @@ -14346,7 +14346,7 @@ dependencies = [ [[package]] name = "rosetta-server-astar" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "async-trait", @@ -14370,7 +14370,7 @@ dependencies = [ [[package]] name = "rosetta-server-ethereum" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "async-trait", @@ -14399,7 +14399,7 @@ dependencies = [ [[package]] name = "rosetta-server-polkadot" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "async-trait", @@ -14420,7 +14420,7 @@ dependencies = [ [[package]] name = "rosetta-tx-ethereum" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "rosetta-config-ethereum", @@ -14431,7 +14431,7 @@ dependencies = [ [[package]] name = "rosetta-tx-polkadot" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "anyhow", "blake2-rfc", @@ -14445,7 +14445,7 @@ dependencies = [ [[package]] name = "rosetta-types" version = "0.6.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "serde", "serde_json", @@ -14454,7 +14454,7 @@ dependencies = [ [[package]] name = "rosetta-utils" version = "0.1.0" -source = "git+https://github.com/analog-labs/chain-connectors?rev=37e2cd1#37e2cd13b9a958542137a6674ea26314f39e59d7" +source = "git+https://github.com/analog-labs/chain-connectors?rev=5bbe80d#5bbe80d8f257dc25c3cb842b42aae2aa74581cda" dependencies = [ "bytes", "futures-timer", diff --git a/analog-gmp/foundry.toml b/analog-gmp/foundry.toml index 82cb75a8d..3f1eb27e5 100644 --- a/analog-gmp/foundry.toml +++ b/analog-gmp/foundry.toml @@ -21,7 +21,7 @@ solc = '0.8.28' # - https://ethereumclassic.org/knowledge/history evm_version = 'shanghai' optimizer = true -optimizer_runs = 200000 +optimizer_runs = 500000 ############### # EVM options # diff --git a/analog-gmp/remappings.txt b/analog-gmp/remappings.txt deleted file mode 100644 index bf85bb85a..000000000 --- a/analog-gmp/remappings.txt +++ /dev/null @@ -1,8 +0,0 @@ -forge-std/=lib/forge-std/src/ -@frost-evm/=lib/frost-evm/sol/ -@solmate/=lib/solmate/src/ -ds-test/=lib/solmate/lib/ds-test/src/ -@universal-factory/=lib/universal-factory/src/ -@evm-interpreter/=lib/evm-interpreter/src/ -@analog-gmp/=src/ -@analog-gmp-testing/=test/ diff --git a/analog-gmp/src/GatewayProxy.sol b/analog-gmp/src/GatewayProxy.sol index e6bf128b0..65566d251 100644 --- a/analog-gmp/src/GatewayProxy.sol +++ b/analog-gmp/src/GatewayProxy.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.0; import {ERC1967} from "./utils/ERC1967.sol"; -import {Context, CreateKind, IUniversalFactory} from "@universal-factory/IUniversalFactory.sol"; +import {Context, CreateKind, IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; contract GatewayProxy { /** diff --git a/analog-gmp/src/Primitives.sol b/analog-gmp/src/Primitives.sol index b279c95fa..b4e879335 100644 --- a/analog-gmp/src/Primitives.sol +++ b/analog-gmp/src/Primitives.sol @@ -281,7 +281,7 @@ library PrimitiveUtils { bytes memory onGmpReceived = callback.callback; bytes32 dataHash; assembly ("memory-safe") { - let offset := add(onGmpReceived, 0xa4) + let offset := add(onGmpReceived, 0xc4) dataHash := keccak256(add(offset, 0x20), mload(offset)) } callback.eip712hash = bytes32(GMP_VERSION); @@ -332,9 +332,10 @@ library PrimitiveUtils { // | 0x0124..0x0144 <- onGmpReceived.id // | 0x0144..0x0164 <- onGmpReceived.network // | 0x0164..0x0184 <- onGmpReceived.source - // | 0x0184..0x01a4 <- onGmpReceived.data.offset - // | 0x01a4..0x01c4 <- onGmpReceived.data.length - // | 0x01c4........ <- onGmpReceived.data + // | 0x0184..0x01a4 <- onGmpReceived.nonce + // | 0x01a4..0x01c4 <- onGmpReceived.data.offset + // | 0x01c4..0x01e4 <- onGmpReceived.data.length + // | 0x01e4........ <- onGmpReceived.data if (isCalldata) { GmpMessage calldata m = _intoCalldataPointer(message); callback.source = m.source; @@ -345,10 +346,11 @@ library PrimitiveUtils { callback.nonce = m.nonce; bytes calldata data = m.data; callback.callback = abi.encodeWithSignature( - "onGmpReceived(bytes32,uint128,bytes32,bytes)", + "onGmpReceived(bytes32,uint128,bytes32,uint64,bytes)", callback.eip712hash, callback.srcNetwork, callback.source, + callback.nonce, data ); } else { @@ -360,10 +362,11 @@ library PrimitiveUtils { callback.gasLimit = m.gasLimit; callback.nonce = m.nonce; callback.callback = abi.encodeWithSignature( - "onGmpReceived(bytes32,uint128,bytes32,bytes)", + "onGmpReceived(bytes32,uint128,bytes32,uint64,bytes)", callback.eip712hash, callback.srcNetwork, callback.source, + callback.nonce, m.data ); } diff --git a/analog-gmp/src/interfaces/IGmpReceiver.sol b/analog-gmp/src/interfaces/IGmpReceiver.sol index 64fccd4a3..85c1a62a2 100644 --- a/analog-gmp/src/interfaces/IGmpReceiver.sol +++ b/analog-gmp/src/interfaces/IGmpReceiver.sol @@ -17,7 +17,7 @@ interface IGmpReceiver { * @param payload The message payload with no specified format * @return 32 byte result which will be stored together with GMP message */ - function onGmpReceived(bytes32 id, uint128 network, bytes32 source, bytes calldata payload) + function onGmpReceived(bytes32 id, uint128 network, bytes32 source, uint64 nonce, bytes calldata payload) external payable returns (bytes32); diff --git a/analog-gmp/test/Batching.t.sol b/analog-gmp/test/Batching.t.sol index 573103f3a..4aa7878bf 100644 --- a/analog-gmp/test/Batching.t.sol +++ b/analog-gmp/test/Batching.t.sol @@ -5,8 +5,8 @@ pragma solidity >=0.8.0; import {Test, console, Vm} from "forge-std/Test.sol"; import {VmSafe} from "forge-std/Vm.sol"; -import {FactoryUtils} from "@universal-factory/FactoryUtils.sol"; -import {IUniversalFactory} from "@universal-factory/IUniversalFactory.sol"; +import {FactoryUtils} from "../lib/universal-factory/src/FactoryUtils.sol"; +import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; import {TestUtils, SigningKey, SigningUtils} from "./TestUtils.sol"; import {GasSpender} from "./utils/GasSpender.sol"; import {BaseTest} from "./utils/BaseTest.sol"; @@ -161,6 +161,7 @@ contract Batching is BaseTest { 0x0000000000000000000000000000000000000000000000000000000000000000, 1, 0x0000000000000000000000000000000000000000000000000000000000000000, + 0, abi.encode(gasToWaste) ) ); diff --git a/analog-gmp/test/Gateway.t.sol b/analog-gmp/test/Gateway.t.sol index 4c94fb507..82c0933b9 100644 --- a/analog-gmp/test/Gateway.t.sol +++ b/analog-gmp/test/Gateway.t.sol @@ -328,6 +328,7 @@ contract GatewayTest is BaseTest { 0x0000000000000000000000000000000000000000000000000000000000000000, 1, 0x0000000000000000000000000000000000000000000000000000000000000000, + 0, abi.encode(uint256(1234)) ) ); diff --git a/analog-gmp/test/GatewayProxy.t.sol b/analog-gmp/test/GatewayProxy.t.sol index 66e904997..5d6c97cac 100644 --- a/analog-gmp/test/GatewayProxy.t.sol +++ b/analog-gmp/test/GatewayProxy.t.sol @@ -3,8 +3,8 @@ pragma solidity >=0.8.0; -import {IUniversalFactory} from "@universal-factory/IUniversalFactory.sol"; -import {FactoryUtils} from "@universal-factory/FactoryUtils.sol"; +import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; +import {FactoryUtils} from "../lib/universal-factory/src/FactoryUtils.sol"; import {Test, console} from "forge-std/Test.sol"; import {VmSafe} from "forge-std/Vm.sol"; import {TestUtils, SigningKey, SigningUtils} from "./TestUtils.sol"; diff --git a/analog-gmp/test/GmpTestTools.sol b/analog-gmp/test/GmpTestTools.sol index eded8415e..c00c087c3 100644 --- a/analog-gmp/test/GmpTestTools.sol +++ b/analog-gmp/test/GmpTestTools.sol @@ -11,7 +11,7 @@ import {GatewayProxy} from "../src/GatewayProxy.sol"; import {IGateway} from "../src/interfaces/IGateway.sol"; import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; import {GmpMessage, TssKey, Network, Signature, GmpSender, PrimitiveUtils} from "../src/Primitives.sol"; -import {IUniversalFactory} from "@universal-factory/IUniversalFactory.sol"; +import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; library GmpTestTools { /** diff --git a/analog-gmp/test/MockERC20.sol b/analog-gmp/test/MockERC20.sol index d7c39b246..524645cfc 100644 --- a/analog-gmp/test/MockERC20.sol +++ b/analog-gmp/test/MockERC20.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; -import {ERC20} from "@solmate/tokens/ERC20.sol"; +import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; import {IGateway} from "../src/interfaces/IGateway.sol"; @@ -73,7 +73,7 @@ contract MockERC20 is ERC20, IGmpReceiver { ); } - function onGmpReceived(bytes32 id, uint128 network, bytes32 sender, bytes calldata data) + function onGmpReceived(bytes32 id, uint128 network, bytes32 sender, uint64, bytes calldata data) external payable returns (bytes32) diff --git a/analog-gmp/test/TestUtils.sol b/analog-gmp/test/TestUtils.sol index bbeaf7a07..6ec189582 100644 --- a/analog-gmp/test/TestUtils.sol +++ b/analog-gmp/test/TestUtils.sol @@ -5,11 +5,11 @@ pragma solidity >=0.8.0; import {VmSafe, Vm} from "forge-std/Vm.sol"; import {console} from "forge-std/console.sol"; -import {Schnorr} from "@frost-evm/Schnorr.sol"; -import {SECP256K1} from "@frost-evm/SECP256K1.sol"; +import {Schnorr} from "../lib/frost-evm/sol/Schnorr.sol"; +import {SECP256K1} from "../lib/frost-evm/sol/SECP256K1.sol"; import {BranchlessMath} from "../src/utils/BranchlessMath.sol"; -import {IUniversalFactory} from "@universal-factory/IUniversalFactory.sol"; -import {FactoryUtils} from "@universal-factory/FactoryUtils.sol"; +import {IUniversalFactory} from "../lib/universal-factory/src/IUniversalFactory.sol"; +import {FactoryUtils} from "../lib/universal-factory/src/FactoryUtils.sol"; import {IGateway} from "../src/interfaces/IGateway.sol"; import {Gateway, GatewayEIP712} from "../src/Gateway.sol"; import {GatewayProxy} from "../src/GatewayProxy.sol"; diff --git a/analog-gmp/test/utils/BaseTest.sol b/analog-gmp/test/utils/BaseTest.sol index 9559308ec..1eeedbb6e 100644 --- a/analog-gmp/test/utils/BaseTest.sol +++ b/analog-gmp/test/utils/BaseTest.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; -import {IUniversalFactory} from "@universal-factory/IUniversalFactory.sol"; -import {FactoryUtils} from "@universal-factory/FactoryUtils.sol"; -import {Interpreter} from "@evm-interpreter/Interpreter.sol"; +import {IUniversalFactory} from "../../lib/universal-factory/src/IUniversalFactory.sol"; +import {FactoryUtils} from "../../lib/universal-factory/src/FactoryUtils.sol"; +import {Interpreter} from "../../lib/evm-interpreter/src/Interpreter.sol"; import {Test, console, Vm} from "forge-std/Test.sol"; import {VmSafe} from "forge-std/Vm.sol"; diff --git a/analog-gmp/test/utils/GasSpender.sol b/analog-gmp/test/utils/GasSpender.sol index 799d179fa..265c74a6c 100644 --- a/analog-gmp/test/utils/GasSpender.sol +++ b/analog-gmp/test/utils/GasSpender.sol @@ -17,7 +17,7 @@ contract GasSpender is IGmpReceiver { // 0x07 0x3d RETURNDATASIZE // 0x08 0x6020 PUSH1 0x20 // 0x0a 0x91 SWAP2 - // 0x0b 0x6064 PUSH1 0x64 + // 0x0b 0x6084 PUSH1 0x84 // 0x0d 0x35 CALLDATALOAD -- Load the payload offset from the calldata // 0x0e 0x6024 PUSH1 0x24 // 0x10 0x01 ADD @@ -61,7 +61,7 @@ contract GasSpender is IGmpReceiver { // `=>0x3a 0x5b JUMPDEST // 0x3b 0xf3 RETURN bytes private constant BYTECODE = - hex"5a600201803d523d60209160643560240135146018575bfd5b60365a116018575a604903565b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5bf3"; + hex"5a600201803d523d60209160843560240135146018575bfd5b60365a116018575a604903565b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5bf3"; constructor() payable { bytes memory bytecode = BYTECODE; @@ -70,7 +70,7 @@ contract GasSpender is IGmpReceiver { } } - function onGmpReceived(bytes32, uint128, bytes32, bytes calldata payload) external payable returns (bytes32) { + function onGmpReceived(bytes32, uint128, bytes32, uint64, bytes calldata payload) external payable returns (bytes32) { unchecked { // OBS: This is just an example on how this contract works, the actual code is implemented directly in // low level EVM, as defined in the `BYTECODE` constant. diff --git a/analog-gmp/test/utils/GasSpender.t.sol b/analog-gmp/test/utils/GasSpender.t.sol index 4f284ffcb..a838caf2e 100644 --- a/analog-gmp/test/utils/GasSpender.t.sol +++ b/analog-gmp/test/utils/GasSpender.t.sol @@ -20,6 +20,7 @@ contract GasSpenderTest is BaseTest { 0x0000000000000000000000000000000000000000000000000000000000000000, 1, 0x0000000000000000000000000000000000000000000000000000000000000000, + 0, abi.encode(gasToWaste) ) ); diff --git a/analog-gmp/test/utils/GmpProxy.sol b/analog-gmp/test/utils/GmpProxy.sol index 4af245eb0..3fe0ebe34 100644 --- a/analog-gmp/test/utils/GmpProxy.sol +++ b/analog-gmp/test/utils/GmpProxy.sol @@ -7,6 +7,7 @@ import {ERC1967} from "../../src/utils/ERC1967.sol"; import {IGmpReceiver} from "../../src/interfaces/IGmpReceiver.sol"; import {IGateway} from "../../src/interfaces/IGateway.sol"; import {BranchlessMath} from "../../src/utils/BranchlessMath.sol"; +import {console} from "forge-std/console.sol"; contract GmpProxy is IGmpReceiver { using BranchlessMath for uint256; @@ -36,12 +37,22 @@ contract GmpProxy is IGmpReceiver { return GATEWAY.submitMessage{value: value}(message.dest, message.destNetwork, message.gasLimit, message.data); } - function onGmpReceived(bytes32 id, uint128, bytes32, bytes calldata payload) external payable returns (bytes32) { - // For testing purpose - // we keep the original struct in payload so we dont depend on OnGmpReceived call since it doesnt provide everything. - (GmpMessage memory message) = abi.decode(payload, (GmpMessage)); - message.data = payload; - + function onGmpReceived(bytes32 id, uint128 srcNetwork, bytes32 src, uint64 nonce, bytes calldata payload) external payable returns (bytes32) { + // when estimating gas an insane amount of gas is provided + uint256 gasLimit = gasleft(); + // this is the constant added to gasLimit + unchecked { console.log(300_000 - gasLimit); } + uint64 msgGasLimit; + unchecked { msgGasLimit = uint64(gasLimit + 579); } + GmpMessage memory message = GmpMessage({ + source: src, + srcNetwork: uint16(srcNetwork), + dest: address(this), + destNetwork: NETWORK_ID, + gasLimit: msgGasLimit, + nonce: nonce, + data: payload + }); emit MessageReceived(message); return id; } diff --git a/analog-gmp/test/utils/GmpProxy.t.sol b/analog-gmp/test/utils/GmpProxy.t.sol new file mode 100644 index 000000000..813163c73 --- /dev/null +++ b/analog-gmp/test/utils/GmpProxy.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import {Test, console, Vm} from "forge-std/Test.sol"; +import {IGateway} from "../../src/interfaces/IGateway.sol"; +import {IGmpReceiver} from "../../src/interfaces/IGmpReceiver.sol"; +import {GmpProxy} from "./GmpProxy.sol"; + +contract MockGateway is IGateway { + uint16 _networkId; + + constructor(uint16 network) { + _networkId = network; + } + + function networkId() external view returns (uint16) { + return _networkId; + } + + function estimateMessageCost( + uint16, + uint256, + uint256 + ) external pure returns (uint256) { + return 0; + } + + function submitMessage( + address, + uint16, + uint256, + bytes calldata + ) external payable returns (bytes32) { + return 0x0; + } +} + +contract GmpProxyTest is Test { + MockGateway gateway; + GmpProxy proxy; + + function setUp() external { + gateway = new MockGateway(1); + proxy = new GmpProxy(address(gateway)); + } + + function test_onGmpReceived() external { + proxy.onGmpReceived{gas: 300000}( + 0x0, + 0, + 0x0, + 0, + abi.encode(0x0) + ); + } +} diff --git a/config/docker/Dockerfile.tc-cli b/config/docker/Dockerfile.tc-cli index f68b94a0d..fe65bdba7 100644 --- a/config/docker/Dockerfile.tc-cli +++ b/config/docker/Dockerfile.tc-cli @@ -1,8 +1,9 @@ -FROM alpine:latest as certs -RUN apk --update add ca-certificates +FROM ghcr.io/foundry-rs/foundry:latest as foundry -FROM scratch -COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -COPY tc-cli tc-cli +FROM ubuntu:22.04 +COPY --from=foundry /usr/local/bin/cast /bin/cast +COPY tc-cli /bin/tc-cli COPY envs /etc/envs -ENTRYPOINT ["/tc-cli"] +COPY analog-gmp /analog-gmp +ENV ANALOG_GMP_DIR /analog-gmp +ENTRYPOINT ["/bin/tc-cli"] diff --git a/config/envs/development/config.yaml b/config/envs/development/config.yaml index 624baa8ff..8b2e5e1d8 100644 --- a/config/envs/development/config.yaml +++ b/config/envs/development/config.yaml @@ -1,5 +1,5 @@ config: - chronicle_timechain_funds: 1. + chronicle_funds: 1. timechain_url: 'wss://rpc.development.analog.one' prices_path: 'prices.csv' contracts: @@ -14,8 +14,9 @@ networks: blockchain: "rust" network: "0" url: "https://gmp-grpc-1.development.analog.one:443" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -31,8 +32,9 @@ networks: blockchain: "rust" network: "1" url: "https://gmp-grpc-2.development.analog.one:443" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -48,8 +50,9 @@ networks: blockchain: "rust" network: "0" url: "https://gmp-grpc-3.development.analog.one:443" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -65,8 +68,9 @@ networks: blockchain: "rust" network: "1" url: "https://gmp-grpc-4.development.analog.one:443" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -82,8 +86,9 @@ networks: blockchain: "ethereum" network: "sepolia" url: "wss://ethereum-sepolia-rpc.publicnode.com" + admin_funds: 100. gateway_funds: 1. - chronicle_target_funds: .1 + chronicle_funds: .1 batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -99,8 +104,9 @@ networks: blockchain: "astar" network: "shibuya" url: "wss://rpc.shibuya.astar.network" + admin_funds: 100. gateway_funds: 1. - chronicle_target_funds: .1 + chronicle_funds: .1 batch_size: 256 batch_offset: 0 batch_gas_limit: 10000 @@ -116,8 +122,9 @@ networks: blockchain: "ethereum" network: "dev" url: "wss://eth-local.development.analog.one" + admin_funds: 100. gateway_funds: 1. - chronicle_target_funds: .1 + chronicle_funds: .1 batch_size: 256 batch_offset: 0 batch_gas_limit: 10000 diff --git a/config/envs/integration/config.yaml b/config/envs/integration/config.yaml index 42daabb9d..6ba54dee2 100644 --- a/config/envs/integration/config.yaml +++ b/config/envs/integration/config.yaml @@ -1,5 +1,5 @@ config: - chronicle_timechain_funds: 10. + chronicle_funds: 10. timechain_url: 'wss://archive.integration.analog.one' prices_path: 'prices.csv' contracts: @@ -15,7 +15,7 @@ networks: network: "sepolia" url: "wss://ethereum-sepolia-rpc.publicnode.com" gateway_funds: 1. - chronicle_target_funds: .1 + chronicle_funds: .1 batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -31,7 +31,7 @@ networks: network: "shibuya" url: "wss://rpc.shibuya.astar.network" gateway_funds: 1. - chronicle_target_funds: .1 + chronicle_funds: .1 batch_size: 256 batch_offset: 0 batch_gas_limit: 10000 diff --git a/config/envs/local/config.yaml b/config/envs/local/config.yaml index 4aa924dbd..258aa1528 100644 --- a/config/envs/local/config.yaml +++ b/config/envs/local/config.yaml @@ -1,5 +1,5 @@ config: - chronicle_timechain_funds: 10. + chronicle_funds: 10. timechain_url: 'ws://validator:9944' prices_path: 'prices.csv' contracts: @@ -14,8 +14,9 @@ networks: blockchain: "rust" network: "0" url: "http://chain-0-grpc:3000" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -34,8 +35,9 @@ networks: blockchain: "rust" network: "1" url: "http://chain-1-grpc:3000" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -47,11 +49,12 @@ networks: shard_threshold: 1 2: backend: "evm" - blockchain: "ethereum" + blockchain: "anvil" network: "dev" url: "ws://chain-2-evm:8545" + admin_funds: 100. gateway_funds: 1. - chronicle_target_funds: 1. + chronicle_funds: 1. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000000 @@ -63,11 +66,12 @@ networks: shard_threshold: 1 3: backend: "evm" - blockchain: "ethereum" + blockchain: "anvil" network: "dev" url: "ws://chain-3-evm:8545" + admin_funds: 100. gateway_funds: 1. - chronicle_target_funds: 1. + chronicle_funds: 1. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000000 diff --git a/config/envs/local/local-evm.yaml b/config/envs/local/local-evm.yaml index 53428c76a..c925c4b52 100644 --- a/config/envs/local/local-evm.yaml +++ b/config/envs/local/local-evm.yaml @@ -1,5 +1,5 @@ config: - chronicle_timechain_funds: 10. + chronicle_funds: 10. timechain_url: 'ws://validator:9944' prices_path: 'prices.csv' contracts: @@ -11,11 +11,12 @@ contracts: networks: 2: backend: "evm" - blockchain: "ethereum" + blockchain: "anvil" network: "dev" url: "ws://chain-2-evm:8545" + admin_funds: 100. gateway_funds: 1. - chronicle_target_funds: 1. + chronicle_funds: 1. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000000 @@ -27,11 +28,12 @@ networks: shard_threshold: 1 3: backend: "evm" - blockchain: "ethereum" + blockchain: "anvil" network: "dev" url: "ws://chain-3-evm:8545" + admin_funds: 100. gateway_funds: 1. - chronicle_target_funds: 1. + chronicle_funds: 1. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000000 diff --git a/config/envs/local/local-grpc-tss.yaml b/config/envs/local/local-grpc-tss.yaml index 3b4788893..6354d7521 100644 --- a/config/envs/local/local-grpc-tss.yaml +++ b/config/envs/local/local-grpc-tss.yaml @@ -1,5 +1,5 @@ config: - chronicle_timechain_funds: 10. + chronicle_funds: 10. timechain_url: 'ws://validator:9944' prices_path: 'prices.csv' contracts: @@ -14,8 +14,9 @@ networks: blockchain: "rust" network: "0" url: "http://chain-0-grpc:3000" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -34,8 +35,9 @@ networks: blockchain: "rust" network: "1" url: "http://chain-1-grpc:3000" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 diff --git a/config/envs/local/local-grpc.yaml b/config/envs/local/local-grpc.yaml index 930ab5609..602ea618a 100644 --- a/config/envs/local/local-grpc.yaml +++ b/config/envs/local/local-grpc.yaml @@ -1,5 +1,5 @@ config: - chronicle_timechain_funds: 10. + chronicle_funds: 10. timechain_url: 'ws://validator:9944' prices_path: 'prices.csv' contracts: @@ -14,8 +14,9 @@ networks: blockchain: "rust" network: "0" url: "http://chain-0-grpc:3000" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 @@ -34,8 +35,9 @@ networks: blockchain: "rust" network: "1" url: "http://chain-1-grpc:3000" + admin_funds: 100. gateway_funds: 10. - chronicle_target_funds: 10. + chronicle_funds: 10. batch_size: 64 batch_offset: 0 batch_gas_limit: 10000 diff --git a/docker-compose.yml b/docker-compose.yml index 736363dcf..b20baffa1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,26 +76,11 @@ services: - grpc chain-2-evm: - image: 'ethereum/client-go:v1.14.11' + image: 'ghcr.io/foundry-rs/foundry:latest' command: - - '--dev' - - '--dev.gaslimit=30000000' - - '--ipcdisable' - - '--dev.period=2' - - '--rpc.txfeecap=0' - - '--rpc.allow-unprotected-txs=true' - - '--http' - - '--http.addr=0.0.0.0' - - '--http.port=8545' - - '--http.vhosts=*' - - '--http.corsdomain=*' - - '--http.api=eth,debug,admin,txpool,web3,net' - - '--ws' - - '--ws.addr=0.0.0.0' - - '--ws.port=8545' - - '--ws.origins=*' - - '--ws.api=eth,debug,admin,txpool,web3,net' - - '--verbosity=4' + - 'anvil -b 2 --steps-tracing --base-fee 0' + environment: + ANVIL_IP_ADDR: '0.0.0.0' profiles: - evm @@ -117,26 +102,11 @@ services: - evm chain-3-evm: - image: 'ethereum/client-go:v1.14.11' + image: 'ghcr.io/foundry-rs/foundry:latest' command: - - '--dev' - - '--dev.gaslimit=30000000' - - '--ipcdisable' - - '--dev.period=2' - - '--rpc.txfeecap=0' - - '--rpc.allow-unprotected-txs=true' - - '--http' - - '--http.addr=0.0.0.0' - - '--http.port=8545' - - '--http.vhosts=*' - - '--http.corsdomain=*' - - '--http.api=eth,debug,admin,txpool,web3,net' - - '--ws' - - '--ws.addr=0.0.0.0' - - '--ws.port=8545' - - '--ws.origins=*' - - '--ws.api=eth,debug,admin,txpool,web3,net' - - '--verbosity=4' + - 'anvil -b 2 --steps-tracing --base-fee 0' + environment: + ANVIL_IP_ADDR: '0.0.0.0' profiles: - evm diff --git a/gmp/evm/Cargo.toml b/gmp/evm/Cargo.toml index d698910fd..495e22c9e 100644 --- a/gmp/evm/Cargo.toml +++ b/gmp/evm/Cargo.toml @@ -16,11 +16,7 @@ async-trait.workspace = true futures.workspace = true hex.workspace = true reqwest = { version = "0.12.7", default-features = false, features = ["json", "rustls-tls-native-roots"] } -rosetta-client = { git = "https://github.com/analog-labs/chain-connectors", rev = "37e2cd1" } -rosetta-server = { git = "https://github.com/analog-labs/chain-connectors", rev = "37e2cd1" } -rosetta-server-ethereum = { git = "https://github.com/analog-labs/chain-connectors", rev = "37e2cd1" } -rosetta-ethereum-backend = { git = "https://github.com/analog-labs/chain-connectors", rev = "37e2cd1" } -rosetta-ethereum-types = { git = "https://github.com/analog-labs/chain-connectors", rev = "37e2cd1" } +rosetta-client = { git = "https://github.com/analog-labs/chain-connectors", rev = "5bbe80d" } sha3 = { version = "0.10", default-features = false } serde.workspace = true serde_json = "1.0.127" @@ -28,3 +24,4 @@ thiserror = "2.0.9" time-primitives = { workspace = true, default-features = true } tokio.workspace = true tracing.workspace = true +hex-literal = "0.4.1" diff --git a/gmp/evm/src/lib.rs b/gmp/evm/src/lib.rs index 32ac21845..87762dbf6 100644 --- a/gmp/evm/src/lib.rs +++ b/gmp/evm/src/lib.rs @@ -5,23 +5,26 @@ use async_trait::async_trait; use futures::Stream; use reqwest::Client; use rosetta_client::{ - query::GetLogs, types::AccountIdentifier, AtBlock, CallResult, FilterBlockOption, - GetTransactionCount, SubmitResult, TransactionReceipt, Wallet, -}; -use rosetta_ethereum_backend::{jsonrpsee::Adapter, EthereumRpc}; -use rosetta_server::ws::{default_client, DefaultClient}; -use rosetta_server_ethereum::utils::{ - DefaultFeeEstimatorConfig, EthereumRpcExt, PolygonFeeEstimatorConfig, + query::GetLogs, + rosetta_ethereum_backend::{jsonrpsee::Adapter, EthereumRpc}, + rosetta_server::ws::{default_client, DefaultClient}, + rosetta_server_ethereum::utils::{ + DefaultFeeEstimatorConfig, EthereumRpcExt, PolygonFeeEstimatorConfig, + }, + types::AccountIdentifier, + AtBlock, Blockchain, CallResult, FilterBlockOption, GetTransactionCount, SubmitResult, + TransactionReceipt, Wallet, }; use serde::Deserialize; use sha3::{Digest, Keccak256}; use sol::{u256, Network, TssKey}; use std::ops::Range; use std::pin::Pin; +use std::process::Command; use std::sync::Arc; use thiserror::Error; use time_primitives::{ - Address, BatchId, ConnectorParams, Gateway, GatewayMessage, GmpEvent, GmpMessage, IChain, + Address, BatchId, ConnectorParams, Gateway, GatewayMessage, GmpEvent, GmpMessage, Hash, IChain, IConnector, IConnectorAdmin, IConnectorBuilder, MessageId, NetworkId, Route, TssPublicKey, TssSignature, }; @@ -52,6 +55,7 @@ pub struct Connector { network_id: NetworkId, wallet: Arc, backend: Adapter, + url: String, cctp_sender: Option, cctp_attestation: String, cctp_queue: Arc>>, @@ -71,6 +75,38 @@ impl Connector { .await } + async fn raw_evm_call( + &self, + contract: [u8; 20], + call: Vec, + amount: u128, + nonce: Option, + gas_limit: Option, + ) -> Result<(Vec, TransactionReceipt, [u8; 32])> { + let result = self.wallet.eth_send_call(contract, call, amount, nonce, gas_limit).await?; + let (result, receipt, tx_hash) = match result { + SubmitResult::Executed { result, receipt, tx_hash } => (result, receipt, tx_hash), + SubmitResult::Timeout { tx_hash } => { + anyhow::bail!("tx 0x{} timed out", hex::encode(tx_hash)) + }, + }; + let result = match result { + CallResult::Success(result) => { + tracing::info!("tx 0x{} succeeded", hex::encode(tx_hash)); + result + }, + CallResult::Revert(reason) => { + anyhow::bail!( + "tx 0x{} reverted because {}", + hex::encode(tx_hash), + hex::encode(reason) + ); + }, + CallResult::Error => anyhow::bail!("tx 0x{} failed", hex::encode(tx_hash)), + }; + Ok((result, receipt, tx_hash.into())) + } + async fn evm_call( &self, contract: Address, @@ -80,20 +116,9 @@ impl Connector { gas_limit: Option, ) -> Result<(T::Return, TransactionReceipt, [u8; 32])> { let contract: [u8; 20] = contract[12..32].try_into().unwrap(); - let result = self - .wallet - .eth_send_call(contract, call.abi_encode(), amount, nonce, gas_limit) - .await?; - let SubmitResult::Executed { - result: CallResult::Success(result), - receipt, - tx_hash, - } = result - else { - anyhow::bail!("{:?}", result) - }; - tracing::info!("evm_call success: {:?}", tx_hash); - Ok((T::abi_decode_returns(&result, true)?, receipt, tx_hash.into())) + let (result, receipt, tx_hash) = + self.raw_evm_call(contract, call.abi_encode(), amount, nonce, gas_limit).await?; + Ok((T::abi_decode_returns(&result, true)?, receipt, tx_hash)) } async fn evm_view( @@ -116,10 +141,16 @@ impl Connector { ) -> Result<(Address, u64)> { let mut contract = get_contract_from_slice(abi)?; contract.extend(constructor.abi_encode()); - let tx_hash = self.wallet.eth_deploy_contract(contract).await?.tx_hash().0; - let tx_receipt = self.wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); + let tx_hash = self.wallet.eth_deploy_contract(contract).await?.tx_hash(); + let tx_receipt = self.wallet.eth_transaction_receipt(tx_hash.0).await?.unwrap(); let address = tx_receipt.contract_address.unwrap(); let block_number = tx_receipt.block_number.unwrap(); + tracing::info!( + "contract deployed at {:?} in block {} with tx {:?}", + address, + block_number, + tx_hash + ); Ok((t_addr(address.0.into()), block_number)) } @@ -130,33 +161,20 @@ impl Connector { factory_address: [u8; 20], call: Vec, ) -> Result<(AlloyAddress, u64)> { - let result = self - .wallet - .eth_send_call(factory_address, call, 0, None, Some(20_000_000)) - .await?; - let SubmitResult::Executed { result, receipt, tx_hash } = result else { - anyhow::bail!("tx timed out"); - }; - match result { - CallResult::Success(_) => { - let log = receipt - .logs - .iter() - .find(|log| log.address.as_bytes() == factory_address) - .with_context(|| format!("Log with factory address not found: {}", tx_hash))?; - let topic = log - .topics - .first() - .with_context(|| "Unable to find topics in tx receipt")? - .as_bytes(); - let contract_address = AlloyAddress::from_slice(&topic[12..]); - Ok((contract_address, receipt.block_number.unwrap())) - }, - CallResult::Revert(reason) => { - anyhow::bail!("Deployment reverted: {tx_hash:?}: {:?}", hex::encode(reason)) - }, - CallResult::Error => anyhow::bail!("Failed to deploy contract"), - } + let (_, receipt, tx_hash) = + self.raw_evm_call(factory_address, call, 0, None, Some(20_000_000)).await?; + let log = receipt + .logs + .iter() + .find(|log| log.address.as_bytes() == factory_address) + .with_context(|| format!("tx {} logs not found", hex::encode(tx_hash)))?; + let topic = log + .topics + .first() + .with_context(|| format!("tx {} topic not found", hex::encode(tx_hash)))? + .as_bytes(); + let contract_address = AlloyAddress::from_slice(&topic[12..]); + Ok((contract_address, receipt.block_number.unwrap())) } async fn deploy_factory(&self, config: &DeploymentConfig) -> Result<()> { @@ -171,7 +189,7 @@ impl Connector { //Step3: send eth_rawTransaction let tx_hash = self.backend.send_raw_transaction(tx.into()).await?; - tracing::info!("factory deployed with tx: {:?}", tx_hash); + tracing::info!("factory deployed with tx {:?}", tx_hash); Ok(()) } @@ -201,7 +219,7 @@ impl Connector { let factory_address = a_addr(self.parse_address(&config.factory_address)?).0 .0; let (gateway_address, _) = self.deploy_contract_with_factory(factory_address, gateway_create_call).await?; - tracing::info!("gateway deployed at: {:?}", gateway_address); + tracing::info!("gateway deployed at {}", gateway_address); Ok(gateway_address) } @@ -280,7 +298,7 @@ impl Connector { proxy_address ); } - tracing::info!("proxy deployed at: {}", proxy_address); + tracing::info!("proxy deployed at {}", proxy_address); Ok((proxy_address, block)) } @@ -375,15 +393,17 @@ impl IConnectorBuilder for Connector { where Self: Sized, { + let (blockchain, private_key) = if params.blockchain == "anvil" { + let private_key = hex_literal::hex![ + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + ]; + (Blockchain::Ethereum, Some(private_key)) + } else { + (params.blockchain.parse()?, None) + }; let wallet = Arc::new( - Wallet::new( - params.blockchain.parse()?, - ¶ms.network, - ¶ms.url, - ¶ms.mnemonic, - None, - ) - .await?, + Wallet::new(blockchain, ¶ms.network, ¶ms.url, ¶ms.mnemonic, private_key) + .await?, ); let client = default_client(¶ms.url, None) .await @@ -393,6 +413,7 @@ impl IConnectorBuilder for Connector { network_id: params.network_id, wallet, backend: adapter, + url: params.url, cctp_sender: params.cctp_sender, cctp_attestation: params.cctp_attestation.unwrap_or("".into()), cctp_queue: Default::default(), @@ -426,16 +447,7 @@ impl IChain for Connector { (config.currency_decimals, config.currency_symbol) } /// Uses a faucet to fund the account when possible. - async fn faucet(&self) -> Result<()> { - let balance = match self.network_id() { - 6 => 10u128.pow(25), // astar - 2 => 10u128.pow(29), // ethereum - 3 => 10u128.pow(29), // ethereum - network_id => { - tracing::info!("network {network_id} doesn't support faucet"); - return Ok(()); - }, - }; + async fn faucet(&self, balance: u128) -> Result<()> { self.wallet.faucet(balance, None).await?; Ok(()) } @@ -605,10 +617,8 @@ impl IConnectorAdmin for Connector { // check if uf already deployed let config: DeploymentConfig = serde_json::from_slice(additional_params)?; let factory_address = a_addr(self.parse_address(&config.factory_address)?).0 .0; - let is_factory_deployed = self - .backend - .get_code(factory_address.into(), rosetta_ethereum_types::AtBlock::Latest) - .await?; + let is_factory_deployed = + self.backend.get_code(factory_address.into(), AtBlock::Latest).await?; if is_factory_deployed.is_empty() { self.deploy_factory(&config).await?; @@ -618,10 +628,7 @@ impl IConnectorAdmin for Connector { let proxy_addr = self.compute_proxy_address(&config, proxy)?; // check if proxy is deployed - let is_proxy_deployed = self - .backend - .get_code(proxy_addr.into(), rosetta_ethereum_types::AtBlock::Latest) - .await?; + let is_proxy_deployed = self.backend.get_code(proxy_addr.into(), AtBlock::Latest).await?; if !is_proxy_deployed.is_empty() { tracing::debug!("Proxy already deployed, Please upgrade the gateway contract"); @@ -690,15 +697,48 @@ impl IConnectorAdmin for Connector { self.evm_call(gateway, call, 0, None, None).await?; Ok(()) } + /// Estimates the message gas limit. + async fn estimate_message_gas_limit( + &self, + contract: Address, + src_network: NetworkId, + src: Address, + payload: Vec, + ) -> Result { + let call = sol::IGmpReceiver::onGmpReceivedCall { + id: [0; 32].into(), + network: src_network.into(), + source: src.into(), + nonce: 0, + payload: payload.into(), + }; + let gas_limit = self + .wallet + .eth_send_call_estimate_gas(a_addr(contract).into(), call.abi_encode(), 0) + .await?; + Ok(gas_limit) + } /// Estimates the message cost. - async fn estimate_message_cost(&self, gateway: Address, msg: &GmpMessage) -> Result { - let dest = msg.dest_network; - let gas_limit = msg.gas_limit; - let msg: sol::GmpMessage = msg.clone().into(); - let bytes = msg.abi_encode(); + async fn estimate_message_cost( + &self, + gateway: Address, + dest_network: NetworkId, + gas_limit: u128, + payload: Vec, + ) -> Result { + let msg = sol::GmpMessage { + source: [0; 32].into(), + srcNetwork: 0, + dest: [0; 20].into(), + destNetwork: 0, + gasLimit: 0, + nonce: 0, + data: payload.into(), + }; let call = sol::Gateway::estimateMessageCostCall { - networkid: dest, - messageSize: U256::from(bytes.len()), + networkid: dest_network, + // abi_encoded_size returns the size without the 4 byte selector + messageSize: U256::from(msg.abi_encoded_size() + 4), gasLimit: U256::from(gas_limit), }; let result = self.evm_view(gateway, call, None).await?; @@ -711,12 +751,26 @@ impl IConnectorAdmin for Connector { .await } /// Sends a message using the test contract. - async fn send_message(&self, msg: GmpMessage) -> Result { - anyhow::ensure!(msg.src_network == self.network_id, "invalid source network id"); - let src = msg.src; - let gas_cost = msg.gas_cost; - let call = sol::GmpTester::sendMessageCall { msg: msg.into() }; - let result = self.evm_call(src, call, gas_cost, None, None).await?; + async fn send_message( + &self, + contract: Address, + dest_network: NetworkId, + dest: Address, + gas_limit: u128, + gas_cost: u128, + payload: Vec, + ) -> Result { + let msg = sol::GmpMessage { + srcNetwork: self.network_id, + source: contract.into(), + destNetwork: dest_network, + dest: a_addr(dest), + nonce: 0, + gasLimit: gas_limit as _, + data: payload.into(), + }; + let call = sol::GmpTester::sendMessageCall { msg }; + let result = self.evm_call(contract, call, gas_cost, None, None).await?; let id: MessageId = *result.0._0; Ok(id) } @@ -789,6 +843,26 @@ impl IConnectorAdmin for Connector { self.evm_call(gateway, call, 0, None, None).await?; Ok(()) } + /// Debug a transaction. + async fn debug_transaction(&self, hash: Hash) -> Result { + let analog_gmp_dir = + std::env::var("ANALOG_GMP_DIR").context("failed to find ANALOG_GMP_DIR")?; + let output = Command::new("cast") + .current_dir(analog_gmp_dir) + .arg("run") + .arg("--rpc-url") + .arg(&self.url) + .arg("--with-local-artifacts") + .arg(hex::encode(hash)) + .output() + .context("failed to run cast")?; + if !output.status.success() { + let err = std::str::from_utf8(&output.stderr).ok().unwrap_or_default(); + anyhow::bail!("cast exited with {}: {err}", output.status); + } + let stdout = std::str::from_utf8(&output.stdout)?; + Ok(stdout.into()) + } } fn compute_create2_address( diff --git a/gmp/evm/src/sol.rs b/gmp/evm/src/sol.rs index bf8e396dd..9f73f1e06 100644 --- a/gmp/evm/src/sol.rs +++ b/gmp/evm/src/sol.rs @@ -184,6 +184,13 @@ alloy_sol_types::sol! { payable returns (address); } + + interface IGmpReceiver { + function onGmpReceived(bytes32 id, uint128 network, bytes32 source, uint64 nonce, bytes calldata payload) + external + payable + returns (bytes32); + } } pub fn u256(bytes: &[u8]) -> U256 { diff --git a/gmp/grpc/build.rs b/gmp/grpc/build.rs index cf469d2a7..968e58ec2 100644 --- a/gmp/grpc/build.rs +++ b/gmp/grpc/build.rs @@ -29,6 +29,7 @@ fn main() { .method(method("routes", "Routes").build()) .method(method("set_route", "SetRoute").build()) .method(method("deploy_test", "DeployTest").build()) + .method(method("estimate_message_gas_limit", "EstimateMessageGasLimit").build()) .method(method("estimate_message_cost", "EstimateMessageCost").build()) .method(method("send_message", "SendMessage").build()) .method(method("recv_messages", "RecvMessages").build()) diff --git a/gmp/grpc/src/lib.rs b/gmp/grpc/src/lib.rs index 65a668d6a..186971cae 100644 --- a/gmp/grpc/src/lib.rs +++ b/gmp/grpc/src/lib.rs @@ -98,8 +98,8 @@ impl IChain for Connector { self.address } /// Uses a faucet to fund the account when possible. - async fn faucet(&self) -> Result<()> { - let request = Request::new(proto::FaucetRequest {}); + async fn faucet(&self, balance: u128) -> Result<()> { + let request = Request::new(proto::FaucetRequest { balance }); self.client.lock().await.faucet(request).await?; Ok(()) } @@ -255,15 +255,59 @@ impl IConnectorAdmin for Connector { let response = self.client.lock().await.deploy_test(request).await?.into_inner(); Ok((response.address, response.block)) } + /// Estimates the message gas limit. + async fn estimate_message_gas_limit( + &self, + contract: Address, + src_network: NetworkId, + src: Address, + payload: Vec, + ) -> Result { + let request = Request::new(proto::EstimateMessageGasLimitRequest { + contract, + src_network, + src, + payload, + }); + let response = + self.client.lock().await.estimate_message_gas_limit(request).await?.into_inner(); + Ok(response.gas_limit) + } /// Estimates the message cost. - async fn estimate_message_cost(&self, gateway: Address, msg: &GmpMessage) -> Result { - let request = Request::new(proto::EstimateMessageCostRequest { gateway, msg: msg.clone() }); + async fn estimate_message_cost( + &self, + gateway: Address, + dest_network: NetworkId, + gas_limit: u128, + payload: Vec, + ) -> Result { + let request = Request::new(proto::EstimateMessageCostRequest { + gateway, + dest_network, + gas_limit, + payload, + }); let response = self.client.lock().await.estimate_message_cost(request).await?.into_inner(); Ok(response.cost) } - /// Sends a message using the test contract. - async fn send_message(&self, msg: GmpMessage) -> Result { - let request = Request::new(proto::SendMessageRequest { msg }); + /// Sends a message using the test contract and returns the message id. + async fn send_message( + &self, + src: Address, + dest_network: NetworkId, + dest: Address, + gas_limit: u128, + gas_cost: u128, + payload: Vec, + ) -> Result { + let request = Request::new(proto::SendMessageRequest { + src, + dest_network, + dest, + gas_limit, + gas_cost, + payload, + }); let response = self.client.lock().await.send_message(request).await?.into_inner(); Ok(response.message_id) } diff --git a/gmp/grpc/src/main.rs b/gmp/grpc/src/main.rs index b3fd032d5..dfa10e9c1 100644 --- a/gmp/grpc/src/main.rs +++ b/gmp/grpc/src/main.rs @@ -54,8 +54,11 @@ impl Gmp for ConnectorWrapper { &self, request: Request, ) -> GmpResult { - let (connector, _) = self.connector(request)?; - connector.faucet().await.map_err(|err| Status::unknown(err.to_string()))?; + let (connector, msg) = self.connector(request)?; + connector + .faucet(msg.balance) + .await + .map_err(|err| Status::unknown(err.to_string()))?; Ok(Response::new(proto::FaucetResponse {})) } @@ -243,13 +246,25 @@ impl Gmp for ConnectorWrapper { Ok(Response::new(proto::DeployTestResponse { address, block })) } + async fn estimate_message_gas_limit( + &self, + request: Request, + ) -> GmpResult { + let (connector, msg) = self.connector(request)?; + let gas_limit = connector + .estimate_message_gas_limit(msg.contract, msg.src_network, msg.src, msg.payload) + .await + .map_err(|err| Status::unknown(err.to_string()))?; + Ok(Response::new(proto::EstimateMessageGasLimitResponse { gas_limit })) + } + async fn estimate_message_cost( &self, request: Request, ) -> GmpResult { let (connector, msg) = self.connector(request)?; let cost = connector - .estimate_message_cost(msg.gateway, &msg.msg) + .estimate_message_cost(msg.gateway, msg.dest_network, msg.gas_limit, msg.payload) .await .map_err(|err| Status::unknown(err.to_string()))?; Ok(Response::new(proto::EstimateMessageCostResponse { cost })) @@ -261,7 +276,14 @@ impl Gmp for ConnectorWrapper { ) -> GmpResult { let (connector, msg) = self.connector(request)?; let message_id = connector - .send_message(msg.msg) + .send_message( + msg.src, + msg.dest_network, + msg.dest, + msg.gas_limit, + msg.gas_cost, + msg.payload, + ) .await .map_err(|err| Status::unknown(err.to_string()))?; Ok(Response::new(proto::SendMessageResponse { message_id })) diff --git a/gmp/grpc/src/proto.rs b/gmp/grpc/src/proto.rs index 350d8dae4..de32c9914 100644 --- a/gmp/grpc/src/proto.rs +++ b/gmp/grpc/src/proto.rs @@ -1,12 +1,14 @@ use serde::{Deserialize, Serialize}; use serde_big_array::Array; use time_primitives::{ - Address, BatchId, Gateway, GatewayMessage, GmpEvent, GmpMessage, MessageId, Route, + Address, BatchId, Gateway, GatewayMessage, GmpEvent, GmpMessage, MessageId, NetworkId, Route, TssPublicKey, TssSignature, }; #[derive(Serialize, Deserialize)] -pub struct FaucetRequest {} +pub struct FaucetRequest { + pub balance: u128, +} #[derive(Serialize, Deserialize)] pub struct FaucetResponse {} @@ -162,10 +164,25 @@ pub struct DeployTestResponse { pub block: u64, } +#[derive(Serialize, Deserialize)] +pub struct EstimateMessageGasLimitRequest { + pub contract: Address, + pub src_network: NetworkId, + pub src: Address, + pub payload: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct EstimateMessageGasLimitResponse { + pub gas_limit: u128, +} + #[derive(Serialize, Deserialize)] pub struct EstimateMessageCostRequest { pub gateway: Address, - pub msg: GmpMessage, + pub dest_network: NetworkId, + pub gas_limit: u128, + pub payload: Vec, } #[derive(Serialize, Deserialize)] @@ -175,7 +192,12 @@ pub struct EstimateMessageCostResponse { #[derive(Serialize, Deserialize)] pub struct SendMessageRequest { - pub msg: GmpMessage, + pub src: Address, + pub dest_network: NetworkId, + pub dest: Address, + pub gas_limit: u128, + pub gas_cost: u128, + pub payload: Vec, } #[derive(Serialize, Deserialize)] diff --git a/gmp/rust/src/lib.rs b/gmp/rust/src/lib.rs index d5d8c9926..e25dd8bc4 100644 --- a/gmp/rust/src/lib.rs +++ b/gmp/rust/src/lib.rs @@ -23,7 +23,6 @@ use time_primitives::{ const BLOCK_TIME: u64 = 1; const FINALIZATION_TIME: u64 = 2; -const FAUCET: u128 = 1_000_000_000; const BLOCKS: TableDefinition = TableDefinition::new("blocks"); const BALANCE: TableDefinition = TableDefinition::new("balance"); @@ -193,12 +192,11 @@ impl IChain for Connector { currency() } - async fn faucet(&self) -> Result<()> { + async fn faucet(&self, balance: u128) -> Result<()> { let tx = self.db.begin_write()?; { let mut t = tx.open_table(BALANCE)?; - let balance = read_balance(&t, self.address)?; - t.insert(self.address, balance + FAUCET)?; + t.insert(self.address, balance)?; } tx.commit()?; Ok(()) @@ -449,27 +447,57 @@ impl IConnectorAdmin for Connector { Ok((tester, block)) } - async fn estimate_message_cost(&self, _gateway: Address, msg: &GmpMessage) -> Result { - Ok(msg.bytes.len() as u128 * 100) + async fn estimate_message_gas_limit( + &self, + _contract: Address, + _src_network: NetworkId, + _src: Address, + _payload: Vec, + ) -> Result { + Ok(100_000) } - async fn send_message(&self, mut msg: GmpMessage) -> Result { - anyhow::ensure!(msg.src_network == self.network_id, "invalid source network id"); - let addr = msg.src; + async fn estimate_message_cost( + &self, + _gateway: Address, + _dest_network: NetworkId, + gas_limit: u128, + payload: Vec, + ) -> Result { + Ok(gas_limit + payload.len() as u128 * 100) + } + async fn send_message( + &self, + src: Address, + dest_network: NetworkId, + dest: Address, + gas_limit: u128, + gas_cost: u128, + payload: Vec, + ) -> Result { let tx = self.db.begin_write()?; let id = { // read nonce let mut t = tx.open_table(NONCE)?; - let nonce = t.get((msg.src, addr))?.map(|a| a.value()).unwrap_or_default(); - // set nonce - msg.nonce = nonce; + let nonce = t.get((src, dest))?.map(|a| a.value()).unwrap_or_default(); + // construct msg + let msg = GmpMessage { + src_network: self.network_id, + src, + dest_network, + dest, + nonce, + gas_limit, + gas_cost, + bytes: payload, + }; let id = msg.message_id(); // increment nonce - t.insert((msg.src, addr), nonce + 1)?; + t.insert((src, dest), nonce + 1)?; // read gateway address let t = tx.open_table(GATEWAY)?; - let gateway = t.get(addr)?.context("tester not deployed")?.value(); + let gateway = t.get(src)?.context("tester not deployed")?.value(); // insert gateway event let mut t = tx.open_multimap_table(EVENTS)?; @@ -591,8 +619,8 @@ mod tests { src, dest, nonce: 0, - gas_limit: 0, - gas_cost: 0, + gas_limit: 100_000, + gas_cost: 100_000, bytes: vec![], } } @@ -603,8 +631,8 @@ mod tests { let chain = connector(network, 0).await?; let shard = MockTssSigner::new(0); assert_eq!(chain.balance(chain.address()).await?, 0); - chain.faucet().await?; - assert_eq!(chain.balance(chain.address()).await?, FAUCET); + chain.faucet(100_000).await?; + assert_eq!(chain.balance(chain.address()).await?, 100_000); let (gateway, block) = chain.deploy_gateway("".as_ref(), "".as_ref(), "".as_ref()).await?; chain.transfer(gateway, 10_000).await?; assert_eq!(chain.balance(gateway).await?, 10_000); @@ -615,9 +643,14 @@ mod tests { assert_eq!(events, vec![GmpEvent::ShardRegistered(shard.public_key())]); let (src, _) = chain.deploy_test(gateway, "".as_ref()).await?; let (dest, _) = chain.deploy_test(gateway, "".as_ref()).await?; - let mut msg = gmp_msg(src, dest); - msg.gas_cost = chain.estimate_message_cost(gateway, &msg).await?; - chain.send_message(msg.clone()).await?; + let payload = vec![]; + let gas_limit = + chain.estimate_message_gas_limit(dest, network, src, payload.clone()).await?; + let gas_cost = chain + .estimate_message_cost(gateway, network, gas_limit, payload.clone()) + .await?; + chain.send_message(src, network, dest, gas_limit, gas_cost, payload).await?; + let msg = gmp_msg(src, dest); let current2 = chain.block_stream().next().await.unwrap(); let events = chain.read_events(gateway, current..current2).await?; assert_eq!(events, vec![GmpEvent::MessageReceived(msg.clone())]); diff --git a/primitives/src/gmp.rs b/primitives/src/gmp.rs index d8e9ba010..00581e1b2 100644 --- a/primitives/src/gmp.rs +++ b/primitives/src/gmp.rs @@ -354,7 +354,7 @@ pub trait IChain: Send + Sync + 'static { /// Human readable connector account identifier. fn address(&self) -> Address; /// Uses a faucet to fund the account when possible. - async fn faucet(&self) -> Result<()>; + async fn faucet(&self, balance: u128) -> Result<()>; /// Transfers an amount to an account. async fn transfer(&self, address: Address, amount: u128) -> Result<()>; /// Queries the account balance. @@ -412,10 +412,32 @@ pub trait IConnectorAdmin: IConnector { async fn set_route(&self, gateway: Address, route: Route) -> Result<()>; /// Deploys a test contract. async fn deploy_test(&self, gateway: Address, tester: &[u8]) -> Result<(Address, u64)>; + /// Estimates the message gas limit. + async fn estimate_message_gas_limit( + &self, + contract: Address, + src_network: NetworkId, + src: Address, + payload: Vec, + ) -> Result; /// Estimates the message cost. - async fn estimate_message_cost(&self, gateway: Address, msg: &GmpMessage) -> Result; + async fn estimate_message_cost( + &self, + gateway: Address, + dest_network: NetworkId, + gas_limit: u128, + payload: Vec, + ) -> Result; /// Sends a message using the test contract and returns the message id. - async fn send_message(&self, msg: GmpMessage) -> Result; + async fn send_message( + &self, + src: Address, + dest_network: NetworkId, + dest: Address, + gas_limit: u128, + gas_cost: u128, + payload: Vec, + ) -> Result; /// Receives messages from test contract. async fn recv_messages(&self, contract: Address, blocks: Range) -> Result>; @@ -425,6 +447,10 @@ pub trait IConnectorAdmin: IConnector { async fn block_gas_limit(&self) -> Result; /// Withdraw gateway funds. async fn withdraw_funds(&self, gateway: Address, amount: u128, address: Address) -> Result<()>; + /// Debug a transaction. + async fn debug_transaction(&self, _tx: Hash) -> Result { + anyhow::bail!("debugging transactions is not supported on this backend"); + } } #[cfg(feature = "std")] diff --git a/scripts/build_docker.sh b/scripts/build_docker.sh index c2ffb2089..eaeb8a11e 100755 --- a/scripts/build_docker.sh +++ b/scripts/build_docker.sh @@ -68,6 +68,8 @@ forge build --root analog-gmp mkdir -p target/docker/tc-cli rm -rf target/docker/tc-cli/envs cp -rL config/envs target/docker/tc-cli/envs +rm -rf target/docker/tc-cli/analog-gmp +cp -r analog-gmp target/docker/tc-cli/analog-gmp build_image () { local TARGET="target/$rustTarget/$profile/$1" diff --git a/tc-cli/src/config.rs b/tc-cli/src/config.rs index cefafc845..8e51f0b4b 100644 --- a/tc-cli/src/config.rs +++ b/tc-cli/src/config.rs @@ -96,7 +96,7 @@ struct ConfigYaml { #[serde(deny_unknown_fields)] pub struct GlobalConfig { prices_path: PathBuf, - pub chronicle_timechain_funds: String, + pub chronicle_funds: String, pub timechain_url: String, } @@ -124,8 +124,9 @@ pub struct NetworkConfig { pub blockchain: String, pub network: String, pub url: String, + pub admin_funds: Option, pub gateway_funds: String, - pub chronicle_target_funds: String, + pub chronicle_funds: String, pub batch_size: u32, pub batch_offset: u32, pub batch_gas_limit: u128, diff --git a/tc-cli/src/lib.rs b/tc-cli/src/lib.rs index e96ac31e1..cf13a8986 100644 --- a/tc-cli/src/lib.rs +++ b/tc-cli/src/lib.rs @@ -215,7 +215,22 @@ impl Tc { } pub async fn faucet(&self, network: NetworkId) -> Result<()> { - self.connector(network)?.faucet().await + let config = self.config.network(network)?; + let Some(admin_funds) = config.admin_funds.as_ref() else { + return Ok(()); + }; + let admin_funds = self.parse_balance(Some(network), admin_funds)?; + let current_admin_funds = self.balance(Some(network), self.address(Some(network))?).await?; + let faucet = admin_funds - current_admin_funds; + if faucet == 0 { + return Ok(()); + } + self.println( + None, + format!("faucet {network} {}", self.format_balance(Some(network), faucet)?), + ) + .await?; + self.connector(network)?.faucet(faucet).await } pub async fn balance(&self, network: Option, address: Address) -> Result { @@ -667,7 +682,7 @@ impl Tc { self.set_network_config(network).await?; gateway } else { - self.println(None, "deploying gateway").await?; + self.println(None, format!("deploying gateway {network}")).await?; let (gateway, block) = connector .deploy_gateway(&contracts.additional_params, &contracts.proxy, &contracts.gateway) .await?; @@ -848,10 +863,7 @@ impl Tc { impl Tc { pub async fn deploy_network(&self, network: NetworkId) -> Result { let config = self.config.network(network)?; - if self.balance(Some(network), self.address(Some(network))?).await? == 0 { - self.println(None, "admin target balance is 0, using faucet").await?; - self.faucet(network).await?; - } + self.faucet(network).await?; let gateway = self.register_network(network).await?; let gateway_funds = self.parse_balance(Some(network), &config.gateway_funds)?; self.fund(Some(network), gateway, gateway_funds, "gateway").await?; @@ -860,16 +872,16 @@ impl Tc { pub async fn deploy_chronicle(&self, chronicle: &str) -> Result<()> { let chronicle = self.wait_for_chronicle(chronicle).await?; - let funds = self.parse_balance(None, &self.config.global().chronicle_timechain_funds)?; + let funds = self.parse_balance(None, &self.config.global().chronicle_funds)?; self.fund(None, chronicle.account.clone().into(), funds, "chronicle timechain account") .await?; let config = self.config.network(chronicle.network)?; - let chronicle_target_funds = - self.parse_balance(Some(chronicle.network), &config.chronicle_target_funds)?; + let chronicle_funds = + self.parse_balance(Some(chronicle.network), &config.chronicle_funds)?; self.fund( Some(chronicle.network), chronicle.address, - chronicle_target_funds, + chronicle_funds, "chronicle target account", ) .await?; @@ -933,36 +945,67 @@ impl Tc { connector.deploy_test(gateway, &contracts.tester).await } - pub async fn estimate_message_cost(&self, msg: &GmpMessage) -> Result { - let (connector, gateway) = self.gateway(msg.src_network).await?; - connector.estimate_message_cost(gateway, msg).await + pub async fn estimate_message_gas_limit( + &self, + dest_network: NetworkId, + dest_addr: Address, + src_network: NetworkId, + src_addr: Address, + payload: Vec, + ) -> Result { + let connector = self.connector(dest_network)?; + connector + .estimate_message_gas_limit(dest_addr, src_network, src_addr, payload) + .await + } + + pub async fn estimate_message_cost( + &self, + src_network: NetworkId, + dest_network: NetworkId, + gas_limit: u128, + payload: Vec, + ) -> Result { + let (connector, gateway) = self.gateway(src_network).await?; + connector.estimate_message_cost(gateway, dest_network, gas_limit, payload).await } - pub async fn send_message(&self, msg: GmpMessage) -> Result { - let connector = self.connector(msg.src_network)?; - let dest_network = msg.dest_network; - let dest = msg.dest; - let gas_cost = msg.gas_cost; + #[allow(clippy::too_many_arguments)] + pub async fn send_message( + &self, + src_network: NetworkId, + src_addr: Address, + dest_network: NetworkId, + dest_addr: Address, + gas_limit: u128, + gas_cost: u128, + payload: Vec, + ) -> Result { + let connector = self.connector(src_network)?; let id = self .println( None, format!( - "send message to {} {} @{}gas", + "send message to {} {} with {} gas for {}", dest_network, - self.format_address(Some(dest_network), dest)?, - gas_cost, + self.format_address(Some(dest_network), dest_addr)?, + gas_limit, + self.format_balance(Some(src_network), gas_cost)?, ), ) .await?; - let msg_id = connector.send_message(msg).await?; + let msg_id = connector + .send_message(src_addr, dest_network, dest_addr, gas_limit, gas_cost, payload) + .await?; self.println( Some(id), format!( - "sent message {} to {} {} @{}gas", + "sent message {} to {} {} with {} gas for {}", hex::encode(msg_id), dest_network, - self.format_address(Some(dest_network), dest)?, - gas_cost, + self.format_address(Some(dest_network), dest_addr)?, + gas_limit, + self.format_balance(Some(src_network), gas_cost)?, ), ) .await?; @@ -1091,4 +1134,9 @@ impl Tc { let logs = loki::logs(query, since).await?; self.print_table(None, "logs", logs).await } + + pub async fn debug_transaction(&self, network: NetworkId, hash: Hash) -> Result { + let connector = self.connector(network)?; + connector.debug_transaction(hash).await + } } diff --git a/tc-cli/src/main.rs b/tc-cli/src/main.rs index d5c802c91..bd761d309 100644 --- a/tc-cli/src/main.rs +++ b/tc-cli/src/main.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; use std::path::PathBuf; use std::str::FromStr; use tc_cli::{Query, Sender, Tc}; -use time_primitives::{BatchId, GmpMessage, NetworkId, ShardId, TaskId}; +use time_primitives::{BatchId, Hash, NetworkId, ShardId, TaskId}; use tracing_subscriber::filter::EnvFilter; #[derive(Clone, Debug)] @@ -145,11 +145,27 @@ enum Command { network_id: NetworkId, batch_id: BatchId, }, + EstimateMessageGasLimit { + dest_network: NetworkId, + dest_addr: String, + src_network: NetworkId, + src_addr: String, + payload: String, + }, + EstimateMessageGasCost { + src_network: NetworkId, + dest_network: NetworkId, + gas_limit: u128, + payload: String, + }, SendMessage { - network: NetworkId, - tester: String, - dest: NetworkId, + src_network: NetworkId, + src_addr: String, + dest_network: NetworkId, dest_addr: String, + gas_limit: u128, + gas_cost: u128, + payload: String, }, SmokeTest { src: NetworkId, @@ -174,6 +190,10 @@ enum Command { ForceShardOffline { shard_id: ShardId, }, + DebugTransaction { + network: NetworkId, + hash: String, + }, } #[tokio::main] @@ -190,7 +210,9 @@ async fn main() { } async fn real_main() -> Result<()> { - let filter = EnvFilter::from_default_env().add_directive("tc_cli=info".parse()?); + let filter = EnvFilter::from_default_env() + .add_directive("tc_cli=info".parse().unwrap()) + .add_directive("gmp_evm=info".parse().unwrap()); tracing_subscriber::fmt().with_env_filter(filter).init(); let sender = Sender::new(); let args = Args::parse(); @@ -336,44 +358,69 @@ async fn real_main() -> Result<()> { Command::CompleteBatch { network_id, batch_id } => { tc.complete_batch(network_id, batch_id).await? }, + Command::EstimateMessageGasLimit { + dest_network, + dest_addr, + src_network, + src_addr, + payload, + } => { + let src_addr = tc.parse_address(Some(src_network), &src_addr)?; + let dest_addr = tc.parse_address(Some(dest_network), &dest_addr)?; + let payload = hex::decode(payload)?; + let gas_limit = tc + .estimate_message_gas_limit(dest_network, dest_addr, src_network, src_addr, payload) + .await?; + tc.println(None, gas_limit.to_string()).await?; + }, + Command::EstimateMessageGasCost { + src_network, + dest_network, + gas_limit, + payload, + } => { + let payload = hex::decode(payload)?; + let gas_cost = + tc.estimate_message_cost(src_network, dest_network, gas_limit, payload).await?; + tc.println(None, gas_cost.to_string()).await?; + }, Command::SendMessage { - network, - tester, - dest, + src_network, + src_addr, + dest_network, dest_addr, + gas_limit, + gas_cost, + payload, } => { - let tester = tc.parse_address(Some(network), &tester)?; - let dest_addr = tc.parse_address(Some(dest), &dest_addr)?; - let mut msg = GmpMessage { - src_network: network, - src: tester, - dest_network: dest, - dest: dest_addr, - nonce: 0, - gas_limit: 300_000, - gas_cost: 0, - bytes: vec![], - }; - msg.gas_cost = tc.estimate_message_cost(&msg).await?; - let msg_id = tc.send_message(msg).await?; + let src_addr = tc.parse_address(Some(src_network), &src_addr)?; + let dest_addr = tc.parse_address(Some(dest_network), &dest_addr)?; + let payload = hex::decode(payload)?; + let msg_id = tc + .send_message( + src_network, + src_addr, + dest_network, + dest_addr, + gas_limit, + gas_cost, + payload, + ) + .await?; tc.println(None, hex::encode(msg_id)).await?; }, Command::SmokeTest { src, dest } => { let (src_addr, dest_addr) = tc.setup_test(src, dest).await?; let mut blocks = tc.finality_notification_stream(); let (_, start) = blocks.next().await.context("expected block")?; - let mut msg = GmpMessage { - src_network: src, - src: src_addr, - dest_network: dest, - dest: dest_addr, - nonce: 0, - gas_limit: 300_000, - gas_cost: 0, - bytes: vec![], - }; - msg.gas_cost = tc.estimate_message_cost(&msg).await?; - let msg_id = tc.send_message(msg).await?; + let payload = vec![]; + let gas_limit = tc + .estimate_message_gas_limit(dest, dest_addr, src, src_addr, payload.clone()) + .await?; + let gas_cost = tc.estimate_message_cost(src, dest, gas_limit, payload.clone()).await?; + let msg_id = tc + .send_message(src, src_addr, dest, dest_addr, gas_limit, gas_cost, payload.clone()) + .await?; let mut id = None; let (exec, end) = loop { let (_, end) = blocks.next().await.context("expected block")?; @@ -402,22 +449,24 @@ async fn real_main() -> Result<()> { let mut blocks = tc.finality_notification_stream(); let (_, start) = blocks.next().await.context("expected block")?; let mut msgs = HashSet::new(); - let mut gas_cost = 0; + let payload = vec![]; + let gas_limit = tc + .estimate_message_gas_limit(dest, dest_addr, src, src_addr, payload.clone()) + .await?; + let gas_cost = tc.estimate_message_cost(src, dest, gas_limit, payload.clone()).await?; for _ in 0..num_messages { - let mut msg = GmpMessage { - src_network: src, - src: src_addr, - dest_network: dest, - dest: dest_addr, - nonce: 0, - gas_limit: 300_000, - gas_cost: 0, - bytes: vec![], - }; - msg.gas_cost = tc.estimate_message_cost(&msg).await?; - gas_cost += msg.gas_cost; - let msg = tc.send_message(msg).await?; - msgs.insert(msg); + let msg_id = tc + .send_message( + src, + src_addr, + dest, + dest_addr, + gas_limit, + gas_cost, + payload.clone(), + ) + .await?; + msgs.insert(msg_id); } let mut id = None; while let Some((_, block)) = blocks.next().await { @@ -434,14 +483,14 @@ async fn real_main() -> Result<()> { let num_received = num_messages - msgs.len() as u16; let blocks = block - start; let throughput = num_received as f64 / blocks as f64; - let avg_gas_cost = gas_cost / num_messages as u128; id = Some( tc.println( id, format!( r#"{num_received} out of {num_messages} received in {blocks} blocks throughput {throughput:.3} msgs/block - avg gas cost {avg_gas_cost}"# + msg cost {}"#, + tc.format_balance(Some(src), gas_cost)?, ), ) .await?, @@ -461,6 +510,13 @@ async fn real_main() -> Result<()> { let address = tc.parse_address(Some(network), &address)?; tc.withdraw_funds(network, amount, address).await?; }, + Command::DebugTransaction { network, hash } => { + let hash = hash.strip_prefix("0x").unwrap_or(&hash); + let hash: Hash = + hex::decode(hash)?.try_into().map_err(|_| anyhow::anyhow!("invalid hash"))?; + let output = tc.debug_transaction(network, hash).await?; + tc.println(None, output).await?; + }, } tracing::info!("executed query in {}s", now.elapsed().unwrap().as_secs()); Ok(())