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 #23

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open

GMP V2 #23

wants to merge 17 commits into from

Conversation

Lohann
Copy link
Collaborator

@Lohann Lohann commented Oct 3, 2024

New Design

The new design supports batching and easier signature verification, the only entry point for all timechain messages is the submitV1 method, that accept an "generic" InboundMessage as parameter, this design is inspired in Snowbridge Gateway Contract. the InboundMessage.params is parsed based on InboundMessage.command.

struct GmpMessage { ... }
struct TssKey { ... }
struct SetRoute { ... }
struct Migrate { ... }

/**
 * @dev Messages from Timechain take the form of these commands.
 */
enum Command {
    GMP,
    SetShards,
    SetRoute,
    Migrate
}

struct InboundMessage {
    /// @dev The signature of the message
    Signature signature;
    /// @dev The channel nonce
    uint64 nonce;
    /// @dev The maximum gas allowed for message dispatch
    uint64 maxDispatchGas;
    /// @dev The maximum fee per gas
    uint256 maxFeePerGas;
    /// @dev The command to execute
    Command command;
    /// @dev The Parameters for the command
    bytes params;
}

/**
 * @dev Required interface of an Gateway compliant contract
 */
interface IExecutor {
    event InboundMessageDispatched(
	bytes32 indexed id,
	TssKey[] shardKey,
    );
    event GmpExecuted(
        bytes32 indexed id, GmpSender indexed source, address indexed dest, GmpStatus status, bytes32 result
    );
    event RouteUpdated(
        uint16 indexed networkId,
        UFloat9x56 relativeGasPrice,
        uint128 baseFee,
        uint64 gasLimit
    );
    event ShardRegistered(TssKey[] keys);
    event ShardUnregistered(TssKey[] keys);
    event KeySetChanged(bytes32 indexed id, TssKey[] revoked, TssKey[] registered);

    
    error NotEnoughGas();
    error Unauthorized();

    /**
     * Execute any Inbound messages from a Timechain.
     * @param message timechain message.
     */
    function submitV1(InboundMessage calldata message) external payable;
}

Commands parameters

  • GMP: GmpMessage[].
  • SetShards: TssKey[] this method delete all existing set of keys, and replace by this new one.
  • SetRoute SetRoute[] update or create all provided routes.
  • Migrate (address implementation, bytes callback) contains the new implementation address, and an optional callback for migration purposes, the the bytes is empty, no callback is called. Obs: the gateway verifies if the implementation exists.

The method submitV1 ONLY verifies the signature in the InboundMessage.signature if the msg.sender != admin(), it basically means that the admin account can freely submit InboundMessages without replay protection, etc.

Permissionless Deployment

Motivation

@penumbra23 frequently get blocked due error prone manual steps required to deploy the Gateway + GatewayProxy for testing, and setup a new network currently rely in a Vanity privake-key hold by me, if this key ever get leaked or we do an mistake and use the nonce 0 of this key, we lose that address forever in the given network.
Also deploying a contract using a regular EOA doesn't emit any event, much less tell us what code was deployed.

Solution

This PR also includes some important changes that allows permissionless deployments of the gateway contract in any existing or future network, it allows chronicles or actually any person to deploy the gateway at an deterministic address in any future network, for that we use the novel Universal Factory together with Counterfactual Interactions, example:
https://github.com/Analog-Labs/universal-factory/tree/main/test/examples#2-callbacks-and-custom-owned-addresses

dvc94ch

This comment was marked as outdated.

