diff --git a/src/Gateway.sol b/src/Gateway.sol index 54505a5..10e6265 100644 --- a/src/Gateway.sol +++ b/src/Gateway.sol @@ -54,11 +54,6 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { using RouteStore for RouteStore.NetworkInfo; using NetworkIDHelpers for NetworkID; - /** - * @dev Non-zero value used to initialize the `prevMessageHash` storage - */ - bytes32 internal constant FIRST_MESSAGE_PLACEHOLDER = bytes32(uint256(2 ** 256 - 1)); - /** * @dev Selector of `GmpCreated` event. * keccak256("GmpCreated(bytes32,bytes32,address,uint16,uint256,uint256,bytes)"); @@ -75,7 +70,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { mapping(bytes32 => GmpInfo) private _messages; // Hash of the previous GMP message submitted. - bytes32 public prevMessageHash; + mapping(address => uint256) private _nonces; // Replay protection mechanism, stores the hash of the executed messages // messageHash => shardId @@ -97,13 +92,8 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { // EIP-712 typed hash function initialize(address proxyAdmin, TssKey[] calldata keys, Network[] calldata networks) external { require(PROXY_ADDRESS == address(this) || msg.sender == FACTORY, "only proxy can be initialize"); - require(prevMessageHash == 0, "already initialized"); ERC1967.setAdmin(proxyAdmin); - // Initialize the prevMessageHash with a non-zero value to avoid the first GMP to spent more gas, - // once initialize the storage cost 21k gas, while alter it cost just 2800 gas. - prevMessageHash = FIRST_MESSAGE_PLACEHOLDER; - // Register networks RouteStore.getMainStorage().initialize(networks, NetworkID.wrap(NETWORK_ID)); @@ -115,6 +105,10 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { emit KeySetChanged(bytes32(0), revoked, keys); } + function nonceOf(address account) external view returns (uint256) { + return _nonces[account]; + } + function gmpInfo(bytes32 id) external view returns (GmpInfo memory) { return _messages[id]; } @@ -302,31 +296,24 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { // We use 20 bytes for represent the address and 1 bit for the contract flag GmpSender source = msg.sender.toSender(false); - // Salt is equal to the previous message id (EIP-712 hash), this allows us to establish a sequence and eaily query the message history. - bytes32 prevHash = prevMessageHash; - - // if the messageHash is the first message, we use a zero salt - uint256 salt = BranchlessMath.ternary(prevHash == FIRST_MESSAGE_PLACEHOLDER, 0, uint256(prevHash)); + // Nonce is per sender, it's incremented for every message sent. + uint256 nextNonce = _nonces[msg.sender]++; - // Create GMP message and update prevMessageHash - bytes memory payload; - { - GmpMessage memory message = - GmpMessage(source, NETWORK_ID, destinationAddress, routeId, executionGasLimit, salt, data); - prevHash = message.eip712hash(); - prevMessageHash = prevHash; - payload = message.data; - } + // Create GMP message and update nonce + GmpMessage memory message = + GmpMessage(source, NETWORK_ID, destinationAddress, routeId, executionGasLimit, nextNonce, data); // Emit `GmpCreated` event without copy the data, to simplify the gas estimation. - _emitGmpCreated(prevHash, source, destinationAddress, routeId, executionGasLimit, salt, payload); + _emitGmpCreated( + message.eip712hash(), source, destinationAddress, routeId, executionGasLimit, nextNonce, message.data + ); } /** * @dev Emit `GmpCreated` event without copy the data, to simplify the gas estimation. */ function _emitGmpCreated( - bytes32 prevHash, + bytes32 messageID, GmpSender source, address destinationAddress, uint16 destinationNetwork, @@ -348,8 +335,8 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { mstore(add(ptr, 0x60), 0x80) // data offset let size := and(add(mload(payload), 31), 0xffffffe0) size := add(size, 160) - log4(ptr, size, GMP_CREATED_EVENT_SELECTOR, prevHash, source, destinationAddress) - mstore(0, prevHash) + log4(ptr, size, GMP_CREATED_EVENT_SELECTOR, messageID, source, destinationAddress) + mstore(0, messageID) return(0, 32) } } diff --git a/src/Primitives.sol b/src/Primitives.sol index 70698c4..424f410 100644 --- a/src/Primitives.sol +++ b/src/Primitives.sol @@ -221,11 +221,7 @@ library PrimitiveUtils { } } - function memToCallback(GmpMessage memory message) - internal - pure - returns (GmpCallback memory callback) - { + function memToCallback(GmpMessage memory message) internal pure returns (GmpCallback memory callback) { MessagePtr ptr; assembly { ptr := message @@ -233,11 +229,7 @@ library PrimitiveUtils { _intoCallback(ptr, false, callback); } - function intoCallback(GmpMessage calldata message) - internal - pure - returns (GmpCallback memory callback) - { + function intoCallback(GmpMessage calldata message) internal pure returns (GmpCallback memory callback) { MessagePtr ptr; assembly { ptr := message @@ -288,10 +280,7 @@ library PrimitiveUtils { * @param message GmpMessage from calldata to be encoded * @param callback `GmpCallback` struct */ - function _intoCallback(MessagePtr message, bool isCalldata, GmpCallback memory callback) - private - pure - { + function _intoCallback(MessagePtr message, bool isCalldata, GmpCallback memory callback) private pure { // | MEMORY OFFSET | RESERVED FIELD | // | 0x0000..0x0020 <- GmpCallback.eip712hash // | 0x0020..0x0040 <- GmpCallback.source @@ -319,7 +308,10 @@ library PrimitiveUtils { callback.salt = m.salt; callback.callback = abi.encodeWithSignature( "onGmpReceived(bytes32,uint128,bytes32,bytes)", - callback.eip712hash, callback.srcNetwork, callback.source, m.data + callback.eip712hash, + callback.srcNetwork, + callback.source, + m.data ); } else { GmpMessage memory m = _intoMemoryPointer(message); @@ -331,7 +323,10 @@ library PrimitiveUtils { callback.salt = m.salt; callback.callback = abi.encodeWithSignature( "onGmpReceived(bytes32,uint128,bytes32,bytes)", - callback.eip712hash, callback.srcNetwork, callback.source, m.data + callback.eip712hash, + callback.srcNetwork, + callback.source, + m.data ); } // Compute the message ID diff --git a/src/storage/Routes.sol b/src/storage/Routes.sol index e153de6..a32bf6f 100644 --- a/src/storage/Routes.sol +++ b/src/storage/Routes.sol @@ -45,12 +45,7 @@ library RouteStore { * @param baseFee Base fee for cross-chain message approval on destination, in terms of source native gas token. * @param gasLimit The maximum amount of gas we allow on this particular network. */ - event RouteUpdated( - uint16 indexed networkId, - UFloat9x56 relativeGasPrice, - uint128 baseFee, - uint64 gasLimit - ); + event RouteUpdated(uint16 indexed networkId, UFloat9x56 relativeGasPrice, uint128 baseFee, uint64 gasLimit); /** * @dev Shard info stored in the Gateway Contract @@ -179,9 +174,7 @@ library RouteStore { stored.baseFee = route.baseFee; } - emit RouteUpdated( - route.networkId.asUint(), stored.relativeGasPrice, stored.baseFee, stored.gasLimit - ); + emit RouteUpdated(route.networkId.asUint(), stored.relativeGasPrice, stored.baseFee, stored.gasLimit); } /** @@ -190,11 +183,7 @@ library RouteStore { * @param networks List of networks to initialize. * @param networkdID The network id of this chain. */ - function initialize( - MainStorage storage store, - Network[] calldata networks, - NetworkID networkdID - ) internal { + function initialize(MainStorage storage store, Network[] calldata networks, NetworkID networkdID) internal { for (uint256 i = 0; i < networks.length; i++) { Network calldata network = networks[i]; (bool created, NetworkInfo storage info) = getOrAdd(store, NetworkID.wrap(network.id)); diff --git a/src/utils/GasUtils.sol b/src/utils/GasUtils.sol index e1ca990..cd2c86c 100644 --- a/src/utils/GasUtils.sol +++ b/src/utils/GasUtils.sol @@ -15,7 +15,7 @@ library GasUtils { /** * @dev How much gas is used until the first `gasleft()` instruction is executed. */ - uint256 internal constant EXECUTION_SELECTOR_OVERHEAD = 496; + uint256 internal constant EXECUTION_SELECTOR_OVERHEAD = 429 + 67; /** * @dev Base cost of the `IExecutor.execute` method. @@ -25,7 +25,12 @@ library GasUtils { /** * @dev Base cost of the `IGateway.submitMessage` method. */ - uint256 internal constant SUBMIT_BASE_COST = 23449; + uint256 internal constant SUBMIT_BASE_COST = 23525; + + /** + * @dev Extra gas cost of the first `IGateway.submitMessage` method. + */ + uint256 internal constant FIRST_MESSAGE_EXTRA_COST = 17100; /** * @dev Compute the gas cost of memory expansion. diff --git a/test/Gateway.t.sol b/test/Gateway.t.sol index 6a5a4db..feebe21 100644 --- a/test/Gateway.t.sol +++ b/test/Gateway.t.sol @@ -403,7 +403,7 @@ contract GatewayBase is Test { assertEq( ctx.executionCost, - GasUtils.submitMessageGasCost(uint16(gmp.data.length)) - 4500, + GasUtils.submitMessageGasCost(uint16(gmp.data.length)) - 4500 + 17100, "unexpected submit message gas cost" ); } @@ -458,12 +458,13 @@ contract GatewayBase is Test { emit IGateway.GmpCreated( id, GmpSender.unwrap(gmp.source), gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.salt, gmp.data ); + ctx.gasLimit += 17100; assertEq(ctx.submitMessage(gmp), id, "unexpected GMP id"); // Verify the execution cost assertEq( ctx.executionCost, - GasUtils.submitMessageGasCost(uint16(gmp.data.length)), + GasUtils.submitMessageGasCost(uint16(gmp.data.length)) + 17100, "unexpected submit message gas cost" ); @@ -622,7 +623,7 @@ contract GatewayBase is Test { ctx.execute(sig, gmp); } - function testSubmitGmpMessage() external { + function test_submitGmpMessage() external { vm.txGasPrice(1); GmpSender gmpSender = TestUtils.createTestAccount(1000 ether).toSender(false); GmpMessage memory gmp = GmpMessage({ @@ -637,7 +638,7 @@ contract GatewayBase is Test { bytes32 id = gmp.eip712hash(); // Check the previous message hash - assertEq(gateway.prevMessageHash(), bytes32(uint256(2 ** 256 - 1)), "wrong previous message hash"); + assertEq(gateway.nonceOf(gmp.source.toAddress()), 0, "wrong previous message hash"); CallOptions memory ctx = CallOptions({ from: gmpSender.toAddress(), @@ -672,10 +673,10 @@ contract GatewayBase is Test { // Verify the gas cost uint256 expectedCost = GasUtils.submitMessageGasCost(uint16(gmp.data.length)) - 6500; - assertEq(ctx.executionCost, expectedCost, "unexpected execution gas cost in first call"); + assertEq(ctx.executionCost, expectedCost + 17100, "unexpected execution gas cost in first call"); // Now the second GMP message should have the salt equals to previous gmp hash - gmp.salt = uint256(id); + gmp.salt = gateway.nonceOf(gmp.source.toAddress()); id = gmp.eip712hash(); // Expect event