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

GMP V2 #28

Merged
merged 36 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3be877e
Update events
Lohann Nov 12, 2024
558e0f2
Fix unit tests
Lohann Nov 13, 2024
603413f
Create GmpCallback
Lohann Nov 13, 2024
40a81ec
Update gas values
Lohann Nov 13, 2024
74088e7
Check for target chain block gas limit before accept GMP message
Lohann Nov 13, 2024
e350659
Remove unused code from gateway
Lohann Nov 13, 2024
ae16b6f
Update gas meter again..
Lohann Nov 13, 2024
9932eb5
Remove unused code
Lohann Nov 13, 2024
c68ce25
Add docs to 'GmpCallback' struct
Lohann Nov 13, 2024
bfb187f
Refactor variable names
Lohann Nov 13, 2024
a4e4f22
Improve documentation
Lohann Nov 13, 2024
0e600bb
Fix unit tests
Lohann Nov 29, 2024
7ec87bb
GatewayProxy
Lohann Nov 29, 2024
f69c65e
allow build src
Lohann Nov 29, 2024
3a941f5
Fixed unit tests
Lohann Nov 29, 2024
f9e6eae
Remove unused code
Lohann Nov 29, 2024
8f604eb
Cleanup TestUtils.sol
Lohann Nov 29, 2024
dba2c1b
Remove unused import
Lohann Nov 29, 2024
c34bd5a
update refund constants
Lohann Nov 29, 2024
7e34a3b
Remove more commented code
Lohann Nov 29, 2024
d371876
Replace shards
Lohann Dec 3, 2024
0ec53a5
update shards
Lohann Dec 3, 2024
ca8c5a4
add admin method
Lohann Dec 3, 2024
af3baf3
accept 3 or 2 as parity bit
Lohann Dec 3, 2024
1f9e31b
Fix update route method
Lohann Dec 3, 2024
9234bc0
created GmpProxy
Lohann Dec 3, 2024
074bad7
Remove unused code
Lohann Dec 3, 2024
fca744f
Fix unit tests
Lohann Dec 10, 2024
cd8069c
Update remaining tests
Lohann Dec 10, 2024
f63bee7
forge fmt
Lohann Dec 10, 2024
4a23994
Remove source contract bitflag
Lohann Dec 10, 2024
0944826
Updates GmpProxy.sol (#29)
haider-rs Dec 12, 2024
e5651e9
Merge branch 'origin/main' into feature/rebase-gmp-v2
Lohann Dec 16, 2024
4c59910
Remove duplicated
Lohann Dec 16, 2024
1465b46
forge fmt
Lohann Dec 16, 2024
5156fbf
Improve foundry.toml docs
Lohann Dec 16, 2024
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
48 changes: 44 additions & 4 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,24 +1,64 @@
[profile.default]
src = "src"
test = "test"
out = "out"
libs = ["lib"]
match_contract = ".+Test"
# Permissions
fs_permissions = [{ access = "read", path = "./lib/universal-factory/abi" }]

# Lint
########
# Lint #
########
deny_warnings = true

# Solc options
solc = '0.8.25'
################
# Solc options #
################
solc = '0.8.28'
# Keep `shanghai` once blockchains such as `Astar/Shibuya` and
# `Ethereum Classic` doesn't support `cancun` yet.
# - https://github.com/rust-ethereum/evm/issues/290
# - https://ethereumclassic.org/knowledge/history
evm_version = 'shanghai'
optimizer = true
optimizer_runs = 200000

# EVM options
###############
# EVM options #
###############
gas_limit = 30000000
gas_price = 1
block_base_fee_per_gas = 0
block_gas_limit = 30000000

#####################
# optimizer details #
#####################
[profile.default.optimizer_details]
yul = true
# The peephole optimizer is always on if no details are given,
# use details to switch it off.
peephole = true
# The inliner is always off if no details are given,
# use details to switch it on.
inliner = true
# The unused jumpdest remover is always on if no details are given,
# use details to switch it off.
jumpdest_remover = true
# Sometimes re-orders literals in commutative operations.
order_literals = true
# Removes duplicate code blocks
deduplicate = false
# Common subexpression elimination, this is the most complicated step but
# can also provide the largest gain.
cse = true
# Optimize representation of literal numbers and strings in code.
constant_optimizer = true
# Use unchecked arithmetic when incrementing the counter of for loops
# under certain circumstances. It is always on if no details are given.
simple_counter_for_loop_unchecked_increment = true

# Fuzz tests options
[fuzz]
# Reduce the numbers of runs if fuzz tests takes too long in your machine.
Expand Down
22 changes: 20 additions & 2 deletions scripts/Deploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,31 @@ import {
TssKey,
GmpMessage,
UpdateKeysMessage,
UpdateNetworkInfo,
Signature,
Network,
GmpStatus,
GmpSender,
PrimitiveUtils
} from "../src/Primitives.sol";

/**
* @dev Message payload used to update the network info.
* @param networkId Domain EIP-712 - Replay Protection Mechanism.
* @param domainSeparator Domain EIP-712 - Replay Protection Mechanism.
* @param gasLimit The maximum amount of gas we allow on this particular network.
* @param relativeGasPrice Gas price of destination chain, in terms of the source chain token.
* @param baseFee Base fee for cross-chain message approval on destination, in terms of source native gas token.
* @param mortality maximum block in which this message is valid.
*/
struct UpdateNetworkInfo {
uint16 networkId;
bytes32 domainSeparator;
uint64 gasLimit;
UFloat9x56 relativeGasPrice;
uint128 baseFee;
uint64 mortality;
}

contract MigrateGateway is Script {
using NetworkIDHelpers for NetworkID;
using FactoryUtils for IUniversalFactory;
Expand Down Expand Up @@ -477,7 +494,8 @@ contract MigrateGateway is Script {
bytes memory initializer = abi.encodeCall(Gateway.initialize, (config.proxyAdmin, emptyShards, emptyNetworks));

vm.startBroadcast(deployer);
address deployed = address(new GatewayProxy(implementation, initializer));
// address deployed = address(new GatewayProxy(implementation, initializer));
address deployed = address(new GatewayProxy(config.proxyAdmin)); // TODO: fix me
vm.stopBroadcast();
console.log(" PROXY ADDRESS", deployed);
console.log(" DEPLOYMENT STATUS", deployed == config.proxy ? "Success" : "Address Mismatch");
Expand Down
92 changes: 36 additions & 56 deletions src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

pragma solidity >=0.8.0;

import {Schnorr} from "@frost-evm/Schnorr.sol";
import {Schnorr} from "./utils/Schnorr.sol";
import {BranchlessMath} from "./utils/BranchlessMath.sol";
import {GasUtils} from "./utils/GasUtils.sol";
import {ERC1967} from "./utils/ERC1967.sol";
Expand All @@ -18,7 +18,6 @@ import {
TssKey,
GmpMessage,
UpdateKeysMessage,
UpdateNetworkInfo,
Signature,
Network,
Route,
Expand Down Expand Up @@ -61,7 +60,6 @@ abstract contract GatewayEIP712 {

contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
using PrimitiveUtils for UpdateKeysMessage;
using PrimitiveUtils for UpdateNetworkInfo;
using PrimitiveUtils for GmpMessage;
using PrimitiveUtils for address;
using BranchlessMath for uint256;
Expand All @@ -83,6 +81,11 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
bytes32 private constant GMP_CREATED_EVENT_SELECTOR =
0x0114885f90b5168242aa31b7afb9c2e9f88e90ce329c893d3e6c56021c4c03a5;

/**
* @dev The address of the `UniversalFactory` contract, must be the same on all networks.
*/
address internal constant FACTORY = 0x0000000000001C4Bf962dF86e38F0c10c7972C6E;

// GMP message status
mapping(bytes32 => GmpInfo) private _messages;

Expand All @@ -104,20 +107,6 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
uint64 blockNumber; // block in which the message was processed
}

/**
* @dev Network info stored in the Gateway Contract
* @param domainSeparator Domain EIP-712 - Replay Protection Mechanism.
* @param gasLimit The maximum amount of gas we allow on this particular network.
* @param relativeGasPrice Gas price of destination chain, in terms of the source chain token.
* @param baseFee Base fee for cross-chain message approval on destination, in terms of source native gas token.
*/
struct NetworkInfo {
bytes32 domainSeparator;
uint64 gasLimit;
UFloat9x56 relativeGasPrice;
uint128 baseFee;
}

/**
* @dev Network info stored in the Gateway Contract
* @param id Message unique id.
Expand All @@ -139,18 +128,17 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
constructor(uint16 network, address proxy) payable GatewayEIP712(network, proxy) {}

// EIP-712 typed hash
function initialize(address admin, TssKey[] calldata keys, Network[] calldata networks) external {
require(PROXY_ADDRESS == address(this), "only proxy can be initialize");
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(admin);
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), computeDomainSeparator);
// _updateNetworks(networks);

// Register keys
ShardStore.getMainStorage().registerTssKeys(keys);
Expand Down Expand Up @@ -250,25 +238,20 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
assembly {
// Using low-level assembly because the GMP is considered executed
// regardless if the call reverts or not.
let ptr := add(callback, 32)
let size := mload(callback)
mstore(callback, 0)

// returns 1 if the call succeed, and 0 if it reverted
mstore(0, 0)
success :=
call(
gasLimit, // call gas limit defined in the GMP message or 50% of the block gas limit
dest, // dest address
0, // value in wei to transfer (always zero for GMP)
ptr, // input memory pointer
size, // input size
callback, // output memory pointer
add(callback, 32), // input memory pointer
mload(callback), // input size
0, // output memory pointer
32 // output size (fixed 32 bytes)
)

// Get Result, reuse data to keep a predictable memory expansion
result := mload(callback)
mstore(callback, size)
result := mload(0)
}

// Update GMP status
Expand All @@ -293,7 +276,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
uint256 initialGas = gasleft();
// Add the solidity selector overhead to the initial gas, this way we guarantee that
// the `initialGas` represents the actual gas that was available to this contract.
initialGas = initialGas.saturatingAdd(437);
initialGas = initialGas.saturatingAdd(GasUtils.EXECUTION_SELECTOR_OVERHEAD);

// Theoretically we could remove the destination network field
// and fill it up with the network id of the contract, then the signature will fail.
Expand All @@ -315,7 +298,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
// Refund the chronicle gas
unchecked {
// Compute GMP gas used
uint256 gasUsed = 7223;
uint256 gasUsed = 7152;
gasUsed = gasUsed.saturatingAdd(GasUtils.txBaseCost());
gasUsed = gasUsed.saturatingAdd(GasUtils.proxyOverheadGasCost(uint16(msg.data.length), 64));
gasUsed = gasUsed.saturatingAdd(initialGas - gasleft());
Expand Down Expand Up @@ -352,7 +335,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
require(msg.value >= route.estimateWeiCost(data, executionGasLimit), "insufficient tx value");

// We use 20 bytes for represent the address and 1 bit for the contract flag
GmpSender source = msg.sender.toSender(tx.origin != msg.sender);
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;
Expand Down Expand Up @@ -422,7 +405,6 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
view
returns (uint256)
{
// NetworkInfo storage network = _networkInfo[networkid];
RouteStore.NetworkInfo memory route = RouteStore.getMainStorage().get(NetworkID.wrap(networkid));

// Estimate the cost
Expand All @@ -442,7 +424,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
* @param data The data to send to the recipient (in case it is a contract)
*/
function withdraw(uint256 amount, address recipient, bytes calldata data) external returns (bytes memory output) {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
// Check if the recipient is a contract
if (recipient.code.length > 0) {
bool success;
Expand Down Expand Up @@ -483,38 +465,38 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
*/
function shardAt(uint256 index) external view returns (TssKey memory) {
(ShardStore.ShardID xCoord, ShardStore.ShardInfo storage shard) = ShardStore.getMainStorage().at(index);
return TssKey({xCoord: uint256(ShardStore.ShardID.unwrap(xCoord)), yParity: shard.yParity});
return TssKey({xCoord: uint256(ShardStore.ShardID.unwrap(xCoord)), yParity: shard.yParity + 2});
}

/**
* @dev Register a single Shards with provided TSS public key.
*/
function setShard(TssKey calldata publicKey) external {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().register(publicKey);
}

/**
* @dev Register Shards in batch.
*/
function setShard(TssKey[] calldata publicKeys) external {
require(msg.sender == _getAdmin(), "unauthorized");
ShardStore.getMainStorage().registerTssKeys(publicKeys);
function setShards(TssKey[] calldata publicKeys) external {
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().replaceTssKeys(publicKeys);
}

/**
* @dev Revoke a single shard TSS Key.
*/
function revokeShard(TssKey calldata publicKey) external {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().revoke(publicKey);
}

/**
* @dev Revoke Shards in batch.
*/
function revokeShard(TssKey[] calldata publicKeys) external {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().revokeKeys(publicKeys);
}

Expand All @@ -533,15 +515,15 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
* @dev Create or update a single route
*/
function setRoute(Route calldata info) external {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
RouteStore.getMainStorage().createOrUpdateRoute(info);
}

/**
* @dev Create or update an array of routes
*/
function setRoute(Route[] calldata values) external {
require(msg.sender == _getAdmin(), "unauthorized");
function setRoutes(Route[] calldata values) external {
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
require(values.length > 0, "routes cannot be empty");
RouteStore.MainStorage storage store = RouteStore.getMainStorage();
for (uint256 i = 0; i < values.length; i++) {
Expand All @@ -553,33 +535,31 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
ADMIN LOGIC
//////////////////////////////////////////////////////////////*/

function _getAdmin() private view returns (address admin) {
admin = ERC1967.getAdmin();
// If the admin slot is empty, then the 0xd4833be6144AF48d4B09E5Ce41f826eEcb7706D6 is the admin
admin = BranchlessMath.ternary(admin == address(0x0), 0xd4833be6144AF48d4B09E5Ce41f826eEcb7706D6, admin);
function admin() external view returns (address) {
return ERC1967.getAdmin();
}

function setAdmin(address newAdmin) external payable {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ERC1967.setAdmin(newAdmin);
}

// OBS: remove != revoke (when revoked, you cannot register again)
function sudoRemoveShards(TssKey[] calldata revokedKeys) external payable {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().revokeKeys(revokedKeys);
emit KeySetChanged(bytes32(0), revokedKeys, new TssKey[](0));
}

function sudoAddShards(TssKey[] calldata newKeys) external payable {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().registerTssKeys(newKeys);
emit KeySetChanged(bytes32(0), new TssKey[](0), newKeys);
}

// DANGER: This function is for migration purposes only, it allows the admin to set any storage slot.
function sudoSetStorage(uint256[2][] calldata values) external payable {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
require(values.length > 0, "invalid values");

uint256 prev = 0;
Expand All @@ -604,7 +584,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
}

function upgrade(address newImplementation) external payable {
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");

// Store the address of the implementation contract
ERC1967.setImplementation(newImplementation);
Expand All @@ -615,7 +595,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
payable
returns (bytes memory returndata)
{
require(msg.sender == _getAdmin(), "unauthorized");
require(msg.sender == ERC1967.getAdmin(), "unauthorized");

// Store the address of the implementation contract
ERC1967.setImplementation(newImplementation);
Expand Down
Loading
Loading