From a342962e4def07faf1a6ebc7008d7697847923b7 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 4 Aug 2020 16:06:09 -0400 Subject: [PATCH 1/7] pushing chainid validation changes --- chains/ethereum/chain.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index 3ec70258a..c8d97a55c 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -21,6 +21,7 @@ The writer recieves the message and creates a proposals on-chain. Once a proposa package ethereum import ( + "fmt" "math/big" bridge "github.com/ChainSafe/ChainBridge/bindings/Bridge" @@ -111,7 +112,6 @@ func InitializeChain(chainCfg *core.ChainConfig, logger log15.Logger, sysErr cha if err != nil { return nil, err } - err = conn.EnsureHasBytecode(cfg.bridgeContract) if err != nil { return nil, err @@ -130,6 +130,11 @@ func InitializeChain(chainCfg *core.ChainConfig, logger log15.Logger, sysErr cha return nil, err } + chainId, err := bridgeContract.ChainID(conn.CallOpts()) + if chainId != uint8(chainCfg.Id) { + return nil, fmt.Errorf("chainId and configuration chainId do not match") + } + erc20HandlerContract, err := erc20Handler.NewERC20Handler(cfg.erc20HandlerContract, conn.Client()) if err != nil { return nil, err From 6fe0b9573d6b9a1215d2f424389229e79fd39897 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 5 Aug 2020 15:26:29 -0400 Subject: [PATCH 2/7] changing test --- chains/ethereum/chain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chains/ethereum/chain_test.go b/chains/ethereum/chain_test.go index cb6e05983..e05c412b4 100644 --- a/chains/ethereum/chain_test.go +++ b/chains/ethereum/chain_test.go @@ -21,7 +21,7 @@ func TestChain_ListenerShutdownOnFailure(t *testing.T) { client := ethtest.NewClient(t, TestEndpoint, AliceKp) contracts := deployTestContracts(t, client, msg.ChainId(1)) cfg := &core.ChainConfig{ - Id: msg.ChainId(0), + Id: msg.ChainId(1), Name: "alice", Endpoint: TestEndpoint, From: keystore.AliceKey, From 9177c1dcce7c232bc3d2d8bcb915723ee98b1966 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 7 Aug 2020 15:59:59 -0400 Subject: [PATCH 3/7] adding if statement for error --- chains/ethereum/chain.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index c8d97a55c..d4e67e9c9 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -131,6 +131,10 @@ func InitializeChain(chainCfg *core.ChainConfig, logger log15.Logger, sysErr cha } chainId, err := bridgeContract.ChainID(conn.CallOpts()) + if err != nil { + return nil, err + } + if chainId != uint8(chainCfg.Id) { return nil, fmt.Errorf("chainId and configuration chainId do not match") } From d8033d7a37b1b13ecf4858b168dedcee0cae6d9f Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 10 Aug 2020 12:48:24 -0400 Subject: [PATCH 4/7] added new chains page --- docs/chains/chains.md | 214 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 docs/chains/chains.md diff --git a/docs/chains/chains.md b/docs/chains/chains.md new file mode 100644 index 000000000..eee97d3fd --- /dev/null +++ b/docs/chains/chains.md @@ -0,0 +1,214 @@ +# Ethereum Implementation Specification + +- [Transfer Flow](#transfer-flow) + * [As Source Chain](#as-source-chain) + * [As Destination Chain](#as-destination-chain) +- [Bridge Contract](#bridge-contract) +- [Handler Contracts](#handler-contracts) + * [ERC20 & ERC721 Handlers](#erc20--erc721-handlers) + * [ERC20 Handler](#erc20-handler) + * [ERC721 Handler](#erc721-handler) + * [Generic Handler](#generic-handler) +- [Administration](#administration) + +The solidity implementation of ChainBridge should consist of a central Bridge contract, and will delegate specific functionality to [handlers](#handler-contracts). Fungible and non-fungible compatibility should be focused on ERC20 and ERC721 tokens. + +# Transfer Flow + +## As Source Chain +1. Some user calls the `deposit` function on the bridge contract. A `depositRecord` is created on the bridge and a call is delgated to a handler contract specified by the provided `resourceID`. + +2. The specified handler's `deposit` function validates the parameters provided by the user. If successful, a `depositRecord` is created on the handler. + +3. If the call delegated to the handler is succesful, the bridge emits a `Deposit` event. + +4. Relayers parse the `Deposit` event and retrieve the associated `DepositRecord` from the handler to construct a message. + + +## As Destination Chain +1. A Relayer calls `voteProposal` on the bridge contract. If a `proposal` corresponding with the parameters passed in does not exist, it is created and the Relayer's vote is recorded. If the proposal already exists, the Relayer's vote is simply recorded. + +3. Once we have met some vote threshold for a `proposal`, the bridge emits a `ProposalFinalized` event. + +4. Upon seeing a `ProposalFinalized` event, Relayers call the`executeDeposit`function on the bridge. `executeDeposit` delegates a call to a handler contract specified by the associated `resourceID`. + +5. The specified handler's `executeDeposit` function validates the paramters provided and makes a call to some contract to complete the transfer. + + +# Bridge Contract + +Users and relayers will interact with the `Bridge` contract. This delegates calls to the handler contracts for deposits and executing proposals. + +``` +function deposit (uint8 destinationChainID, bytes32 resourceID, bytes calldata data) +``` + +# Handler Contracts + +To provide modularity and break out the necessary contract logic, the implementation uses a notion of handlers. A handler is defined for ERC20, ERC721 and generic transfers. These map directly to the Fungible, Non-Fungible, and generic transfer types. + +A handler must fulfill two interfaces: +``` +// Will be called by the bridge contract to initiate a transfer +function deposit(uint8 destinationChainID, uint64 depositNonce, address depositer, bytes calldata data) +``` + +``` +// TODO: This would be more aptly named executeProposal +// Called by the bridge contract to complete a transfer +function executeDeposit(bytes calldata data) +``` + +The `calldata` field is the parameters required for the handler. The exact serialization is defined for each handler. + +## ERC20 & ERC721 Handlers + +These handlers share a lot of similarities. + +These handlers are responsible for transferring ERC assets. They should provide the ability for the bridge to take ownership of tokens and release tokens to execute transfers. + +Different configurations may require different interface interactions. For example, it may make sense to mint and burn a token that is originally from another chain. If supply needs to be controlled, transferring tokens in and out of a reserve may be desired instead. To support either case handlers should associate each resource ID/token contract with one of these: + +- `transferFrom()` - The user approves the handler to move the tokens prior to initiating the transfer. The handler will call `transferFrom()` as part of the transfer initiation. For the inverse, the handler will call `transfer()` to release tokens from the handlers ownership. +- `mint()`/`burn()` - The user approves the handler to move the tokens prior to initiating the transfer. The handler will call `burnFrom()` as part of the transfer initiation. For the inverse, the handler will call `mint()` to release tokens to the recipient (and must have privileges to do so). + + +## ERC20 Handler + +### Calldata for `deposit()` +| Data | Type | Location | +| - | - | - | +| Amount | uint256 | 0 - 31 | +| Recipient Address Length | uint256 | 32 - 63 | +| Recipient Address | bytes | 63 - END | + + +### Calldata for `executeDeposit()` +| Data | Type | Location | +| - | - | - | +| Amount | uint256 | 0 - 31 | +| Recipient Address Length | uint256 | 32 - 63 | +| Recipient Address | bytes | 64 - END | + +## ERC721 Handler + +### Metadata + +The `tokenURI` should be used as the `metadata` field if the contract supports the Metadata extension (interface ID `0x5b5e139f`). + + +### Calldata for `deposit()` +| Data | Type | Location | +| - | - | - | +| TokenID | uint256 | 0 - 31 | +| Recipient Address Length | uint256 | 32 - 63 | +| Recipient Address | bytes | 63 - END | + +### Calldata for `executeDeposit()` +| Data | Type | Location | +| - | - | - | +| TokenID | uint256 | 0 - 31 | +| Recipient Address Length | uint256 | 32 - 63 | +| Recipient Address | bytes | 64 - 95 | +| Metadata Length | uint256 | 96 - 127 | +| Metadata | bytes | 128 - END | + + +## Generic Handler + +As well as associating a resource ID to a contract address, the generic handler should allow specific functions on those contracts to be used. To allow for this we must: + +1. Use [function selectors](https://solidity.readthedocs.io/en/v0.6.4/abi-spec.html#function-selector) to identify functions. +2. Require functions that accept `bytes` as a the only parameter **OR** require the data already be ABI encoded for the function + +### Deposit + +In a generic context, a deposit is simply the initiation of a transfer of a piece of data. To (optionally) allow this data to be validated for transfer the deposit mechanism should pass the data to a specified function and proceed with the transfer if the call succeeds (ie. does not revert). A function selector of `0x00` should skip the deposit function call. + +### Execute + +An execution function must be specified. When `executeDeposit()` is called on the handler it should pass the `metadata` field to the specified function. + + +### Calldata for `deposit()` +| Data | Type | Location | +| - | - | - | +| Metadata Length | uint256 | 0 - 31 | +| Metadata | bytes | 32 - END | + +### Calldata for `execute()` +| Data | Type | Location | +| - | - | - | +| Metadata Length | uint256 | 0 - 31 | +| Metadata | bytes | 32 - END | + +# Administration + +The contracts should be controlled by an admin account. This should control the relayer set, manage the resource IDs, and specify the handlers. It should also be able to pause and unpause transfers at any times. + +# Substrate Implementation Specification + +- [Events](#events) +- [Inter-Pallet Communication](#inter-pallet-communication) +- [Bridge Account ID & Origin Check](#bridge-account-id--origin-check) +- [Executing Calls](#executing-calls) + +The ChainBridge Substrate implementation will consist of a Substrate pallet that can be integrated into a runtime to enable bridging of additional pallet functionality. + +Due to the complexities of the Substrate API we must define some limitations to the supported calls, however the pallet should define a `Proposal` type equivalent to a dispatchable call to theoretically allow for any call to be made. + +```rust +pub trait Trait: system::Trait { + type Proposal: Parameter + Dispatchable + EncodeLike + GetDispatchInfo; +} +``` + +# Events + +To easily distinguish different transfer types we should define three event types: + +```rust +/// FungibleTransfer is for relaying fungibles (dest_id, nonce, resource_id, amount, recipient, metadata) +FungibleTransfer(ChainId, DepositNonce, ResourceId, U256, Vec) + +/// NonFungibleTransfer is for relaying NFTS (dest_id, nonce, resource_id, token_id, recipient, metadata) +NonFungibleTransfer(ChainId, DepositNonce, ResourceId, Vec, Vec, Vec) + +/// GenericTransfer is for a generic data payload (dest_id, nonce, resource_id, metadata) +GenericTransfer(ChainId, DepositNonce, ResourceId, Vec) +``` + +These can be observed by relayers and should provide enough context to construct transfer messages. + +# Inter-Pallet Communication + +The ChainBridge pallet is intended to be combined with other pallets to define what is being bridged. To allow for this we must define some methods that other pallets can call to initiate transfers: + +```rust +pub fn transfer_fungible(dest_id: ChainId, resource_id: ResourceId, to: Vec, amount: U256,) + +pub fn transfer_nonfungible(dest_id: ChainId, resource_id: ResourceId, token_id: Vec, to: Vec, metadata: Vec) + +pub fn transfer_generic(dest_id: ChainId, resource_id: ResourceId, metadata: Vec) +``` + +These should result in the associated event being emitted with the correct parameters. + +# Bridge Account ID & Origin Check + +To allow the bridge pallet to take ownership of tokens a `ModuleId` should be used to derive an `AccountId`. + +A bridge origin check (implementing `EnsureOrigin`) should also be provided. Other pallets should be able to use this to check the origin of call is the bridge pallet, indicating the execution of a proposal. + +# Executing Calls + +The pallet should support dispatching of arbitrary calls as the result of successful proposal. Resource IDs should be mapped to specific calls to define their behaviour. Relayers will need to resolve resource IDs to calls in order to submit a proposal. The pallet should provide a mapping of resource IDs to method names that can be updated by the admin. + +Compatible calls are restrained to the following signature to allow relayers to understand how to construct the calls: +- Fungible: `Call(origin, recipient: AccountId, amount: u128)` +- Non-Fungible: `Call(origin, recipient: AccountId, tokenId: U256, metadata: Vec)` +- Generic: `Call(origin, data: Vec)` + + +*Note: Calls in substrate are resolved based on a pallet and call index. The pallet index depends on the ordering of pallets in the runtime, and the call index on the ordering of calls in the pallet. As these may change during a runtime upgrade, relayers should use the actual method name string to reference calls* + From f5f792a337daa734a91e439989f2e0d8c613f5d5 Mon Sep 17 00:00:00 2001 From: luu-alex Date: Mon, 10 Aug 2020 15:39:06 -0400 Subject: [PATCH 5/7] removing chains.md --- docs/chains/chains.md | 214 ------------------------------------------ 1 file changed, 214 deletions(-) delete mode 100644 docs/chains/chains.md diff --git a/docs/chains/chains.md b/docs/chains/chains.md deleted file mode 100644 index eee97d3fd..000000000 --- a/docs/chains/chains.md +++ /dev/null @@ -1,214 +0,0 @@ -# Ethereum Implementation Specification - -- [Transfer Flow](#transfer-flow) - * [As Source Chain](#as-source-chain) - * [As Destination Chain](#as-destination-chain) -- [Bridge Contract](#bridge-contract) -- [Handler Contracts](#handler-contracts) - * [ERC20 & ERC721 Handlers](#erc20--erc721-handlers) - * [ERC20 Handler](#erc20-handler) - * [ERC721 Handler](#erc721-handler) - * [Generic Handler](#generic-handler) -- [Administration](#administration) - -The solidity implementation of ChainBridge should consist of a central Bridge contract, and will delegate specific functionality to [handlers](#handler-contracts). Fungible and non-fungible compatibility should be focused on ERC20 and ERC721 tokens. - -# Transfer Flow - -## As Source Chain -1. Some user calls the `deposit` function on the bridge contract. A `depositRecord` is created on the bridge and a call is delgated to a handler contract specified by the provided `resourceID`. - -2. The specified handler's `deposit` function validates the parameters provided by the user. If successful, a `depositRecord` is created on the handler. - -3. If the call delegated to the handler is succesful, the bridge emits a `Deposit` event. - -4. Relayers parse the `Deposit` event and retrieve the associated `DepositRecord` from the handler to construct a message. - - -## As Destination Chain -1. A Relayer calls `voteProposal` on the bridge contract. If a `proposal` corresponding with the parameters passed in does not exist, it is created and the Relayer's vote is recorded. If the proposal already exists, the Relayer's vote is simply recorded. - -3. Once we have met some vote threshold for a `proposal`, the bridge emits a `ProposalFinalized` event. - -4. Upon seeing a `ProposalFinalized` event, Relayers call the`executeDeposit`function on the bridge. `executeDeposit` delegates a call to a handler contract specified by the associated `resourceID`. - -5. The specified handler's `executeDeposit` function validates the paramters provided and makes a call to some contract to complete the transfer. - - -# Bridge Contract - -Users and relayers will interact with the `Bridge` contract. This delegates calls to the handler contracts for deposits and executing proposals. - -``` -function deposit (uint8 destinationChainID, bytes32 resourceID, bytes calldata data) -``` - -# Handler Contracts - -To provide modularity and break out the necessary contract logic, the implementation uses a notion of handlers. A handler is defined for ERC20, ERC721 and generic transfers. These map directly to the Fungible, Non-Fungible, and generic transfer types. - -A handler must fulfill two interfaces: -``` -// Will be called by the bridge contract to initiate a transfer -function deposit(uint8 destinationChainID, uint64 depositNonce, address depositer, bytes calldata data) -``` - -``` -// TODO: This would be more aptly named executeProposal -// Called by the bridge contract to complete a transfer -function executeDeposit(bytes calldata data) -``` - -The `calldata` field is the parameters required for the handler. The exact serialization is defined for each handler. - -## ERC20 & ERC721 Handlers - -These handlers share a lot of similarities. - -These handlers are responsible for transferring ERC assets. They should provide the ability for the bridge to take ownership of tokens and release tokens to execute transfers. - -Different configurations may require different interface interactions. For example, it may make sense to mint and burn a token that is originally from another chain. If supply needs to be controlled, transferring tokens in and out of a reserve may be desired instead. To support either case handlers should associate each resource ID/token contract with one of these: - -- `transferFrom()` - The user approves the handler to move the tokens prior to initiating the transfer. The handler will call `transferFrom()` as part of the transfer initiation. For the inverse, the handler will call `transfer()` to release tokens from the handlers ownership. -- `mint()`/`burn()` - The user approves the handler to move the tokens prior to initiating the transfer. The handler will call `burnFrom()` as part of the transfer initiation. For the inverse, the handler will call `mint()` to release tokens to the recipient (and must have privileges to do so). - - -## ERC20 Handler - -### Calldata for `deposit()` -| Data | Type | Location | -| - | - | - | -| Amount | uint256 | 0 - 31 | -| Recipient Address Length | uint256 | 32 - 63 | -| Recipient Address | bytes | 63 - END | - - -### Calldata for `executeDeposit()` -| Data | Type | Location | -| - | - | - | -| Amount | uint256 | 0 - 31 | -| Recipient Address Length | uint256 | 32 - 63 | -| Recipient Address | bytes | 64 - END | - -## ERC721 Handler - -### Metadata - -The `tokenURI` should be used as the `metadata` field if the contract supports the Metadata extension (interface ID `0x5b5e139f`). - - -### Calldata for `deposit()` -| Data | Type | Location | -| - | - | - | -| TokenID | uint256 | 0 - 31 | -| Recipient Address Length | uint256 | 32 - 63 | -| Recipient Address | bytes | 63 - END | - -### Calldata for `executeDeposit()` -| Data | Type | Location | -| - | - | - | -| TokenID | uint256 | 0 - 31 | -| Recipient Address Length | uint256 | 32 - 63 | -| Recipient Address | bytes | 64 - 95 | -| Metadata Length | uint256 | 96 - 127 | -| Metadata | bytes | 128 - END | - - -## Generic Handler - -As well as associating a resource ID to a contract address, the generic handler should allow specific functions on those contracts to be used. To allow for this we must: - -1. Use [function selectors](https://solidity.readthedocs.io/en/v0.6.4/abi-spec.html#function-selector) to identify functions. -2. Require functions that accept `bytes` as a the only parameter **OR** require the data already be ABI encoded for the function - -### Deposit - -In a generic context, a deposit is simply the initiation of a transfer of a piece of data. To (optionally) allow this data to be validated for transfer the deposit mechanism should pass the data to a specified function and proceed with the transfer if the call succeeds (ie. does not revert). A function selector of `0x00` should skip the deposit function call. - -### Execute - -An execution function must be specified. When `executeDeposit()` is called on the handler it should pass the `metadata` field to the specified function. - - -### Calldata for `deposit()` -| Data | Type | Location | -| - | - | - | -| Metadata Length | uint256 | 0 - 31 | -| Metadata | bytes | 32 - END | - -### Calldata for `execute()` -| Data | Type | Location | -| - | - | - | -| Metadata Length | uint256 | 0 - 31 | -| Metadata | bytes | 32 - END | - -# Administration - -The contracts should be controlled by an admin account. This should control the relayer set, manage the resource IDs, and specify the handlers. It should also be able to pause and unpause transfers at any times. - -# Substrate Implementation Specification - -- [Events](#events) -- [Inter-Pallet Communication](#inter-pallet-communication) -- [Bridge Account ID & Origin Check](#bridge-account-id--origin-check) -- [Executing Calls](#executing-calls) - -The ChainBridge Substrate implementation will consist of a Substrate pallet that can be integrated into a runtime to enable bridging of additional pallet functionality. - -Due to the complexities of the Substrate API we must define some limitations to the supported calls, however the pallet should define a `Proposal` type equivalent to a dispatchable call to theoretically allow for any call to be made. - -```rust -pub trait Trait: system::Trait { - type Proposal: Parameter + Dispatchable + EncodeLike + GetDispatchInfo; -} -``` - -# Events - -To easily distinguish different transfer types we should define three event types: - -```rust -/// FungibleTransfer is for relaying fungibles (dest_id, nonce, resource_id, amount, recipient, metadata) -FungibleTransfer(ChainId, DepositNonce, ResourceId, U256, Vec) - -/// NonFungibleTransfer is for relaying NFTS (dest_id, nonce, resource_id, token_id, recipient, metadata) -NonFungibleTransfer(ChainId, DepositNonce, ResourceId, Vec, Vec, Vec) - -/// GenericTransfer is for a generic data payload (dest_id, nonce, resource_id, metadata) -GenericTransfer(ChainId, DepositNonce, ResourceId, Vec) -``` - -These can be observed by relayers and should provide enough context to construct transfer messages. - -# Inter-Pallet Communication - -The ChainBridge pallet is intended to be combined with other pallets to define what is being bridged. To allow for this we must define some methods that other pallets can call to initiate transfers: - -```rust -pub fn transfer_fungible(dest_id: ChainId, resource_id: ResourceId, to: Vec, amount: U256,) - -pub fn transfer_nonfungible(dest_id: ChainId, resource_id: ResourceId, token_id: Vec, to: Vec, metadata: Vec) - -pub fn transfer_generic(dest_id: ChainId, resource_id: ResourceId, metadata: Vec) -``` - -These should result in the associated event being emitted with the correct parameters. - -# Bridge Account ID & Origin Check - -To allow the bridge pallet to take ownership of tokens a `ModuleId` should be used to derive an `AccountId`. - -A bridge origin check (implementing `EnsureOrigin`) should also be provided. Other pallets should be able to use this to check the origin of call is the bridge pallet, indicating the execution of a proposal. - -# Executing Calls - -The pallet should support dispatching of arbitrary calls as the result of successful proposal. Resource IDs should be mapped to specific calls to define their behaviour. Relayers will need to resolve resource IDs to calls in order to submit a proposal. The pallet should provide a mapping of resource IDs to method names that can be updated by the admin. - -Compatible calls are restrained to the following signature to allow relayers to understand how to construct the calls: -- Fungible: `Call(origin, recipient: AccountId, amount: u128)` -- Non-Fungible: `Call(origin, recipient: AccountId, tokenId: U256, metadata: Vec)` -- Generic: `Call(origin, data: Vec)` - - -*Note: Calls in substrate are resolved based on a pallet and call index. The pallet index depends on the ordering of pallets in the runtime, and the call index on the ordering of calls in the pallet. As these may change during a runtime upgrade, relayers should use the actual method name string to reference calls* - From 2bee44ff4323010be93c34a4d9306957ac321f14 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 12 Aug 2020 09:44:43 -0400 Subject: [PATCH 6/7] updating text --- chains/ethereum/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index d4e67e9c9..d74576471 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -136,7 +136,7 @@ func InitializeChain(chainCfg *core.ChainConfig, logger log15.Logger, sysErr cha } if chainId != uint8(chainCfg.Id) { - return nil, fmt.Errorf("chainId and configuration chainId do not match") + return nil, fmt.Errorf("chainId (%s) and configuration chainId (%s) do not match", chainId, chainCfg.Id) } erc20HandlerContract, err := erc20Handler.NewERC20Handler(cfg.erc20HandlerContract, conn.Client()) From 2107f7e6fe5cf07e42caad4217b06e996af1e6b1 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 12 Aug 2020 10:17:20 -0400 Subject: [PATCH 7/7] changing type --- chains/ethereum/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chains/ethereum/chain.go b/chains/ethereum/chain.go index d74576471..e718d0aec 100644 --- a/chains/ethereum/chain.go +++ b/chains/ethereum/chain.go @@ -136,7 +136,7 @@ func InitializeChain(chainCfg *core.ChainConfig, logger log15.Logger, sysErr cha } if chainId != uint8(chainCfg.Id) { - return nil, fmt.Errorf("chainId (%s) and configuration chainId (%s) do not match", chainId, chainCfg.Id) + return nil, fmt.Errorf("chainId (%d) and configuration chainId (%d) do not match", chainId, chainCfg.Id) } erc20HandlerContract, err := erc20Handler.NewERC20Handler(cfg.erc20HandlerContract, conn.Client())