ethereum: EVM fork protection

Added a `uint256 evmChainId` state variable to the Core, Token and NFT Bridge
contracts. `evmChainId` is compared to `block.chainid` in the `chainId`
function. When they don't match, a "bad fork" Chain ID is returned,
otherwise the Wormhole Chain ID is returned.
This commit is contained in:
Kevin Peters 2022-08-30 16:30:32 +00:00 committed by kev1n-peters
parent d83e44cf6b
commit 9d477e0585
50 changed files with 232 additions and 61 deletions

View File

@ -51,7 +51,6 @@ export async function query_contract_evm(
result.isInitialized = await core.isInitialized(result.implementation)
break
case "TokenBridge":
// TODO: add finality (need new sdk release)
contract_address = contract_address ? contract_address : contracts.token_bridge;
if (contract_address === undefined) {
throw Error(`Unknown token bridge contract on ${network} for ${chain}`)
@ -63,6 +62,9 @@ export async function query_contract_evm(
result.isInitialized = await tb.isInitialized(result.implementation)
result.tokenImplementation = await tb.tokenImplementation()
result.chainId = await tb.chainId()
// TODO: need new sdk release to expose this function in BridgeImplementation
const tb2 = new ethers.Contract(contract_address, ["function finality() public view returns (uint8)"], provider)
result.finality = await tb2.finality()
result.governanceChainId = await tb.governanceChainId()
result.governanceContract = await tb.governanceContract()
result.WETH = await tb.WETH()
@ -75,7 +77,6 @@ export async function query_contract_evm(
}
break
case "NFTBridge":
// TODO: add finality (need new sdk release)
contract_address = contract_address ? contract_address : contracts.nft_bridge;
if (contract_address === undefined) {
throw Error(`Unknown nft bridge contract on ${network} for ${chain}`)
@ -87,6 +88,9 @@ export async function query_contract_evm(
result.isInitialized = await nb.isInitialized(result.implementation)
result.tokenImplementation = await nb.tokenImplementation()
result.chainId = await nb.chainId()
// TODO: need new sdk release to expose this function in NFTBridgeImplementation
const nb2 = new ethers.Contract(contract_address, ["function finality() public view returns (uint8)"], provider)
result.finality = await nb2.finality()
result.governanceChainId = await nb.governanceChainId()
result.governanceContract = await nb.governanceContract()
result.registrations = {}

View File

@ -55,7 +55,7 @@ spec:
command:
- /bin/sh
- -c
- "sed -i 's/CHAIN_ID=0x2/CHAIN_ID=0x4/g' .env && npm run migrate && npx truffle exec scripts/deploy_test_token.js && npx truffle exec scripts/register_solana_chain.js && npx truffle exec scripts/register_terra_chain.js && npx truffle exec scripts/register_terra2_chain.js && npx truffle exec scripts/register_eth_chain.js && npx truffle exec scripts/register_algo_chain.js && nc -lkp 2000 0.0.0.0"
- "sed -i 's/CHAIN_ID=0x2/CHAIN_ID=0x4/g;s/EVM_CHAIN_ID=1/EVM_CHAIN_ID=1397/g' .env && npm run migrate && npx truffle exec scripts/deploy_test_token.js && npx truffle exec scripts/register_solana_chain.js && npx truffle exec scripts/register_terra_chain.js && npx truffle exec scripts/register_terra2_chain.js && npx truffle exec scripts/register_eth_chain.js && npx truffle exec scripts/register_algo_chain.js && nc -lkp 2000 0.0.0.0"
readinessProbe:
periodSeconds: 1
failureThreshold: 300

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=0xc
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=787
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xc

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0xc
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=597
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xc

View File

@ -6,3 +6,4 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=23
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=42161

View File

@ -6,3 +6,4 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=23
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=421611

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=0x9
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=1313161554
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0x9

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0x9
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=1313161555
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0x9

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=0xE
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=42220
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xE

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0xE
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=44787
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xE

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=0xa
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=250
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xa

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0xa
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=4002
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xa

View File

@ -6,3 +6,4 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=25
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=100

View File

@ -6,3 +6,4 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=25
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=77

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=0xb
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=686
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xb

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0xb
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=596
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xb

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=0xd
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=8217
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xd

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0xd
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=1001
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0xd

View File

@ -6,3 +6,4 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=16
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_CHAIN_ID=1284

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0x10
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_CHAIN_ID=1287
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0x10

View File

@ -6,6 +6,7 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=0x11
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=245022926
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0x11

View File

@ -6,3 +6,4 @@ INIT_SIGNERS=["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"]
INIT_CHAIN_ID=24
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=10

View File

@ -6,3 +6,4 @@ INIT_SIGNERS=["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"]
INIT_CHAIN_ID=24
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=420

View File

@ -10,6 +10,7 @@ INIT_SIGNERS= # ["0x0000000000000000000000000000000000000000"]
INIT_CHAIN_ID= # 0x2
INIT_GOV_CHAIN_ID= # 0x3
INIT_GOV_CONTRACT= # 0x000000000000000000000000000000000000000000000000000000000000000
INIT_EVM_CHAIN_ID= # 1
# Bridge Migrations # Example Format
BRIDGE_INIT_CHAIN_ID= # 0x02

View File

@ -3,6 +3,7 @@ INIT_SIGNERS=["0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"]
INIT_CHAIN_ID=0x2
INIT_GOV_CHAIN_ID=0x1
INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
INIT_EVM_CHAIN_ID=1
# Bridge Migrations
BRIDGE_INIT_CHAIN_ID=0x2

View File

@ -27,9 +27,17 @@ contract Getters is State {
}
function chainId() public view returns (uint16) {
if (evmChainId() != block.chainid) {
// reduce the likelihood of forked chain ID collisions
return type(uint16).max - 32 + uint16(block.chainid % 32);
}
return _state.provider.chainId;
}
function evmChainId() public view returns (uint256) {
return _state.evmChainId;
}
function governanceChainId() public view returns (uint16){
return _state.provider.governanceChainId;
}

View File

@ -32,6 +32,31 @@ contract Implementation is Governance {
function initialize() initializer public virtual {
// this function needs to be exposed for an upgrade to pass
uint256 evmChainId;
uint16 chain = _state.provider.chainId;
// Wormhole chain ids explicitly enumerated
if (chain == 2) { evmChainId = 1; // ethereum
} else if (chain == 4) { evmChainId = 56; // bsc
} else if (chain == 5) { evmChainId = 137; // polygon
} else if (chain == 6) { evmChainId = 43114; // avalanche
} else if (chain == 7) { evmChainId = 42262; // oasis
} else if (chain == 9) { evmChainId = 1313161554; // aurora
} else if (chain == 10) { evmChainId = 250; // fantom
} else if (chain == 11) { evmChainId = 686; // karura
} else if (chain == 12) { evmChainId = 787; // acala
} else if (chain == 13) { evmChainId = 8217; // klaytn
} else if (chain == 14) { evmChainId = 42220; // celo
} else if (chain == 16) { evmChainId = 1284; // moonbeam
} else if (chain == 17) { evmChainId = 245022934; // neon
} else if (chain == 23) { evmChainId = 42161; // arbitrum
} else if (chain == 24) { evmChainId = 10; // optimism
} else if (chain == 25) { evmChainId = 100; // gnosis
} else {
revert("Unknown chain id.");
}
setEvmChainId(evmChainId);
}
modifier initializer() {

View File

@ -45,4 +45,9 @@ contract Setters is State {
function setNextSequence(address emitter, uint64 sequence) internal {
_state.sequences[emitter] = sequence;
}
function setEvmChainId(uint256 evmChainId) internal {
require(evmChainId == block.chainid, "invalid evmChainId");
_state.evmChainId = evmChainId;
}
}

View File

@ -14,7 +14,8 @@ contract Setup is Setters, ERC1967Upgrade {
address[] memory initialGuardians,
uint16 chainId,
uint16 governanceChainId,
bytes32 governanceContract
bytes32 governanceContract,
uint256 evmChainId
) public {
require(initialGuardians.length > 0, "no guardians specified");
@ -31,6 +32,8 @@ contract Setup is Setters, ERC1967Upgrade {
setGovernanceChainId(governanceChainId);
setGovernanceContract(governanceContract);
setEvmChainId(evmChainId);
_upgradeTo(implementation);
}
}

View File

@ -41,6 +41,9 @@ contract Storage {
mapping(address => bool) initializedImplementations;
uint256 messageFee;
// EIP-155 Chain ID
uint256 evmChainId;
}
}

View File

@ -20,10 +20,15 @@ import "./token/TokenImplementation.sol";
contract Bridge is BridgeGovernance, ReentrancyGuard {
using BytesLib for bytes;
modifier onlyEvmChainId() {
require(evmChainId() == block.chainid, "invalid evmChainId");
_;
}
/*
* @dev Produce a AssetMeta message for a given token
*/
function attestToken(address tokenAddress, uint32 nonce) public payable returns (uint64 sequence) {
function attestToken(address tokenAddress, uint32 nonce) public payable onlyEvmChainId returns (uint64 sequence) {
// decimals, symbol & token are not part of the core ERC20 token standard, so we need to support contracts that dont implement them
(,bytes memory queriedDecimals) = tokenAddress.staticcall(abi.encodeWithSignature("decimals()"));
(,bytes memory queriedSymbol) = tokenAddress.staticcall(abi.encodeWithSignature("symbol()"));
@ -66,7 +71,7 @@ contract Bridge is BridgeGovernance, ReentrancyGuard {
bytes32 recipient,
uint256 arbiterFee,
uint32 nonce
) public payable returns (uint64 sequence) {
) public payable onlyEvmChainId returns (uint64 sequence) {
BridgeStructs.TransferResult
memory transferResult = _wrapAndTransferETH(arbiterFee);
sequence = logTransfer(
@ -98,7 +103,7 @@ contract Bridge is BridgeGovernance, ReentrancyGuard {
bytes32 recipient,
uint32 nonce,
bytes memory payload
) public payable returns (uint64 sequence) {
) public payable onlyEvmChainId returns (uint64 sequence) {
BridgeStructs.TransferResult
memory transferResult = _wrapAndTransferETH(0);
sequence = logTransferWithPayload(
@ -158,7 +163,7 @@ contract Bridge is BridgeGovernance, ReentrancyGuard {
bytes32 recipient,
uint256 arbiterFee,
uint32 nonce
) public payable nonReentrant returns (uint64 sequence) {
) public payable nonReentrant onlyEvmChainId returns (uint64 sequence) {
BridgeStructs.TransferResult memory transferResult = _transferTokens(
token,
amount,
@ -195,7 +200,7 @@ contract Bridge is BridgeGovernance, ReentrancyGuard {
bytes32 recipient,
uint32 nonce,
bytes memory payload
) public payable nonReentrant returns (uint64 sequence) {
) public payable nonReentrant onlyEvmChainId returns (uint64 sequence) {
BridgeStructs.TransferResult memory transferResult = _transferTokens(
token,
amount,
@ -354,7 +359,7 @@ contract Bridge is BridgeGovernance, ReentrancyGuard {
finality()
);
}
function updateWrapped(bytes memory encodedVm) external returns (address token) {
function updateWrapped(bytes memory encodedVm) external onlyEvmChainId returns (address token) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason);
@ -374,7 +379,7 @@ contract Bridge is BridgeGovernance, ReentrancyGuard {
return wrapped;
}
function createWrapped(bytes memory encodedVm) external returns (address token) {
function createWrapped(bytes memory encodedVm) external onlyEvmChainId returns (address token) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason);
@ -484,7 +489,7 @@ contract Bridge is BridgeGovernance, ReentrancyGuard {
}
// Execute a Transfer message
function _completeTransfer(bytes memory encodedVm, bool unwrapWETH) internal returns (bytes memory) {
function _completeTransfer(bytes memory encodedVm, bool unwrapWETH) internal onlyEvmChainId returns (bytes memory) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason);

View File

@ -27,9 +27,17 @@ contract BridgeGetters is BridgeState {
}
function chainId() public view returns (uint16){
if (evmChainId() != block.chainid) {
// reduce the likelihood of forked chain ID collisions
return type(uint16).max - 32 + uint16(block.chainid % 32);
}
return _state.provider.chainId;
}
function evmChainId() public view returns (uint256) {
return _state.evmChainId;
}
function governanceChainId() public view returns (uint16){
return _state.provider.governanceChainId;
}

View File

@ -17,27 +17,31 @@ contract BridgeImplementation is Bridge {
function initialize() initializer public virtual {
// this function needs to be exposed for an upgrade to pass
address tokenContract;
uint16 chain = chainId();
uint256 evmChainId;
uint16 chain = _state.provider.chainId;
// Wormhole chain ids explicitly enumerated
if (chain == 2) { tokenContract = 0x44bD47a8Bc18398227d6f40E1693Cf897bb9855E; // ethereum
} else if (chain == 4) { tokenContract = 0x1877a83023A87849D89a076466531b6a5DEa7eb2; // bsc
} else if (chain == 5) { tokenContract = 0xB9A1c8873a7a36c2Eb6D8c1A19702106CdAE6edd; // polygon
} else if (chain == 6) { tokenContract = 0x276a65900C97A3726319742e74F75bC4f56A0BfD; // avalanche
} else if (chain == 7) { tokenContract = 0x95BeDdFba786Aa1A5b3294aa6166cB125B961e34; // oasis
} else if (chain == 9) { tokenContract = 0x1Cd0b07Dc82482f057b3cf19775e8453309c5356; // aurora
} else if (chain == 10) { tokenContract = 0x40D0A808241cafd9D70700963d205FeA9c0B1C9D; // fantom
} else if (chain == 11) { tokenContract = 0x9002933919Aa83c38D01bDfBd788A9dfF42f3880; // karura
// Acala EVM was down at the time of this migration
// } else if (chain == 12) { tokenContract = 0x0000000000000000000000000000000000000000; // acala
} else if (chain == 13) { tokenContract = 0xA7601785478622E720d41454CB390852cd2B9788; // klaytn
} else if (chain == 14) { tokenContract = 0xADE06bc75Dc1FC3fB7442e0CFb8Ca544B23aF789; // celo
if (chain == 2) { evmChainId = 1; // ethereum
} else if (chain == 4) { evmChainId = 56; // bsc
} else if (chain == 5) { evmChainId = 137; // polygon
} else if (chain == 6) { evmChainId = 43114; // avalanche
} else if (chain == 7) { evmChainId = 42262; // oasis
} else if (chain == 9) { evmChainId = 1313161554; // aurora
} else if (chain == 10) { evmChainId = 250; // fantom
} else if (chain == 11) { evmChainId = 686; // karura
} else if (chain == 12) { evmChainId = 787; // acala
} else if (chain == 13) { evmChainId = 8217; // klaytn
} else if (chain == 14) { evmChainId = 42220; // celo
} else if (chain == 16) { evmChainId = 1284; // moonbeam
} else if (chain == 17) { evmChainId = 245022934; // neon
} else if (chain == 23) { evmChainId = 42161; // arbitrum
} else if (chain == 24) { evmChainId = 10; // optimism
} else if (chain == 25) { evmChainId = 100; // gnosis
} else {
revert("Unknown chain id.");
}
setTokenImplementation(tokenContract);
setEvmChainId(evmChainId);
}
modifier initializer() {

View File

@ -58,4 +58,9 @@ contract BridgeSetters is BridgeState {
function setFinality(uint8 finality) internal {
_state.provider.finality = finality;
}
function setEvmChainId(uint256 evmChainId) internal {
require(evmChainId == block.chainid, "invalid evmChainId");
_state.evmChainId = evmChainId;
}
}

View File

@ -17,7 +17,8 @@ contract BridgeSetup is BridgeSetters, ERC1967Upgrade {
bytes32 governanceContract,
address tokenImplementation,
address WETH,
uint8 finality
uint8 finality,
uint256 evmChainId
) public {
setChainId(chainId);
@ -32,6 +33,8 @@ contract BridgeSetup is BridgeSetters, ERC1967Upgrade {
setFinality(finality);
setEvmChainId(evmChainId);
_upgradeTo(implementation);
}
}

View File

@ -46,6 +46,9 @@ contract BridgeStorage {
// Mapping of bridge contracts on other chains
mapping(uint16 => bytes32) bridgeImplementations;
// EIP-155 Chain ID
uint256 evmChainId;
}
}

View File

@ -19,8 +19,13 @@ import "./token/NFTImplementation.sol";
contract NFTBridge is NFTBridgeGovernance {
using BytesLib for bytes;
modifier onlyEvmChainId() {
require(evmChainId() == block.chainid, "invalid evmChainId");
_;
}
// Initiate a Transfer
function transferNFT(address token, uint256 tokenID, uint16 recipientChain, bytes32 recipient, uint32 nonce) public payable returns (uint64 sequence) {
function transferNFT(address token, uint256 tokenID, uint16 recipientChain, bytes32 recipient, uint32 nonce) public payable onlyEvmChainId returns (uint64 sequence) {
// determine token parameters
uint16 tokenChain;
bytes32 tokenAddress;
@ -97,7 +102,7 @@ contract NFTBridge is NFTBridgeGovernance {
}(nonce, encoded, finality());
}
function completeTransfer(bytes memory encodedVm) public {
function completeTransfer(bytes memory encodedVm) public onlyEvmChainId {
_completeTransfer(encodedVm);
}

View File

@ -27,9 +27,17 @@ contract NFTBridgeGetters is NFTBridgeState {
}
function chainId() public view returns (uint16){
if (evmChainId() != block.chainid) {
// reduce the likelihood of forked chain ID collisions
return type(uint16).max - 32 + uint16(block.chainid % 32);
}
return _state.provider.chainId;
}
function evmChainId() public view returns (uint256) {
return _state.evmChainId;
}
function governanceChainId() public view returns (uint16){
return _state.provider.governanceChainId;
}

View File

@ -17,28 +17,31 @@ contract NFTBridgeImplementation is NFTBridge {
function initialize() initializer public virtual {
// this function needs to be exposed for an upgrade to pass
uint8 finality;
uint16 chain = chainId();
uint256 evmChainId;
uint16 chain = _state.provider.chainId;
// Wormhole chain ids explicitly enumerated
if (chain == 2) { finality = 15; // ethereum
} else if (chain == 4) { finality = 15; // bsc
} else if (chain == 5) { finality = 15; // polygon
} else if (chain == 6) { finality = 1; // avalanche
} else if (chain == 7) { finality = 1; // oasis
} else if (chain == 9) { finality = 1; // aurora
} else if (chain == 10) { finality = 1; // fantom
} else if (chain == 11) { finality = 1; // karura
} else if (chain == 12) { finality = 1; // acala
} else if (chain == 13) { finality = 1; // klaytn
} else if (chain == 14) { finality = 1; // celo
} else if (chain == 16) { finality = 1; // moonbeam
} else if (chain == 17) { finality = 32; // neon
if (chain == 2) { evmChainId = 1; // ethereum
} else if (chain == 4) { evmChainId = 56; // bsc
} else if (chain == 5) { evmChainId = 137; // polygon
} else if (chain == 6) { evmChainId = 43114; // avalanche
} else if (chain == 7) { evmChainId = 42262; // oasis
} else if (chain == 9) { evmChainId = 1313161554; // aurora
} else if (chain == 10) { evmChainId = 250; // fantom
} else if (chain == 11) { evmChainId = 686; // karura
} else if (chain == 12) { evmChainId = 787; // acala
} else if (chain == 13) { evmChainId = 8217; // klaytn
} else if (chain == 14) { evmChainId = 42220; // celo
} else if (chain == 16) { evmChainId = 1284; // moonbeam
} else if (chain == 17) { evmChainId = 245022934; // neon
} else if (chain == 23) { evmChainId = 42161; // arbitrum
} else if (chain == 24) { evmChainId = 10; // optimism
} else if (chain == 25) { evmChainId = 100; // gnosis
} else {
revert("Unknown chain id.");
}
setFinality(finality);
setEvmChainId(evmChainId);
}
modifier initializer() {

View File

@ -58,4 +58,9 @@ contract NFTBridgeSetters is NFTBridgeState {
function setFinality(uint8 finality) internal {
_state.provider.finality = finality;
}
function setEvmChainId(uint256 evmChainId) internal {
require(evmChainId == block.chainid, "invalid evmChainId");
_state.evmChainId = evmChainId;
}
}

View File

@ -16,7 +16,8 @@ contract NFTBridgeSetup is NFTBridgeSetters, ERC1967Upgrade {
uint16 governanceChainId,
bytes32 governanceContract,
address tokenImplementation,
uint8 finality
uint8 finality,
uint256 evmChainId
) public {
setChainId(chainId);
@ -29,6 +30,8 @@ contract NFTBridgeSetup is NFTBridgeSetters, ERC1967Upgrade {
setFinality(finality);
setEvmChainId(evmChainId);
_upgradeTo(implementation);
}
}

View File

@ -50,6 +50,9 @@ contract NFTBridgeStorage {
// Mapping of spl token info caches (chainID => nativeAddress => SPLCache)
mapping(uint256 => SPLCache) splCache;
// EIP-155 Chain ID
uint256 evmChainId;
}
}

View File

@ -13,4 +13,26 @@ contract TestBridge is Bridge, Test {
bytes32 converted = bytes32(uint256(uint160(bytes20(_truncateAddress(b)))));
require(converted == b, "truncate does not roundrip");
}
function testChainId() public {
vm.chainId(1);
setChainId(1);
setEvmChainId(1);
assertEq(chainId(), 1);
assertEq(evmChainId(), 1);
vm.expectRevert("invalid evmChainId");
setEvmChainId(1337);
assertEq(chainId(), 1);
assertEq(evmChainId(), 1);
// fork occurs, block.chainid changes
vm.chainId(10001);
assertEq(chainId(), 65520);
assertEq(evmChainId(), 1);
setEvmChainId(10001);
assertEq(chainId(), 1);
assertEq(evmChainId(), 10001);
}
}

View File

@ -9,6 +9,7 @@ const initialSigners = JSON.parse(process.env.INIT_SIGNERS);
const chainId = process.env.INIT_CHAIN_ID;
const governanceChainId = process.env.INIT_GOV_CHAIN_ID;
const governanceContract = process.env.INIT_GOV_CONTRACT; // bytes32
const evmChainId = process.env.INIT_EVM_CHAIN_ID;
module.exports = async function (deployer) {
// deploy setup
@ -24,7 +25,8 @@ module.exports = async function (deployer) {
initialSigners,
chainId,
governanceChainId,
governanceContract
governanceContract,
evmChainId
).encodeABI();
// deploy proxy

View File

@ -11,6 +11,7 @@ const governanceChainId = process.env.BRIDGE_INIT_GOV_CHAIN_ID;
const governanceContract = process.env.BRIDGE_INIT_GOV_CONTRACT; // bytes32
const WETH = process.env.BRIDGE_INIT_WETH;
const finality = process.env.BRIDGE_INIT_FINALITY;
const evmChainId = process.env.INIT_EVM_CHAIN_ID;
module.exports = async function (deployer) {
// deploy token implementation
@ -32,7 +33,8 @@ module.exports = async function (deployer) {
governanceContract,
TokenImplementation.address,
WETH,
finality
finality,
evmChainId
).encodeABI();
// deploy proxy

View File

@ -10,6 +10,7 @@ const chainId = process.env.BRIDGE_INIT_CHAIN_ID;
const governanceChainId = process.env.BRIDGE_INIT_GOV_CHAIN_ID;
const governanceContract = process.env.BRIDGE_INIT_GOV_CONTRACT; // bytes32
const finality = process.env.BRIDGE_INIT_FINALITY;
const evmChainId = process.env.INIT_EVM_CHAIN_ID;
module.exports = async function (deployer) {
// deploy token implementation
@ -30,7 +31,8 @@ module.exports = async function (deployer) {
governanceChainId,
governanceContract,
TokenImplementation.address,
finality
finality,
evmChainId
).encodeABI();
// deploy proxy

View File

@ -22,6 +22,7 @@ contract("Bridge", function () {
const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
const testChainId = "2";
const testEvmChainId = "1";
const testFinality = "1";
const testGovernanceChainId = "1";
const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
@ -49,6 +50,10 @@ contract("Bridge", function () {
const chainId = await initialized.methods.chainId().call();
assert.equal(chainId, testChainId);
// evm chain id
const evmChainId = await initialized.methods.evmChainId().call();
assert.equal(evmChainId, testEvmChainId);
// finality
const finality = await initialized.methods.finality().call();
assert.equal(finality, testFinality);
@ -653,7 +658,7 @@ contract("Bridge", function () {
"10",
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
"234",
"0x"+additionalPayload
"0x" + additionalPayload
).send({
value: 0,
from: accounts[0],
@ -871,7 +876,7 @@ contract("Bridge", function () {
from: accounts[1],
gasLimit: 2000000
});
} catch(e) {
} catch (e) {
hadSenderError = e.message.includes('revert invalid sender')
}
assert.equal(hadSenderError, true)
@ -1151,7 +1156,7 @@ contract("Bridge", function () {
"10",
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
"234",
"0x"+additionalPayload
"0x" + additionalPayload
).send({
value: amount,
from: accounts[0],
@ -1343,7 +1348,7 @@ const signAndEncodeVM = async function (
for (let i in signers) {
const ec = new elliptic.ec("secp256k1");
const key = ec.keyFromPrivate(signers[i]);
const signature = key.sign(hash.substr(2), {canonical: true});
const signature = key.sign(hash.substr(2), { canonical: true });
const packSig = [
web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),

View File

@ -19,6 +19,7 @@ contract("NFT", function () {
const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
const testChainId = "2";
const testEvmChainId = "1";
const testFinality = "1";
const testGovernanceChainId = "1";
const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
@ -43,6 +44,10 @@ contract("NFT", function () {
const chainId = await initialized.methods.chainId().call();
assert.equal(chainId, testChainId);
// evm chain id
const evmChainId = await initialized.methods.evmChainId().call();
assert.equal(evmChainId, testEvmChainId);
// finality
const finality = await initialized.methods.finality().call();
assert.equal(finality, testFinality);
@ -698,7 +703,7 @@ const signAndEncodeVM = async function (
for (let i in signers) {
const ec = new elliptic.ec("secp256k1");
const key = ec.keyFromPrivate(signers[i]);
const signature = key.sign(hash.substr(2), {canonical: true});
const signature = key.sign(hash.substr(2), { canonical: true });
const packSig = [
web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),

View File

@ -20,6 +20,7 @@ const BridgeImplementationFullABI = jsonfile.readFileSync("build/contracts/Bridg
contract("Update Bridge", function (accounts) {
if (config.network === "test") return;
const testChainId = "2";
const testEvmChainId = "1";
const testGovernanceChainId = "1";
const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
let WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
@ -41,7 +42,8 @@ contract("Update Bridge", function (accounts) {
testGovernanceContract,
TokenImplementation.address,
WETH,
testFinality
testFinality,
testEvmChainId
).encodeABI();
const deploy = await TokenBridge.new(BridgeSetup.address, initData);

View File

@ -13,7 +13,7 @@ const testSigner3PK = "87b45997ea577b93073568f06fc4838cffc1d01f90fc4d57f936957f3
const testBadSigner1PK = "87b45997ea577b93073568f06fc4838cffc1d01f90fc4d57f936957f3c4d99fc";
const core = '0x' + Buffer.from("Core").toString("hex").padStart(64,0)
const core = '0x' + Buffer.from("Core").toString("hex").padStart(64, 0)
const actionContractUpgrade = "01"
const actionGuardianSetUpgrade = "02"
const actionMessageFee = "03"
@ -68,6 +68,7 @@ contract("Wormhole", function () {
const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
const testSigner3 = web3.eth.accounts.privateKeyToAccount(testSigner3PK);
const testChainId = "2";
const testEvmChainId = "1";
const testGovernanceChainId = "1";
const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
@ -88,6 +89,10 @@ contract("Wormhole", function () {
const chainId = await initialized.methods.chainId().call();
assert.equal(chainId, testChainId);
// evm chain id
const evmChainId = await initialized.methods.evmChainId().call();
assert.equal(evmChainId, testEvmChainId);
// governance
const governanceChainId = await initialized.methods.governanceChainId().call();
assert.equal(governanceChainId, testGovernanceChainId);
@ -125,7 +130,7 @@ contract("Wormhole", function () {
"0x1",
"0x1",
32
).send({
).send({
value: 0, // fees are set to 0 initially
from: accounts[0]
});
@ -172,7 +177,7 @@ contract("Wormhole", function () {
"0x" + nonceHex,
"0x1",
32
).send({
).send({
value: 0, // fees are set to 0 initially
from: accounts[0]
});
@ -856,7 +861,7 @@ const signAndEncodeVM = async function (
for (let i in signers) {
const ec = new elliptic.ec("secp256k1");
const key = ec.keyFromPrivate(signers[i]);
const signature = key.sign(hash.substr(2), {canonical: true});
const signature = key.sign(hash.substr(2), { canonical: true });
const packSig = [
web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
@ -908,7 +913,7 @@ const signAndEncodeVMFixedIndex = async function (
for (let i in signers) {
const ec = new elliptic.ec("secp256k1");
const key = ec.keyFromPrivate(signers[i]);
const signature = key.sign(hash.substr(2), {canonical: true});
const signature = key.sign(hash.substr(2), { canonical: true });
const packSig = [
// Fixing the index to be zero to product a non-monotonic VM