Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make nonce sequential #31

Merged
merged 4 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 16 additions & 29 deletions src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)");
Expand All @@ -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
Expand All @@ -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));

Expand All @@ -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];
}
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
}
Expand Down
27 changes: 11 additions & 16 deletions src/Primitives.sol
Original file line number Diff line number Diff line change
Expand Up @@ -221,23 +221,15 @@ 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
}
_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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
17 changes: 3 additions & 14 deletions src/storage/Routes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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));
Expand Down
9 changes: 7 additions & 2 deletions src/utils/GasUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
13 changes: 7 additions & 6 deletions test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
}
Expand Down Expand Up @@ -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"
);

Expand Down Expand Up @@ -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({
Expand All @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
Loading