Skip to content

fix(cct-sdk): Validate EVM deployment receipt contract address - #421

Merged
mervin-link merged 4 commits into
cct-sdkfrom
fix/DAPP-11480-verify-receipt-address
Sep 28, 2026
Merged

mervin-link merged 4 commits into
cct-sdkfrom
fix/DAPP-11480-verify-receipt-address

Conversation

@mervin-link

@mervin-link mervin-link commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

What

  • DAPP-11480
  • Validate the deployment receipt address against the CREATE address derived from the submitting wallet and nonce
  • Add shared tests for valid lowercase, substituted, and malformed receipt addresses

Why

  • Prevent returning an incorrect or malformed contract address after an EVM deployment

@mervin-link
mervin-link requested a review from a team as a code owner September 14, 2026 15:04
@vercel

vercel Bot commented Sep 14, 2026

Copy link
Copy Markdown

You must have Developer access to commit code to Chainlink Labs on Vercel. If you contact an administrator and receive Developer access, commit again to see your changes.

Learn more: https://vercel.com/docs/accounts/team-members-and-roles/access-roles#team-level-roles

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

CI Test Report

❌ 2 of 3371 tests did not pass (pass 3363, fail 2, cancelled 0, skipped 6) in 6m 40s

Failed suites: ccip-sdk/src/cct/evm/advanced-pool-hooks/operations/deploy-advanced-pool-hooks.test.ts, ccip-sdk/src/evm/fork.test.ts

Failed tests
test at ccip-sdk/src/cct/evm/advanced-pool-hooks/operations/deploy-advanced-pool-hooks.test.ts:200:5
✖ returns the hash, deployed address, and explorer verification input (15.322239ms)
  Error [CCTTxFailedError]: deployAdvancedPoolHooks failed: deployment returned an unexpected contract address
      at DeployAdvancedPoolHooks.execute (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/cct/evm/operation.ts:218:13)
      at async TestContext.<anonymous> (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/ccip-sdk/src/cct/evm/advanced-pool-hooks/operations/deploy-advanced-pool-hooks.test.ts:201:22)
      at async Test.run (node:internal/test_runner/test:1409:7)
      at async Promise.all (index 0)
      at async Suite.run (node:internal/test_runner/test:1905:7)
      at async Suite.processPendingSubtests (node:internal/test_runner/test:974:7) {
    _isCCIPError: true,
    code: 'CCT_TX_FAILED',
    context: { txHash: '0xabababababababababababababababababababababababababababababababab', expectedAddress: '0x8F7a45eBDe059392E46A46DCc14AB24681A961Ea', returnedAddress: '0x7777777777777777777777777777777777777777', operation: 'deployAdvancedPoolHooks', reason: 'deployment returned an unexpected contract address' },
    isTransient: false,
    retryAfterMs: undefined,
    recovery: 'Verify the RPC endpoint; the expected deployment address is in context.expectedAddress.',
    [cause]: undefined
  }

test at ccip-sdk/src/evm/fork.test.ts:308:5
✖ should send via a v1.5 lane and emit CCIPSendRequested (6260.389023ms)
  Error: missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x96f4e9f9000000000000000000000000000000000000000000000000e4ce0fa640d887f1000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002133700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044181dcf100000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000", "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "to": "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.17.0)
      at makeError (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/node_modules/ethers/lib.esm/utils/errors.js:132:21)
      at getBuiltinCallException (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/node_modules/ethers/lib.esm/abi/abi-coder.js:102:12)
      at AbiCoder.getBuiltinCallException (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/node_modules/ethers/lib.esm/abi/abi-coder.js:203:16)
      at JsonRpcProvider.getRpcError (file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/node_modules/ethers/lib.esm/providers/provider-jsonrpc.js:691:32)
      at file:///home/runner/work/ccip-tools-ts/ccip-tools-ts/node_modules/ethers/lib.esm/providers/provider-jsonrpc.js:298:45
      at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
    code: 'CALL_EXCEPTION',
    action: 'estimateGas',
    data: null,
    reason: null,
    transaction: { to: '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59', data: '0x96f4e9f9000000000000000000000000000000000000000000000000e4ce0fa640d887f1000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002133700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044181dcf100000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000', from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' },
    invocation: null,
    revert: null,
    shortMessage: 'missing revert data',
    info: { error: { code: -32603, message: 'failed to get storage for 0x097D90c9d3E0B50Ca60e1ae45F6A81010f9FB534 at 42144584401307208567528880435679660272147860748695294829831540321263584120810: HTTP error 521 with body: <!DOCTYPE html>\n<!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en-US"> <![endif]-->\n<!--[if IE 7]>    <html class="no-js ie7 oldie" lang="en-US"> <![endif]-->\n<!--[if IE 8]>    <html class="no-js ie8 oldie" lang="en-US"> <![endif]-->\n<!--[if gt IE 8]><!--> <html class="no-js" lang="en-US"> <!--<![endif]-->\n<head>\n\n<title>ethpandaops.io | 521: Web server is down</title>\n<meta charset="UTF-8" />\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n<meta http-equiv="X-UA-Compatible" content="IE=Edge" />\n<meta name="robots" content="noindex, nofollow" />\n<meta name="viewport" content="width=device-width,initial-scale=1" />\n<link rel="stylesheet" id="cf_styles-css" href="/cdn-cgi/styles/main.css" />\n</head>\n<body>\n<div id="cf-wrapper">\n    <div id="cf-error-details" class="p-0">\n        <header class="mx-auto pt-10 lg:pt-6 lg:px-8 w-240 lg:w-full mb-8">\n            <h1 class="inline-block sm:block sm:mb-2 font-light text-60 lg:text-4xl text-black-dark leading-tight mr-2">\n                <span class="inline-block">Web server is down</span>\n                <span class="code-label">Error code 521</span>\n            </h1>\n            <div>\n                Visit <a href="https://www.cloudflare.com/5xx-error-landing?utm_source=errorcode_521&utm_campaign=rpc.sepolia.ethpandaops.io" target="_blank" rel="noopener noreferrer">cloudflare.com</a> for more information.\n            </div>\n            <div class="mt-3">2026-09-28 10:43:02 UTC</div>\n        </header>\n        <div class="my-8 bg-gradient-gray">\n            <div class="w-240 lg:w-full mx-auto">\n                <div class="clearfix md:px-8">\n                    <div id="cf-browser-status" class=" relative w-1/3 md:w-full py-15 md:p-0 md:py-8 md:text-left md:border-solid md:border-0 md:border-b md:border-gray-400 overflow-hidden float-left md:float-none text-center">\n  <div class="relative mb-10 md:m-0">\n    \n    <span class="cf-icon-browser block md:hidden h-20 bg-center bg-no-repeat"></span>\n    <span class="cf-icon-ok w-12 h-12 absolute left-1/2 md:left-auto md:right-0 md:top-0 -ml-6 -bottom-4"></span>\n    \n  </div>\n  <span class="md:block w-full truncate">You</span>\n  <h3 class="md:inline-block mt-3 md:mt-0 text-2xl text-gray-600 font-light leading-1.3">\n  \n    Browser\n  \n  </h3>\n  \n  <span class="leading-1.3 text-2xl text-green-success">Working</span>\n  \n</div>\n                    <div id="cf-cloudflare-status" class=" relative w-1/3 md:w-full py-15 md:p-0 md:py-8 md:text-left md:border-solid md:border-0 md:border-b md:border-gray-400 overflow-hidden float-left md:float-none text-center">\n  <div class="relative mb-10 md:m-0">\n    <a href="https://www.cloudflare.com/5xx-error-landing?utm_source=errorcode_521&#38;utm_campaign=rpc.sepolia.ethpandaops.io" target="_blank" rel="noopener noreferrer">\n    <span class="cf-icon-cloud block md:hidden h-20 bg-center bg-no-repeat"></span>\n    <span class="cf-icon-ok w-12 h-12 absolute left-1/2 md:left-auto md:right-0 md:top-0 -ml-6 -bottom-4"></span>\n    </a>\n  </div>\n  <span class="md:block w-full truncate">Dallas</span>\n  <h3 class="md:inline-block mt-3 md:mt-0 text-2xl text-gray-600 font-light leading-1.3">\n  <a href="https://www.cloudflare.com/5xx-error-landing?utm_source=errorcode_521&utm_campaign=rpc.sepolia.ethpandaops.io" target="_blank" rel="noopener noreferrer">\n    Cloudflare\n  </a>\n  </h3>\n  \n  <span class="leading-1.3 text-2xl text-green-success">Working</span>\n  \n</div>\n                    <div id="cf-host-status" class="cf-error-source relative w-1/3 md:w-full py-15 md:p-0 md:py-8 md:text-left md:border-solid md:border-0 md:border-b md:border-gray-400 overflow-hidden float-left md:float-none text-center">\n  <div class="relative mb-10 md:m-0">\n    \n    <span class="cf-icon-server block md:hidden h-20 bg-center bg-no-repeat"></span>\n    <span class="cf-icon-error w-12 h-12 absolute left-1/2 md:left-auto md:right-0 md:top-0 -ml-6 -bottom-4"></span>\n    \n  </div>\n  <span class="md:block w-full truncate">rpc.sepolia.ethpandaops.io</span>\n  <h3 class="md:inline-block mt-3 md:mt-0 text-2xl text-gray-600 font-light leading-1.3">\n  \n    Host\n  \n  </h3>\n  \n  <span class="leading-1.3 text-2xl text-red-error">Error</span>\n  \n</div>\n                </div>\n            </div>\n        </div>\n\n        <div class="w-240 lg:w-full mx-auto mb-8 lg:px-8">\n            <div class="clearfix">\n                <div class="w-1/2 md:w-full float-left pr-6 md:pb-10 md:pr-0 leading-relaxed">\n                    <h2 class="text-3xl font-normal leading-1.3 mb-4">What happened?</h2>\n                    <p>The web server is not returning a connection. As a result, the web page is not displaying.</p>\n                </div>\n                <div class="w-1/2 md:w-full float-left leading-relaxed">\n                    <h2 class="text-3xl font-normal leading-1.3 mb-4">What can I do?</h2>\n                    \n                <h3 class="text-15 font-semibold mb-2">If you are a visitor of this website:</h3>\n                <p class="mb-6">Please try again in a few minutes.</p>\n\n                <h3 class="text-15 font-semibold mb-2">If you are the owner of this website:</h3>\n                <p><span>Contact your hosting provider letting them know your web server is not responding.</span> <a rel="noopener noreferrer" href="https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-521/">Additional troubleshooting information</a>.</p>\n                </div>\n            </div>\n        </div>\n\n        <div class="cf-error-footer cf-wrapper w-240 lg:w-full py-10 sm:py-4 sm:px-8 mx-auto text-center sm:text-left border-solid border-0 border-t border-gray-300">\n    <p class="text-13">\n      <span class="cf-footer-item sm:block sm:mb-1">Cloudflare Ray ID: <strong class="font-semibold">a422251308936aea</strong></span>\n      <span class="cf-footer-separator sm:hidden">&bull;</span>\n      <span id="cf-footer-item-ip" class="cf-footer-item hidden sm:block sm:mb-1">\n        Your IP:\n        <button type="button" id="cf-footer-ip-reveal" class="cf-footer-ip-reveal-btn">Click to reveal</button>\n        <span class="hidden" id="cf-footer-ip">20.171.32.194</span>\n        <span class="cf-footer-separator sm:hidden">&bull;</span>\n      </span>\n      <span class="cf-footer-item sm:block sm:mb-1"><span>Performance &amp; security by</span> <a rel="noopener noreferrer" href="https://www.cloudflare.com/5xx-error-landing?utm_source=errorcode_521&#38;utm_campaign=rpc.sepolia.ethpandaops.io" id="brand_link" target="_blank">Cloudflare</a></span>\n      \n    </p>\n    <script>(function(){function d(){var b=a.getElementById("cf-footer-item-ip"),c=a.getElementById
… truncated …

Summary

ℹ tests 3371
ℹ suites 876
ℹ pass 3363
ℹ fail 2
ℹ cancelled 0
ℹ skipped 6
ℹ todo 0
ℹ duration_ms 399658.387385
Coverage report
File                                   | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------------------------------|---------|----------|---------|---------|-------------------
All files                              |   85.68 |    83.56 |   77.82 |   85.68 |                   
 ccip-cli/src                          |    87.5 |    59.25 |   66.66 |    87.5 |                   
  index.ts                             |    87.5 |    59.25 |   66.66 |    87.5 | ...85-186,192-197 
 ccip-cli/src/commands                 |   52.96 |    75.47 |    62.9 |   52.96 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  lane-latency.ts                      |   70.76 |    83.33 |   33.33 |   70.76 | ...,76-83,119-128 
  lane.ts                              |   86.98 |    68.42 |     100 |   86.98 | ...41-144,148-150 
  manual-exec.ts                       |   20.45 |      100 |       0 |   20.45 | ...42-152,154-350 
  parse.ts                             |    55.2 |      100 |       0 |    55.2 | 47-51,58-65,67-96 
  search.ts                            |   81.25 |      100 |       0 |   81.25 | 24-29             
  send.ts                              |   14.23 |      100 |       0 |   14.23 | ...08-236,238-548 
  show.ts                              |    75.8 |    63.75 |     100 |    75.8 | ...17-320,401-403 
  supported-tokens.ts                  |   17.93 |      100 |       0 |   17.93 | ...80-353,355-368 
  token.ts                             |   22.72 |      100 |       0 |   22.72 | ...3,60-67,69-132 
  types.ts                             |     100 |      100 |     100 |     100 |                   
  utils.ts                             |   88.03 |    79.48 |   96.66 |   88.03 | ...12-722,730-738 
 ccip-cli/src/commands/search          |   53.89 |    76.19 |    12.5 |   53.89 |                   
  messages.ts                          |   53.89 |    76.19 |    12.5 |   53.89 | ...36-258,260-295 
 ccip-cli/src/providers                |    56.7 |    78.74 |   26.66 |    56.7 |                   
  aptos.ts                             |   51.47 |      100 |       0 |   51.47 | ...,95-96,105-136 
  evm.ts                               |   72.25 |    63.63 |      75 |   72.25 | ...34,137-149,169 
  index.ts                             |   78.47 |    83.33 |      90 |   78.47 | ...09,279,333-381 
  solana.ts                            |   44.89 |      100 |       0 |   44.89 | ...12-113,122-147 
  sui.ts                               |    49.5 |      100 |       0 |    49.5 | ...57-258,267-301 
  ton.ts                               |   15.03 |      100 |       0 |   15.03 | 24-153            
 ccip-cli/src/providers/canton         |   69.57 |     76.4 |   45.71 |   69.57 |                   
  auth.ts                              |   82.41 |    73.01 |   53.33 |   82.41 | ...17-333,464-469 
  config.ts                            |   75.64 |       75 |     100 |   75.64 | 36-43,48-58       
  index.ts                             |     100 |      100 |     100 |     100 |                   
  wallet.ts                            |   53.09 |      100 |   33.33 |   53.09 | ...58-260,411-452 
 ccip-cli/src/verifiers                |   82.26 |    90.47 |   33.33 |   82.26 |                   
  grpc-transport.ts                    |     100 |       90 |   66.66 |     100 | 48,57             
  options.ts                           |   62.12 |      100 |       0 |   62.12 | 12-20,34-37,52-63 
 ccip-sdk/src                          |   95.77 |    88.65 |   93.67 |   95.77 |                   
  chain.ts                             |   96.86 |    83.33 |   85.71 |   96.86 | ...2565,2598-2599 
  commits.ts                           |   97.01 |    91.57 |     100 |   97.01 | 104-111,193-194   
  execution.ts                         |   93.13 |    88.23 |     100 |   93.13 | ...53-159,192-199 
  explorer.ts                          |     100 |      100 |     100 |     100 |                   
  extra-args.ts                        |     100 |    89.74 |     100 |     100 | 225,260,297,312   
  fetch.ts                             |   97.83 |     93.5 |   97.87 |   97.83 | ...-944,1190-1192 
  gas.ts                               |   85.14 |    86.04 |      75 |   85.14 | ...82-293,402-451 
  http-status.ts                       |     100 |      100 |     100 |     100 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  messages.ts                          |    79.2 |    47.16 |      40 |    79.2 | ...99-310,314-327 
  networks.ts                          |     100 |      100 |     100 |     100 |                   
  offchain.ts                          |   84.64 |    76.92 |     100 |   84.64 | ...18-227,236-237 
  requests.ts                          |    95.4 |    90.58 |     100 |    95.4 | ...54-655,680-685 
  supported-chains.ts                  |     100 |      100 |     100 |     100 |                   
  types.ts                             |     100 |      100 |     100 |     100 |                   
  utils.ts                             |   99.28 |    96.29 |     100 |   99.28 | ...12,217-218,220 
 ccip-sdk/src/api                      |   93.56 |    83.33 |   95.23 |   93.56 |                   
  index.ts                             |   93.56 |    83.33 |   95.23 |   93.56 | ...73-876,929-958 
 ccip-sdk/src/aptos                    |   78.99 |    78.27 |   76.19 |   78.99 |                   
  exec.ts                              |   29.31 |      100 |       0 |   29.31 | 18-58             
  hasher.ts                            |   75.89 |       80 |   66.66 |   75.89 | 17-36,50-56       
  index.ts                             |   69.45 |       80 |   67.44 |   69.45 | ...1094,1098-1109 
  logs.ts                              |   96.65 |    77.68 |    92.3 |   96.65 | ...1244-1254,1731 
  send.ts                              |    25.6 |      100 |       0 |    25.6 | ...2,63-80,93-125 
  token.ts                             |   23.75 |       75 |     100 |   23.75 | 35-156            
  types.ts                             |   65.97 |      100 |       0 |   65.97 | 26-33,65-89       
 ccip-sdk/src/canton                   |   53.02 |    77.67 |   48.63 |   53.02 |                   
  addressCodec.ts                      |   95.12 |    95.38 |   91.66 |   95.12 | ...69-176,277-280 
  amount.ts                            |   92.59 |       70 |     100 |   92.59 | 22-23             
  ccv-addresses.ts                     |   96.99 |    79.16 |     100 |   96.99 | 19-20,22,29       
  defaults.ts                          |   99.21 |     92.3 |     100 |   99.21 | 90                
  events.ts                            |    54.4 |    67.03 |    42.1 |    54.4 | ...52-564,570-579 
  index.ts                             |    34.8 |    61.76 |   13.88 |    34.8 | ...2644,2647-2649 
  networks.ts                          |     100 |      100 |     100 |     100 |                   
  submit-commands.ts                   |   87.58 |       60 |     100 |   87.58 | ...95,123-127,134 
  types.ts                             |     100 |      100 |     100 |     100 |                   
  update-id.ts                         |   98.11 |    88.88 |     100 |   98.11 | 52                
 ccip-sdk/src/canton/authentication    |   93.05 |    79.31 |    92.3 |   93.05 |                   
  authorization-code.ts                |   89.83 |    84.31 |    90.9 |   89.83 | ...58-376,432-443 
  client-credentials.ts                |   91.28 |       75 |     100 |   91.28 | ...92-196,198-202 
  index.ts                             |    96.8 |       75 |     100 |    96.8 | 168-173           
  metadata.ts                          |   81.89 |    44.44 |      50 |   81.89 | ...55,73-78,93-94 
  static.ts                            |     100 |      100 |     100 |     100 |                   
  token-source.ts                      |   98.88 |    77.77 |     100 |   98.88 | 165-166           
  types.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/canton/client            |    57.4 |    60.86 |   22.85 |    57.4 |                   
  client.ts                            |   55.62 |    59.09 |   22.85 |   55.62 | ...37-756,771-791 
  index.ts                             |     100 |      100 |     100 |     100 |                   
 ...dk/src/canton/explicit-disclosures |   74.54 |    67.74 |   53.19 |   74.54 |                   
  acs.ts                               |   87.42 |    67.47 |   89.28 |   87.42 | ...53,574,609-610 
  eds.ts                               |   55.04 |      100 |       0 |   55.04 | ...19-426,430-435 
 ccip-sdk/src/canton/token-metadata    |   61.37 |      100 |       0 |   61.37 |                   
  client.ts                            |   61.37 |      100 |       0 |   61.37 | 111-178,185-189   
 ...dk/src/canton/transfer-instruction |    52.4 |      100 |       0 |    52.4 |                   
  client.ts                            |    52.4 |      100 |       0 |    52.4 | 104-197,204-208   
 ccip-sdk/src/cct                      |     100 |      100 |     100 |     100 |                   
  errors.ts                            |     100 |      100 |     100 |     100 |                   
  operation.ts                         |     100 |      100 |     100 |     100 |                   
  query.ts                             |     100 |      100 |     100 |     100 |                   
  token-manager.ts                     |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/canton               |   66.13 |    67.92 |   51.61 |   66.13 |                   
  encoding.ts                          |   84.21 |       75 |   66.66 |   84.21 | 49-57             
  gateway.test.helpers.ts              |   20.79 |       50 |       0 |   20.79 | ...90-133,143-226 
  index.ts                             |    89.5 |      100 |   54.54 |    89.5 | ...07-110,114-117 
  operation.ts                         |    90.1 |     61.9 |   85.71 |    90.1 | ...46-150,181-192 
  query.ts                             |     100 |      100 |     100 |     100 |                   
  validate.ts                          |   65.51 |    61.11 |     100 |   65.51 | ...75,77-82,84-85 
 ...rc/cct/canton/token-admin-registry |   78.04 |       50 |   66.66 |   78.04 |                   
  shared.ts                            |   78.04 |       50 |   66.66 |   78.04 | ...94-108,116-121 
 ...on/token-admin-registry/operations |   95.89 |    77.77 |     100 |   95.89 |                   
  get-token-admin-registry.ts          |   95.65 |    77.14 |     100 |   95.65 | 65-70,120,183     
  index.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/canton/token-pool    |   68.57 |    95.23 |   33.33 |   68.57 |                   
  shared.ts                            |   68.57 |    95.23 |   33.33 |   68.57 | ...58-463,469-480 
 ...c/cct/canton/token-pool/operations |   83.59 |    65.83 |    90.9 |   83.59 |                   
  apply-chain-updates.ts               |   49.49 |      100 |      25 |   49.49 | ...46-181,184-197 
  deploy-token-pool.ts                 |   84.57 |    65.62 |     100 |   84.57 | ...19-224,278-307 
  get-rate-limiter-state.ts            |   96.47 |    76.92 |     100 |   96.47 | 70-75             
  get-token-pool-state.ts              |   95.06 |    59.32 |     100 |   95.06 | ...32,157,180,292 
  index.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/evm                  |   94.78 |     96.2 |   44.27 |   94.78 |                   
  index.ts                             |   93.92 |      100 |   30.47 |   93.92 | ...3181,3213-3216 
  operation.ts                         |     100 |      100 |     100 |     100 |                   
  query.ts                             |     100 |      100 |     100 |     100 |                   
  submit.ts                            |     100 |    81.48 |     100 |     100 | 28,57,77-78,96    
  validate.ts                          |   98.11 |    98.07 |     100 |   98.11 | 95-99             
 ...dk/src/cct/evm/advanced-pool-hooks |     100 |     87.5 |     100 |     100 |                   
  contracts.ts                         |     100 |     87.5 |     100 |     100 | 67                
 ...evm/advanced-pool-hooks/operations |     100 |      100 |     100 |     100 |                   
  deploy-advanced-pool-hooks.ts        |     100 |      100 |     100 |     100 |                   
 .../cct/evm/artifacts/bytecode/V2_0_0 |     100 |      100 |     100 |     100 |                   
  advanced-pool-hooks.ts               |     100 |      100 |     100 |     100 |                   
  burn-from-mint-token-pool.ts         |     100 |      100 |     100 |     100 |                   
  burn-mint-token-pool.ts              |     100 |      100 |     100 |     100 |                   
  burn-with-from-mint-token-pool.ts    |     100 |      100 |     100 |     100 |                   
  cross-chain-token.ts                 |     100 |      100 |     100 |     100 |                   
  erc20-lockbox.ts                     |     100 |      100 |     100 |     100 |                   
  lock-release-token-pool.ts           |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/evm/lockbox          |     100 |    96.96 |     100 |     100 |                   
  contracts.ts                         |     100 |    96.96 |     100 |     100 | 95                
 ...sdk/src/cct/evm/lockbox/operations |     100 |      100 |     100 |     100 |                   
  authorize-callers.ts                 |     100 |      100 |     100 |     100 |                   
  deploy-lockbox.ts                    |     100 |      100 |     100 |     100 |                   
  deposit.ts                           |     100 |      100 |     100 |     100 |                   
  withdraw.ts                          |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/evm/token            |   98.96 |    86.36 |     100 |   98.96 |                   
  contracts.ts                         |   98.47 |    89.58 |     100 |   98.47 | 87-88,110,236-237 
  roles.ts                             |     100 |    77.77 |     100 |     100 | 55-61,80-86       
 ...k/src/cct/evm/token-admin-registry |     100 |      100 |     100 |     100 |                   
  contracts.ts                         |     100 |      100 |     100 |     100 |                   
 ...vm/token-admin-registry/operations |     100 |      100 |     100 |     100 |                   
  accept-admin.ts                      |     100 |      100 |     100 |     100 |                   
  get-supported-tokens.ts              |     100 |      100 |     100 |     100 |                   
  get-token-admin-registry.ts          |     100 |      100 |     100 |     100 |                   
  register-admin.ts                    |     100 |      100 |     100 |     100 |                   
  set-pool.ts                          |     100 |      100 |     100 |     100 |                   
  transfer-admin.ts                    |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/evm/token-pool       |     100 |    99.11 |     100 |     100 |                   
  contracts.ts                         |     100 |     98.7 |     100 |     100 | 561               
  rate-limit.ts                        |     100 |      100 |     100 |     100 |                   
  remote-pool.ts                       |     100 |      100 |     100 |     100 |                   
 .../src/cct/evm/token-pool/operations |   99.69 |    96.49 |     100 |   99.69 |                   
  accept-pool-ownership.ts             |     100 |      100 |     100 |     100 |                   
  add-remote-pool.ts                   |     100 |      100 |     100 |     100 |                   
  apply-allowlist-updates.ts           |     100 |    96.29 |     100 |     100 | 229               
  apply-chain-updates.ts               |     100 |      100 |     100 |     100 |                   
  ...en-transfer-fee-config-updates.ts |   98.17 |     93.1 |     100 |   98.17 | 160-163           
  deploy-token-pool.ts                 |     100 |    85.71 |     100 |     100 | 96-99             
  get-advanced-pool-hooks.ts           |     100 |      100 |     100 |     100 |                   
  get-allowed-finality-config.ts       |     100 |      100 |     100 |     100 |                   
  get-dynamic-config.ts                |     100 |      100 |     100 |     100 |                   
  get-fee.ts                           |     100 |     90.9 |     100 |     100 | 56                
  get-lockbox.ts                       |     100 |      100 |     100 |     100 |                   
  get-rebalancer.ts                    |     100 |      100 |     100 |     100 |                   
  get-token-pool-remotes.ts            |     100 |      100 |     100 |     100 |                   
  get-token-pool-state.ts              |   98.61 |       95 |     100 |   98.61 | 282-285           
  get-token-transfer-fee-config.ts     |   94.79 |    57.14 |     100 |   94.79 | 63,66-69          
  provide-liquidity.ts                 |     100 |      100 |     100 |     100 |                   
  remove-remote-pool.ts                |     100 |      100 |     100 |     100 |                   
  set-allowed-finality-config.ts       |     100 |      100 |     100 |     100 |                   
  set-chain-rate-limiter-configs.ts    |     100 |    97.05 |     100 |     100 | 112               
  set-dynamic-config.ts                |     100 |      100 |     100 |     100 |                   
  set-rate-limit-admin.ts              |     100 |      100 |     100 |     100 |                   
  set-rebalancer.ts                    |     100 |      100 |     100 |     100 |                   
  set-remote-pool.ts                   |     100 |      100 |     100 |     100 |                   
  transfer-liquidity.ts                |     100 |      100 |     100 |     100 |                   
  transfer-pool-ownership.ts           |     100 |      100 |     100 |     100 |                   
  update-advanced-pool-hooks.ts        |     100 |      100 |     100 |     100 |                   
  withdraw-fee-tokens.ts               |     100 |      100 |     100 |     100 |                   
  withdraw-liquidity.ts                |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/evm/token/operations |     100 |      100 |     100 |     100 |                   
  accept-default-admin-transfer.ts     |     100 |      100 |     100 |     100 |                   
  accept-token-ownership.ts            |     100 |      100 |     100 |     100 |                   
  approve-token.ts                     |     100 |      100 |     100 |     100 |                   
  begin-default-admin-transfer.ts      |     100 |      100 |     100 |     100 |                   
  cancel-default-admin-transfer.ts     |     100 |      100 |     100 |     100 |                   
  deploy-token.ts                      |     100 |      100 |     100 |     100 |                   
  get-burners.ts                       |     100 |      100 |     100 |     100 |                   
  get-minters.ts                       |     100 |      100 |     100 |     100 |                   
  grant-burn-role.ts                   |     100 |      100 |     100 |     100 |                   
  grant-mint-and-burn-roles.ts         |     100 |      100 |     100 |     100 |                   
  grant-mint-role.ts                   |     100 |      100 |     100 |     100 |                   
  is-burner.ts                         |     100 |      100 |     100 |     100 |                   
  is-minter.ts                         |     100 |      100 |     100 |     100 |                   
  mint.ts                              |     100 |      100 |     100 |     100 |                   
  revoke-burn-role.ts                  |     100 |      100 |     100 |     100 |                   
  revoke-mint-role.ts                  |     100 |      100 |     100 |     100 |                   
  set-ccip-admin.ts                    |     100 |      100 |     100 |     100 |                   
  transfer-token-ownership.ts          |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/solana               |   99.71 |    96.91 |   98.05 |   99.71 |                   
  index.ts                             |   99.67 |      100 |   97.22 |   99.67 | 258-259,2428-2433 
  operation.ts                         |     100 |      100 |     100 |     100 |                   
  query.ts                             |     100 |      100 |     100 |     100 |                   
  serialize.ts                         |     100 |     92.3 |     100 |     100 | 27                
  submit.ts                            |   99.23 |     87.5 |     100 |   99.23 | 123               
  validate.ts                          |     100 |     98.4 |     100 |     100 | 288,395           
 ccip-sdk/src/cct/solana/programs      |     100 |      100 |     100 |     100 |                   
  alt.ts                               |     100 |      100 |     100 |     100 |                   
  fee-quoter.ts                        |     100 |      100 |     100 |     100 |                   
  router.ts                            |     100 |      100 |     100 |     100 |                   
  token-pool.ts                        |     100 |      100 |     100 |     100 |                   
  token.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/cct/solana/token         |     100 |      100 |     100 |     100 |                   
  constants.ts                         |     100 |      100 |     100 |     100 |                   
 ...rc/cct/solana/token-admin-registry |     100 |      100 |     100 |     100 |                   
  constants.ts                         |     100 |      100 |     100 |     100 |                   
 ...na/token-admin-registry/operations |   99.62 |     98.8 |     100 |   99.62 |                   
  accept-admin.ts                      |     100 |      100 |     100 |     100 |                   
  append-to-lookup-table.ts            |     100 |      100 |     100 |     100 |                   
  create-lookup-table.ts               |     100 |      100 |     100 |     100 |                   
  get-supported-tokens.ts              |     100 |      100 |     100 |     100 |                   
  get-token-admin-registry.ts          |     100 |      100 |     100 |     100 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  ...override-pending-administrator.ts |     100 |      100 |     100 |     100 |                   
  register-admin.ts                    |   98.03 |       95 |     100 |   98.03 | 219-223           
  set-pool.ts                          |     100 |      100 |     100 |     100 |                   
  transfer-admin.ts                    |     100 |      100 |     100 |     100 |                   
 ...c/cct/solana/token-pool/operations |   99.63 |    98.49 |     100 |   99.63 |                   
  accept-ownership.ts                  |     100 |      100 |     100 |     100 |                   
  append-remote-pool-addresses.ts      |     100 |      100 |     100 |     100 |                   
  apply-chain-updates.ts               |     100 |    98.43 |     100 |     100 | 149               
  configure-allowlist.ts               |     100 |      100 |     100 |     100 |                   
  create-token-multisig.ts             |    95.9 |     92.5 |     100 |    95.9 | ...01-102,104-109 
  delete-chain-remote-config.ts        |     100 |      100 |     100 |     100 |                   
  deploy-token-pool.ts                 |     100 |      100 |     100 |     100 |                   
  edit-chain-remote-config.ts          |     100 |      100 |     100 |     100 |                   
  get-token-pool-remotes.ts            |     100 |      100 |     100 |     100 |                   
  get-token-pool-state.ts              |     100 |      100 |     100 |     100 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  init-chain-remote-config.ts          |     100 |      100 |     100 |     100 |                   
  provide-liquidity.ts                 |     100 |      100 |     100 |     100 |                   
  remove-from-allowlist.ts             |     100 |      100 |     100 |     100 |                   
  set-can-accept-liquidity.ts          |     100 |      100 |     100 |     100 |                   
  set-chain-rate-limit.ts              |   99.11 |    96.15 |     100 |   99.11 | 64-65             
  set-rate-limit-admin.ts              |     100 |      100 |     100 |     100 |                   
  set-rebalancer.ts                    |     100 |      100 |     100 |     100 |                   
  transfer-ownership.ts                |     100 |      100 |     100 |     100 |                   
  withdraw-liquidity.ts                |     100 |      100 |     100 |     100 |                   
 ...dk/src/cct/solana/token/operations |     100 |    97.98 |     100 |     100 |                   
  approve-token.ts                     |     100 |      100 |     100 |     100 |                   
  create-token-account.ts              |     100 |      100 |     100 |     100 |                   
  deploy-token.ts                      |     100 |      100 |     100 |     100 |                   
  get-token-info.ts                    |     100 |       70 |     100 |     100 | 69,80-81          
  index.ts                             |     100 |      100 |     100 |     100 |                   
  mint-tokens.ts                       |     100 |      100 |     100 |     100 |                   
  set-token-authority.ts               |     100 |    97.61 |     100 |     100 | 139               
  update-metadata-authority.ts         |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/errors                   |   90.59 |    77.25 |   60.14 |   90.59 |                   
  CCIPError.ts                         |     100 |      100 |     100 |     100 |                   
  codes.ts                             |     100 |      100 |     100 |     100 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  pure.ts                              |     100 |       75 |     100 |     100 | 32                
  recovery.ts                          |     100 |      100 |     100 |     100 |                   
  specialized.ts                       |   88.47 |    74.16 |   58.01 |   88.47 | ...3710,3733-3742 
  utils.ts                             |   94.44 |    81.48 |     100 |   94.44 | 15,17,22,24       
 ccip-sdk/src/evm                      |   93.36 |    82.39 |   93.16 |   93.36 |                   
  const.ts                             |   98.66 |    92.85 |     100 |   98.66 | 122-123           
  errors.ts                            |   91.98 |    81.57 |     100 |   91.98 | ...85,239-242,247 
  extra-args.ts                        |   98.07 |    63.33 |     100 |   98.07 | ...85-186,211-212 
  fork.test.data.ts                    |     100 |      100 |     100 |     100 |                   
  gas.ts                               |   98.19 |    63.15 |     100 |   98.19 | 90-91,93          
  hasher.ts                            |     100 |     92.3 |     100 |     100 | 135               
  index.ts                             |   88.82 |     80.3 |    90.9 |   88.82 | ...2718,2873-2904 
  logs.ts                              |   99.16 |    92.02 |      90 |   99.16 | 69-70,75-76       
  messageCodec.ts                      |     100 |      100 |     100 |     100 |                   
  messages.ts                          |     100 |      100 |     100 |     100 |                   
  offchain.ts                          |    87.5 |    71.42 |     100 |    87.5 | 13-14             
  simulate.ts                          |     100 |    94.82 |     100 |     100 | 109-111,425       
  types.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/evm/viem                 |   79.76 |    90.62 |   69.23 |   79.76 |                   
  client-adapter.ts                    |     100 |       90 |     100 |     100 | 48,74             
  index.ts                             |     100 |      100 |     100 |     100 |                   
  wallet-adapter.ts                    |   63.09 |     90.9 |   55.55 |   63.09 | ...91-124,131-157 
 ccip-sdk/src/hasher                   |   94.29 |    78.94 |     100 |   94.29 |                   
  common.ts                            |     100 |      100 |     100 |     100 |                   
  hasher.ts                            |     100 |    66.66 |     100 |     100 | 19                
  index.ts                             |     100 |      100 |     100 |     100 |                   
  merklemulti.ts                       |   93.43 |       78 |     100 |   93.43 | ...06-307,315-316 
 ccip-sdk/src/shared                   |   85.32 |    84.33 |   82.35 |   85.32 |                   
  bcs-codecs.ts                        |   79.07 |    60.86 |   66.66 |   79.07 | ...42-252,260-269 
  codec.ts                             |    94.3 |    93.22 |     100 |    94.3 | 139-140,178-186   
  constants.ts                         |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/solana                   |   83.48 |     77.6 |   88.37 |   83.48 |                   
  cleanup.ts                           |   26.95 |    66.66 |   33.33 |   26.95 | ...59-101,114-227 
  exec.ts                              |   69.11 |    62.96 |   66.66 |   69.11 | ...69-473,513-514 
  extra-args.ts                        |   59.49 |    76.47 |      60 |   59.49 | 86-122,132-190    
  fork.test.data.ts                    |     100 |      100 |     100 |     100 |                   
  gas.ts                               |   92.06 |    68.57 |     100 |   92.06 | ...,81-90,170-171 
  hasher.ts                            |   96.49 |    81.81 |     100 |   96.49 | 64-67             
  index.ts                             |   87.08 |     77.5 |   90.74 |   87.08 | ...2150,2154-2187 
  logs.ts                              |   90.11 |     80.7 |     100 |   90.11 | ...50-151,163-164 
  offchain.ts                          |     100 |      100 |     100 |     100 |                   
  patchBorsh.ts                        |   78.31 |       50 |     100 |   78.31 | ...47,65-66,72-78 
  send.ts                              |    95.7 |    53.48 |     100 |    95.7 | ...03,346-347,421 
  signatures-cache.ts                  |   79.78 |    71.15 |    87.5 |   79.78 | ...51-260,274-275 
  token-admin-registry.ts              |     100 |    96.29 |     100 |     100 | 27                
  types.ts                             |     100 |      100 |     100 |     100 |                   
  utils.ts                             |   85.41 |    83.73 |   88.46 |   85.41 | ...47-650,893-904 
  v1.ts                                |   96.46 |    81.81 |     100 |   96.46 | 62-65,118-121     
 ccip-sdk/src/sui                      |   73.33 |    70.69 |   81.14 |   73.33 |                   
  discovery.ts                         |   47.37 |    48.27 |      60 |   47.37 | ...34-782,785-819 
  events.ts                            |   90.35 |    72.98 |     100 |   90.35 | ...22-625,685-698 
  exec.ts                              |   30.71 |      100 |       0 |   30.71 | 37-89,101-153     
  hasher.ts                            |   98.19 |    66.66 |     100 |   98.19 | 35,51             
  index.ts                             |   76.25 |    70.38 |   79.68 |   76.25 | ...2344,2360-2361 
  logs.ts                              |   97.89 |    74.02 |     100 |   97.89 | ...62-165,186-187 
  objects.ts                           |    54.6 |    83.33 |   83.33 |    54.6 | ...57-313,324-467 
  types.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/sui/manuallyExec         |   53.36 |       75 |      50 |   53.36 |                   
  encoder.ts                           |    83.9 |    66.66 |     100 |    83.9 | 51-58,73-77,82    
  index.ts                             |   33.82 |      100 |       0 |   33.82 | 47-136            
 ccip-sdk/src/ton                      |   86.42 |    81.09 |   88.54 |   86.42 |                   
  exec.ts                              |     100 |      100 |     100 |     100 |                   
  extra-args.ts                        |   98.66 |    72.72 |     100 |   98.66 | 156-157,222       
  hasher.ts                            |   78.07 |    77.77 |      75 |   78.07 | 100-108,156-187   
  index.ts                             |    85.5 |    80.18 |   79.68 |    85.5 | ...2205,2212-2213 
  logs.ts                              |    94.2 |    84.43 |     100 |    94.2 | ...17-624,680-683 
  send.ts                              |   95.52 |    66.66 |     100 |   95.52 | 37-44,188         
  ton-cache.ts                         |     100 |    96.55 |     100 |     100 | 102               
  types.ts                             |   91.24 |    81.25 |     100 |   91.24 | ...61-63,71-74,92 
  utils.ts                             |   63.71 |    78.18 |    90.9 |   63.71 | ...37-395,397-400 
 ccip-sdk/src/verifiers                |   98.13 |       91 |     100 |   98.13 |                   
  codec.ts                             |   96.87 |    89.13 |     100 |   96.87 | 51-53,70,97       
  index.ts                             |     100 |      100 |     100 |     100 |                   
  transport.ts                         |   98.54 |     87.5 |     100 |   98.54 | 84-85             
 scripts                               |    89.2 |    67.46 |     100 |    89.2 |                   
  test-endpoints.ts                    |   97.76 |     82.6 |     100 |   97.76 | 157-159,222-223   
  useResource.ts                       |   83.57 |    61.66 |     100 |   83.57 | ...81-286,301-302 
---------------------------------------|---------|----------|---------|---------|-------------------

@aelmanaa

aelmanaa commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Review — PR #421 (DAPP-11480) fix(cct-sdk): Validate EVM deployment receipt contract address

  • Branch: fix/DAPP-11480-verify-receipt-address → cct-sdk · Reviewed head: ecb616ed (1 commit) · Base: cct-sdk @ aa3968d5
  • Verdict: APPROVE the change — the receipt-integrity check is correct, contract-faithful, and proven live. Plus one
    MAJOR doc-drift
    (§5, stale public @throws) and three minor additions (§6: document the EOA-CREATE assumption;
    test the null-receipt + no-nonce paths; the @throws) — all implemented + verified green (SDK tsc/lint clean, op suite
    12/12). Verified against the committed head in isolation (§8).
  • Change: EVMDeployOperation.execute no longer trusts the RPC's receipt.contractAddress. submit() now also
    returns the { sender, nonce } it broadcast; execute computes expectedAddress = getAddress(getCreateAddress({ from: sender, nonce })) and throws CCTTxFailedError if the receipt address is null, un-checksummable, or !== expectedAddress
    — otherwise returns the locally-derived expectedAddress. A submit guard rejects a tx with no resolved nonce.
  • Method: three-lens review (type-safety, architecture/correctness, DX/errors) + on-chain grounding against
    chainlink-ccip + a live Ethereum-Sepolia deploy through the patched path.

1. What it fixes, and why it is correct

A deploy op's whole output is the new contract address, which downstream steps then configure (a pool's token, a
LockRelease pool's lockbox, …). Reading it from the RPC's receipt.contractAddress trusts the node: a buggy or hostile
RPC could return a wrong address and the SDK would hand back — and the caller would wire up — a contract nobody deployed.

For a plain EOA CREATE the deployed address is deterministic: keccak256(rlp([sender, nonce]))[12:]. The fix derives
it locally from the (sender, nonce) it actually broadcast and returns that, so the result is RPC-independent and any
substituted/garbled receipt address is caught. This only holds if every deploy op is a genuine EOA CREATE — it is (§2).


2. On-chain ground truth (chainlink-ccip, v2.0.0) — every deploy op is a direct EOA CREATE

All three EVMDeployOperation subclasses build a no-to creation tx (deployTx(bytecode, encodedArgs) in the base
buildUnsigned), and DeployToken.artifact() is hardcoded to TokenVersion.V2_0_0. The contracts they deploy are all
standalone, direct-constructor contracts — no factory, no CREATE2:

  • CrossChainToken 2.0.0 — chains/evm/contracts/tokens/CrossChainToken.sol:18,35-46, constructor(ConstructorParams, address burnMintRoleAdmin, address owner). Plain new/CREATE.
  • BurnMintTokenPool 2.0.0 — chains/evm/contracts/pools/BurnMintTokenPool.sol:16-27, direct constructor(token, localTokenDecimals, advancedPoolHooks, rmnProxy, router).
  • LockReleaseTokenPool 2.0.0 — chains/evm/contracts/pools/LockReleaseTokenPool.sol:17,24-40, direct constructor(token, …, lockBox).
  • ERC20LockBox 2.0.0 — chains/evm/contracts/pools/ERC20LockBox.sol:17,34-42, direct constructor(address token).

The factory path — FactoryBurnMintERC20 deployed via Create2.deploy(...) in TokenPoolFactory.sol — is a different
contract type that exists only under the v1.6.x tags
(contracts-ccip-v1.6.1), not on the v2 tree the SDK deploys. So
none of these deployments is a factory/CREATE2 deploy, and getCreateAddress(sender, nonce) is the correct address for
each. getCreateAddress(from, nonce) = keccak256(rlp([from, nonce]))[12:] is the correct EOA-CREATE derivation (Yellow
Paper / EIP-161), matching ethers' getCreateAddress. (The committed PR does not document this EOA-CREATE assumption
in the code — see the §6 recommendation to add the @remarks caveat.)


3. The fix (inline diff, operation.ts)

const { response, receipt, sender, nonce } = await submit(chain, params.wallet, unsigned, this.name)
const expectedAddress = getAddress(getCreateAddress({ from: sender, nonce }))
const returnedAddress = receipt.contractAddress
if (returnedAddress == null)
  throw new CCTTxFailedError(this.name, 'deployment produced no contract address',
    { context: { txHash: response.hash, expectedAddress, returnedAddress } })
let contractAddress: string
try { contractAddress = getAddress(returnedAddress) }
catch { throw new CCTTxFailedError(this.name, 'deployment returned an invalid contract address',
    { context: { txHash: response.hash, expectedAddress, returnedAddress } }) }
if (contractAddress !== expectedAddress)
  throw new CCTTxFailedError(this.name, 'deployment returned an unexpected contract address',
    { context: { txHash: response.hash, expectedAddress, returnedAddress } })
return { hash: response.hash, contractAddress: expectedAddress, verification: { … } }

submit.ts now returns { sender, nonce } (nonce = tx.nonce after populateTransaction, with a guard
if (tx.nonce == null) throw new CCTTxFailedError(operation, 'transaction has no nonce')), and rethrows a
CCTTxFailedError as-is so the nonce guard is not re-wrapped as a transient error.


4. Live proof (Ethereum Sepolia, chainId 11155111) — verbatim

Fixtures, each a clickable Sepolia Etherscan link:

Thing Address / tx
Deployer (sender) 0x9d087fC03ae39b088326b67fA3C788236645b717
Real deploy tx (through the patched execute) 0xe52d379cb196af53b523deac278fad54c84a809cb999eaf057a327c2e90d8f9f
Deployed ERC20LockBox (SDK-returned == derived == receipt) 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
token it escrows (CrossChainToken 2.0.0) 0x227f0db66e9bf42a28e1e38438c5f19d0adb2df0

4.1 Happy path — SDK-returned address == getCreateAddress(on-chain tx.from, tx.nonce) == receipt (live)

A real deployLockbox through the patched execute, then the address re-derived independently from the mined
transaction's own from/nonce:

=== 11480 verify-receipt-address — live Ethereum Sepolia deploy through the patched execute ===
  deploy tx:            0xe52d379cb196af53b523deac278fad54c84a809cb999eaf057a327c2e90d8f9f
  SDK-returned address: 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  on-chain tx.from:     0x9d087fC03ae39b088326b67fA3C788236645b717
  on-chain tx.nonce:    6208
  getCreateAddress(tx.from, tx.nonce) = 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  receipt.contractAddress             = 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  ALL THREE AGREE: true  (SDK == getCreateAddress == receipt)

4.2 Threat — a substituted receipt address is caught, anchored to the same real (sender, nonce) (live)

The exact comparison execute runs (contractAddress !== expectedAddress), against the genuine locally-derived address:

=== threat: a substituted receipt address is caught by the SDK check (anchored to real values) ===
  expectedAddress (real, locally derived): 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  substituted RPC address:                 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
  SDK check "contractAddress !== expectedAddress" -> true (true = throws "unexpected contract address")

So on a genuine deploy all three sources agree and the returned address is real and openable on-chain; had the RPC
returned any other address, the derived expectedAddress (independent of the RPC) would reject it.

4.3 Verification summary

Claim Evidence Live
Deploy returns the deterministic CREATE address, RPC-independent §4.1 all-three-agree, tx 0xe52d…8f9f ✅
The returned address is the real on-chain contract §4.1 receipt == returned; §4 Etherscan ✅
A substituted receipt address is rejected (!== expectedAddress) §4.2 anchored to real (sender, nonce=6208) ✅
Every deploy op is EOA CREATE (no factory/CREATE2) → check valid §2 chainlink-ccip v2.0.0 constructors ✅ (grounded)
null / malformed receipt address rejected with context.{expected,returned} operation.test.ts (3 cases) mocked

5. MAJOR — public @throws on the deploy methods understate the new behavior

cct/evm/index.ts — deployToken (:1033), deployTokenPool (:1480), deployLockbox (:1527) all still read
@throws {@link CCTTxFailedError} if the tx reverts, fails, or mines without an address. The diff updated the internal
execute JSDoc ("without the expected contract address") but not these three public forwarders, so the documented public
surface omits the two genuinely-new observable failures: an invalid/un-checksummable receipt address and a
mismatch vs getCreateAddress. Fix: update all three to e.g. "…or mines at an unexpected/invalid contract address".

6. Recommended additions (minor — implemented + verified green in the review worktree)

The committed head ecb616ed carries the executable fix and 3 tests but under-documents and under-tests the new
behavior. All three below are implemented, tsc/oxfmt clean, op suite 12/12 with them (patch saved — §8):

  1. Document the plain-EOA-CREATE assumption (the load-bearing precondition, §2) — add an @remarks on
    EVMDeployOperation.execute: "Assumes a plain EOA CREATE deployment: the expected address is
    getCreateAddress(signer, nonce). Correct for every deploy op here (each builds a no-to creation tx), but would not
    hold for a factory/CREATE2 or a smart-account signer whose address is not the CREATE-deriving EOA — such a deployment
    would be rejected below."
  2. Cover the two new paths the PR adds but does not test: a null receipt.contractAddress, and the submit no-nonce
    guard (asserting the reason is not double-wrapped). The 3 committed tests cover only lowercase-accepted /
    substituted-rejected / malformed-rejected.
  3. Public @throws — the §5 MAJOR.

Two optional NITs (not applied):

  • Missing next-action clause. Messages distinguish the cases and carry context.{expectedAddress,returnedAddress}
    (good), but every sibling CCTTxFailedError ends with a remedy (validate.ts:365, contracts.ts:471/476); a short
    "the RPC may be misconfigured" hint would match house style. Internal invariant, no caller param.
  • Redundant getAddress. operation.ts wraps getCreateAddress(...) in getAddress(...); it already returns
    checksummed. Harmless/defensive.

7. Verified consistent (no action)

  • Invariant correct for every deploy op: all three subclasses inherit the base buildUnsigned→deployTx (no-to),
    no factory/CREATE2 anywhere; the address is fully determined by (from, nonce). Architecture/type/DX lenses: correct on
    all counts.
  • nonce source: captured as tx.nonce after populateTransaction — the exact nonce submitTransaction uses, so
    it equals the nonce the mined CREATE consumes; the nextNonce/nonceConsumed/rollbackNonce interaction stays correct
    and the new guard rethrows without double-wrapping.
  • Threat closed: the returned address derives from wallet.getAddress() + the locally-populated nonce, RPC-independent;
    a hostile RPC cannot launder a fake contractAddress. The non-deploy EVMOperation.execute reads nothing spoofable
    from the receipt (returns only hash) — no residual gap.
  • Error type/shape: bare CCTTxFailedError matches every sibling deploy/tx failure (no "mismatch" class exists to
    reuse); context: { txHash, expectedAddress, returnedAddress } rides the standard ...options.context spread.
  • Returning expectedAddress: asserted equal to the receipt, so identical — and the locally-derived one is the more
    trustworthy of the two.

8. Types / build / tests (verified in isolation)

Note on the worktree: the review worktree also held uncommitted edits (my §6 recommendations + §5 fix). To avoid
conflating them with the PR, I stashed them and measured the committed head ecb616ed clean, then restored.

Committed head ecb616ed (PR as submitted), SDK workspace:

tsc --noEmit  → EXIT 0
oxfmt --check → EXIT 0
oxlint        → EXIT 0
operation.test.ts + submit.test.ts → tests 10, pass 10, fail 0   (operation 3 + submit 7)

npm run check returns EXIT 1, but not from this diff: oxfmt/oxlint/tsc all pass; the non-zero is the
generate:lint stage reporting drift in generated files (ccip-sdk/src/selectors.ts, ccip-sdk/src/api/index.ts) —
network-regenerated, not in the diff, and the same drift the repo clears with its routine chore: selectors commits.
The diff itself introduces no any/unsafe as; the sync nonce capture and submit return-widening typecheck cleanly.

Committed head + my §5/§6 additions: tsc EXIT 0, oxfmt EXIT 0, op suite tests 12, pass 12, fail 0 (adds the
null-receipt and no-nonce tests).


9. Recommendation

Approve. The receipt-integrity check is correct, grounded against every v2.0.0 CCT deploy constructor (§2), and proven
live: on a real Sepolia deploy the SDK-returned address is the deterministic CREATE address, equal to both the derived
value and the on-chain receipt, and any substituted address is provably rejected (§4). Recommended before merge: the
§5 MAJOR (stale public @throws) and the §6 additions (document the EOA-CREATE assumption; add null-receipt +
no-nonce tests) — all implemented in the review worktree and verified green (SDK tsc/lint clean, op suite 12/12). None
blocks the current diff.

@mervin-link

Copy link
Copy Markdown
Collaborator Author

Review — PR #421 (DAPP-11480) fix(cct-sdk): Validate EVM deployment receipt contract address

  • Branch: fix/DAPP-11480-verify-receipt-address → cct-sdk · Reviewed head: ecb616ed (1 commit) · Base: cct-sdk @ aa3968d5
  • Verdict: APPROVE the change — the receipt-integrity check is correct, contract-faithful, and proven live. Plus one
    MAJOR doc-drift
    (§5, stale public @throws) and three minor additions (§6: document the EOA-CREATE assumption;
    test the null-receipt + no-nonce paths; the @throws) — all implemented + verified green (SDK tsc/lint clean, op suite
    12/12). Verified against the committed head in isolation (§8).
  • Change: EVMDeployOperation.execute no longer trusts the RPC's receipt.contractAddress. submit() now also
    returns the { sender, nonce } it broadcast; execute computes expectedAddress = getAddress(getCreateAddress({ from: sender, nonce })) and throws CCTTxFailedError if the receipt address is null, un-checksummable, or !== expectedAddress
    — otherwise returns the locally-derived expectedAddress. A submit guard rejects a tx with no resolved nonce.
  • Method: three-lens review (type-safety, architecture/correctness, DX/errors) + on-chain grounding against
    chainlink-ccip + a live Ethereum-Sepolia deploy through the patched path.

1. What it fixes, and why it is correct

A deploy op's whole output is the new contract address, which downstream steps then configure (a pool's token, a LockRelease pool's lockbox, …). Reading it from the RPC's receipt.contractAddress trusts the node: a buggy or hostile RPC could return a wrong address and the SDK would hand back — and the caller would wire up — a contract nobody deployed.

For a plain EOA CREATE the deployed address is deterministic: keccak256(rlp([sender, nonce]))[12:]. The fix derives it locally from the (sender, nonce) it actually broadcast and returns that, so the result is RPC-independent and any substituted/garbled receipt address is caught. This only holds if every deploy op is a genuine EOA CREATE — it is (§2).

2. On-chain ground truth (chainlink-ccip, v2.0.0) — every deploy op is a direct EOA CREATE

All three EVMDeployOperation subclasses build a no-to creation tx (deployTx(bytecode, encodedArgs) in the base buildUnsigned), and DeployToken.artifact() is hardcoded to TokenVersion.V2_0_0. The contracts they deploy are all standalone, direct-constructor contracts — no factory, no CREATE2:

  • CrossChainToken 2.0.0 — chains/evm/contracts/tokens/CrossChainToken.sol:18,35-46, constructor(ConstructorParams, address burnMintRoleAdmin, address owner). Plain new/CREATE.
  • BurnMintTokenPool 2.0.0 — chains/evm/contracts/pools/BurnMintTokenPool.sol:16-27, direct constructor(token, localTokenDecimals, advancedPoolHooks, rmnProxy, router).
  • LockReleaseTokenPool 2.0.0 — chains/evm/contracts/pools/LockReleaseTokenPool.sol:17,24-40, direct constructor(token, …, lockBox).
  • ERC20LockBox 2.0.0 — chains/evm/contracts/pools/ERC20LockBox.sol:17,34-42, direct constructor(address token).

The factory path — FactoryBurnMintERC20 deployed via Create2.deploy(...) in TokenPoolFactory.sol — is a different contract type that exists only under the v1.6.x tags (contracts-ccip-v1.6.1), not on the v2 tree the SDK deploys. So none of these deployments is a factory/CREATE2 deploy, and getCreateAddress(sender, nonce) is the correct address for each. getCreateAddress(from, nonce) = keccak256(rlp([from, nonce]))[12:] is the correct EOA-CREATE derivation (Yellow Paper / EIP-161), matching ethers' getCreateAddress. (The committed PR does not document this EOA-CREATE assumption in the code — see the §6 recommendation to add the @remarks caveat.)

3. The fix (inline diff, operation.ts)

const { response, receipt, sender, nonce } = await submit(chain, params.wallet, unsigned, this.name)
const expectedAddress = getAddress(getCreateAddress({ from: sender, nonce }))
const returnedAddress = receipt.contractAddress
if (returnedAddress == null)
  throw new CCTTxFailedError(this.name, 'deployment produced no contract address',
    { context: { txHash: response.hash, expectedAddress, returnedAddress } })
let contractAddress: string
try { contractAddress = getAddress(returnedAddress) }
catch { throw new CCTTxFailedError(this.name, 'deployment returned an invalid contract address',
    { context: { txHash: response.hash, expectedAddress, returnedAddress } }) }
if (contractAddress !== expectedAddress)
  throw new CCTTxFailedError(this.name, 'deployment returned an unexpected contract address',
    { context: { txHash: response.hash, expectedAddress, returnedAddress } })
return { hash: response.hash, contractAddress: expectedAddress, verification: { … } }

submit.ts now returns { sender, nonce } (nonce = tx.nonce after populateTransaction, with a guard if (tx.nonce == null) throw new CCTTxFailedError(operation, 'transaction has no nonce')), and rethrows a CCTTxFailedError as-is so the nonce guard is not re-wrapped as a transient error.

4. Live proof (Ethereum Sepolia, chainId 11155111) — verbatim

Fixtures, each a clickable Sepolia Etherscan link:

Thing Address / tx
Deployer (sender) 0x9d087fC03ae39b088326b67fA3C788236645b717
Real deploy tx (through the patched execute) 0xe52d379cb196af53b523deac278fad54c84a809cb999eaf057a327c2e90d8f9f
Deployed ERC20LockBox (SDK-returned == derived == receipt) 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
token it escrows (CrossChainToken 2.0.0) 0x227f0db66e9bf42a28e1e38438c5f19d0adb2df0

4.1 Happy path — SDK-returned address == getCreateAddress(on-chain tx.from, tx.nonce) == receipt (live)

A real deployLockbox through the patched execute, then the address re-derived independently from the mined transaction's own from/nonce:

=== 11480 verify-receipt-address — live Ethereum Sepolia deploy through the patched execute ===
  deploy tx:            0xe52d379cb196af53b523deac278fad54c84a809cb999eaf057a327c2e90d8f9f
  SDK-returned address: 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  on-chain tx.from:     0x9d087fC03ae39b088326b67fA3C788236645b717
  on-chain tx.nonce:    6208
  getCreateAddress(tx.from, tx.nonce) = 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  receipt.contractAddress             = 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  ALL THREE AGREE: true  (SDK == getCreateAddress == receipt)

4.2 Threat — a substituted receipt address is caught, anchored to the same real (sender, nonce) (live)

The exact comparison execute runs (contractAddress !== expectedAddress), against the genuine locally-derived address:

=== threat: a substituted receipt address is caught by the SDK check (anchored to real values) ===
  expectedAddress (real, locally derived): 0xBB20C959E940aB92a5A57A36532223eAF950F8F3
  substituted RPC address:                 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
  SDK check "contractAddress !== expectedAddress" -> true (true = throws "unexpected contract address")

So on a genuine deploy all three sources agree and the returned address is real and openable on-chain; had the RPC returned any other address, the derived expectedAddress (independent of the RPC) would reject it.

4.3 Verification summary

Claim Evidence Live
Deploy returns the deterministic CREATE address, RPC-independent §4.1 all-three-agree, tx 0xe52d…8f9f ✅
The returned address is the real on-chain contract §4.1 receipt == returned; §4 Etherscan ✅
A substituted receipt address is rejected (!== expectedAddress) §4.2 anchored to real (sender, nonce=6208) ✅
Every deploy op is EOA CREATE (no factory/CREATE2) → check valid §2 chainlink-ccip v2.0.0 constructors ✅ (grounded)
null / malformed receipt address rejected with context.{expected,returned} operation.test.ts (3 cases) mocked

5. MAJOR — public @throws on the deploy methods understate the new behavior

cct/evm/index.ts — deployToken (:1033), deployTokenPool (:1480), deployLockbox (:1527) all still read @throws {@link CCTTxFailedError} if the tx reverts, fails, or mines without an address. The diff updated the internal execute JSDoc ("without the expected contract address") but not these three public forwarders, so the documented public surface omits the two genuinely-new observable failures: an invalid/un-checksummable receipt address and a mismatch vs getCreateAddress. Fix: update all three to e.g. "…or mines at an unexpected/invalid contract address".

6. Recommended additions (minor — implemented + verified green in the review worktree)

The committed head ecb616ed carries the executable fix and 3 tests but under-documents and under-tests the new behavior. All three below are implemented, tsc/oxfmt clean, op suite 12/12 with them (patch saved — §8):

  1. Document the plain-EOA-CREATE assumption (the load-bearing precondition, §2) — add an @remarks on
    EVMDeployOperation.execute: "Assumes a plain EOA CREATE deployment: the expected address is
    getCreateAddress(signer, nonce). Correct for every deploy op here (each builds a no-to creation tx), but would not
    hold for a factory/CREATE2 or a smart-account signer whose address is not the CREATE-deriving EOA — such a deployment
    would be rejected below."
  2. Cover the two new paths the PR adds but does not test: a null receipt.contractAddress, and the submit no-nonce
    guard (asserting the reason is not double-wrapped). The 3 committed tests cover only lowercase-accepted /
    substituted-rejected / malformed-rejected.
  3. Public @throws — the §5 MAJOR.

Two optional NITs (not applied):

  • Missing next-action clause. Messages distinguish the cases and carry context.{expectedAddress,returnedAddress}
    (good), but every sibling CCTTxFailedError ends with a remedy (validate.ts:365, contracts.ts:471/476); a short
    "the RPC may be misconfigured" hint would match house style. Internal invariant, no caller param.
  • Redundant getAddress. operation.ts wraps getCreateAddress(...) in getAddress(...); it already returns
    checksummed. Harmless/defensive.

7. Verified consistent (no action)

  • Invariant correct for every deploy op: all three subclasses inherit the base buildUnsigned→deployTx (no-to),
    no factory/CREATE2 anywhere; the address is fully determined by (from, nonce). Architecture/type/DX lenses: correct on
    all counts.
  • nonce source: captured as tx.nonce after populateTransaction — the exact nonce submitTransaction uses, so
    it equals the nonce the mined CREATE consumes; the nextNonce/nonceConsumed/rollbackNonce interaction stays correct
    and the new guard rethrows without double-wrapping.
  • Threat closed: the returned address derives from wallet.getAddress() + the locally-populated nonce, RPC-independent;
    a hostile RPC cannot launder a fake contractAddress. The non-deploy EVMOperation.execute reads nothing spoofable
    from the receipt (returns only hash) — no residual gap.
  • Error type/shape: bare CCTTxFailedError matches every sibling deploy/tx failure (no "mismatch" class exists to
    reuse); context: { txHash, expectedAddress, returnedAddress } rides the standard ...options.context spread.
  • Returning expectedAddress: asserted equal to the receipt, so identical — and the locally-derived one is the more
    trustworthy of the two.

8. Types / build / tests (verified in isolation)

Note on the worktree: the review worktree also held uncommitted edits (my §6 recommendations + §5 fix). To avoid conflating them with the PR, I stashed them and measured the committed head ecb616ed clean, then restored.

Committed head ecb616ed (PR as submitted), SDK workspace:

tsc --noEmit  → EXIT 0
oxfmt --check → EXIT 0
oxlint        → EXIT 0
operation.test.ts + submit.test.ts → tests 10, pass 10, fail 0   (operation 3 + submit 7)

npm run check returns EXIT 1, but not from this diff: oxfmt/oxlint/tsc all pass; the non-zero is the generate:lint stage reporting drift in generated files (ccip-sdk/src/selectors.ts, ccip-sdk/src/api/index.ts) — network-regenerated, not in the diff, and the same drift the repo clears with its routine chore: selectors commits. The diff itself introduces no any/unsafe as; the sync nonce capture and submit return-widening typecheck cleanly.

Committed head + my §5/§6 additions: tsc EXIT 0, oxfmt EXIT 0, op suite tests 12, pass 12, fail 0 (adds the null-receipt and no-nonce tests).

9. Recommendation

Approve. The receipt-integrity check is correct, grounded against every v2.0.0 CCT deploy constructor (§2), and proven live: on a real Sepolia deploy the SDK-returned address is the deterministic CREATE address, equal to both the derived value and the on-chain receipt, and any substituted address is provably rejected (§4). Recommended before merge: the §5 MAJOR (stale public @throws) and the §6 additions (document the EOA-CREATE assumption; add null-receipt + no-nonce tests) — all implemented in the review worktree and verified green (SDK tsc/lint clean, op suite 12/12). None blocks the current diff.

@aelmanaa Addressed §5 MAJOR and recommended additions.

@mervin-link
mervin-link merged commit 1012862 into cct-sdk Sep 28, 2026
2 of 4 checks passed
@mervin-link
mervin-link deleted the fix/DAPP-11480-verify-receipt-address branch September 28, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants