Bug: Anvil->Terra operator can't verify EVM deposit — hash mismatch #89

Closed
opened 2026-03-31 08:34:08 +00:00 by Brouie · 11 comments
Brouie commented 2026-03-31 08:34:08 +00:00 (Migrated from gitlab.com)

Anvil->LocalTerra TKNA transfer deposits successfully on EVM, auto-withdraw submits on Terra, but operator can't find matching EVM deposit. operator log shows:

EVM deposit not found on this chain (zero timestamp)
No matching EVM deposit found for withdraw hash. This withdrawal cannot be approved until the deposit is confirmed on EVM.

details from operator log:

  • xchain_hash_id: 0x04179cac73997e42d871957c9e612cc7f839e98b379a87b4d99cc19cbd1c0600
  • src_chain: 0x00000001 (Anvil)
  • nonce: 6
  • amount: 995000000000000000
  • token: terra17p9r...ydrqk7 (TKNA)
  • known_evm_chains: 2 (checked both Anvil and Anvil1, neither has it)

the frontend computed an xchainHashId and submitted withdraw on Terra, but the hash doesn't match what the EVM bridge stored for that deposit. operator queries EVM bridge, gets zero timestamp, skips approval.

note: Terra->Anvil direction works fine (tested same session, auto-withdraw success). Anvil->Terra TKNA also worked on 3/30 (pre-register-tokens fix). possible regression from 74b5f3e changes.

tested on commit 74b5f3e, clean make start-qa, laptop Vite workflow.

Anvil->LocalTerra TKNA transfer deposits successfully on EVM, auto-withdraw submits on Terra, but operator can't find matching EVM deposit. operator log shows: EVM deposit not found on this chain (zero timestamp) No matching EVM deposit found for withdraw hash. This withdrawal cannot be approved until the deposit is confirmed on EVM. details from operator log: - xchain_hash_id: 0x04179cac73997e42d871957c9e612cc7f839e98b379a87b4d99cc19cbd1c0600 - src_chain: 0x00000001 (Anvil) - nonce: 6 - amount: 995000000000000000 - token: terra17p9r...ydrqk7 (TKNA) - known_evm_chains: 2 (checked both Anvil and Anvil1, neither has it) the frontend computed an xchainHashId and submitted withdraw on Terra, but the hash doesn't match what the EVM bridge stored for that deposit. operator queries EVM bridge, gets zero timestamp, skips approval. note: Terra->Anvil direction works fine (tested same session, auto-withdraw success). Anvil->Terra TKNA also worked on 3/30 (pre-register-tokens fix). possible regression from 74b5f3e changes. tested on commit 74b5f3e, clean make start-qa, laptop Vite workflow.
PlasticDigits commented 2026-04-01 01:39:59 +00:00 (Migrated from gitlab.com)

@Brouie This should be resolved, please verify

@Brouie This should be resolved, please verify
Brouie commented 2026-04-01 04:27:15 +00:00 (Migrated from gitlab.com)

still reproducing on 003bf31. Anvil->LocalTerra 99.50 TKNA: deposit succeeds, auto-withdraw submits on Terra, but operator still can't find EVM deposit. same zero timestamp error. xchain_hash_id=0x2fddd1f694a8804913d91f18e0662110f847dff15dd731fcd3248c8bb402c136, nonce=1, src_chain=0x00000001.

still reproducing on 003bf31. Anvil->LocalTerra 99.50 TKNA: deposit succeeds, auto-withdraw submits on Terra, but operator still can't find EVM deposit. same zero timestamp error. xchain_hash_id=0x2fddd1f694a8804913d91f18e0662110f847dff15dd731fcd3248c8bb402c136, nonce=1, src_chain=0x00000001.
Brouie commented 2026-04-01 04:55:07 +00:00 (Migrated from gitlab.com)

mentioned in issue #67

mentioned in issue #67
Brouie commented 2026-04-01 04:55:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #96

mentioned in issue #96
PlasticDigits commented 2026-04-01 11:46:03 +00:00 (Migrated from gitlab.com)

mentioned in commit 98fccdd05b

mentioned in commit 98fccdd05b1fb536ba58f4740c1fdeb2f34f805c
Brouie commented 2026-04-02 04:23:55 +00:00 (Migrated from gitlab.com)

still reproducing on 02d8e6a (latest pull 4/2). tested Anvil->Terra with TKNA via MetaMask->Keplr.

frontend flow works perfectly now -- deposit done, submit hash auto-submitted to Terra, tx confirmed. but operator keeps looping "EVM deposit not found on this chain (zero timestamp)" for xchain_hash_id=0x2fddd1f...

dug into the root cause. the token encoding is mismatched between EVM and Terra:

EVM side: tokenRegistry.getDestToken(TKNA, 0x00000002) returns 0x18844b4621683e1f... which is keccak256("terra17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgsydrqk7") -- the keccak of the full bech32 string.

expected encoding: the new HashLib.t.sol cross-chain parity tests (CW20_TOKEN_BYTES32, test_TransferHash_EvmToTerra_CW20_CrossChainParity) expect CW20 tokens encoded as bech32-decoded bytes left-padded to 32 bytes (0x00000000000000000000000035743074956c710800e83198011ccbd4ddf1556d).