/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 x) internal pure returns (uint256 r) {
function log2(uint256 x) internal pure returns (uint256) {
Copy link
Collaborator Author

@Lohann Lohann Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no logical changes here, just updated the documentation to match the PR I opened on OpenZeppelin SDK:
OpenZeppelin/openzeppelin-contracts#5236

@Lohann Lohann changed the title Setup network script GMP V2 Oct 10, 2024
@Lohann Lohann marked this pull request as ready for review October 10, 2024 14:25
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from Lohann Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from Lohann Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from dvc94ch Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from Lohann Oct 11, 2024
@Analog-Labs Analog-Labs deleted a comment from Lohann Oct 11, 2024
@0x1100010010
Copy link
Contributor

A few questions from team here @Lohann

  • How to list shards using this interface?
  • How to create signatures?
  • There are 2 GMP executed events, what are their role
  • U-256 in the interface requires special paring from Rust, is there any specific reason for this?
  • Whats the Nonce in this implementation?
  • Messge ordering implementation details
  • How devops is going to use Universal deployer, is there any usage doc?

@Lohann
Copy link
Collaborator Author

Lohann commented Oct 15, 2024

How to list shards using this interface?

Good point, this interface currently doesn't supports on-chain shard listing, currently the only way to list the shards is off-chain by listening to all ShardRegistered and ShardUnregistered events.

Why?
In solidity a Map is not iterable, to make it iterable requires creating a new Index Array, the drawback is that it requires an extra storage slot for store a shard, and update two slots for remove or edit an Shard.
To have O(1) cost for get/insert/remove a shard, the index must be bidirecional (the array index must be stored together with the value), which in some circumstances requires an extra storage slot. That's why I haven't added it before, because adds extra complexity, and is already possible to list all shards offchain if you build the state from all emited events, once AFAIK there was no requirement to list it on-chain, besides being easier to who use the api, it adds extra complexity to the code and a significant cost on-chain.

Unless there's a requirement to know the current Shard Set on-chain, my suggestion to this dilema is create the NewShardSet event, which contains the list with all active shards, and remove ShardRegistered and ShardUnregistered events.

@Lohann
Copy link
Collaborator Author

Lohann commented Oct 15, 2024

How to create signatures?

Signatures works as follow:

bytes32 constant private DOMAIN_SEPARATOR = ...; // EIP-712 domain separator 
bytes32 constant private INBOUND_MESSAGE_TYPE_HASH = keccak256("InboundMessage(uint64 nonce,uint64 maxDispatchGas,uint256 maxFeePerGas,uint256 command,bytes params)");
InboundMessage memory inbound = ...;
bytes32 paramsHash = keccak256(inbound.params);
bytes32 messageHash = keccak256(abi.encode(
    INBOUND_MESSAGE_TYPE_HASH,
    inbound.nonce,
    inbound.maxDispatchGas,
    inbound.maxFeePerGas,
    inbound.command,
    paramsHash
));
bytes32 sigBytes = abi.encodePacked(
    hex"1901",
    DOMAIN_SEPARATOR,
    messageHash
);
bytes32 sighash = keccak256(sigBytes);

Signature memory signature = sign(sighash);

The shards signs the sighash.
Obs: the signature is only required if the msg.sender is not the admin, the signature field is ignored if the admin is the sender.

@Lohann
Copy link
Collaborator Author

Lohann commented Oct 15, 2024

There are 2 GMP executed events, what are their role

My mistake, removed the copy.

@Lohann
Copy link
Collaborator Author

Lohann commented Oct 15, 2024

U256 in the interface requires special paring from Rust, is there any specific reason for this?

You mean the uint256 maxFeePerGas; field? Yes there's a reason for this, for the same reason the msg.value is an U256, because this field also represents a value and is the type used internally by any ethereum implementation. If you use an u128 to represent a value, you always have the risk of some chain with a lot of decimals overflow and break the protocol, the RUST code must adapt to the EVM interface, not the opposite.

Btw notice that even so rust doesn't support u256 natively, substrate supports it natively:
https://github.com/paritytech/polkadot-sdk/blob/v1.16.0-rc3/substrate/primitives/core/src/uint.rs#L20

Also u256 and i256 are PRIMITIVEs defined in the scale-codec :
https://github.com/paritytech/scale-info/blob/v2.11.3/src/ty/mod.rs#L375-L377

@Lohann
Copy link
Collaborator Author

Lohann commented Oct 15, 2024

Whats the Nonce in this implementation?

This have the same role as the nonce in a transaction, is sequence number, issued by the timechain, used to prevent message replay and guarantee consistency.

This is crucial, because assuming an asynchoronous unidirecional channel, the order on which the messages are processed ALTERS the final result, example: Execute a Register/Revoke in a different order, makes the final Shard Set be different, this is not desired, that's why the gateway enforces sequence.

@Lohann
Copy link
Collaborator Author

Lohann commented Oct 15, 2024

Message ordering implementation details

Currently the nonce is used as sequence, no parallelization is supported whoever this is not needed once messages can be executed in batches.

@Lohann
Copy link
Collaborator Author

Lohann commented Oct 15, 2024

How devops is going to use Universal deployer, is there any usage doc?

There's an exaustive documentation in the Universal Factory repo, with comments and code examples:
https://github.com/Analog-Labs/universal-factory

The documentation assumes the reader have some basic understand of Ethereum Protocol and smart-contract development, such as EIP-1014 CREATE2, Upgradeable Proxy Pattern, EIP-1153 Transient Storage VS regular Storage, transaction execution flow, reentrancy attacks, etc.

There was also an hands-on meeting in which @penumbra23, @ManojJiSharma, @BogiLoco and @foravneet and anyone interested participated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants