Harmony Horizon
- date: 2023-02-24
- last updated: 2023-02-24
Overview
This document reviews the horizon current implementation, development tasks that need to be done to support POW and offers some thoughts on next steps to support Ethereum 2.0 and other chains.
Further thoughs on ETH 2.0 support, removing the ETHHASH logic and SPV client and potentially replacing with MMR trees per epoch and checkpoints similar to Harmony Light Client on Ethereum, can find inspiration in near-rainbow.
Approach
Horizon 2.0 approach is to use validity proofs implemented by on-chain smart contracts.
Proving Mechanisms
Ethereum Light Client
- ETH 2.0 support see here
- Queuing mechanism should be implemented to queue bridge transactions. The queue can be polled as part of the block relay functionality to process bridge transactions once the blocks have been relayed.
- Consider whether we can use p2p messaging to receive published blocks rather than looping and polling via an RPC.
Harmony Light Client
- Needs to implement a process to
submitCheckpoint
. eprove
logic needs to be reviewed- Queuing mechanism should be implemented to queue bridge transactions. The queue can be polled as part of the
submitCheckpoint
functionality to process bridge transactions once the blocks have been relayed. - Need to facilitate the core protocol MMR enhancements PR
Relayer Mechanisms
Sequencing of Transactions: Needs to be implemented and TokenMap
in bridge.js
needs to be refactored. Below is the current sequence flow and areas for improvements.
- Ethereum Mapping Request
- Relay of Block to EthereumLightClient.sol on Harmony
- The block has to be relayed before we can process the Harmony Mapping request, as we have just executed the transaction the relayer usually has not relayed the block so this will fail.
- There must be an additional 25 blocks on Ethereum before this block can be considered part of the canonical chain.
- This logic needs to be rewritten to break down execution for 1. the ethereum mapping request 2. After a 25 block delay the Harmony Proof validation and executing the Harmony Mapping Request**
- Harmony Mapping Request
- Relay of Checkpoint to HarmonyLightClient.sol on Ethereum
- A
submitCheckpoint
inHarmonyLightClient.sol
needs to have called either for the next epoch or for a checkpoint, after the block the harmony mapping transaction was in.** - Automatic submission of checkpoints to the Harmony Light Client has not been developed as yet. (It is not part of the
ethRelay.js
). And so the checkpoint would need to be manually submitted before the Ethereum Mapping could take place.
- A
- Etherem Process Harmony Mapping Acknowledgement
Light Client Functionality
Ethereum Light Client
- ETH 2.0 support see here
- Queuing mechanism should be implemented to queue bridge transactions. The queue can be polled as part of the block relay functionality to process bridge transactions once the blocks have been relayed.
- Consider whether we can use p2p messaging to receive published blocks rather than looping and polling via an RPC.
Harmony Light Client
- Needs to implement a process to
submitCheckpoint
. eprove
logic needs to be reviewed- Queuing mechanism should be implemented to queue bridge transactions. The queue can be polled as part of the
submitCheckpoint
functionality to process bridge transactions once the blocks have been relayed. - Need to facilitate the core protocol MMR enhancements PR
Token Lockers
Note: The key difference between TokenLockerOnEthereum.sol
and TokenLockerOnHarmony.sol
is the proof validation. TokenLockerOnEthereum.sol
uses ./lib/MMRVerifier.sol
to validate the Mountain Merkle Ranges on Harmony and HarmonyProver.sol
. TokenLockerOnHarmony.sol
imports ./lib/MPTValidatorV2.sol
to validate Merkle Patrica Trie and ./EthereumLightClient.sol
.
MultiChain Support
- Need to support other chains
- EVM: BSC, Polygon, Avalanche, Arbitrum, Optimism
- Bitcoin
- NEAR
- Solana
- Polkadot
Code Review
The code reviewed is from a fork of harmony-one/horizon. The fork is johnwhitton/horizon branch refactorV2. This is part of the horizon v2 initiative to bride a trustless bridge after the initial horizon hack. The code is incomplete and the original codebase did not support ethereum 2.0 (only ethereum 1.0). Nevertheless there are a number of useful components developed which can be leveraged in building a trustless bridge.
On-chain (Solidity) Code Review
Note: here we document functionality developed in solidity. We recommend reading the Open Zeppelin Contract Documentation specifically the utilities have a number of utitlies we leverage around signing and proving. We tend to utilize the openzeppelin-contracts-upgradeabe repository when building over the documented openzeppelin-contracts repository as we are often working with contracts which we wish to upgrade, there should be equivalent contracts in both repositories.
OpenZeppelin Utilities
- Utilities: Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives.
- Math: Standard math utilities missing in the Solidity language.
- Cryptography
- ECDSA: Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
- SignatureChecker: Signature verification helper that can be used instead of ECDSA.recover to seamlessly support both ECDSA signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like Argent and Gnosis Safe.
- MerkleProof: These functions deal with verification of Merkle Tree proofs.
- EIP712: EIP 712 is a standard for hashing and signing of typed structured data.
- Escrow: Base escrow contract, holds funds designated for a payee until they withdraw them.
- Introspection: This set of interfaces and contracts deal with type introspection of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract’s interface.
- Data Structures
- BitMaps: Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. Largely inspired by Uniswap’s merkle-distributor.
- EnumerableMap: Library for managing an enumerable variant of Solidity’s mapping type.
- EnumerableSet: Library for managing sets of primitive types.
- DoubleEndedQueue: A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of the sequence (called front and back).
- Checkpoints: This library defines the
History
struct, for checkpointing values as they change at different points in time, and later looking up past values by block number. See Votes as an example.
- Libraries
- Create2: Helper to make usage of the
CREATE2
EVM opcode easier and safer. - Address: Collection of functions related to the address type
- Arrays: Collection of functions related to array types.
- Base64: Provides a set of functions to operate with Base64 strings.
- Counters: Provides counters that can only be incremented, decremented or reset.
- Strings: String operations.
- StorageSlot: Library for reading and writing primitive types to specific storage slots. Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
- Multicall: Provides a function to batch together multiple calls in a single external call.
- Create2: Helper to make usage of the
Cryptographic Primitives
- ethash: used in proving ethereum 1.0 ethash proof of work.
- MerkleRoot.sol: provides the ability to getRootHash for a given epoch. Needs to be initialized with a start and end epoch and an initial merkle root.
- Prime.sol: Determins if a number is likely to be prime, based on the Miller-Rabin primality test
- binary.sol: Binary number manipulation.
- ethash.sol: Provides the ability to verifyHash using a hashimto function and fnv hashing.
- kecakk512.sol: Keccak512 hash function supporting SHA-3.
- lib: utility library
- ECVerify.sol: Verify's a signature and returns the signer address.
- EthUtils: hexString and byte manipulation
- MMR.sol: Merkle Mountain Range solidity library
- MMRWrapper.sol: Merkle Mountain Range wrapper functions.
- MPT.sol: Merkle Patricie Tries validation tools (uses RLPReader.sol)
- MPTValidatorV2.sol: Merkle Particia Tries validation tools improved by LayerZero
- RLPEncode.sol: A simple RLP encoding library.
- RLPReader.sol: RLP Reader
- Safecast.sol: Safe casting function for Uints.
Proving Mechanisms
Ethereum 1.0 contracts deployed to Harmony- EthereumLightClient.sol: Light Client for Ethereum 1.0, stores a mapping of blocks existing in the Canonical Chain verified using EthHash.
- EthereumParser.sol: Parse RLP-encoded block header into BlockHeader data structure and transactions with data fields order as defined in the Tx struct.
- EthereumProver.sol: Computes the hash of the Merkle-Patricia-Trie hash of the input and Validates a Merkle-Patricia-Trie proof. If the proof proves the inclusion of some key-value pair in the trie, the value is returned.
Note these contracts were planned to be implemented with Harmony Light Client support which includes Merkle Mountain Ranges (see this PR and this review). The planned timeline for implementing this had not been finalized as of Feb 2023.
- HarmonyLightClient.sol: Allows submission of checkpoints and manages mappings for
checkPointBlocks
(holding blockHeader information including the Merkle Mountain Range Root fieldmmrRoot
). - HarmonyParser.sol: Parse RLP-encoded block header into BlockHeader data structure and transactions with data fields order as defined in the Transaction struct.
- HarmonyProver.sol: Verification functions for Blocks, Transaction, Receipts etc. Verification is done by verifying MerkleProofs via
MPTValidator2.sol
.
Token Lockers
- BridgeToken.sol: ERC20 contract used for managing bridged tokens.
- FaucetToken.sol: ERC20 Token Faucet used for testing on testnets.
- TokenLocker.sol: Locks Bridged Tokens
- TokenLockerOnEthereum.sol: Ethereum TokenLocker
- TokenLockerOnHarmony.sol: Harmony TokenLocker
- TokenRegistry.sol: Responsible for mapping tokens between chains and minting new bridged tokens.
Off-chain (Javascript) Code Review
On-chain interaction
- bridge
- bridge.js: Interacts with provers and tokenLockers on the respective chains to perform the bridging of tokens across chains.
- contract.js: Responsible for deploying contracts, mapping tokens between chains and checking token status.
- ethBridge.js: extends bridge.js with a constructor for Ethereum
- hmyBridge.js: extens bridge.js with a constructor for Harmony
- token.js: interacts with ERC20 and FaucetToken (for testing).
- index.js: Command Line Interface commands.
Command Line Interface
- cli: CLI is a utility that provides a command-line interface to all the components to the Horizon bridge and allow performing end-to-end bridge functionalities.
- elsc.js: Ethereum Light Client deployed on Harmony. Supports deployment, status checks and querying block information.
- ethRelay.js: Block Relayer from Ethereum to Harmony
- everifier.js: Ethereum Verifier for Harmony. Supports the deployment of the verifier and validating Merkle Patricia Trie proofs from Harmony.
- index.js: Commands for the CLI.
Ethereum Light Client
- elc: Ethereum Light Client (ELC) is a SPV-based light client implemented as a smart contract that receives and stores Ethereum block header information.
- MerkleRoot.json: Holds starting epoch and Merkle root information.
- MerkleRootSol.js: Deploys a MerkleRoot.sol contract on Harmony for the given Ethereum epoch and merkle root information.
- client.js: Interaction with the Client.sol (the Ethereum Light Client deployed on Harmony).
- eth2one.js: Relays blocks from ethereum to Harmony.
- proofDump: Allows logging of dagProofs for blocks and epochs and writing them to files.
Proving Mechanisms
Ethereum Prover- eprover: EProver is a utility that provides verifiable proof about user’s Ethereum tx, e.g., lock tx.
- Receipt.js: Allows retreival of a receipt from Rpc, buffer or hex and serailiation of receipt.
- index.js: exports Eprover
- txProof.js: Takes a transaction hash and gets a receipt proof (sha3 hash, recieptRoor, proof and an encoded txIndex).
Relayer Mechanisms
Ethereum to Harmony Relayer- eth2hmy-relay: Eth2Hmy relay downloads the Ethereum block headers, extract information and relay it to ELC smart contract on Harmony.
Cryptographic Primitives
- eth2hmy-relay/lib: Library of functions used by the Ethereum to Harmony Relay
- DagPropf.js: Checks if a dag exists for an epoch, loads DAG for an epoch and verify header and getProof using the epoch's DAG.
- MmapDB.js: Merkle database functionality by extending Memory Map.
- getBlockHeader.js: Get Block information.
- merkel.js: MerkleTree functionality including construction of MerkleTrees and getting proofs, hex proofs, combined hashes, get Paired Elements and layers.
- ethashProof: ethash proving mechanisms
- BlockProof.js: Exports getHeaderProof, parseRlpHeader, getBlockByNumber
- DagMTreeEpoch.js: Exports generateDagMTree, genearateDagMTreeRange
- MerkelRootSol.js: Creates a MerkleRoot.sol contract for an inputted merkleInfo.
- lib
- configure.js: Configure TokenLocker and Faucet contracts.
- ethEthers.js: Shim over ethers allowing the instantiation of connections using a configured private key.
- logger.js: Logging Functions
- utils.ts: Utility functions including (buffer2hex, rpcWrapper, toRLPHeader, getReceiptLight, getReceipt, getReceiptRlp, getReceiptTrie,hex2key,index2key, expandkey, getReceiptProof, getTransactionProof, getAccountProof, getStorageProof, getKeyFromProof, fullToMin)
- @ethereumjs/block: Implements schema and functions related to Ethereum's block. (Ethereum 1.0 or Execution Chain for Ethereum 2.0)
- ethereumjs-util: A collection of utility functions for Ethereum. It can be used in Node.js and in the browser with browserify.
- ethers: A complete, compact and simple library for Ethereum and ilk, written in TypeScript.
- miller-rabin: implements Miller Rabin primality test
- mmap-io: Memory Map for node.js
- sha3: A pure JavaScript implementation of the Keccak family of cryptographic hashing algorithms, most notably including Keccak and SHA3.
Light Client Functionality
Token Lockers
References
Appendices
Appendix A: Current Implementation Walkthough
Following is a detailed walk though of the current implementation of the Ethereum Light Client and the flow for mapping tokens from Ethereum to Harmony.
Ethereum Light Client (on Harmony)
Design Existing Design
- DAG is generated for each Ethereum EPOCH: This takes a couple of hours and has a size of approx 1GB.
- Relayer is run to replicate each block header to the SPV Client on Harmony.
- EthereumLightClient.sol addBlockHeader: Adds each block header to the Ethereum Light Client.
- Transactions are Verified
# Start the relayer (note: replace the etherum light client address below)
# relay [options] <ethUrl> <hmyUrl> <elcAddress> relay eth block header to elc on hmy
yarn cli ethRelay relay http://localhost:8645 http://localhost:9500 0x3Ceb74A902dc5fc11cF6337F68d04cB834AE6A22
- DAG Generation can be done explicity by calling
dagProve
from the CLI or it is done automatically bygetHeaderProof
inethHashProof/BlockProof.js
which is called fromblockRelay
incli/ethRelay.js
. - Relaying of Block Headers is done by
blockRelayLoop
incli/ethRelay.js
which- Reads the last block header from EthereumLightClient.sol
- Loops through calling an Ethereum RPC per block to retrieve the blockHeader using
return eth.getBlock(blockNo).then(fromRPC)
in functiongetBlockByNumber
ineth2hmy-relay/getBlockHeader.js
- Adding BlockHeaders is done by
await elc.addBlockHeader(rlpHeader, proofs.dagData, proofs.proofs)
which is called fromcli/ethRelay.js
.addBlockHeader
inEthereumLightClient.sol
- calculates the blockHeader Hash
- and checks that it
- hasn't already been relayed,
- is the next block to be added,
- has a valid timestamp
- has a valid difficulty
- has a valid Proof of Work (POW)
- Check if the canonical chain needs to be replaced by another fork
Mapping Tokens (Ethereum to Harmony)
Design- If the Token Has not already been mapped on Harmony
- Harmony: Create an ERC20 Token
- Harmony: Map the Ethereum Token to the new ERC20 Contract
- Ethereum: Validate the Harmony Mapping Transaction
- Ethereum: Map the Harmony ERC20 token to the existing Ethereum Token
- Harmony: Validate the Ethereum mapping Transaction
Note: The key difference between TokenLockerOnEthereum.sol
and TokenLockerOnHarmony.sol
is the proof validation. TokenLockerOnEthereum.sol
uses ./lib/MMRVerifier.sol
to validate the Mountain Merkle Ranges on Harmony and HarmonyProver.sol
. TokenLockerOnHarmony.sol
imports ./lib/MPTValidatorV2.sol
to validate Merkle Patrica Trie and ./EthereumLightClient.sol
.
Note: validateAndExecuteProof
is responsible for creation of the BridgeTokens on the destination chain it does this by calling execute
call in TokenLockerLocker.sol
which then calls the function onTokenMapReqEvent
in TokenRegistry.sol
which creates a new Bridge Token BridgedToken mintAddress = new BridgedToken{salt: salt}();
and then initializes it. This uses (RLP) Serialization
Note: The shims in ethWeb3.js
provide simplified functions for ContractAt
, ContractDeploy
, sendTx
and addPrivateKey
and have a constructor which uses process.env.PRIVATE_KEY
.
# Map the Tokens
# map <ethUrl> <ethBridge> <hmyUrl> <hmyBridge> <token>
yarn cli Bridge map http://localhost:8645 0x017f8C7d1Cb04dE974B8aC1a6B8d3d74bC74E7E1 http://localhost:9500 0x017f8C7d1Cb04dE974B8aC1a6B8d3d74bC74E7E1 0x4e59AeD3aCbb0cb66AF94E893BEE7df8B414dAB1
- The CLI calls
tokenMap
insrc/bridge/contract.js
to- Instantiate the Ethereum Bridge and Harmony Bridge Contracts
- Calls
TokenMap
inscr/bridge/bridge.js
to- Issue a token Map request on Ethereum
const mapReq = await src.IssueTokenMapReq(token)
- Acknowledge the Map Request on Harmony
const mapAck = await Bridge.CrossRelayEthHmy(src, dest, mapReq)
- Issue a token Map request on Harmony
return Bridge.CrossRelayHmyEth(dest, src, mapAck.transactionHash)
- Issue a token Map request on Ethereum
- Bridge Map is called in src.cli.index.js and it calls
tokenMap
inbridge/contract.js
which- Get srcBridge Contract on Ethereum
TokenLockerOnEthereum.sol
fromethBridge.js
it also instantiates aneprover
usingtools/eprover/index.js
which callstxProof.js
which uses eth-proof npm package. Note: this is marked with a //TODO need to test and develop proving logic on Harmony. - Get destBridge Contract on Hamony
TokenLockerOnHarmony.sol
fromhmyBridge.js
it also instantiates anhprove
usingtools/eprover/index.js
which callstxProof.js
which uses eth-proof npm package. - calls
TokenMap
inbridge.js
- Get srcBridge Contract on Ethereum
TokenMap
Calls IssueTokenMapReq (on the Ethreum Locker) returning themapReq.transactionHash
IssueTokenMapReq(token)
is held inbridge.js
as part of the bridge class- It calls
issueTokenMapReq
onTokenLockerOnEthereum.sol
which is implemented byTokenRegistry.sol
issueTokenMapReq
checks if the token has already been mapped if not it was emitting aTokenMapReq
with the details of the token to be mapped. However this was commented out as it was felt that, if it has not been mapped, we use thetransactionHash
of the mapping request` to drive the logic below (not the event).
TokenMap
callsBridge.CrossRelay
with the IssueTokenMapReq.hash to- gets the proof of the transaction on Ethereum via
getProof
callingprover.ReceiptProof
which calls the eprover and returnsproof
withhash: sha3(resp.header.serialize()),
root: resp.header.receiptRoot,
proof: encode(resp.receiptProof),
key: encode(Number(resp.txIndex)) // '0x12' => Nunmber
- We then call
dest.ExecProof(proof)
to execute the proof on Harmony- This calls
validateAndExecuteProof
onTokenLockerOnHarmony.sol
with theproofData
from above, which- requires
lightclient.VerifyReceiptsHash(blockHash, rootHash),
implemented by./EthereumLightClient.sol
- This returns
return bytes32(blocks[uint256(blockHash)].receiptsRoot) == receiptsHash;
- Which means the block has to be relayed first, as we have just executed the transaction the relayer usually has not relayed the block so this will fail
- This returns
- requires
lightclient.isVerified(uint256(blockHash)
implemented by./EthereumLightClient.sol
- This returns
return canonicalBlocks[blockHash] && blocks[blockHash].number + 25 < blocks[canonicalHead].number;
- Which means there must be an additional 25 blocks on Ethereum before this can be processed. This logic needs to be rewritten to break down execution for 1. the ethereum mapping request 2. After a 25 block delay the Harmony Proof validation and executing the Harmony Mapping Request
- This returns
require(spentReceipt[receiptHash] == false, "double spent!");
to ensure that we haven't already executed this proof- gets the
rlpdata
usingEthereumProver.validateMPTProof
implemented byEthereumProver.sol
which- Validates a Merkle-Patricia-Trie proof.
- Returns a value whose inclusion is proved or an empty byte array for a proof of exclusion
- marks
spentReceipt[receiptHash] = true;
execute(rlpdata)
implemented byTokenLocker.sol
which callsonTokenMapReqEvent(topics, Data)
implemented byTokenRegistry.sol
address tokenReq = address(uint160(uint256(topics[1])));
gets the address of the token to be mapped.- require
address(RxMapped[tokenReq]) == address(0)
that the token has not already been mapped. address(RxMapped[tokenReq]) == address(0)
creates a new BridgedToken implemented byBridgedToken.sol
contract BridgedToken is ERC20Upgradeable, ERC20BurnableUpgradeable, OwnableUpgradeable
it is a standard openzepplin ERC20 Burnable, Ownable, Upgradeable token
mintAddress.initialize
initialize the token with the samename
,symbol
anddecimals
as the ethereum bridged tokenRxMappedInv[address(mintAddress)] = tokenReq;
updates the inverse Key Value MappingRxMapped[tokenReq] = mintAddress;
updates the Ethereum mapped tokensRxTokens.push(mintAddress);
add the newly created token to a list of bridged tokensemit TokenMapAck(tokenReq, address(mintAddress));
require(executedEvents > 0, "no valid event")
to check if it executed the mapping correctly.
- requires
- This calls
- gets the proof of the transaction on Ethereum via
- We then take the Harmony Mapping
transactionHash
and repeat the above process to prove the Harmony mapping acknowledgment on Ethereum (Cross Relay second call)return Bridge.CrossRelay(dest, src, mapAck.transactionHash);
- gets the proof of the transaction on Harmony via
getProof
callingprover.ReceiptProof
which calls the eprover and returnsproof
with _hash: sha3(resp.header.serialize()),
_root: resp.header.receiptRoot,
_proof: encode(resp.receiptProof),
_key: encode(Number(resp.txIndex)) // '0x12' => Nunmber
- We then call
dest.ExecProof(proof)
to execute the proof on Ethereum- This calls
validateAndExecuteProof
onTokenLokerOnEthereum.sol
with theproofData
from above, whichrequire(lightclient.isValidCheckPoint(header.epoch, mmrProof.root),
implemented byHarmonyLightClient.sol
return epochMmrRoots[epoch][mmrRoot]
which means that the epoch has to have had a checkpoint submitted viasubmitCheckpoint
bytes32 blockHash = HarmonyParser.getBlockHash(header);
gets the blockHash implemented byHarmonyParser.sol
- This returns
return keccak256(getBlockRlpData(header));
getBlockRlpData
creates a listbytes[] memory list = new bytes[](15);
and uses statements likelist[0] = RLPEncode.encodeBytes(abi.encodePacked(header.parentHash));
to perform Recursive-Length Prefix (RLP) Serialization implemented byRLPEncode.sol
- This returns
HarmonyProver.verifyHeader(header, mmrProof);
verifys the header implemented byHarmonyProver.sol
bytes32 blockHash = HarmonyParser.getBlockHash(header);
gets the blockHash implemented byHarmonyParser.sol
as abovevalid = MMRVerifier.inclusionProof(proof.root, proof.width, proof.index, blockHash, proof.peaks, proof.siblings);
verifys the proff using the Merkle Mountain Range Proof passedMMRVerifier.MMRProof memory proof
and theblockHash
.-
NOTE: This means that a
submitCheckpoint
inHarmonyLightClient.sol
needs to have called either for the next epoch or for a checkpoint, after the block the harmony mapping transaction was in. -
NOTE: Automatic submission of checkpoints to the Harmony Light Client has not been developed as yet. (It is not part of the
ethRelay.js
). And so the checkpoint would need to be manually submitted before the Ethereum Mapping could take place.
require(spentReceipt[receiptHash] == false, "double spent!");
ensure that we haven't already processed this mapping request`HarmonyProver.verifyReceipt(header, receiptdata)
ensure the receiptdata is validspentReceipt[receiptHash] = true;
marks the receipt as having been processedexecute(receiptdata.expectedValue);
implemented byTokenLocker.sol
which callsonTokenMapAckEvent(topics)
implemented byTokenRegistry.sol
address tokenReq = address(uint160(uint256(topics[1])));
address tokenAck = address(uint160(uint256(topics[2])));
require(TxMapped[tokenReq] == address(0), "missing mapping to acknowledge");
TxMapped[tokenReq] = tokenAck;
TxMappedInv[tokenAck] = IERC20Upgradeable(tokenReq);
TxTokens.push(IERC20Upgradeable(tokenReq));
- This calls
- We then call
- Upon completion of tokenMap control is passed back to Bridge Map which
- Calls TokenPair on Ethereum
- Calls ethTokenInfo to get the status of the ERC20
- Calls hmyTokenInfo to get the tokenStatus on Harmony