so the EVM contract hashes with keccak256(bech32_string) while the test vectors expect bech32_decode(addr) left-padded. different token bytes in = different xchainHashId out = operator cant match deposit to withdrawal.

looks like the token registration step (qa:full-token-setup / registerAllTokens) is writing the keccak encoding for CW20 destTokens when it should be writing the bech32-decoded left-padded encoding.

operator binary (March 27 build) is fine -- it just does getDeposit(hash) lookups, doesnt compute hashes itself.

still reproducing on 02d8e6a (latest pull 4/2). tested Anvil->Terra with TKNA via MetaMask->Keplr. frontend flow works perfectly now -- deposit done, submit hash auto-submitted to Terra, tx confirmed. but operator keeps looping "EVM deposit not found on this chain (zero timestamp)" for xchain_hash_id=0x2fddd1f... dug into the root cause. the token encoding is mismatched between EVM and Terra: **EVM side:** tokenRegistry.getDestToken(TKNA, 0x00000002) returns 0x18844b4621683e1f... which is keccak256("terra17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgsydrqk7") -- the keccak of the full bech32 string. **expected encoding:** the new HashLib.t.sol cross-chain parity tests (CW20_TOKEN_BYTES32, test_TransferHash_EvmToTerra_CW20_CrossChainParity) expect CW20 tokens encoded as bech32-decoded bytes left-padded to 32 bytes (0x00000000000000000000000035743074956c710800e83198011ccbd4ddf1556d). so the EVM contract hashes with keccak256(bech32_string) while the test vectors expect bech32_decode(addr) left-padded. different token bytes in = different xchainHashId out = operator cant match deposit to withdrawal. looks like the token registration step (qa:full-token-setup / registerAllTokens) is writing the keccak encoding for CW20 destTokens when it should be writing the bech32-decoded left-padded encoding. operator binary (March 27 build) is fine -- it just does getDeposit(hash) lookups, doesnt compute hashes itself.
Brouie commented 2026-04-02 04:33:13 +00:00 (Migrated from gitlab.com)

mentioned in issue #94

mentioned in issue #94
Brouie commented 2026-04-02 05:28:58 +00:00 (Migrated from gitlab.com)

hash fix VERIFIED on 5e9d03b. Anvil->Terra with TKNA -- deposit, submit hash, and operator approval all passed. the token encoding fix resolved the mismatch.

however execution is blocked at step 4: "transfer amount exceeds the maximum daily rate limit". this is a Terra-side withdraw rate limit config issue, not related to the hash. may need setExplicitUnlimitedWithdrawRateLimit or equivalent for the QA tokens on Terra.

marking the hash mismatch itself as fixed. the rate limit is a separate issue -- should i file it separately or add to #96?

hash fix VERIFIED on 5e9d03b. Anvil->Terra with TKNA -- deposit, submit hash, and operator approval all passed. the token encoding fix resolved the mismatch. however execution is blocked at step 4: "transfer amount exceeds the maximum daily rate limit". this is a Terra-side withdraw rate limit config issue, not related to the hash. may need setExplicitUnlimitedWithdrawRateLimit or equivalent for the QA tokens on Terra. marking the hash mismatch itself as fixed. the rate limit is a separate issue -- should i file it separately or add to #96?
PlasticDigits commented 2026-04-02 05:38:08 +00:00 (Migrated from gitlab.com)

For automated e2e testing setExplicitUnlimitedWithdrawRateLimit is necessary because it allows for less flakey testing, a lot of times you and I forget to reset infra before running e2e tests which makes some tests fail when they shouldnt. Ideally the infra should always be reset before each e2e run but its not always practical. However, the comment on the function is wrong. It is not something that should be ever used in production environments, because rate limiting is a key part of our security model. Rate limiting doesnt stop an attacker, but it mitigates the amount of damage that an attacker can do. As an example, look at the solana perp dex hack of $200m today. If that perp dex had rate limiting, the attack would not have been able to extract that much assets.

For automated e2e testing setExplicitUnlimitedWithdrawRateLimit is necessary because it allows for less flakey testing, a lot of times you and I forget to reset infra before running e2e tests which makes some tests fail when they shouldnt. Ideally the infra should always be reset before each e2e run but its not always practical. However, the comment on the function is wrong. It is not something that should be ever used in production environments, because rate limiting is a key part of our security model. Rate limiting doesnt stop an attacker, but it mitigates the amount of damage that an attacker can do. As an example, look at the solana perp dex hack of $200m today. If that perp dex had rate limiting, the attack would not have been able to extract that much assets.
Brouie commented 2026-04-04 03:18:12 +00:00 (Migrated from gitlab.com)

closing -- verified on 5e9d03b, hash fix confirmed working. Anvil->Terra deposit+submit+operator approval all pass. step 4 rate limit is expected behavior (separate from this issue).

closing -- verified on 5e9d03b, hash fix confirmed working. Anvil->Terra deposit+submit+operator approval all pass. step 4 rate limit is expected behavior (separate from this issue).
Brouie (Migrated from gitlab.com) closed this issue 2026-04-04 03:18:32 +00:00
PlasticDigits commented 2026-08-17 14:30:11 +00:00 (Migrated from gitlab.com)

mentioned in issue #136

mentioned in issue #136
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-bridge-monorepo#89
No description provided.