From 825d7d5f8ad2c3e096cf1cde89871b1a5cf0427a Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Sun, 5 Jan 2025 17:14:22 -0300 Subject: [PATCH] Add gasCost to GmpCreated --- src/Gateway.sol | 28 +++++++++++++++++++--------- src/interfaces/IGateway.sol | 4 +++- src/storage/Routes.sol | 23 +++++++++++++++++++++++ src/utils/GasUtils.sol | 6 +++--- test/Example.t.sol | 18 +++++++++++++++++- test/Gateway.t.sol | 35 +++++++++++++++++++++++++++-------- 6 files changed, 92 insertions(+), 22 deletions(-) diff --git a/src/Gateway.sol b/src/Gateway.sol index fdab92f..7c30cb2 100644 --- a/src/Gateway.sol +++ b/src/Gateway.sol @@ -60,10 +60,10 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { /** * @dev Selector of `GmpCreated` event. - * keccak256("GmpCreated(bytes32,bytes32,address,uint16,uint256,uint256,bytes)"); + * keccak256("GmpCreated(bytes32,bytes32,address,uint16,uint64,uint64,uint256,bytes)"); */ bytes32 private constant GMP_CREATED_EVENT_SELECTOR = - 0x0114885f90b5168242aa31b7afb9c2e9f88e90ce329c893d3e6c56021c4c03a5; + 0xd9047a7e6f40289fbb463a818e2a4e84b4ef712beab3dc322f4bd3ab13dd79a3; /** * @dev The address of the `UniversalFactory` contract, must be the same on all networks. @@ -500,7 +500,8 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { // Check if the provided parameters are valid // See `RouteStorage.estimateWeiCost` at `storage/Routes.sol` for more details. RouteStore.NetworkInfo memory route = RouteStore.getMainStorage().get(NetworkID.wrap(routeId)); - require(msg.value >= route.estimateWeiCost(data, executionGasLimit), "insufficient tx value"); + (uint256 gasCost, uint256 fee) = route.estimateCost(data, executionGasLimit); + require(msg.value >= fee, "insufficient tx value"); // We use 20 bytes for represent the address and 1 bit for the contract flag GmpSender source = msg.sender.toSender(false); @@ -514,7 +515,14 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { // Emit `GmpCreated` event without copy the data, to simplify the gas estimation. _emitGmpCreated( - message.eip712hash(), source, destinationAddress, routeId, executionGasLimit, nextNonce, message.data + message.eip712hash(), + source, + destinationAddress, + routeId, + executionGasLimit, + gasCost, + nextNonce, + message.data ); } @@ -527,6 +535,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { address destinationAddress, uint16 destinationNetwork, uint256 executionGasLimit, + uint256 gasCost, uint256 salt, bytes memory payload ) private { @@ -537,13 +546,14 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { // return prevHash; // ``` assembly { - let ptr := sub(payload, 0x80) - mstore(ptr, destinationNetwork) // dest network + let ptr := sub(payload, 0xa0) + mstore(add(ptr, 0x00), destinationNetwork) // dest network mstore(add(ptr, 0x20), executionGasLimit) // gas limit - mstore(add(ptr, 0x40), salt) // salt - mstore(add(ptr, 0x60), 0x80) // data offset + mstore(add(ptr, 0x40), gasCost) // gasCost + mstore(add(ptr, 0x60), salt) // salt + mstore(add(ptr, 0x80), 0xa0) // data offset let size := and(add(mload(payload), 31), 0xffffffe0) - size := add(size, 160) + size := add(size, 192) log4(ptr, size, GMP_CREATED_EVENT_SELECTOR, messageID, source, destinationAddress) mstore(0, messageID) return(0, 32) diff --git a/src/interfaces/IGateway.sol b/src/interfaces/IGateway.sol index 4ba3e87..9f98f1d 100644 --- a/src/interfaces/IGateway.sol +++ b/src/interfaces/IGateway.sol @@ -16,6 +16,7 @@ interface IGateway { * @param destinationAddress the target address on the destination chain. * @param destinationNetwork the target chain where the contract call will be made. * @param executionGasLimit the gas limit available for the contract call + * @param gasCost the gas limit available for the contract call * @param salt salt is equal to the previous message id (EIP-712 hash). * @param data message data with no specified format */ @@ -24,7 +25,8 @@ interface IGateway { bytes32 indexed source, address indexed destinationAddress, uint16 destinationNetwork, - uint256 executionGasLimit, + uint64 executionGasLimit, + uint64 gasCost, uint256 salt, bytes data ); diff --git a/src/storage/Routes.sol b/src/storage/Routes.sol index a32bf6f..0c7f798 100644 --- a/src/storage/Routes.sol +++ b/src/storage/Routes.sol @@ -19,6 +19,7 @@ library RouteStore { using EnumerableSet for EnumerableSet.Map; using NetworkIDHelpers for NetworkID; using UFloatMath for UFloat9x56; + using BranchlessMath for uint256; /** * @dev Namespace of the routes storage `analog.one.gateway.routes`. @@ -234,6 +235,28 @@ library RouteStore { require(messageSize <= MAX_PAYLOAD_SIZE, "maximum payload size exceeded"); } + /** + * @dev Utility function for measure the wei cost of a GMP message. + */ + function estimateCost(NetworkInfo memory route, bytes calldata data, uint256 gasLimit) + internal + pure + returns (uint256 gasCost, uint256 fee) + { + // Guarantee the networks exists and `data` is less than `MAX_PAYLOAD_SIZE` + _checkPreconditions(route, data.length, gasLimit); + + // Compute base cost + uint256 nonZeros = GasUtils.countNonZerosCalldata(data); + uint256 zeros = data.length - nonZeros; + + // Compute execution cost + gasCost = GasUtils.estimateGas(uint16(nonZeros), uint16(zeros), gasLimit); + + // Calculate the gas cost: gasPrice * gasCost + baseFee + fee = UFloatMath.saturatingMul(route.relativeGasPrice, gasCost).saturatingAdd(route.baseFee); + } + /** * @dev Utility function for measure the wei cost of a GMP message. */ diff --git a/src/utils/GasUtils.sol b/src/utils/GasUtils.sol index cf92685..13a5d5f 100644 --- a/src/utils/GasUtils.sol +++ b/src/utils/GasUtils.sol @@ -16,7 +16,7 @@ library GasUtils { * @dev How much gas is used until the first `gasleft()` instruction is executed in the `Gateway.batchExecute` method. * * HOW TO UPDATE THIS VALUE: - * 1. Run `forge test --match-test=TempTest --debug` + * 1. Run `forge test --match-test=test_gasMeter --fuzz-runs=1 --debug` * 2. Move the cursor until you enter the `src/Gateway.sol` file. * 3. Execute the opcodes until you reach the first `GAS` opcode. * 4. Execute the GAS opcode then copy the `Gas used in call` value to the constant below. @@ -30,7 +30,7 @@ library GasUtils { * @dev How much gas is used until the first `gasleft()` instruction is executed. * * HOW TO UPDATE THIS VALUE: - * 1. Run `forge test --match-test=TempTest --debug` + * 1. Run `forge test --match-test=test_submitMessageMeter --fuzz-runs=1 --debug` * 2. Move the cursor until you enter the `src/Gateway.sol` file. * 3. Execute the opcodes until you reach the first `GAS` opcode. * 4. Execute the GAS opcode then copy the `Gas used in call` value to the constant below. @@ -48,7 +48,7 @@ library GasUtils { /** * @dev Base cost of the `IGateway.submitMessage` method. */ - uint256 internal constant SUBMIT_BASE_COST = 23873; + uint256 internal constant SUBMIT_BASE_COST = 24116; /** * @dev Extra gas cost of the first `IGateway.submitMessage` method. diff --git a/test/Example.t.sol b/test/Example.t.sol index 111dac7..22ba360 100644 --- a/test/Example.t.sol +++ b/test/Example.t.sol @@ -15,6 +15,7 @@ import {GatewayProxy} from "../src/GatewayProxy.sol"; import {IGateway} from "../src/interfaces/IGateway.sol"; import {IGmpReceiver} from "../src/interfaces/IGmpReceiver.sol"; import {IExecutor} from "../src/interfaces/IExecutor.sol"; +import {GasUtils} from "../src/utils/GasUtils.sol"; import { GmpMessage, UpdateKeysMessage, @@ -113,11 +114,26 @@ contract ExampleTest is Test { data: abi.encode(MockERC20.CrossChainTransfer({from: ALICE, to: BOB, amount: 100})) }); + // Calculate the expect GMP gas cost + uint256 gasCost; + { + uint256 nonZeros = GasUtils.countNonZeros(gmp.data); + uint256 zeros = gmp.data.length - nonZeros; + gasCost = GasUtils.estimateGas(uint16(nonZeros), uint16(zeros), gmp.gasLimit); + } + // Expect `GmpCreated` to be emitted bytes32 messageID = gmp.eip712hash(); vm.expectEmit(true, true, true, true, address(srcGateway)); emit IGateway.GmpCreated( - messageID, GmpSender.unwrap(gmp.source), gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.salt, gmp.data + messageID, + GmpSender.unwrap(gmp.source), + gmp.dest, + gmp.destNetwork, + uint64(gmp.gasLimit), + uint64(gasCost), + gmp.salt, + gmp.data ); { diff --git a/test/Gateway.t.sol b/test/Gateway.t.sol index 602e347..9ecd4e0 100644 --- a/test/Gateway.t.sol +++ b/test/Gateway.t.sol @@ -507,7 +507,6 @@ contract GatewayTest is BaseTest { bytes memory encoded = abi.encodeCall(IGateway.submitMessage, (gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.data)); assertEq(encoded.length, ((gmp.data.length + 31) & 0xffe0) + 164, "wrong encoded length"); - emit log_named_bytes(" calldata", encoded); baseCost = TestUtils.calculateBaseCost(encoded); } @@ -533,8 +532,16 @@ contract GatewayTest is BaseTest { bytes32 id = gmp.eip712hash(); vm.expectEmit(true, true, true, true); emit IGateway.GmpCreated( - id, GmpSender.unwrap(gmp.source), gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.salt, gmp.data + id, + GmpSender.unwrap(gmp.source), + gmp.dest, + gmp.destNetwork, + uint64(gmp.gasLimit), + uint64(ctx.value), + gmp.salt, + gmp.data ); + console.log("expect: ", ctx.value); ctx.gasLimit += 17100; assertEq(ctx.submitMessage(gmp), id, "unexpected GMP id"); @@ -738,14 +745,19 @@ contract GatewayTest is BaseTest { vm.expectRevert("insufficient tx value"); ctx.submitMessage(gmp); - // Expect event + // Submit message with sufficient funds + ctx.value += 1; vm.expectEmit(true, true, true, true); emit IGateway.GmpCreated( - id, GmpSender.unwrap(gmp.source), gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.salt, gmp.data + id, + GmpSender.unwrap(gmp.source), + gmp.dest, + gmp.destNetwork, + uint64(gmp.gasLimit), + uint64(ctx.value), + gmp.salt, + gmp.data ); - - // Submit message with sufficient funds - ctx.value += 1; assertEq(ctx.submitMessage(gmp), id, "unexpected GMP id"); // Verify the gas cost @@ -759,7 +771,14 @@ contract GatewayTest is BaseTest { // Expect event vm.expectEmit(true, true, true, true); emit IGateway.GmpCreated( - id, GmpSender.unwrap(gmp.source), gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.salt, gmp.data + id, + GmpSender.unwrap(gmp.source), + gmp.dest, + gmp.destNetwork, + uint64(gmp.gasLimit), + uint64(ctx.value), + gmp.salt, + gmp.data ); assertEq(ctx.submitMessage(gmp), id, "unexpected GMP id"); assertEq(ctx.executionCost, expectedCost - 6800, "unexpected execution gas cost in second call");