Flow: Observing incoming cross-chain transactions from EVM-compatible chains
Observation and processing of incoming cross-chain transactions starts in the ExternalChainWatcher()
function. This function runs the observeInTX()
function every chainconfig.BlockTime
seconds, up to a maximum of 24 seconds. For Ethereum, this would be 12 seconds.
The observeInTX()
function has the bulk of the functionality. It first ensures that the chain the transaction is coming from has been whitelisted. All external chains are blacklisted by default. It then ensures to go back to the last confirmed block (i.e., it does not take the latest block on the tip of the chain as confirmed). This prevents potential issues where a chain reorganization would lead to a double spend.
It then looks for any emitted ZetaSent
events from the last checked block to the latest confirmed block. It ensures to get emitted events only from the Connector contract, as otherwise anyone would be able to deploy their own contract and emit events of the same signature to reach this code path.
For each ZetaSent
event, the code uses the PostSend()
function to construct a MsgVoteOnObservedInboundTx
message to broadcast to Zetacore. The coin type for the amount of coins being sent across chains is set to CoinType_Zeta
for this case, which means ZetaSent
events are only used to transfer ZETA across chains. This message will get picked up by the x/crosschain
module. See for more information on what happens then.
Next, the code does the same thing for Deposited
events from the ERC20Custody
contract, except this time the coin type is set to CoinType_ERC20
.
Finally, one other thing users can do is transfer native tokens to a designated TSS address. When they do this, the transfer is noted as a "gas deposit". This is how users pay for gas in cross-chain transactions, and observeInTX()
handles this case for both EVM- and Klaytn-based chains. The logic is largely the same — a MsgVoteOnObservedInboundTx
message is constructed with the coin type set to CoinType_Gas
and then subsequently broadcasted.
The inputs that are controllable in this flow are emitted event parameters. Specifically, for the ZetaSent
event, a user would be able to fully control the following parameters:
We found one issue where, if the destinationChainId
is set to a nonexistent chain, then a nil pointer dereference
exception will be thrown inside observeInTX()
.
The code also specifically checks to see if destinationAddress
is set to the ZETA token contract address. If it is, the code logs a potential attack attempt, but it continues to run normally otherwise.
For the Deposited
event, a user would be able to fully control the following parameters:
None of the parameters are checked or used in the code. They are simply passed into the MsgVoteOnObservedInboundTx
message that is later broadcasted.
For more information on how these parameters are used in the x/crosschain
module, please see .