From 5083e86f961c170291e54581ff61d6a08c45e861 Mon Sep 17 00:00:00 2001 From: andrejrakic Date: Fri, 19 Sep 2025 15:47:33 +0200 Subject: [PATCH 1/6] feat: implement EVM Extra Args V2 encoding and decoding --- packages/ccip-svm/package-lock.json | 953 ------------------ packages/ccip-svm/package.json | 5 +- .../src/__tests__/evmExtraArgsV2.test.ts | 179 ++++ .../ccip-svm/src/encoders/evmExtraArgsV2.ts | 81 ++ packages/ccip-svm/src/index.ts | 1 + pnpm-lock.yaml | 338 ++++++- 6 files changed, 566 insertions(+), 991 deletions(-) delete mode 100644 packages/ccip-svm/package-lock.json create mode 100644 packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts create mode 100644 packages/ccip-svm/src/encoders/evmExtraArgsV2.ts diff --git a/packages/ccip-svm/package-lock.json b/packages/ccip-svm/package-lock.json deleted file mode 100644 index c6bed98..0000000 --- a/packages/ccip-svm/package-lock.json +++ /dev/null @@ -1,953 +0,0 @@ -{ - "name": "@chainlink/ccip-svm", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@chainlink/ccip-svm", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@solana/kit": "^3.0.3", - "viem": "^2.37.6" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", - "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==", - "license": "MIT" - }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@solana/accounts": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-3.0.3.tgz", - "integrity": "sha512-KqlePrlZaHXfu8YQTCxN204ZuVm9o68CCcUr6l27MG2cuRUtEM1Ta0iR8JFkRUAEfZJC4Cu0ZDjK/v49loXjZQ==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/rpc-spec": "3.0.3", - "@solana/rpc-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/addresses": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-3.0.3.tgz", - "integrity": "sha512-AuMwKhJI89ANqiuJ/fawcwxNKkSeHH9CApZd2xelQQLS7X8uxAOovpcmEgiObQuiVP944s9ScGUT62Bdul9qYg==", - "license": "MIT", - "dependencies": { - "@solana/assertions": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/nominal-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/assertions": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-3.0.3.tgz", - "integrity": "sha512-2qspxdbWp2y62dfCIlqeWQr4g+hE8FYSSwcaP6itwMwGRb8393yDGCJfI/znuzJh6m/XVWhMHIgFgsBwnevCmg==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/codecs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-3.0.3.tgz", - "integrity": "sha512-GOHwTlIQsCoJx9Ryr6cEf0FHKAQ7pY4aO4xgncAftrv0lveTQ1rPP2inQ1QT0gJllsIa8nwbfXAADs9nNJxQDA==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "3.0.3", - "@solana/codecs-data-structures": "3.0.3", - "@solana/codecs-numbers": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/options": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/codecs-core": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-3.0.3.tgz", - "integrity": "sha512-emKykJ3h1DmnDOY29Uv9eJXP8E/FHzvlUBJ6te+5EbKdFjj7vdlKYPfDxOI6iGdXTY+YC/ELtbNBh6QwF2uEDQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/codecs-data-structures": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-3.0.3.tgz", - "integrity": "sha512-R15cLp8riJvToXziW8lP6AMSwsztGhEnwgyGmll32Mo0Yjq+hduW2/fJrA/TJs6tA/OgTzMQjlxgk009EqZHCw==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "3.0.3", - "@solana/codecs-numbers": "3.0.3", - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/codecs-numbers": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-3.0.3.tgz", - "integrity": "sha512-pfXkH9J0glrM8qj6389GAn30+cJOxzXLR2FsPOHCUMXrqLhGjMMZAWhsQkpOQ37SGc/7EiQsT/gmyGC7gxHqJQ==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "3.0.3", - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/codecs-strings": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-3.0.3.tgz", - "integrity": "sha512-VHBXnnTVtcQ1j+7Vrz+qSYo38no+jiHRdGnhFspRXEHNJbllzwKqgBE7YN3qoIXH+MKxgJUcwO5KHmdzf8Wn2A==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "3.0.3", - "@solana/codecs-numbers": "3.0.3", - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "fastestsmallesttextencoderdecoder": "^1.0.22", - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/errors": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-3.0.3.tgz", - "integrity": "sha512-1l84xJlHNva6io62PcYfUamwWlc0eM95nHgCrKX0g0cLoC6D6QHYPCEbEVkR+C5UtP9JDgyQM8MFiv+Ei5tO9Q==", - "license": "MIT", - "dependencies": { - "chalk": "5.6.2", - "commander": "14.0.0" - }, - "bin": { - "errors": "bin/cli.mjs" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/fast-stable-stringify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-3.0.3.tgz", - "integrity": "sha512-ED0pxB6lSEYvg+vOd5hcuQrgzEDnOrURFgp1ZOY+lQhJkQU6xo+P829NcJZQVP1rdU2/YQPAKJKEseyfe9VMIw==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/functional": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-3.0.3.tgz", - "integrity": "sha512-2qX1kKANn8995vOOh5S9AmF4ItGZcfbny0w28Eqy8AFh+GMnSDN4gqpmV2LvxBI9HibXZptGH3RVOMk82h1Mpw==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/instruction-plans": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/instruction-plans/-/instruction-plans-3.0.3.tgz", - "integrity": "sha512-eqoaPtWtmLTTpdvbt4BZF5H6FIlJtXi9H7qLOM1dLYonkOX2Ncezx5NDCZ9tMb2qxVMF4IocYsQnNSnMfjQF1w==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/instructions": "3.0.3", - "@solana/promises": "3.0.3", - "@solana/transaction-messages": "3.0.3", - "@solana/transactions": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/instructions": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-3.0.3.tgz", - "integrity": "sha512-4csIi8YUDb5j/J+gDzmYtOvq7ZWLbCxj4t0xKn+fPrBk/FD2pK29KVT3Fu7j4Lh1/ojunQUP9X4NHwUexY3PnA==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "3.0.3", - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/keys": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-3.0.3.tgz", - "integrity": "sha512-tp8oK9tMadtSIc4vF4aXXWkPd4oU5XPW8nf28NgrGDWGt25fUHIydKjkf2hPtMt9i1WfRyQZ33B5P3dnsNqcPQ==", - "license": "MIT", - "dependencies": { - "@solana/assertions": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/nominal-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/kit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-3.0.3.tgz", - "integrity": "sha512-CEEhCDmkvztd1zbgADsEQhmj9GyWOOGeW1hZD+gtwbBSF5YN1uofS/pex5MIh/VIqKRj+A2UnYWI1V+9+q/lyQ==", - "license": "MIT", - "dependencies": { - "@solana/accounts": "3.0.3", - "@solana/addresses": "3.0.3", - "@solana/codecs": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/functional": "3.0.3", - "@solana/instruction-plans": "3.0.3", - "@solana/instructions": "3.0.3", - "@solana/keys": "3.0.3", - "@solana/programs": "3.0.3", - "@solana/rpc": "3.0.3", - "@solana/rpc-parsed-types": "3.0.3", - "@solana/rpc-spec-types": "3.0.3", - "@solana/rpc-subscriptions": "3.0.3", - "@solana/rpc-types": "3.0.3", - "@solana/signers": "3.0.3", - "@solana/sysvars": "3.0.3", - "@solana/transaction-confirmation": "3.0.3", - "@solana/transaction-messages": "3.0.3", - "@solana/transactions": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/nominal-types": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-3.0.3.tgz", - "integrity": "sha512-aZavCiexeUAoMHRQg4s1AHkH3wscbOb70diyfjhwZVgFz1uUsFez7csPp9tNFkNolnadVb2gky7yBk3IImQJ6A==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/options": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/options/-/options-3.0.3.tgz", - "integrity": "sha512-jarsmnQ63RN0JPC5j9sgUat07NrL9PC71XU7pUItd6LOHtu4+wJMio3l5mT0DHVfkfbFLL6iI6+QmXSVhTNF3g==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "3.0.3", - "@solana/codecs-data-structures": "3.0.3", - "@solana/codecs-numbers": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/programs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-3.0.3.tgz", - "integrity": "sha512-JZlVE3/AeSNDuH3aEzCZoDu8GTXkMpGXxf93zXLzbxfxhiQ/kHrReN4XE/JWZ/uGWbaFZGR5B3UtdN2QsoZL7w==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/promises": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-3.0.3.tgz", - "integrity": "sha512-K+UflGBVxj30XQMHTylHHZJdKH5QG3oj5k2s42GrZ/Wbu72oapVJySMBgpK45+p90t8/LEqV6rRPyTXlet9J+Q==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-3.0.3.tgz", - "integrity": "sha512-3oukAaLK78GegkKcm6iNmRnO4mFeNz+BMvA8T56oizoBNKiRVEq/6DFzVX/LkmZ+wvD601pAB3uCdrTPcC0YKQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/fast-stable-stringify": "3.0.3", - "@solana/functional": "3.0.3", - "@solana/rpc-api": "3.0.3", - "@solana/rpc-spec": "3.0.3", - "@solana/rpc-spec-types": "3.0.3", - "@solana/rpc-transformers": "3.0.3", - "@solana/rpc-transport-http": "3.0.3", - "@solana/rpc-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-api": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-3.0.3.tgz", - "integrity": "sha512-Yym9/Ama62OY69rAZgbOCAy1QlqaWAyb0VlqFuwSaZV1pkFCCFSwWEJEsiN1n8pb2ZP+RtwNvmYixvWizx9yvA==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/keys": "3.0.3", - "@solana/rpc-parsed-types": "3.0.3", - "@solana/rpc-spec": "3.0.3", - "@solana/rpc-transformers": "3.0.3", - "@solana/rpc-types": "3.0.3", - "@solana/transaction-messages": "3.0.3", - "@solana/transactions": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-parsed-types": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-3.0.3.tgz", - "integrity": "sha512-/koM05IM2fU91kYDQxXil3VBNlOfcP+gXE0js1sdGz8KonGuLsF61CiKB5xt6u1KEXhRyDdXYLjf63JarL4Ozg==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-spec": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-3.0.3.tgz", - "integrity": "sha512-MZn5/8BebB6MQ4Gstw6zyfWsFAZYAyLzMK+AUf/rSfT8tPmWiJ/mcxnxqOXvFup/l6D67U8pyGpIoFqwCeZqqA==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/rpc-spec-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-spec-types": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-3.0.3.tgz", - "integrity": "sha512-A6Jt8SRRetnN3CeGAvGJxigA9zYRslGgWcSjueAZGvPX+MesFxEUjSWZCfl+FogVFvwkqfkgQZQbPAGZQFJQ6Q==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-subscriptions": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-3.0.3.tgz", - "integrity": "sha512-LRvz6NaqvtsYFd32KwZ+rwYQ9XCs+DWjV8BvBLsJpt9/NWSuHf/7Sy/vvP6qtKxut692H/TMvHnC4iulg0WmiQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/fast-stable-stringify": "3.0.3", - "@solana/functional": "3.0.3", - "@solana/promises": "3.0.3", - "@solana/rpc-spec-types": "3.0.3", - "@solana/rpc-subscriptions-api": "3.0.3", - "@solana/rpc-subscriptions-channel-websocket": "3.0.3", - "@solana/rpc-subscriptions-spec": "3.0.3", - "@solana/rpc-transformers": "3.0.3", - "@solana/rpc-types": "3.0.3", - "@solana/subscribable": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-subscriptions-api": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-3.0.3.tgz", - "integrity": "sha512-MGgVK3PUS15qsjuhimpzGZrKD/CTTvS0mAlQ0Jw84zsr1RJVdQJK/F0igu07BVd172eTZL8d90NoAQ3dahW5pA==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/keys": "3.0.3", - "@solana/rpc-subscriptions-spec": "3.0.3", - "@solana/rpc-transformers": "3.0.3", - "@solana/rpc-types": "3.0.3", - "@solana/transaction-messages": "3.0.3", - "@solana/transactions": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-subscriptions-channel-websocket": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-3.0.3.tgz", - "integrity": "sha512-zUzUlb8Cwnw+SHlsLrSqyBRtOJKGc+FvSNJo/vWAkLShoV0wUDMPv7VvhTngJx3B/3ANfrOZ4i08i9QfYPAvpQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/functional": "3.0.3", - "@solana/rpc-subscriptions-spec": "3.0.3", - "@solana/subscribable": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3", - "ws": "^8.18.0" - } - }, - "node_modules/@solana/rpc-subscriptions-spec": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-3.0.3.tgz", - "integrity": "sha512-9KpQ32OBJWS85mn6q3gkM0AjQe1LKYlMU7gpJRrla/lvXxNLhI95tz5K6StctpUreVmRWTVkNamHE69uUQyY8A==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/promises": "3.0.3", - "@solana/rpc-spec-types": "3.0.3", - "@solana/subscribable": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-transformers": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-3.0.3.tgz", - "integrity": "sha512-lzdaZM/dG3s19Tsk4mkJA5JBoS1eX9DnD7z62gkDwrwJDkDBzkAJT9aLcsYFfTmwTfIp6uU2UPgGYc97i1wezw==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/functional": "3.0.3", - "@solana/nominal-types": "3.0.3", - "@solana/rpc-spec-types": "3.0.3", - "@solana/rpc-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-transport-http": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-3.0.3.tgz", - "integrity": "sha512-bIXFwr2LR5A97Z46dI661MJPbHnPfcShBjFzOS/8Rnr8P4ho3j/9EUtjDrsqoxGJT3SLWj5OlyXAlaDAvVTOUQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3", - "@solana/rpc-spec": "3.0.3", - "@solana/rpc-spec-types": "3.0.3", - "undici-types": "^7.15.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/rpc-types": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-3.0.3.tgz", - "integrity": "sha512-petWQ5xSny9UfmC3Qp2owyhNU0w9SyBww4+v7tSVyXMcCC9v6j/XsqTeimH1S0qQUllnv0/FY83ohFaxofmZ6Q==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/codecs-numbers": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/nominal-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/signers": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-3.0.3.tgz", - "integrity": "sha512-UwCd/uPYTZiwd283JKVyOWLLN5sIgMBqGDyUmNU3vo9hcmXKv5ZGm/9TvwMY2z35sXWuIOcj7etxJ8OoWc/ObQ==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/instructions": "3.0.3", - "@solana/keys": "3.0.3", - "@solana/nominal-types": "3.0.3", - "@solana/transaction-messages": "3.0.3", - "@solana/transactions": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/subscribable": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-3.0.3.tgz", - "integrity": "sha512-FJ27LKGHLQ5GGttPvTOLQDLrrOZEgvaJhB7yYaHAhPk25+p+erBaQpjePhfkMyUbL1FQbxn1SUJmS6jUuaPjlQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/sysvars": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-3.0.3.tgz", - "integrity": "sha512-GnHew+QeKCs2f9ow+20swEJMH4mDfJA/QhtPgOPTYQx/z69J4IieYJ7fZenSHnA//lJ45fVdNdmy1trypvPLBQ==", - "license": "MIT", - "dependencies": { - "@solana/accounts": "3.0.3", - "@solana/codecs": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/rpc-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/transaction-confirmation": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-3.0.3.tgz", - "integrity": "sha512-dXx0OLtR95LMuARgi2dDQlL1QYmk56DOou5q9wKymmeV3JTvfDExeWXnOgjRBBq/dEfj4ugN1aZuTaS18UirFw==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/keys": "3.0.3", - "@solana/promises": "3.0.3", - "@solana/rpc": "3.0.3", - "@solana/rpc-subscriptions": "3.0.3", - "@solana/rpc-types": "3.0.3", - "@solana/transaction-messages": "3.0.3", - "@solana/transactions": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/transaction-messages": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-3.0.3.tgz", - "integrity": "sha512-s+6NWRnBhnnjFWV4x2tzBzoWa6e5LiIxIvJlWwVQBFkc8fMGY04w7jkFh0PM08t/QFKeXBEWkyBDa/TFYdkWug==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/codecs-data-structures": "3.0.3", - "@solana/codecs-numbers": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/functional": "3.0.3", - "@solana/instructions": "3.0.3", - "@solana/nominal-types": "3.0.3", - "@solana/rpc-types": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/transactions": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-3.0.3.tgz", - "integrity": "sha512-iMX+n9j4ON7H1nKlWEbMqMOpKYC6yVGxKKmWHT1KdLRG7v+03I4DnDeFoI+Zmw56FA+7Bbne8jwwX60Q1vk/MQ==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "3.0.3", - "@solana/codecs-core": "3.0.3", - "@solana/codecs-data-structures": "3.0.3", - "@solana/codecs-numbers": "3.0.3", - "@solana/codecs-strings": "3.0.3", - "@solana/errors": "3.0.3", - "@solana/functional": "3.0.3", - "@solana/instructions": "3.0.3", - "@solana/keys": "3.0.3", - "@solana/nominal-types": "3.0.3", - "@solana/rpc-types": "3.0.3", - "@solana/transaction-messages": "3.0.3" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/abitype": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", - "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3.22.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/fastestsmallesttextencoderdecoder": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", - "license": "CC0-1.0", - "peer": true - }, - "node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/ox": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.3.tgz", - "integrity": "sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.11.0", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "1.9.1", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "abitype": "^1.0.9", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/viem": { - "version": "2.37.6", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.37.6.tgz", - "integrity": "sha512-b+1IozQ8TciVQNdQUkOH5xtFR0z7ZxR8pyloENi/a+RA408lv4LoX12ofwoiT3ip0VRhO5ni1em//X0jn/eW0g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.9.1", - "@noble/hashes": "1.8.0", - "@scure/bip32": "1.7.0", - "@scure/bip39": "1.6.0", - "abitype": "1.1.0", - "isows": "1.0.7", - "ox": "0.9.3", - "ws": "8.18.3" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/packages/ccip-svm/package.json b/packages/ccip-svm/package.json index 638b531..69c4a31 100644 --- a/packages/ccip-svm/package.json +++ b/packages/ccip-svm/package.json @@ -11,7 +11,7 @@ "scripts": { "build": "tsc ", "dev": "tsc -w", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest" }, "repository": { "type": "git", @@ -32,6 +32,7 @@ "viem": "^2.37.6" }, "devDependencies": { - "typescript": "^5.6.3" + "typescript": "^5.6.3", + "vitest": "^3.2.4" } } \ No newline at end of file diff --git a/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts new file mode 100644 index 0000000..3dd7de4 --- /dev/null +++ b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts @@ -0,0 +1,179 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { + encodeEVMExtraArgsV2, + decodeEVMExtraArgsV2, + EVM_EXTRA_ARGS_V2_TAG +} from '../encoders/evmExtraArgsV2' + +describe('EVM Extra Args V2', () => { + let consoleSpy: ReturnType + + beforeEach(() => { + // Spy on console.warn to test warning messages + consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }) + }) + + afterEach(() => { + consoleSpy.mockRestore() + }) + + describe('encodeEVMExtraArgsV2', () => { + it('should encode EVM extra args with default allowOutOfOrderExecution=true', () => { + const original = { + gasLimit: 100000n + } + + const encoded = encodeEVMExtraArgsV2(original) + + // Should not show any warning for default behavior + expect(consoleSpy).not.toHaveBeenCalled() + + const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001"; + + expect(encoded).toBeInstanceOf(Uint8Array) + expect(encoded.length).toBe(68) // 4 bytes tag + 64 bytes ABI encoded data + expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) + }) + + it('should encode EVM extra args with explicit allowOutOfOrderExecution=true', () => { + const original = { + gasLimit: 100000n, + allowOutOfOrderExecution: true + } + + const encoded = encodeEVMExtraArgsV2(original) + + // Should not show any warning for recommended value + expect(consoleSpy).not.toHaveBeenCalled() + + const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001"; + + expect(encoded).toBeInstanceOf(Uint8Array) + expect(encoded.length).toBe(68) + expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) + }) + + it('should encode EVM extra args with allowOutOfOrderExecution=false and show warning', () => { + const original = { + gasLimit: 100000n, + allowOutOfOrderExecution: false + } + + const encoded = encodeEVMExtraArgsV2(original) + + // Should show warning when user overrides to false + expect(consoleSpy).toHaveBeenCalledWith( + `Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.` + ) + + const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000000"; + + expect(encoded).toBeInstanceOf(Uint8Array) + expect(encoded.length).toBe(68) + expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) + }) + + it('should handle token-only transfers (gasLimit = 0)', () => { + const original = { + gasLimit: 0n, + allowOutOfOrderExecution: true + } + + const encoded = encodeEVMExtraArgsV2(original) + + const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"; + + expect(consoleSpy).not.toHaveBeenCalled() + expect(encoded).toBeInstanceOf(Uint8Array) + expect(encoded.length).toBe(68) + expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) + }) + + it('should include correct tag at the beginning', () => { + const encoded = encodeEVMExtraArgsV2({ gasLimit: 0n }) + + // Extract first 4 bytes and convert to uint32 big-endian + const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) + expect(tag).toBe(EVM_EXTRA_ARGS_V2_TAG) + expect(tag).toBe(0x181dcf10) + }) + }) + + describe('decodeEVMExtraArgsV2', () => { + it('should decode EVM extra args correctly with default values', () => { + const original = { + gasLimit: 200000n, + allowOutOfOrderExecution: true + } + + const encoded = encodeEVMExtraArgsV2(original) + const decoded = decodeEVMExtraArgsV2(encoded) + + expect(decoded.gasLimit).toBe(original.gasLimit) + expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution) + }) + + it('should decode EVM extra args with allowOutOfOrderExecution=false', () => { + const original = { + gasLimit: 150000n, + allowOutOfOrderExecution: false + } + + const encoded = encodeEVMExtraArgsV2(original) + const decoded = decodeEVMExtraArgsV2(encoded) + + expect(decoded.gasLimit).toBe(original.gasLimit) + expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution) + }) + + it('should decode token-only transfers (gasLimit = 0)', () => { + const original = { + gasLimit: 0n, + allowOutOfOrderExecution: true + } + + const encoded = encodeEVMExtraArgsV2(original) + const decoded = decodeEVMExtraArgsV2(encoded) + + expect(decoded.gasLimit).toBe(0n) + expect(decoded.allowOutOfOrderExecution).toBe(true) + }) + + it('should throw error for data too short', () => { + const shortData = new Uint8Array(10) // Too short + + expect(() => decodeEVMExtraArgsV2(shortData)).toThrow('Invalid EVM Extra Args V2: data too short') + }) + + it('should throw error for invalid tag', () => { + const invalidData = new Uint8Array(68) + // Set wrong tag (first 4 bytes) + invalidData[0] = 0x12 + invalidData[1] = 0x34 + invalidData[2] = 0x56 + invalidData[3] = 0x78 + + expect(() => decodeEVMExtraArgsV2(invalidData)).toThrow( + 'Invalid EVM Extra Args V2 tag: expected 0x181dcf10, got 0x12345678' + ) + }) + }) + + describe('round-trip encoding/decoding', () => { + it('should maintain data integrity through encode/decode cycle', () => { + const testCases = [ + { gasLimit: 0n, allowOutOfOrderExecution: true }, + { gasLimit: 200000n, allowOutOfOrderExecution: true }, + { gasLimit: 1000000n, allowOutOfOrderExecution: false } + ] + + testCases.forEach((original) => { + const encoded = encodeEVMExtraArgsV2(original) + const decoded = decodeEVMExtraArgsV2(encoded) + + expect(decoded.gasLimit).toBe(original.gasLimit) + expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution ?? true) + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts new file mode 100644 index 0000000..8a19875 --- /dev/null +++ b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts @@ -0,0 +1,81 @@ +import { encodeAbiParameters, parseAbiParameters, decodeAbiParameters } from 'viem' + +/** + * EVM Extra Args V2 tag: bytes4(keccak256("CCIP EVMExtraArgsV2")) + */ +export const EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10 + +export interface EVMExtraArgsV2 { + gasLimit: bigint + allowOutOfOrderExecution?: boolean +} + +/** + * Creates properly encoded extraArgs buffer for EVM destinations + * @param gasLimit - Gas limit for execution on destination chain (use 0 for token-only transfers) + * @param allowOutOfOrderExecution - Whether messages can be executed out of order (default: true) + * @returns Properly encoded extraArgs as Uint8Array + */ +export function encodeEVMExtraArgsV2({ + gasLimit, + allowOutOfOrderExecution = true +}: EVMExtraArgsV2): Uint8Array { + if (allowOutOfOrderExecution === false) { + console.warn( + `Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.` + ) + } + + // bytes4(keccak256("CCIP EVMExtraArgsV2")) = 0x181dcf10 + const tag = new Uint8Array([0x18, 0x1d, 0xcf, 0x10]) + + const encodedExtraArgs = encodeAbiParameters( + parseAbiParameters('uint128 gasLimit, bool allowOutOfOrderExecution'), + [gasLimit, allowOutOfOrderExecution] + ) + + const encodedExtraArgsBytes = new Uint8Array(Buffer.from(encodedExtraArgs.slice(2), 'hex')) + + // abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs) + const result = new Uint8Array(tag.length + encodedExtraArgsBytes.length) + result.set(tag, 0) + result.set(encodedExtraArgsBytes, tag.length) + + return result +} + +/** + * Decodes EVM Extra Args V2 from bytes + * @param data - Encoded extra args bytes + * @returns Decoded EVMExtraArgsV2 object + */ +export function decodeEVMExtraArgsV2(data: Uint8Array): EVMExtraArgsV2 { + // 4 bytes tag + 32 bytes gasLimit + 32 bytes bool = 68 bytes + // For example (100_000 gasLimit and true), 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001 + if (data.length < 68) { + throw new Error('Invalid EVM Extra Args V2: data too short') + } + + const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, false) // big endian + if (tag !== EVM_EXTRA_ARGS_V2_TAG) { + throw new Error(`Invalid EVM Extra Args V2 tag: expected 0x${EVM_EXTRA_ARGS_V2_TAG.toString(16)}, got 0x${tag.toString(16)}`) + } + + const extraArgsData = data.slice(4) + const extraArgsHex = '0x' + Array.from(extraArgsData).map(b => b.toString(16).padStart(2, '0')).join('') + + const [gasLimit, allowOutOfOrderExecution] = decodeAbiParameters( + parseAbiParameters('uint128 gasLimit, bool allowOutOfOrderExecution'), + extraArgsHex as `0x${string}` + ) + + return { + gasLimit, + allowOutOfOrderExecution + } +} + + + + + diff --git a/packages/ccip-svm/src/index.ts b/packages/ccip-svm/src/index.ts index 8d52968..1e68a27 100644 --- a/packages/ccip-svm/src/index.ts +++ b/packages/ccip-svm/src/index.ts @@ -1 +1,2 @@ +export * from './encoders/evmExtraArgsV2'; export * from './errors/custom'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45c0b91..239d252 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,10 +23,10 @@ importers: dependencies: '@chainlink/ccip-js': specifier: ^0.2.1 - version: 0.2.4(a3c99c0fb0fe13a5c24ee996cead66b3) + version: 0.2.4(9f66f6640aaefa29aa7e5aa056ab9158) '@chainlink/ccip-react-components': specifier: ^0.3.0 - version: 0.3.0(1ed1302eb82042231a950ad4c671505f) + version: 0.3.0(db7643b72c37e7ac37a76874ef595e85) '@tanstack/react-query': specifier: ^5.37.1 version: 5.80.7(react@18.3.1) @@ -81,7 +81,7 @@ importers: version: 3.0.9(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-toolbox': specifier: ^5.0.0 - version: 5.0.0(775720b1c6d0d796629832f440f70ca5) + version: 5.0.0(4a8c7a36d0534b6db66906aadca1ca67) '@nomicfoundation/hardhat-viem': specifier: ^2.0.5 version: 2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.25.67) @@ -313,6 +313,9 @@ importers: typescript: specifier: ^5.6.3 version: 5.8.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(jsdom@24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.43.1) packages: @@ -3207,6 +3210,9 @@ packages: '@types/chai@5.2.0': resolution: {integrity: sha512-FWnQYdrG9FAC8KgPVhDFfrPL1FBsL3NtIt2WsxKvwu/61K6HiuDF3xAb7c7w/k9ML2QOUHcwTgU7dKLFPK6sBg==} + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/concat-stream@1.6.1': resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} @@ -3540,6 +3546,9 @@ packages: '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/mocker@2.1.9': resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} peerDependencies: @@ -3551,21 +3560,47 @@ packages: vite: optional: true + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/runner@2.1.9': resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/snapshot@2.1.9': resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/spy@2.1.9': resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@volar/language-core@1.11.1': resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} @@ -4775,6 +4810,9 @@ packages: es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -5082,6 +5120,10 @@ packages: resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} engines: {node: '>=12.0.0'} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5143,6 +5185,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5994,6 +6045,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -6189,6 +6243,9 @@ packages: loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -6737,6 +6794,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -6759,6 +6819,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -7511,6 +7575,9 @@ packages: std-env@3.8.1: resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} @@ -7610,6 +7677,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -7737,18 +7807,34 @@ packages: resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -8179,6 +8265,11 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite-plugin-dts@3.9.1: resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8245,6 +8336,34 @@ packages: jsdom: optional: true + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} @@ -8895,19 +9014,19 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@chainlink/ccip-js@0.2.4(5e3e05d9b50680c4de2bc0a3c2cf5c97)': + '@chainlink/ccip-js@0.2.4(9f66f6640aaefa29aa7e5aa056ab9158)': dependencies: '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(chai@5.2.0)(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-toolbox': 5.0.0(13680ddddf672fc71131616f3a7f7389) - '@nomicfoundation/hardhat-viem': 2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.24.2) + '@nomicfoundation/hardhat-toolbox': 5.0.0(d50884c5ccf68f1f6d25fc3e5da82825) + '@nomicfoundation/hardhat-viem': 2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.25.67) '@openzeppelin/contracts': 5.2.0 chai: 5.2.0 ethers: 6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) mocha: 11.7.0 ts-jest: 29.2.6(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)))(typescript@5.8.3) typescript: 5.8.3 - viem: 2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2) + viem: 2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67) transitivePeerDependencies: - '@babel/core' - '@jest/transform' @@ -8933,19 +9052,19 @@ snapshots: - utf-8-validate - zod - '@chainlink/ccip-js@0.2.4(a3c99c0fb0fe13a5c24ee996cead66b3)': + '@chainlink/ccip-js@0.2.4(e75b9ef2f0597dd774bc03c1382dca08)': dependencies: '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(chai@5.2.0)(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-toolbox': 5.0.0(13680ddddf672fc71131616f3a7f7389) - '@nomicfoundation/hardhat-viem': 2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.25.67) + '@nomicfoundation/hardhat-toolbox': 5.0.0(d50884c5ccf68f1f6d25fc3e5da82825) + '@nomicfoundation/hardhat-viem': 2.0.6(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typescript@5.8.3)(viem@2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67))(zod@3.24.2) '@openzeppelin/contracts': 5.2.0 chai: 5.2.0 ethers: 6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) mocha: 11.7.0 ts-jest: 29.2.6(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)))(typescript@5.8.3) typescript: 5.8.3 - viem: 2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67) + viem: 2.21.25(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2) transitivePeerDependencies: - '@babel/core' - '@jest/transform' @@ -8971,9 +9090,9 @@ snapshots: - utf-8-validate - zod - '@chainlink/ccip-react-components@0.3.0(1ed1302eb82042231a950ad4c671505f)': + '@chainlink/ccip-react-components@0.3.0(db7643b72c37e7ac37a76874ef595e85)': dependencies: - '@chainlink/ccip-js': 0.2.4(5e3e05d9b50680c4de2bc0a3c2cf5c97) + '@chainlink/ccip-js': 0.2.4(e75b9ef2f0597dd774bc03c1382dca08) '@hookform/resolvers': 3.10.0(react-hook-form@7.54.2(react@18.3.1)) '@radix-ui/react-dialog': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-label': 2.1.2(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -10675,28 +10794,7 @@ snapshots: ethereumjs-util: 7.1.5 hardhat: 2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10) - '@nomicfoundation/hardhat-toolbox@5.0.0(13680ddddf672fc71131616f3a7f7389)': - dependencies: - '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(chai@5.2.0)(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-ignition-ethers': 0.15.10(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.10(@nomicfoundation/hardhat-verify@2.0.13(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-network-helpers': 1.0.12(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-verify': 2.0.13(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - '@typechain/ethers-v6': 0.5.1(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3))(typescript@5.8.3) - '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3))(typescript@5.8.3))(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3)) - '@types/chai': 5.2.0 - '@types/mocha': 10.0.10 - '@types/node': 20.19.1 - chai: 5.2.0 - ethers: 6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) - hardhat: 2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10) - hardhat-gas-reporter: 1.0.10(bufferutil@4.0.9)(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) - solidity-coverage: 0.8.14(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) - ts-node: 10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3) - typechain: 8.3.2(typescript@5.8.3) - typescript: 5.8.3 - - '@nomicfoundation/hardhat-toolbox@5.0.0(775720b1c6d0d796629832f440f70ca5)': + '@nomicfoundation/hardhat-toolbox@5.0.0(4a8c7a36d0534b6db66906aadca1ca67)': dependencies: '@nomicfoundation/hardhat-chai-matchers': 2.0.9(@nomicfoundation/hardhat-ethers@3.0.9(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(chai@5.2.0)(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-ethers': 3.0.9(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) @@ -10705,7 +10803,7 @@ snapshots: '@nomicfoundation/hardhat-verify': 2.0.13(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) '@typechain/ethers-v6': 0.5.1(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3))(typescript@5.8.3) '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3))(typescript@5.8.3))(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3)) - '@types/chai': 5.2.0 + '@types/chai': 5.2.2 '@types/mocha': 10.0.10 '@types/node': 22.15.32 chai: 5.2.0 @@ -10717,6 +10815,27 @@ snapshots: typechain: 8.3.2(typescript@5.8.3) typescript: 5.8.3 + '@nomicfoundation/hardhat-toolbox@5.0.0(d50884c5ccf68f1f6d25fc3e5da82825)': + dependencies: + '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(chai@5.2.0)(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ignition-ethers': 0.15.10(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.10(@nomicfoundation/hardhat-verify@2.0.13(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-network-helpers': 1.0.12(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-verify': 2.0.13(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) + '@typechain/ethers-v6': 0.5.1(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3))(typescript@5.8.3) + '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3))(typescript@5.8.3))(ethers@6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.8.3)) + '@types/chai': 5.2.2 + '@types/mocha': 10.0.10 + '@types/node': 20.19.1 + chai: 5.2.0 + ethers: 6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10) + hardhat-gas-reporter: 1.0.10(bufferutil@4.0.9)(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + solidity-coverage: 0.8.14(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10)) + ts-node: 10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3) + typechain: 8.3.2(typescript@5.8.3) + typescript: 5.8.3 + '@nomicfoundation/hardhat-verify@2.0.13(hardhat@2.24.3(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))(typescript@5.8.3)(utf-8-validate@5.0.10))': dependencies: '@ethersproject/abi': 5.8.0 @@ -12428,6 +12547,10 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + '@types/concat-stream@1.6.1': dependencies: '@types/node': 20.19.1 @@ -12823,6 +12946,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 1.2.0 + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + tinyrainbow: 2.0.0 + '@vitest/mocker@2.1.9(vite@5.4.19(@types/node@20.19.1)(terser@5.43.1))': dependencies: '@vitest/spy': 2.1.9 @@ -12831,31 +12962,65 @@ snapshots: optionalDependencies: vite: 5.4.19(@types/node@20.19.1)(terser@5.43.1) + '@vitest/mocker@3.2.4(vite@5.4.19(@types/node@22.15.32)(terser@5.43.1))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.4.19(@types/node@22.15.32)(terser@5.43.1) + '@vitest/pretty-format@2.1.9': dependencies: tinyrainbow: 1.2.0 + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/runner@2.1.9': dependencies: '@vitest/utils': 2.1.9 pathe: 1.1.2 + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + '@vitest/snapshot@2.1.9': dependencies: '@vitest/pretty-format': 2.1.9 magic-string: 0.30.17 pathe: 1.1.2 + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + '@vitest/spy@2.1.9': dependencies: tinyspy: 3.0.2 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/utils@2.1.9': dependencies: '@vitest/pretty-format': 2.1.9 loupe: 3.1.3 tinyrainbow: 1.2.0 + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@volar/language-core@1.11.1': dependencies: '@volar/source-map': 1.11.1 @@ -14516,6 +14681,8 @@ snapshots: es-module-lexer@1.6.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -15065,6 +15232,8 @@ snapshots: expect-type@1.2.0: {} + expect-type@1.2.2: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -15124,6 +15293,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -16426,6 +16599,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.1: dependencies: argparse: 1.0.10 @@ -16625,6 +16800,8 @@ snapshots: loupe@3.1.3: {} + loupe@3.2.1: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -17335,6 +17512,8 @@ snapshots: pathe@1.1.2: {} + pathe@2.0.3: {} + pathval@1.1.1: {} pathval@2.0.0: {} @@ -17354,6 +17533,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: {} + pify@2.3.0: {} pify@3.0.0: {} @@ -18235,6 +18416,8 @@ snapshots: std-env@3.8.1: {} + std-env@3.9.0: {} + stream-shift@1.0.3: {} streamsearch@1.1.0: {} @@ -18353,6 +18536,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + styled-jsx@5.1.1(@babel/core@7.27.4)(react@18.3.1): dependencies: client-only: 0.0.1 @@ -18518,12 +18705,23 @@ snapshots: fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinypool@1.0.2: {} + tinypool@1.1.1: {} + tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} + tinyspy@3.0.2: {} + tinyspy@4.0.4: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -18977,6 +19175,24 @@ snapshots: - supports-color - terser + vite-node@3.2.4(@types/node@22.15.32)(terser@5.43.1): + dependencies: + cac: 6.7.14 + debug: 4.4.1(supports-color@8.1.1) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 5.4.19(@types/node@22.15.32)(terser@5.43.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-plugin-dts@3.9.1(@types/node@20.19.1)(rollup@4.36.0)(typescript@5.8.3)(vite@5.4.19(@types/node@20.19.1)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.43.0(@types/node@20.19.1) @@ -19004,6 +19220,16 @@ snapshots: fsevents: 2.3.3 terser: 5.43.1 + vite@5.4.19(@types/node@22.15.32)(terser@5.43.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.36.0 + optionalDependencies: + '@types/node': 22.15.32 + fsevents: 2.3.3 + terser: 5.43.1 + vitest@2.1.9(@types/node@20.19.1)(jsdom@24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.43.1): dependencies: '@vitest/expect': 2.1.9 @@ -19040,6 +19266,46 @@ snapshots: - supports-color - terser + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(jsdom@24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.43.1): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@5.4.19(@types/node@22.15.32)(terser@5.43.1)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1(supports-color@8.1.1) + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 5.4.19(@types/node@22.15.32)(terser@5.43.1) + vite-node: 3.2.4(@types/node@22.15.32)(terser@5.43.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 22.15.32 + jsdom: 24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vlq@1.0.1: {} vue-template-compiler@2.7.16: From cba258c9d67908d4f68f4ce439d71c7c0b696b68 Mon Sep 17 00:00:00 2001 From: andrejrakic Date: Fri, 19 Sep 2025 16:50:59 +0200 Subject: [PATCH 2/6] feat: Add SVM Extra Args V1 encoding and decoding functionality --- .../src/__tests__/svmExtraArgsV1.test.ts | 273 ++++++++++++++++++ .../ccip-svm/src/encoders/svmExtraArgsV1.ts | 131 +++++++++ packages/ccip-svm/src/index.ts | 1 + 3 files changed, 405 insertions(+) create mode 100644 packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts create mode 100644 packages/ccip-svm/src/encoders/svmExtraArgsV1.ts diff --git a/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts new file mode 100644 index 0000000..af21b17 --- /dev/null +++ b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts @@ -0,0 +1,273 @@ +import { describe, it, expect } from 'vitest' +import { address, generateKeyPair, getAddressFromPublicKey } from '@solana/kit' +import { + encodeSVMExtraArgsV1, + decodeSVMExtraArgsV1, + SVM_EXTRA_ARGS_V1_TAG, + type Address +} from '../encoders/svmExtraArgsV1' + +// Helper function to create a random address +async function createRandomAddress(): Promise
{ + const keyPair = await generateKeyPair() + return await getAddressFromPublicKey(keyPair.publicKey) +} + +describe('SVM Extra Args V1', () => { + describe('encodeSVMExtraArgsV1', () => { + it('should encode SVM extra args with default values', () => { + const original = {} + + const encoded = encodeSVMExtraArgsV1(original) + + const expected = "0x1f3b3aba00000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"; + + expect(encoded).toBeInstanceOf(Uint8Array) + // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length = 53 bytes + expect(encoded.length).toBe(53) + expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) + }) + + it('should encode SVM extra args with all parameters', async () => { + const tokenReceiver = await createRandomAddress() + const account1 = await createRandomAddress() + const account2 = await createRandomAddress() + + const original = { + computeUnits: 400000, + accountIsWritableBitmap: 0b1010n, // 2nd and 4th bits set + allowOutOfOrderExecution: true, + tokenReceiver, + accounts: [account1, account2] + } + + const encoded = encodeSVMExtraArgsV1(original) + + expect(encoded).toBeInstanceOf(Uint8Array) + // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length + 64 bytes accounts = 117 bytes + expect(encoded.length).toBe(117) + }) + + it('should encode minimal SVM extra args', () => { + const original = { + computeUnits: 0, + accountIsWritableBitmap: 0n, + allowOutOfOrderExecution: false, + accounts: [] + } + + const encoded = encodeSVMExtraArgsV1(original) + + const expected = "0x1f3b3aba00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + expect(encoded).toBeInstanceOf(Uint8Array) + expect(encoded.length).toBe(53) // Same as default since no accounts + expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) + }) + + it('should include correct tag at the beginning', () => { + const encoded = encodeSVMExtraArgsV1({}) + + // Extract first 4 bytes and convert to uint32 big-endian + const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) + expect(tag).toBe(SVM_EXTRA_ARGS_V1_TAG) + expect(tag).toBe(0x1f3b3aba) + }) + + it('should encode with many accounts', async () => { + const accounts = await Promise.all(Array.from({ length: 5 }, () => createRandomAddress())) + const original = { + accounts + } + + const encoded = encodeSVMExtraArgsV1(original) + + expect(encoded).toBeInstanceOf(Uint8Array) + // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length + (5 * 32) bytes accounts = 213 bytes + expect(encoded.length).toBe(213) + }) + }) + + describe('decodeSVMExtraArgsV1', () => { + it('should decode SVM extra args with default values', () => { + const original = { + computeUnits: 0, + accountIsWritableBitmap: 0n, + allowOutOfOrderExecution: true, + accounts: [] + } + + const encoded = encodeSVMExtraArgsV1(original) + const decoded = decodeSVMExtraArgsV1(encoded) + + expect(decoded.computeUnits).toBe(0) + expect(decoded.accountIsWritableBitmap).toBe(0n) + expect(decoded.allowOutOfOrderExecution).toBe(true) + expect(decoded.accounts).toHaveLength(0) + expect(typeof decoded.tokenReceiver).toBe('string') // Address is a string type + }) + + it('should decode SVM extra args with all parameters', async () => { + const tokenReceiver = await createRandomAddress() + const account1 = await createRandomAddress() + const account2 = await createRandomAddress() + + const original = { + computeUnits: 400000, + accountIsWritableBitmap: 0b1010n, // 2nd and 4th bits set + allowOutOfOrderExecution: false, + tokenReceiver, + accounts: [account1, account2] + } + + const encoded = encodeSVMExtraArgsV1(original) + const decoded = decodeSVMExtraArgsV1(encoded) + + expect(decoded.computeUnits).toBe(original.computeUnits) + expect(decoded.accountIsWritableBitmap).toBe(original.accountIsWritableBitmap) + expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution) + expect(decoded.tokenReceiver?.toString()).toBe(original.tokenReceiver.toString()) + expect(decoded.accounts?.length).toBe(2) + expect(decoded.accounts?.[0].toString()).toBe(account1.toString()) + expect(decoded.accounts?.[1].toString()).toBe(account2.toString()) + }) + + it('should decode with many accounts', async () => { + const accounts = await Promise.all(Array.from({ length: 5 }, () => createRandomAddress())) + const original = { + computeUnits: 123456, + accounts + } + + const encoded = encodeSVMExtraArgsV1(original) + const decoded = decodeSVMExtraArgsV1(encoded) + + expect(decoded.computeUnits).toBe(original.computeUnits) + expect(decoded.accounts?.length).toBe(5) + accounts.forEach((account, index) => { + expect(decoded.accounts?.[index].toString()).toBe(account.toString()) + }) + }) + + it('should throw error for data too short', () => { + const shortData = new Uint8Array(10) // Too short + + expect(() => decodeSVMExtraArgsV1(shortData)).toThrow('Invalid SVM Extra Args V1: data too short') + }) + + it('should throw error for invalid tag', () => { + const invalidData = new Uint8Array(53) + // Set wrong tag (first 4 bytes) + invalidData[0] = 0x12 + invalidData[1] = 0x34 + invalidData[2] = 0x56 + invalidData[3] = 0x78 + + expect(() => decodeSVMExtraArgsV1(invalidData)).toThrow( + 'Invalid SVM Extra Args V1 tag: expected 0x1f3b3aba, got 0x12345678' + ) + }) + }) + + describe('round-trip encoding/decoding', () => { + it('should maintain data integrity through encode/decode cycle', async () => { + const tokenReceiver = await createRandomAddress() + const accounts = await Promise.all([createRandomAddress(), createRandomAddress(), createRandomAddress()]) + + const testCases = [ + // Minimal case + { + computeUnits: 0, + accountIsWritableBitmap: 0n, + allowOutOfOrderExecution: true, + accounts: [] + }, + // Full case + { + computeUnits: 500000, + accountIsWritableBitmap: 0b11110000n, + allowOutOfOrderExecution: false, + tokenReceiver, + accounts + }, + // Edge values + { + computeUnits: 4294967295, // max uint32 + accountIsWritableBitmap: 18446744073709551615n, // max uint64 + allowOutOfOrderExecution: true, + accounts: [await createRandomAddress()] + }, + // Default values + {} + ] + + testCases.forEach((original, index) => { + const encoded = encodeSVMExtraArgsV1(original) + const decoded = decodeSVMExtraArgsV1(encoded) + + expect(decoded.computeUnits).toBe(original.computeUnits ?? 0) + expect(decoded.accountIsWritableBitmap).toBe(original.accountIsWritableBitmap ?? 0n) + expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution ?? true) + + if (original.tokenReceiver) { + expect(decoded.tokenReceiver?.toString()).toBe(original.tokenReceiver.toString()) + } else { + expect(typeof decoded.tokenReceiver).toBe('string') // Address is a string + } + + expect(decoded.accounts?.length).toBe(original.accounts?.length ?? 0) + original.accounts?.forEach((account, accountIndex) => { + expect(decoded.accounts?.[accountIndex].toString()).toBe(account.toString()) + }) + }) + }) + }) + + describe('specific serialization format validation', () => { + it('should serialize fields in correct Borsh-like format', () => { + const tokenReceiver = address('11111111111111111111111111111112') // System program + const account = address('11111111111111111111111111111112') + + const original = { + computeUnits: 0x12345678, // Will be serialized as little-endian + accountIsWritableBitmap: 0x123456789abcdef0n, // Will be serialized as little-endian + allowOutOfOrderExecution: false, // Will be serialized as 0 + tokenReceiver, + accounts: [account] + } + + const encoded = encodeSVMExtraArgsV1(original) + + // Verify tag (big-endian) + expect(encoded[0]).toBe(0x1f) + expect(encoded[1]).toBe(0x3b) + expect(encoded[2]).toBe(0x3a) + expect(encoded[3]).toBe(0xba) + + // Verify computeUnits (little-endian u32) + expect(encoded[4]).toBe(0x78) + expect(encoded[5]).toBe(0x56) + expect(encoded[6]).toBe(0x34) + expect(encoded[7]).toBe(0x12) + + // Verify accountIsWritableBitmap (little-endian u64) + expect(encoded[8]).toBe(0xf0) + expect(encoded[9]).toBe(0xde) + expect(encoded[10]).toBe(0xbc) + expect(encoded[11]).toBe(0x9a) + expect(encoded[12]).toBe(0x78) + expect(encoded[13]).toBe(0x56) + expect(encoded[14]).toBe(0x34) + expect(encoded[15]).toBe(0x12) + + // Verify allowOutOfOrderExecution (bool) + expect(encoded[16]).toBe(0x00) // false + + // Verify accounts length (little-endian u32) + expect(encoded[49]).toBe(0x01) // 1 account + expect(encoded[50]).toBe(0x00) + expect(encoded[51]).toBe(0x00) + expect(encoded[52]).toBe(0x00) + }) + }) +}) \ No newline at end of file diff --git a/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts new file mode 100644 index 0000000..1c6cc31 --- /dev/null +++ b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts @@ -0,0 +1,131 @@ +import { address, getAddressEncoder, getAddressDecoder, getAddressFromPublicKey } from '@solana/kit' + +/** + * SVM Extra Args V1 tag: bytes4(keccak256("CCIP SVMExtraArgsV1")) + */ +export const SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba + +export type Address = ReturnType + +export interface SVMExtraArgsV1 { + computeUnits?: number + accountIsWritableBitmap?: bigint + allowOutOfOrderExecution?: boolean + tokenReceiver?: Address + accounts?: Address[] +} + +/** + * Creates properly encoded extraArgs buffer for SVM destinations + * @param options - SVM extra args configuration + * @returns Properly encoded extraArgs as Uint8Array + */ +export function encodeSVMExtraArgsV1({ + computeUnits = 0, + accountIsWritableBitmap = 0n, + allowOutOfOrderExecution = true, + tokenReceiver, + accounts = [] +}: SVMExtraArgsV1): Uint8Array { + // bytes4(keccak256("CCIP SVMExtraArgsV1")) = 0x1f3b3aba + const tag = new Uint8Array([0x1f, 0x3b, 0x3a, 0xba]) + + // Prepare data for Borsh-like serialization + const buffer: number[] = [] + + // Serialize compute_units (u32, little-endian) + const computeUnitsBytes = new Uint8Array(4) + new DataView(computeUnitsBytes.buffer).setUint32(0, computeUnits, true) + buffer.push(...computeUnitsBytes) + + // Serialize account_is_writable_bitmap (u64, little-endian) + const bitmapBytes = new Uint8Array(8) + new DataView(bitmapBytes.buffer).setBigUint64(0, accountIsWritableBitmap, true) + buffer.push(...bitmapBytes) + + // Serialize allow_out_of_order_execution (bool, 1 byte) + buffer.push(allowOutOfOrderExecution ? 1 : 0) + + // Serialize token_receiver ([u8; 32]) + const encoder = getAddressEncoder() + const tokenReceiverBytes = tokenReceiver + ? encoder.encode(tokenReceiver) + : new Uint8Array(32) // Default/zero address + buffer.push(...tokenReceiverBytes) + + // Serialize accounts (Vec<[u8; 32]>) + // First, serialize the length of the vector as u32 little-endian + const accountsLengthBytes = new Uint8Array(4) + new DataView(accountsLengthBytes.buffer).setUint32(0, accounts.length, true) + buffer.push(...accountsLengthBytes) + + // Then serialize each account (32 bytes each) + for (const account of accounts) { + buffer.push(...encoder.encode(account)) + } + + // Combine tag + serialized data + const serializedData = new Uint8Array(buffer) + const result = new Uint8Array(tag.length + serializedData.length) + result.set(tag, 0) + result.set(serializedData, tag.length) + + return result +} + +/** + * Decodes SVM Extra Args V1 from bytes + * @param data - Encoded extra args bytes + * @returns Decoded SVMExtraArgsV1 object + */ +export function decodeSVMExtraArgsV1(data: Uint8Array): SVMExtraArgsV1 { + // Check minimum length (4 bytes tag + minimum data) + if (data.length < 4 + 4 + 8 + 1 + 32 + 4) { // tag + computeUnits + bitmap + bool + tokenReceiver + accounts length + throw new Error('Invalid SVM Extra Args V1: data too short') + } + + // Verify tag + const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, false) // big endian + if (tag !== SVM_EXTRA_ARGS_V1_TAG) { + throw new Error(`Invalid SVM Extra Args V1 tag: expected 0x${SVM_EXTRA_ARGS_V1_TAG.toString(16)}, got 0x${tag.toString(16)}`) + } + + let offset = 4 // Skip tag + + // Deserialize compute_units (u32, little-endian) + const computeUnits = new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true) + offset += 4 + + // Deserialize account_is_writable_bitmap (u64, little-endian) + const accountIsWritableBitmap = new DataView(data.buffer, data.byteOffset + offset, 8).getBigUint64(0, true) + offset += 8 + + // Deserialize allow_out_of_order_execution (bool) + const allowOutOfOrderExecution = data[offset] === 1 + offset += 1 + + // Deserialize token_receiver ([u8; 32]) + const tokenReceiverBytes = data.slice(offset, offset + 32) + const decoder = getAddressDecoder() + const tokenReceiver = decoder.decode(tokenReceiverBytes) + offset += 32 + + // Deserialize accounts (Vec<[u8; 32]>) + const accountsLength = new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true) + offset += 4 + + const accounts: Address[] = [] + for (let i = 0; i < accountsLength; i++) { + const accountBytes = data.slice(offset, offset + 32) + accounts.push(decoder.decode(accountBytes)) + offset += 32 + } + + return { + computeUnits, + accountIsWritableBitmap, + allowOutOfOrderExecution, + tokenReceiver, + accounts + } +} \ No newline at end of file diff --git a/packages/ccip-svm/src/index.ts b/packages/ccip-svm/src/index.ts index 1e68a27..71671fd 100644 --- a/packages/ccip-svm/src/index.ts +++ b/packages/ccip-svm/src/index.ts @@ -1,2 +1,3 @@ export * from './encoders/evmExtraArgsV2'; +export * from './encoders/svmExtraArgsV1'; export * from './errors/custom'; From 0a744a8a85e561249f89a3d772e76ccdb42d9dd0 Mon Sep 17 00:00:00 2001 From: andrejrakic Date: Fri, 19 Sep 2025 17:22:26 +0200 Subject: [PATCH 3/6] fix: Remove unnecessary blank lines in decodeEVMExtraArgsV2 function --- packages/ccip-svm/src/encoders/evmExtraArgsV2.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts index 8a19875..c77d5d7 100644 --- a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts +++ b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts @@ -73,9 +73,4 @@ export function decodeEVMExtraArgsV2(data: Uint8Array): EVMExtraArgsV2 { gasLimit, allowOutOfOrderExecution } -} - - - - - +} \ No newline at end of file From 0670d406cc6c018bc907f87cf8a5b1ea9f7d09c2 Mon Sep 17 00:00:00 2001 From: andrejrakic Date: Tue, 23 Sep 2025 22:39:44 +0200 Subject: [PATCH 4/6] chore: refactor extraArgs tests to jest from vitest by keeping the 100% coverage. Add prettier and eslint --- .gitignore | 3 + packages/ccip-svm/.prettierignore | 2 + packages/ccip-svm/.prettierrc | 7 + packages/ccip-svm/eslint.config.js | 69 +++ packages/ccip-svm/jest.config.json | 22 + packages/ccip-svm/package.json | 19 +- .../src/__tests__/evmExtraArgsV2.test.ts | 267 ++++----- .../src/__tests__/svmExtraArgsV1.test.ts | 423 +++++--------- .../ccip-svm/src/encoders/evmExtraArgsV2.ts | 87 +-- .../ccip-svm/src/encoders/svmExtraArgsV1.ts | 208 +++---- packages/ccip-svm/src/errors/custom.ts | 550 ++++++++++-------- packages/ccip-svm/src/index.ts | 6 +- pnpm-lock.yaml | 418 +++++-------- 13 files changed, 994 insertions(+), 1087 deletions(-) create mode 100644 packages/ccip-svm/.prettierignore create mode 100644 packages/ccip-svm/.prettierrc create mode 100644 packages/ccip-svm/eslint.config.js create mode 100644 packages/ccip-svm/jest.config.json diff --git a/.gitignore b/.gitignore index 3589ad7..51819d8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ dist/ *.tsbuildinfo bin .codegpt + +# jest coverage +coverage/ \ No newline at end of file diff --git a/packages/ccip-svm/.prettierignore b/packages/ccip-svm/.prettierignore new file mode 100644 index 0000000..04c01ba --- /dev/null +++ b/packages/ccip-svm/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/packages/ccip-svm/.prettierrc b/packages/ccip-svm/.prettierrc new file mode 100644 index 0000000..87d4114 --- /dev/null +++ b/packages/ccip-svm/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "printWidth": 120 +} \ No newline at end of file diff --git a/packages/ccip-svm/eslint.config.js b/packages/ccip-svm/eslint.config.js new file mode 100644 index 0000000..a614fa1 --- /dev/null +++ b/packages/ccip-svm/eslint.config.js @@ -0,0 +1,69 @@ +import js from '@eslint/js' +import tseslint from '@typescript-eslint/eslint-plugin' +import tsparser from '@typescript-eslint/parser' +import prettier from 'eslint-plugin-prettier' +import prettierConfig from 'eslint-config-prettier' + +export default [ + js.configs.recommended, + { + files: ['**/*.{js,ts}'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + globals: { + console: 'readonly', + process: 'readonly', + Buffer: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + global: 'readonly', + module: 'readonly', + require: 'readonly', + exports: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + prettier: prettier, + }, + rules: { + ...tseslint.configs.recommended.rules, + ...prettierConfig.rules, + 'prettier/prettier': 'error', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, + }, + { + files: ['**/*.test.{js,ts}', '**/__tests__/**/*.{js,ts}'], + languageOptions: { + globals: { + describe: 'readonly', + it: 'readonly', + test: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + beforeAll: 'readonly', + afterAll: 'readonly', + jest: 'readonly', + }, + }, + }, + { + ignores: [ + 'node_modules/**', + 'dist/**', + 'coverage/**', + '*.config.js', + '*.config.json', + ], + }, +] \ No newline at end of file diff --git a/packages/ccip-svm/jest.config.json b/packages/ccip-svm/jest.config.json new file mode 100644 index 0000000..e8fdebe --- /dev/null +++ b/packages/ccip-svm/jest.config.json @@ -0,0 +1,22 @@ +{ + "preset": "ts-jest", + "testEnvironment": "node", + "testMatch": [ + "**/__tests__/**/*.test.ts" + ], + "collectCoverageFrom": [ + "src/**/*.ts", + "!src/**/*.d.ts", + "!src/__tests__/**/*" + ], + "moduleFileExtensions": [ + "ts", + "js", + "json" + ], + "transform": { + "^.+\\.ts$": "ts-jest" + }, + "workerThreads": true, + "testTimeout": 180000 +} \ No newline at end of file diff --git a/packages/ccip-svm/package.json b/packages/ccip-svm/package.json index 69c4a31..9d890c4 100644 --- a/packages/ccip-svm/package.json +++ b/packages/ccip-svm/package.json @@ -9,9 +9,11 @@ "dist" ], "scripts": { - "build": "tsc ", + "build": "tsc", "dev": "tsc -w", - "test": "vitest" + "test": "jest", + "lint": "eslint 'src/**/*.{ts,js}'", + "format": "prettier --write 'src/**/*.{ts,js,json,md}'" }, "repository": { "type": "git", @@ -32,7 +34,16 @@ "viem": "^2.37.6" }, "devDependencies": { - "typescript": "^5.6.3", - "vitest": "^3.2.4" + "@eslint/js": "^9.15.0", + "@types/jest": "^29.5.14", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.7.0", + "prettier": "^3.0.0", + "ts-jest": "^29.2.5", + "typescript": "^5.6.3" } } \ No newline at end of file diff --git a/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts index 3dd7de4..3f48083 100644 --- a/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts +++ b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts @@ -1,179 +1,110 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { - encodeEVMExtraArgsV2, - decodeEVMExtraArgsV2, - EVM_EXTRA_ARGS_V2_TAG -} from '../encoders/evmExtraArgsV2' +import { encodeEVMExtraArgsV2, decodeEVMExtraArgsV2, EVM_EXTRA_ARGS_V2_TAG } from '../encoders/evmExtraArgsV2' +import { keccak256, toBytes } from 'viem' describe('EVM Extra Args V2', () => { - let consoleSpy: ReturnType - - beforeEach(() => { - // Spy on console.warn to test warning messages - consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }) - }) - - afterEach(() => { - consoleSpy.mockRestore() + let consoleSpy: jest.SpyInstance + const evmExtraArgsV2BytesLength = 68 // 4 bytes tag + 32 bytes gasLimit + 32 bytes bool + + beforeEach(() => { + consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}) + }) + + afterEach(() => { + consoleSpy.mockRestore() + }) + + const testCases = [ + { + name: 'should encode with default allowOutOfOrderExecution=true', + input: { gasLimit: 100000n }, + expected: + '0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001', + shouldWarn: false, + }, + { + name: 'should encode with explicit allowOutOfOrderExecution=true', + input: { gasLimit: 100000n, allowOutOfOrderExecution: true }, + expected: + '0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001', + shouldWarn: false, + }, + { + name: 'should encode with allowOutOfOrderExecution=false and show warning', + input: { gasLimit: 100000n, allowOutOfOrderExecution: false }, + expected: + '0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000000', + shouldWarn: true, + }, + { + name: 'should handle token-only transfers (gasLimit = 0)', + input: { gasLimit: 0n, allowOutOfOrderExecution: true }, + expected: + '0x181dcf1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + shouldWarn: false, + }, + ] + + describe('encodeEVMExtraArgsV2', () => { + test.each(testCases)('$name', ({ input, expected, shouldWarn }) => { + const encoded = encodeEVMExtraArgsV2(input) + + if (shouldWarn) { + expect(consoleSpy).toHaveBeenCalledWith( + 'Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.', + ) + } else { + expect(consoleSpy).not.toHaveBeenCalled() + } + + expect(encoded).toBeInstanceOf(Uint8Array) + expect(encoded.length).toBe(evmExtraArgsV2BytesLength) + const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) + expect(tag).toBe(EVM_EXTRA_ARGS_V2_TAG) + expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) }) + }) - describe('encodeEVMExtraArgsV2', () => { - it('should encode EVM extra args with default allowOutOfOrderExecution=true', () => { - const original = { - gasLimit: 100000n - } - - const encoded = encodeEVMExtraArgsV2(original) - - // Should not show any warning for default behavior - expect(consoleSpy).not.toHaveBeenCalled() - - const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001"; - - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(68) // 4 bytes tag + 64 bytes ABI encoded data - expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) - }) - - it('should encode EVM extra args with explicit allowOutOfOrderExecution=true', () => { - const original = { - gasLimit: 100000n, - allowOutOfOrderExecution: true - } - - const encoded = encodeEVMExtraArgsV2(original) - - // Should not show any warning for recommended value - expect(consoleSpy).not.toHaveBeenCalled() - - const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001"; - - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(68) - expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) - }) - - it('should encode EVM extra args with allowOutOfOrderExecution=false and show warning', () => { - const original = { - gasLimit: 100000n, - allowOutOfOrderExecution: false - } - - const encoded = encodeEVMExtraArgsV2(original) - - // Should show warning when user overrides to false - expect(consoleSpy).toHaveBeenCalledWith( - `Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.` - ) - - const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000000"; - - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(68) - expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) - }) - - it('should handle token-only transfers (gasLimit = 0)', () => { - const original = { - gasLimit: 0n, - allowOutOfOrderExecution: true - } + describe('decodeEVMExtraArgsV2', () => { + test.each(testCases)('should decode EVM extra args correctly with $name', ({ input }) => { + const encoded = encodeEVMExtraArgsV2(input) + const decoded = decodeEVMExtraArgsV2(encoded) - const encoded = encodeEVMExtraArgsV2(original) - - const expected = "0x181dcf1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"; - - expect(consoleSpy).not.toHaveBeenCalled() - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(68) - expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) - }) - - it('should include correct tag at the beginning', () => { - const encoded = encodeEVMExtraArgsV2({ gasLimit: 0n }) - - // Extract first 4 bytes and convert to uint32 big-endian - const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) - expect(tag).toBe(EVM_EXTRA_ARGS_V2_TAG) - expect(tag).toBe(0x181dcf10) - }) + expect(decoded.gasLimit).toBe(input.gasLimit) + expect(decoded.allowOutOfOrderExecution).toBe(input.allowOutOfOrderExecution ?? true) }) - describe('decodeEVMExtraArgsV2', () => { - it('should decode EVM extra args correctly with default values', () => { - const original = { - gasLimit: 200000n, - allowOutOfOrderExecution: true - } - - const encoded = encodeEVMExtraArgsV2(original) - const decoded = decodeEVMExtraArgsV2(encoded) - - expect(decoded.gasLimit).toBe(original.gasLimit) - expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution) - }) - - it('should decode EVM extra args with allowOutOfOrderExecution=false', () => { - const original = { - gasLimit: 150000n, - allowOutOfOrderExecution: false - } - - const encoded = encodeEVMExtraArgsV2(original) - const decoded = decodeEVMExtraArgsV2(encoded) - - expect(decoded.gasLimit).toBe(original.gasLimit) - expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution) - }) - - it('should decode token-only transfers (gasLimit = 0)', () => { - const original = { - gasLimit: 0n, - allowOutOfOrderExecution: true - } - - const encoded = encodeEVMExtraArgsV2(original) - const decoded = decodeEVMExtraArgsV2(encoded) - - expect(decoded.gasLimit).toBe(0n) - expect(decoded.allowOutOfOrderExecution).toBe(true) - }) - - it('should throw error for data too short', () => { - const shortData = new Uint8Array(10) // Too short - - expect(() => decodeEVMExtraArgsV2(shortData)).toThrow('Invalid EVM Extra Args V2: data too short') - }) - - it('should throw error for invalid tag', () => { - const invalidData = new Uint8Array(68) - // Set wrong tag (first 4 bytes) - invalidData[0] = 0x12 - invalidData[1] = 0x34 - invalidData[2] = 0x56 - invalidData[3] = 0x78 - - expect(() => decodeEVMExtraArgsV2(invalidData)).toThrow( - 'Invalid EVM Extra Args V2 tag: expected 0x181dcf10, got 0x12345678' - ) - }) + const errorTestCases = [ + { + name: 'data too short', + data: new Uint8Array(10), + expectedError: + 'Invalid EVM Extra Args V2: data too short, expected at least 68 bytes (4 bytes tag + 32 bytes gasLimit + 32 bytes bool). Example (100_000 gasLimit and true): 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001', + }, + { + name: 'invalid tag', + data: (() => { + const invalidData = new Uint8Array(evmExtraArgsV2BytesLength) + invalidData[0] = 0x12 + invalidData[1] = 0x34 + invalidData[2] = 0x56 + invalidData[3] = 0x78 + return invalidData + })(), + expectedError: 'Invalid EVM Extra Args V2 tag: expected 0x181dcf10, got 0x12345678', + }, + ] + + test.each(errorTestCases)('should throw error for $name', ({ data, expectedError }) => { + expect(() => decodeEVMExtraArgsV2(data)).toThrow(expectedError) }) + }) - describe('round-trip encoding/decoding', () => { - it('should maintain data integrity through encode/decode cycle', () => { - const testCases = [ - { gasLimit: 0n, allowOutOfOrderExecution: true }, - { gasLimit: 200000n, allowOutOfOrderExecution: true }, - { gasLimit: 1000000n, allowOutOfOrderExecution: false } - ] - - testCases.forEach((original) => { - const encoded = encodeEVMExtraArgsV2(original) - const decoded = decodeEVMExtraArgsV2(encoded) + describe('EVM_EXTRA_ARGS_V2_TAG constant validation', () => { + it('should have correct EVM_EXTRA_ARGS_V2_TAG value matching bytes4(keccak256("CCIP EVMExtraArgsV2"))', () => { + const hash = keccak256(toBytes('CCIP EVMExtraArgsV2')) + const tagSelector = parseInt(hash.slice(0, 10), 16) // Take first 4 bytes (8 hex chars + 0x prefix) - expect(decoded.gasLimit).toBe(original.gasLimit) - expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution ?? true) - }) - }) + expect(EVM_EXTRA_ARGS_V2_TAG).toBe(tagSelector) }) -}) \ No newline at end of file + }) +}) diff --git a/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts index af21b17..2a35310 100644 --- a/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts +++ b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts @@ -1,273 +1,172 @@ -import { describe, it, expect } from 'vitest' -import { address, generateKeyPair, getAddressFromPublicKey } from '@solana/kit' -import { - encodeSVMExtraArgsV1, - decodeSVMExtraArgsV1, - SVM_EXTRA_ARGS_V1_TAG, - type Address -} from '../encoders/svmExtraArgsV1' +import { address } from '@solana/kit' +import { encodeSVMExtraArgsV1, decodeSVMExtraArgsV1, SVM_EXTRA_ARGS_V1_TAG } from '../encoders/svmExtraArgsV1' +import { keccak256, toBytes } from 'viem' -// Helper function to create a random address -async function createRandomAddress(): Promise
{ - const keyPair = await generateKeyPair() - return await getAddressFromPublicKey(keyPair.publicKey) -} +const MOCK_TOKEN_RECEIVER = address('11111111111111111111111111111112') // System Program ID (Devnet) +const MOCK_ACCOUNT_1 = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') // Token Program ID (Devnet) +const MOCK_ACCOUNT_2 = address('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') // Associated Token Program ID (Devnet) +const MOCK_ACCOUNT_3 = address('So11111111111111111111111111111111111111112') // Wrapped SOL Token Mint (Devnet) +const MOCK_ACCOUNT_4 = address('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU') // USDC Token Mint (Devnet) +const MOCK_ACCOUNT_5 = address('DzcwGnG1kM1i6zE9vR4YmzjL48mF8Eik1gC9CkJTQ7K1') // USDT Token Mint (Devnet) describe('SVM Extra Args V1', () => { - describe('encodeSVMExtraArgsV1', () => { - it('should encode SVM extra args with default values', () => { - const original = {} - - const encoded = encodeSVMExtraArgsV1(original) - - const expected = "0x1f3b3aba00000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"; - - expect(encoded).toBeInstanceOf(Uint8Array) - // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length = 53 bytes - expect(encoded.length).toBe(53) - expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) - }) - - it('should encode SVM extra args with all parameters', async () => { - const tokenReceiver = await createRandomAddress() - const account1 = await createRandomAddress() - const account2 = await createRandomAddress() - - const original = { - computeUnits: 400000, - accountIsWritableBitmap: 0b1010n, // 2nd and 4th bits set - allowOutOfOrderExecution: true, - tokenReceiver, - accounts: [account1, account2] - } - - const encoded = encodeSVMExtraArgsV1(original) - - expect(encoded).toBeInstanceOf(Uint8Array) - // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length + 64 bytes accounts = 117 bytes - expect(encoded.length).toBe(117) - }) - - it('should encode minimal SVM extra args', () => { - const original = { - computeUnits: 0, - accountIsWritableBitmap: 0n, - allowOutOfOrderExecution: false, - accounts: [] - } - - const encoded = encodeSVMExtraArgsV1(original) - - const expected = "0x1f3b3aba00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(53) // Same as default since no accounts - expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) - }) - - it('should include correct tag at the beginning', () => { - const encoded = encodeSVMExtraArgsV1({}) - - // Extract first 4 bytes and convert to uint32 big-endian - const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) - expect(tag).toBe(SVM_EXTRA_ARGS_V1_TAG) - expect(tag).toBe(0x1f3b3aba) - }) - - it('should encode with many accounts', async () => { - const accounts = await Promise.all(Array.from({ length: 5 }, () => createRandomAddress())) - const original = { - accounts - } - - const encoded = encodeSVMExtraArgsV1(original) - - expect(encoded).toBeInstanceOf(Uint8Array) - // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length + (5 * 32) bytes accounts = 213 bytes - expect(encoded.length).toBe(213) - }) + const svmExtraArgsV1MinBytesLength = 4 + 4 + 8 + 1 + 32 + 4 // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length + + const testCases = [ + { + name: 'should encode with default values', + input: {}, + expectedLength: svmExtraArgsV1MinBytesLength, + expectedHex: + '0x1f3b3aba00000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000', + }, + { + name: 'should encode minimal SVM extra args', + input: { + computeUnits: 0, + accountIsWritableBitmap: 0n, + allowOutOfOrderExecution: false, + accounts: [], + }, + expectedLength: svmExtraArgsV1MinBytesLength, + expectedHex: + '0x1f3b3aba00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + }, + { + name: 'should handle token-only transfers (computeUnits = 0)', + input: { computeUnits: 0, allowOutOfOrderExecution: true }, + expectedLength: svmExtraArgsV1MinBytesLength, + }, + { + name: 'should encode SVM extra args with all parameters', + input: { + computeUnits: 400000, + accountIsWritableBitmap: 0b1010n, + allowOutOfOrderExecution: true, + tokenReceiver: MOCK_TOKEN_RECEIVER, + accounts: [MOCK_ACCOUNT_1, MOCK_ACCOUNT_2], + }, + expectedLength: svmExtraArgsV1MinBytesLength + 2 * 32, // 53 + 64 bytes for 2 accounts + }, + { + name: 'should encode with many accounts', + input: { + accounts: [MOCK_ACCOUNT_1, MOCK_ACCOUNT_2, MOCK_ACCOUNT_3, MOCK_ACCOUNT_4, MOCK_ACCOUNT_5], + }, + expectedLength: svmExtraArgsV1MinBytesLength + 5 * 32, // 53 + 160 bytes for 5 accounts + }, + ] + + describe('encodeSVMExtraArgsV1', () => { + test.each(testCases)('$name', ({ input, expectedLength, expectedHex }) => { + const encoded = encodeSVMExtraArgsV1(input) + + expect(encoded).toBeInstanceOf(Uint8Array) + expect(encoded.length).toBe(expectedLength) + + const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) + expect(tag).toBe(SVM_EXTRA_ARGS_V1_TAG) + + if (expectedHex) { + expect(encoded).toEqual(new Uint8Array(Buffer.from(expectedHex.slice(2), 'hex'))) + } }) - - describe('decodeSVMExtraArgsV1', () => { - it('should decode SVM extra args with default values', () => { - const original = { - computeUnits: 0, - accountIsWritableBitmap: 0n, - allowOutOfOrderExecution: true, - accounts: [] - } - - const encoded = encodeSVMExtraArgsV1(original) - const decoded = decodeSVMExtraArgsV1(encoded) - - expect(decoded.computeUnits).toBe(0) - expect(decoded.accountIsWritableBitmap).toBe(0n) - expect(decoded.allowOutOfOrderExecution).toBe(true) - expect(decoded.accounts).toHaveLength(0) - expect(typeof decoded.tokenReceiver).toBe('string') // Address is a string type - }) - - it('should decode SVM extra args with all parameters', async () => { - const tokenReceiver = await createRandomAddress() - const account1 = await createRandomAddress() - const account2 = await createRandomAddress() - - const original = { - computeUnits: 400000, - accountIsWritableBitmap: 0b1010n, // 2nd and 4th bits set - allowOutOfOrderExecution: false, - tokenReceiver, - accounts: [account1, account2] - } - - const encoded = encodeSVMExtraArgsV1(original) - const decoded = decodeSVMExtraArgsV1(encoded) - - expect(decoded.computeUnits).toBe(original.computeUnits) - expect(decoded.accountIsWritableBitmap).toBe(original.accountIsWritableBitmap) - expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution) - expect(decoded.tokenReceiver?.toString()).toBe(original.tokenReceiver.toString()) - expect(decoded.accounts?.length).toBe(2) - expect(decoded.accounts?.[0].toString()).toBe(account1.toString()) - expect(decoded.accounts?.[1].toString()).toBe(account2.toString()) - }) - - it('should decode with many accounts', async () => { - const accounts = await Promise.all(Array.from({ length: 5 }, () => createRandomAddress())) - const original = { - computeUnits: 123456, - accounts - } - - const encoded = encodeSVMExtraArgsV1(original) - const decoded = decodeSVMExtraArgsV1(encoded) - - expect(decoded.computeUnits).toBe(original.computeUnits) - expect(decoded.accounts?.length).toBe(5) - accounts.forEach((account, index) => { - expect(decoded.accounts?.[index].toString()).toBe(account.toString()) - }) - }) - - it('should throw error for data too short', () => { - const shortData = new Uint8Array(10) // Too short - - expect(() => decodeSVMExtraArgsV1(shortData)).toThrow('Invalid SVM Extra Args V1: data too short') - }) - - it('should throw error for invalid tag', () => { - const invalidData = new Uint8Array(53) - // Set wrong tag (first 4 bytes) - invalidData[0] = 0x12 - invalidData[1] = 0x34 - invalidData[2] = 0x56 - invalidData[3] = 0x78 - - expect(() => decodeSVMExtraArgsV1(invalidData)).toThrow( - 'Invalid SVM Extra Args V1 tag: expected 0x1f3b3aba, got 0x12345678' - ) + }) + + describe('decodeSVMExtraArgsV1', () => { + test.each(testCases)('should decode SVM extra args correctly with $name', ({ input }) => { + const encoded = encodeSVMExtraArgsV1(input) + const decoded = decodeSVMExtraArgsV1(encoded) + + expect(decoded.computeUnits).toBe(input.computeUnits ?? 0) + expect(decoded.accountIsWritableBitmap).toBe(input.accountIsWritableBitmap ?? 0n) + expect(decoded.allowOutOfOrderExecution).toBe(input.allowOutOfOrderExecution ?? true) + expect(decoded.accounts?.length).toBe(input.accounts?.length ?? 0) + expect(typeof decoded.tokenReceiver).toBe('string') + + if (input.tokenReceiver) { + expect(decoded.tokenReceiver?.toString()).toBe(input.tokenReceiver.toString()) + } + if (input.accounts) { + input.accounts.forEach((account, index) => { + expect(decoded.accounts?.[index].toString()).toBe(account.toString()) }) + } }) - describe('round-trip encoding/decoding', () => { - it('should maintain data integrity through encode/decode cycle', async () => { - const tokenReceiver = await createRandomAddress() - const accounts = await Promise.all([createRandomAddress(), createRandomAddress(), createRandomAddress()]) - - const testCases = [ - // Minimal case - { - computeUnits: 0, - accountIsWritableBitmap: 0n, - allowOutOfOrderExecution: true, - accounts: [] - }, - // Full case - { - computeUnits: 500000, - accountIsWritableBitmap: 0b11110000n, - allowOutOfOrderExecution: false, - tokenReceiver, - accounts - }, - // Edge values - { - computeUnits: 4294967295, // max uint32 - accountIsWritableBitmap: 18446744073709551615n, // max uint64 - allowOutOfOrderExecution: true, - accounts: [await createRandomAddress()] - }, - // Default values - {} - ] - - testCases.forEach((original, index) => { - const encoded = encodeSVMExtraArgsV1(original) - const decoded = decodeSVMExtraArgsV1(encoded) - - expect(decoded.computeUnits).toBe(original.computeUnits ?? 0) - expect(decoded.accountIsWritableBitmap).toBe(original.accountIsWritableBitmap ?? 0n) - expect(decoded.allowOutOfOrderExecution).toBe(original.allowOutOfOrderExecution ?? true) - - if (original.tokenReceiver) { - expect(decoded.tokenReceiver?.toString()).toBe(original.tokenReceiver.toString()) - } else { - expect(typeof decoded.tokenReceiver).toBe('string') // Address is a string - } - - expect(decoded.accounts?.length).toBe(original.accounts?.length ?? 0) - original.accounts?.forEach((account, accountIndex) => { - expect(decoded.accounts?.[accountIndex].toString()).toBe(account.toString()) - }) - }) - }) + const errorTestCases = [ + { + name: 'data too short', + data: new Uint8Array(10), + expectedError: + 'Invalid SVM Extra Args V1: data too short, expected at least 53 bytes (4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length)', + }, + { + name: 'invalid tag', + data: (() => { + const invalidData = new Uint8Array(svmExtraArgsV1MinBytesLength) + invalidData[0] = 0x12 + invalidData[1] = 0x34 + invalidData[2] = 0x56 + invalidData[3] = 0x78 + return invalidData + })(), + expectedError: 'Invalid SVM Extra Args V1 tag: expected 0x1f3b3aba, got 0x12345678', + }, + ] + + test.each(errorTestCases)('should throw error for $name', ({ data, expectedError }) => { + expect(() => decodeSVMExtraArgsV1(data)).toThrow(expectedError) }) + }) + + describe('serialization format validation', () => { + it('should serialize fields in correct Borsh-like format', () => { + const input = { + computeUnits: 0x12345678, + accountIsWritableBitmap: 0x123456789abcdef0n, + allowOutOfOrderExecution: false, + tokenReceiver: MOCK_TOKEN_RECEIVER, + accounts: [MOCK_ACCOUNT_1], + } + + const encoded = encodeSVMExtraArgsV1(input) + + // Verify tag (big-endian) + expect(encoded[0]).toBe(0x1f) + expect(encoded[1]).toBe(0x3b) + expect(encoded[2]).toBe(0x3a) + expect(encoded[3]).toBe(0xba) + + // Verify computeUnits (little-endian u32) + expect(encoded[4]).toBe(0x78) + expect(encoded[5]).toBe(0x56) + expect(encoded[6]).toBe(0x34) + expect(encoded[7]).toBe(0x12) + + // Verify accountIsWritableBitmap (little-endian u64) + expect(encoded[8]).toBe(0xf0) + expect(encoded[9]).toBe(0xde) + expect(encoded[10]).toBe(0xbc) + expect(encoded[11]).toBe(0x9a) + + // Verify allowOutOfOrderExecution (bool) + expect(encoded[16]).toBe(0x00) // false + + // Verify accounts length (little-endian u32) + expect(encoded[49]).toBe(0x01) // 1 account + expect(encoded[50]).toBe(0x00) + expect(encoded[51]).toBe(0x00) + expect(encoded[52]).toBe(0x00) + }) + }) - describe('specific serialization format validation', () => { - it('should serialize fields in correct Borsh-like format', () => { - const tokenReceiver = address('11111111111111111111111111111112') // System program - const account = address('11111111111111111111111111111112') - - const original = { - computeUnits: 0x12345678, // Will be serialized as little-endian - accountIsWritableBitmap: 0x123456789abcdef0n, // Will be serialized as little-endian - allowOutOfOrderExecution: false, // Will be serialized as 0 - tokenReceiver, - accounts: [account] - } - - const encoded = encodeSVMExtraArgsV1(original) - - // Verify tag (big-endian) - expect(encoded[0]).toBe(0x1f) - expect(encoded[1]).toBe(0x3b) - expect(encoded[2]).toBe(0x3a) - expect(encoded[3]).toBe(0xba) - - // Verify computeUnits (little-endian u32) - expect(encoded[4]).toBe(0x78) - expect(encoded[5]).toBe(0x56) - expect(encoded[6]).toBe(0x34) - expect(encoded[7]).toBe(0x12) - - // Verify accountIsWritableBitmap (little-endian u64) - expect(encoded[8]).toBe(0xf0) - expect(encoded[9]).toBe(0xde) - expect(encoded[10]).toBe(0xbc) - expect(encoded[11]).toBe(0x9a) - expect(encoded[12]).toBe(0x78) - expect(encoded[13]).toBe(0x56) - expect(encoded[14]).toBe(0x34) - expect(encoded[15]).toBe(0x12) - - // Verify allowOutOfOrderExecution (bool) - expect(encoded[16]).toBe(0x00) // false + describe('SVM_EXTRA_ARGS_V1_TAG constant validation', () => { + it('should have correct SVM_EXTRA_ARGS_V1_TAG value matching bytes4(keccak256("CCIP SVMExtraArgsV1"))', () => { + const hash = keccak256(toBytes('CCIP SVMExtraArgsV1')) + const tagSelector = parseInt(hash.slice(0, 10), 16) // Take first 4 bytes (8 hex chars + 0x prefix) - // Verify accounts length (little-endian u32) - expect(encoded[49]).toBe(0x01) // 1 account - expect(encoded[50]).toBe(0x00) - expect(encoded[51]).toBe(0x00) - expect(encoded[52]).toBe(0x00) - }) + expect(SVM_EXTRA_ARGS_V1_TAG).toBe(tagSelector) }) -}) \ No newline at end of file + }) +}) diff --git a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts index c77d5d7..3fd8f32 100644 --- a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts +++ b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts @@ -6,8 +6,8 @@ import { encodeAbiParameters, parseAbiParameters, decodeAbiParameters } from 'vi export const EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10 export interface EVMExtraArgsV2 { - gasLimit: bigint - allowOutOfOrderExecution?: boolean + gasLimit: bigint + allowOutOfOrderExecution?: boolean } /** @@ -16,32 +16,27 @@ export interface EVMExtraArgsV2 { * @param allowOutOfOrderExecution - Whether messages can be executed out of order (default: true) * @returns Properly encoded extraArgs as Uint8Array */ -export function encodeEVMExtraArgsV2({ - gasLimit, - allowOutOfOrderExecution = true -}: EVMExtraArgsV2): Uint8Array { - if (allowOutOfOrderExecution === false) { - console.warn( - `Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.` - ) - } +export function encodeEVMExtraArgsV2({ gasLimit, allowOutOfOrderExecution = true }: EVMExtraArgsV2): Uint8Array { + if (allowOutOfOrderExecution === false) { + console.warn(`Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.`) + } - // bytes4(keccak256("CCIP EVMExtraArgsV2")) = 0x181dcf10 - const tag = new Uint8Array([0x18, 0x1d, 0xcf, 0x10]) + // bytes4(keccak256("CCIP EVMExtraArgsV2")) = 0x181dcf10 + const tag = new Uint8Array([0x18, 0x1d, 0xcf, 0x10]) - const encodedExtraArgs = encodeAbiParameters( - parseAbiParameters('uint128 gasLimit, bool allowOutOfOrderExecution'), - [gasLimit, allowOutOfOrderExecution] - ) + const encodedExtraArgs = encodeAbiParameters(parseAbiParameters('uint128 gasLimit, bool allowOutOfOrderExecution'), [ + gasLimit, + allowOutOfOrderExecution, + ]) - const encodedExtraArgsBytes = new Uint8Array(Buffer.from(encodedExtraArgs.slice(2), 'hex')) + const encodedExtraArgsBytes = new Uint8Array(Buffer.from(encodedExtraArgs.slice(2), 'hex')) - // abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs) - const result = new Uint8Array(tag.length + encodedExtraArgsBytes.length) - result.set(tag, 0) - result.set(encodedExtraArgsBytes, tag.length) + // abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs) + const result = new Uint8Array(tag.length + encodedExtraArgsBytes.length) + result.set(tag, 0) + result.set(encodedExtraArgsBytes, tag.length) - return result + return result } /** @@ -50,27 +45,33 @@ export function encodeEVMExtraArgsV2({ * @returns Decoded EVMExtraArgsV2 object */ export function decodeEVMExtraArgsV2(data: Uint8Array): EVMExtraArgsV2 { - // 4 bytes tag + 32 bytes gasLimit + 32 bytes bool = 68 bytes - // For example (100_000 gasLimit and true), 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001 - if (data.length < 68) { - throw new Error('Invalid EVM Extra Args V2: data too short') - } + if (data.length < 68) { + throw new Error( + 'Invalid EVM Extra Args V2: data too short, expected at least 68 bytes (4 bytes tag + 32 bytes gasLimit + 32 bytes bool). Example (100_000 gasLimit and true): 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001', + ) + } - const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, false) // big endian - if (tag !== EVM_EXTRA_ARGS_V2_TAG) { - throw new Error(`Invalid EVM Extra Args V2 tag: expected 0x${EVM_EXTRA_ARGS_V2_TAG.toString(16)}, got 0x${tag.toString(16)}`) - } + const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, false) // big endian + if (tag !== EVM_EXTRA_ARGS_V2_TAG) { + throw new Error( + `Invalid EVM Extra Args V2 tag: expected 0x${EVM_EXTRA_ARGS_V2_TAG.toString(16)}, got 0x${tag.toString(16)}`, + ) + } - const extraArgsData = data.slice(4) - const extraArgsHex = '0x' + Array.from(extraArgsData).map(b => b.toString(16).padStart(2, '0')).join('') + const extraArgsData = data.slice(4) + const extraArgsHex = + '0x' + + Array.from(extraArgsData) + .map((b) => b.toString(16).padStart(2, '0')) + .join('') - const [gasLimit, allowOutOfOrderExecution] = decodeAbiParameters( - parseAbiParameters('uint128 gasLimit, bool allowOutOfOrderExecution'), - extraArgsHex as `0x${string}` - ) + const [gasLimit, allowOutOfOrderExecution] = decodeAbiParameters( + parseAbiParameters('uint128 gasLimit, bool allowOutOfOrderExecution'), + extraArgsHex as `0x${string}`, + ) - return { - gasLimit, - allowOutOfOrderExecution - } -} \ No newline at end of file + return { + gasLimit, + allowOutOfOrderExecution, + } +} diff --git a/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts index 1c6cc31..f0526f2 100644 --- a/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts +++ b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts @@ -1,4 +1,4 @@ -import { address, getAddressEncoder, getAddressDecoder, getAddressFromPublicKey } from '@solana/kit' +import { address, getAddressEncoder, getAddressDecoder } from '@solana/kit' /** * SVM Extra Args V1 tag: bytes4(keccak256("CCIP SVMExtraArgsV1")) @@ -8,11 +8,11 @@ export const SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba export type Address = ReturnType export interface SVMExtraArgsV1 { - computeUnits?: number - accountIsWritableBitmap?: bigint - allowOutOfOrderExecution?: boolean - tokenReceiver?: Address - accounts?: Address[] + computeUnits?: number + accountIsWritableBitmap?: bigint + allowOutOfOrderExecution?: boolean + tokenReceiver?: Address + accounts?: Address[] } /** @@ -21,56 +21,54 @@ export interface SVMExtraArgsV1 { * @returns Properly encoded extraArgs as Uint8Array */ export function encodeSVMExtraArgsV1({ - computeUnits = 0, - accountIsWritableBitmap = 0n, - allowOutOfOrderExecution = true, - tokenReceiver, - accounts = [] + computeUnits = 0, + accountIsWritableBitmap = 0n, + allowOutOfOrderExecution = true, + tokenReceiver, + accounts = [], }: SVMExtraArgsV1): Uint8Array { - // bytes4(keccak256("CCIP SVMExtraArgsV1")) = 0x1f3b3aba - const tag = new Uint8Array([0x1f, 0x3b, 0x3a, 0xba]) - - // Prepare data for Borsh-like serialization - const buffer: number[] = [] - - // Serialize compute_units (u32, little-endian) - const computeUnitsBytes = new Uint8Array(4) - new DataView(computeUnitsBytes.buffer).setUint32(0, computeUnits, true) - buffer.push(...computeUnitsBytes) - - // Serialize account_is_writable_bitmap (u64, little-endian) - const bitmapBytes = new Uint8Array(8) - new DataView(bitmapBytes.buffer).setBigUint64(0, accountIsWritableBitmap, true) - buffer.push(...bitmapBytes) - - // Serialize allow_out_of_order_execution (bool, 1 byte) - buffer.push(allowOutOfOrderExecution ? 1 : 0) - - // Serialize token_receiver ([u8; 32]) - const encoder = getAddressEncoder() - const tokenReceiverBytes = tokenReceiver - ? encoder.encode(tokenReceiver) - : new Uint8Array(32) // Default/zero address - buffer.push(...tokenReceiverBytes) - - // Serialize accounts (Vec<[u8; 32]>) - // First, serialize the length of the vector as u32 little-endian - const accountsLengthBytes = new Uint8Array(4) - new DataView(accountsLengthBytes.buffer).setUint32(0, accounts.length, true) - buffer.push(...accountsLengthBytes) - - // Then serialize each account (32 bytes each) - for (const account of accounts) { - buffer.push(...encoder.encode(account)) - } - - // Combine tag + serialized data - const serializedData = new Uint8Array(buffer) - const result = new Uint8Array(tag.length + serializedData.length) - result.set(tag, 0) - result.set(serializedData, tag.length) - - return result + // bytes4(keccak256("CCIP SVMExtraArgsV1")) = 0x1f3b3aba + const tag = new Uint8Array([0x1f, 0x3b, 0x3a, 0xba]) + + // Prepare data for Borsh-like serialization + const buffer: number[] = [] + + // Serialize compute_units (u32, little-endian) + const computeUnitsBytes = new Uint8Array(4) + new DataView(computeUnitsBytes.buffer).setUint32(0, computeUnits, true) + buffer.push(...computeUnitsBytes) + + // Serialize account_is_writable_bitmap (u64, little-endian) + const bitmapBytes = new Uint8Array(8) + new DataView(bitmapBytes.buffer).setBigUint64(0, accountIsWritableBitmap, true) + buffer.push(...bitmapBytes) + + // Serialize allow_out_of_order_execution (bool, 1 byte) + buffer.push(allowOutOfOrderExecution ? 1 : 0) + + // Serialize token_receiver ([u8; 32]) + const encoder = getAddressEncoder() + const tokenReceiverBytes = tokenReceiver ? encoder.encode(tokenReceiver) : new Uint8Array(32) // Default/zero address + buffer.push(...tokenReceiverBytes) + + // Serialize accounts (Vec<[u8; 32]>) + // First, serialize the length of the vector as u32 little-endian + const accountsLengthBytes = new Uint8Array(4) + new DataView(accountsLengthBytes.buffer).setUint32(0, accounts.length, true) + buffer.push(...accountsLengthBytes) + + // Then serialize each account (32 bytes each) + for (const account of accounts) { + buffer.push(...encoder.encode(account)) + } + + // Combine tag + serialized data + const serializedData = new Uint8Array(buffer) + const result = new Uint8Array(tag.length + serializedData.length) + result.set(tag, 0) + result.set(serializedData, tag.length) + + return result } /** @@ -79,53 +77,57 @@ export function encodeSVMExtraArgsV1({ * @returns Decoded SVMExtraArgsV1 object */ export function decodeSVMExtraArgsV1(data: Uint8Array): SVMExtraArgsV1 { - // Check minimum length (4 bytes tag + minimum data) - if (data.length < 4 + 4 + 8 + 1 + 32 + 4) { // tag + computeUnits + bitmap + bool + tokenReceiver + accounts length - throw new Error('Invalid SVM Extra Args V1: data too short') - } - - // Verify tag - const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, false) // big endian - if (tag !== SVM_EXTRA_ARGS_V1_TAG) { - throw new Error(`Invalid SVM Extra Args V1 tag: expected 0x${SVM_EXTRA_ARGS_V1_TAG.toString(16)}, got 0x${tag.toString(16)}`) - } - - let offset = 4 // Skip tag - - // Deserialize compute_units (u32, little-endian) - const computeUnits = new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true) - offset += 4 - - // Deserialize account_is_writable_bitmap (u64, little-endian) - const accountIsWritableBitmap = new DataView(data.buffer, data.byteOffset + offset, 8).getBigUint64(0, true) - offset += 8 - - // Deserialize allow_out_of_order_execution (bool) - const allowOutOfOrderExecution = data[offset] === 1 - offset += 1 - - // Deserialize token_receiver ([u8; 32]) - const tokenReceiverBytes = data.slice(offset, offset + 32) - const decoder = getAddressDecoder() - const tokenReceiver = decoder.decode(tokenReceiverBytes) + // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length + if (data.length < 4 + 4 + 8 + 1 + 32 + 4) { + throw new Error( + 'Invalid SVM Extra Args V1: data too short, expected at least 53 bytes (4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length)', + ) + } + + // Verify tag + const tag = new DataView(data.buffer, data.byteOffset, 4).getUint32(0, false) // big endian + if (tag !== SVM_EXTRA_ARGS_V1_TAG) { + throw new Error( + `Invalid SVM Extra Args V1 tag: expected 0x${SVM_EXTRA_ARGS_V1_TAG.toString(16)}, got 0x${tag.toString(16)}`, + ) + } + + let offset = 4 // Skip tag + + // Deserialize compute_units (u32, little-endian) + const computeUnits = new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true) + offset += 4 + + // Deserialize account_is_writable_bitmap (u64, little-endian) + const accountIsWritableBitmap = new DataView(data.buffer, data.byteOffset + offset, 8).getBigUint64(0, true) + offset += 8 + + // Deserialize allow_out_of_order_execution (bool) + const allowOutOfOrderExecution = data[offset] === 1 + offset += 1 + + // Deserialize token_receiver ([u8; 32]) + const tokenReceiverBytes = data.slice(offset, offset + 32) + const decoder = getAddressDecoder() + const tokenReceiver = decoder.decode(tokenReceiverBytes) + offset += 32 + + // Deserialize accounts (Vec<[u8; 32]>) + const accountsLength = new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true) + offset += 4 + + const accounts: Address[] = [] + for (let i = 0; i < accountsLength; i++) { + const accountBytes = data.slice(offset, offset + 32) + accounts.push(decoder.decode(accountBytes)) offset += 32 + } - // Deserialize accounts (Vec<[u8; 32]>) - const accountsLength = new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true) - offset += 4 - - const accounts: Address[] = [] - for (let i = 0; i < accountsLength; i++) { - const accountBytes = data.slice(offset, offset + 32) - accounts.push(decoder.decode(accountBytes)) - offset += 32 - } - - return { - computeUnits, - accountIsWritableBitmap, - allowOutOfOrderExecution, - tokenReceiver, - accounts - } -} \ No newline at end of file + return { + computeUnits, + accountIsWritableBitmap, + allowOutOfOrderExecution, + tokenReceiver, + accounts, + } +} diff --git a/packages/ccip-svm/src/errors/custom.ts b/packages/ccip-svm/src/errors/custom.ts index a0fb6f2..575b4b4 100644 --- a/packages/ccip-svm/src/errors/custom.ts +++ b/packages/ccip-svm/src/errors/custom.ts @@ -1,296 +1,392 @@ export type CustomError = - | Unauthorized - | InvalidRMNRemoteAddress - | InvalidInputsMint - | InvalidVersion - | FeeTokenMismatch - | RedundantOwnerProposal - | ReachedMaxSequenceNumber - | InvalidInputsTokenIndices - | InvalidInputsPoolAccounts - | InvalidInputsTokenAccounts - | InvalidInputsTokenAdminRegistryAccounts - | InvalidInputsLookupTableAccounts - | InvalidInputsLookupTableAccountWritable - | InvalidInputsTokenAmount - | InvalidInputsTransferAllAmount - | InvalidInputsAtaAddress - | InvalidInputsAtaWritable - | InvalidInputsChainSelector - | InsufficientLamports - | InsufficientFunds - | SourceTokenDataTooLarge - | InvalidTokenAdminRegistryInputsZeroAddress - | InvalidTokenAdminRegistryProposedAdmin - | SenderNotAllowed - | InvalidCodeVersion - | InvalidCcipVersionRollback - | InvalidAccountListForPdaDerivation - | InvalidDerivationStage - | InvalidNonceVersion - | InvalidTokenPoolAccountDerivationResponse - | AccountDerivationResponseTooLarge - | DefaultOwnerProposal; + | Unauthorized + | InvalidRMNRemoteAddress + | InvalidInputsMint + | InvalidVersion + | FeeTokenMismatch + | RedundantOwnerProposal + | ReachedMaxSequenceNumber + | InvalidInputsTokenIndices + | InvalidInputsPoolAccounts + | InvalidInputsTokenAccounts + | InvalidInputsTokenAdminRegistryAccounts + | InvalidInputsLookupTableAccounts + | InvalidInputsLookupTableAccountWritable + | InvalidInputsTokenAmount + | InvalidInputsTransferAllAmount + | InvalidInputsAtaAddress + | InvalidInputsAtaWritable + | InvalidInputsChainSelector + | InsufficientLamports + | InsufficientFunds + | SourceTokenDataTooLarge + | InvalidTokenAdminRegistryInputsZeroAddress + | InvalidTokenAdminRegistryProposedAdmin + | SenderNotAllowed + | InvalidCodeVersion + | InvalidCcipVersionRollback + | InvalidAccountListForPdaDerivation + | InvalidDerivationStage + | InvalidNonceVersion + | InvalidTokenPoolAccountDerivationResponse + | AccountDerivationResponseTooLarge + | DefaultOwnerProposal export class Unauthorized extends Error { - static readonly code = 7000; - readonly code = 7000; - readonly name = 'Unauthorized'; - readonly msg = 'The signer is unauthorized'; - constructor(readonly logs?: string[]) { super('7000: The signer is unauthorized'); } + static readonly code = 7000 + readonly code = 7000 + readonly name = 'Unauthorized' + readonly msg = 'The signer is unauthorized' + constructor(readonly logs?: string[]) { + super('7000: The signer is unauthorized') + } } export class InvalidRMNRemoteAddress extends Error { - static readonly code = 7001; - readonly code = 7001; - readonly name = 'InvalidRMNRemoteAddress'; - readonly msg = 'Invalid RMN Remote Address'; - constructor(readonly logs?: string[]) { super('7001: Invalid RMN Remote Address'); } + static readonly code = 7001 + readonly code = 7001 + readonly name = 'InvalidRMNRemoteAddress' + readonly msg = 'Invalid RMN Remote Address' + constructor(readonly logs?: string[]) { + super('7001: Invalid RMN Remote Address') + } } export class InvalidInputsMint extends Error { - static readonly code = 7002; - readonly code = 7002; - readonly name = 'InvalidInputsMint'; - readonly msg = 'Mint account input is invalid'; - constructor(readonly logs?: string[]) { super('7002: Mint account input is invalid'); } + static readonly code = 7002 + readonly code = 7002 + readonly name = 'InvalidInputsMint' + readonly msg = 'Mint account input is invalid' + constructor(readonly logs?: string[]) { + super('7002: Mint account input is invalid') + } } export class InvalidVersion extends Error { - static readonly code = 7003; - readonly code = 7003; - readonly name = 'InvalidVersion'; - readonly msg = 'Invalid version of the onchain state'; - constructor(readonly logs?: string[]) { super('7003: Invalid version of the onchain state'); } + static readonly code = 7003 + readonly code = 7003 + readonly name = 'InvalidVersion' + readonly msg = 'Invalid version of the onchain state' + constructor(readonly logs?: string[]) { + super('7003: Invalid version of the onchain state') + } } export class FeeTokenMismatch extends Error { - static readonly code = 7004; - readonly code = 7004; - readonly name = 'FeeTokenMismatch'; - readonly msg = "Fee token doesn't match transfer token"; - constructor(readonly logs?: string[]) { super("7004: Fee token doesn't match transfer token"); } + static readonly code = 7004 + readonly code = 7004 + readonly name = 'FeeTokenMismatch' + readonly msg = "Fee token doesn't match transfer token" + constructor(readonly logs?: string[]) { + super("7004: Fee token doesn't match transfer token") + } } export class RedundantOwnerProposal extends Error { - static readonly code = 7005; - readonly code = 7005; - readonly name = 'RedundantOwnerProposal'; - readonly msg = 'Proposed owner is the current owner'; - constructor(readonly logs?: string[]) { super('7005: Proposed owner is the current owner'); } + static readonly code = 7005 + readonly code = 7005 + readonly name = 'RedundantOwnerProposal' + readonly msg = 'Proposed owner is the current owner' + constructor(readonly logs?: string[]) { + super('7005: Proposed owner is the current owner') + } } export class ReachedMaxSequenceNumber extends Error { - static readonly code = 7006; - readonly code = 7006; - readonly name = 'ReachedMaxSequenceNumber'; - readonly msg = 'Reached max sequence number'; - constructor(readonly logs?: string[]) { super('7006: Reached max sequence number'); } + static readonly code = 7006 + readonly code = 7006 + readonly name = 'ReachedMaxSequenceNumber' + readonly msg = 'Reached max sequence number' + constructor(readonly logs?: string[]) { + super('7006: Reached max sequence number') + } } export class InvalidInputsTokenIndices extends Error { - static readonly code = 7007; - readonly code = 7007; - readonly name = 'InvalidInputsTokenIndices'; - readonly msg = 'Invalid pool account indices'; - constructor(readonly logs?: string[]) { super('7007: Invalid pool account indices'); } + static readonly code = 7007 + readonly code = 7007 + readonly name = 'InvalidInputsTokenIndices' + readonly msg = 'Invalid pool account indices' + constructor(readonly logs?: string[]) { + super('7007: Invalid pool account indices') + } } export class InvalidInputsPoolAccounts extends Error { - static readonly code = 7008; - readonly code = 7008; - readonly name = 'InvalidInputsPoolAccounts'; - readonly msg = 'Invalid pool accounts'; - constructor(readonly logs?: string[]) { super('7008: Invalid pool accounts'); } + static readonly code = 7008 + readonly code = 7008 + readonly name = 'InvalidInputsPoolAccounts' + readonly msg = 'Invalid pool accounts' + constructor(readonly logs?: string[]) { + super('7008: Invalid pool accounts') + } } export class InvalidInputsTokenAccounts extends Error { - static readonly code = 7009; - readonly code = 7009; - readonly name = 'InvalidInputsTokenAccounts'; - readonly msg = 'Invalid token accounts'; - constructor(readonly logs?: string[]) { super('7009: Invalid token accounts'); } + static readonly code = 7009 + readonly code = 7009 + readonly name = 'InvalidInputsTokenAccounts' + readonly msg = 'Invalid token accounts' + constructor(readonly logs?: string[]) { + super('7009: Invalid token accounts') + } } export class InvalidInputsTokenAdminRegistryAccounts extends Error { - static readonly code = 7010; - readonly code = 7010; - readonly name = 'InvalidInputsTokenAdminRegistryAccounts'; - readonly msg = 'Invalid Token Admin Registry account'; - constructor(readonly logs?: string[]) { super('7010: Invalid Token Admin Registry account'); } + static readonly code = 7010 + readonly code = 7010 + readonly name = 'InvalidInputsTokenAdminRegistryAccounts' + readonly msg = 'Invalid Token Admin Registry account' + constructor(readonly logs?: string[]) { + super('7010: Invalid Token Admin Registry account') + } } export class InvalidInputsLookupTableAccounts extends Error { - static readonly code = 7011; - readonly code = 7011; - readonly name = 'InvalidInputsLookupTableAccounts'; - readonly msg = 'Invalid LookupTable account'; - constructor(readonly logs?: string[]) { super('7011: Invalid LookupTable account'); } + static readonly code = 7011 + readonly code = 7011 + readonly name = 'InvalidInputsLookupTableAccounts' + readonly msg = 'Invalid LookupTable account' + constructor(readonly logs?: string[]) { + super('7011: Invalid LookupTable account') + } } export class InvalidInputsLookupTableAccountWritable extends Error { - static readonly code = 7012; - readonly code = 7012; - readonly name = 'InvalidInputsLookupTableAccountWritable'; - readonly msg = 'Invalid LookupTable account writable access'; - constructor(readonly logs?: string[]) { super('7012: Invalid LookupTable account writable access'); } + static readonly code = 7012 + readonly code = 7012 + readonly name = 'InvalidInputsLookupTableAccountWritable' + readonly msg = 'Invalid LookupTable account writable access' + constructor(readonly logs?: string[]) { + super('7012: Invalid LookupTable account writable access') + } } export class InvalidInputsTokenAmount extends Error { - static readonly code = 7013; - readonly code = 7013; - readonly name = 'InvalidInputsTokenAmount'; - readonly msg = 'Cannot send zero tokens'; - constructor(readonly logs?: string[]) { super('7013: Cannot send zero tokens'); } + static readonly code = 7013 + readonly code = 7013 + readonly name = 'InvalidInputsTokenAmount' + readonly msg = 'Cannot send zero tokens' + constructor(readonly logs?: string[]) { + super('7013: Cannot send zero tokens') + } } export class InvalidInputsTransferAllAmount extends Error { - static readonly code = 7014; - readonly code = 7014; - readonly name = 'InvalidInputsTransferAllAmount'; - readonly msg = 'Must specify zero amount to send alongside transfer_all'; - constructor(readonly logs?: string[]) { super('7014: Must specify zero amount to send alongside transfer_all'); } + static readonly code = 7014 + readonly code = 7014 + readonly name = 'InvalidInputsTransferAllAmount' + readonly msg = 'Must specify zero amount to send alongside transfer_all' + constructor(readonly logs?: string[]) { + super('7014: Must specify zero amount to send alongside transfer_all') + } } export class InvalidInputsAtaAddress extends Error { - static readonly code = 7015; - readonly code = 7015; - readonly name = 'InvalidInputsAtaAddress'; - readonly msg = 'Invalid Associated Token Account address'; - constructor(readonly logs?: string[]) { super('7015: Invalid Associated Token Account address'); } + static readonly code = 7015 + readonly code = 7015 + readonly name = 'InvalidInputsAtaAddress' + readonly msg = 'Invalid Associated Token Account address' + constructor(readonly logs?: string[]) { + super('7015: Invalid Associated Token Account address') + } } export class InvalidInputsAtaWritable extends Error { - static readonly code = 7016; - readonly code = 7016; - readonly name = 'InvalidInputsAtaWritable'; - readonly msg = 'Invalid Associated Token Account writable flag'; - constructor(readonly logs?: string[]) { super('7016: Invalid Associated Token Account writable flag'); } + static readonly code = 7016 + readonly code = 7016 + readonly name = 'InvalidInputsAtaWritable' + readonly msg = 'Invalid Associated Token Account writable flag' + constructor(readonly logs?: string[]) { + super('7016: Invalid Associated Token Account writable flag') + } } export class InvalidInputsChainSelector extends Error { - static readonly code = 7017; - readonly code = 7017; - readonly name = 'InvalidInputsChainSelector'; - readonly msg = 'Chain selector is invalid'; - constructor(readonly logs?: string[]) { super('7017: Chain selector is invalid'); } + static readonly code = 7017 + readonly code = 7017 + readonly name = 'InvalidInputsChainSelector' + readonly msg = 'Chain selector is invalid' + constructor(readonly logs?: string[]) { + super('7017: Chain selector is invalid') + } } export class InsufficientLamports extends Error { - static readonly code = 7018; - readonly code = 7018; - readonly name = 'InsufficientLamports'; - readonly msg = 'Insufficient lamports'; - constructor(readonly logs?: string[]) { super('7018: Insufficient lamports'); } + static readonly code = 7018 + readonly code = 7018 + readonly name = 'InsufficientLamports' + readonly msg = 'Insufficient lamports' + constructor(readonly logs?: string[]) { + super('7018: Insufficient lamports') + } } export class InsufficientFunds extends Error { - static readonly code = 7019; - readonly code = 7019; - readonly name = 'InsufficientFunds'; - readonly msg = 'Insufficient funds'; - constructor(readonly logs?: string[]) { super('7019: Insufficient funds'); } + static readonly code = 7019 + readonly code = 7019 + readonly name = 'InsufficientFunds' + readonly msg = 'Insufficient funds' + constructor(readonly logs?: string[]) { + super('7019: Insufficient funds') + } } export class SourceTokenDataTooLarge extends Error { - static readonly code = 7020; - readonly code = 7020; - readonly name = 'SourceTokenDataTooLarge'; - readonly msg = 'Source token data is too large'; - constructor(readonly logs?: string[]) { super('7020: Source token data is too large'); } + static readonly code = 7020 + readonly code = 7020 + readonly name = 'SourceTokenDataTooLarge' + readonly msg = 'Source token data is too large' + constructor(readonly logs?: string[]) { + super('7020: Source token data is too large') + } } export class InvalidTokenAdminRegistryInputsZeroAddress extends Error { - static readonly code = 7021; - readonly code = 7021; - readonly name = 'InvalidTokenAdminRegistryInputsZeroAddress'; - readonly msg = 'New Admin can not be zero address'; - constructor(readonly logs?: string[]) { super('7021: New Admin can not be zero address'); } + static readonly code = 7021 + readonly code = 7021 + readonly name = 'InvalidTokenAdminRegistryInputsZeroAddress' + readonly msg = 'New Admin can not be zero address' + constructor(readonly logs?: string[]) { + super('7021: New Admin can not be zero address') + } } export class InvalidTokenAdminRegistryProposedAdmin extends Error { - static readonly code = 7022; - readonly code = 7022; - readonly name = 'InvalidTokenAdminRegistryProposedAdmin'; - readonly msg = 'An already owned registry can not be proposed'; - constructor(readonly logs?: string[]) { super('7022: An already owned registry can not be proposed'); } + static readonly code = 7022 + readonly code = 7022 + readonly name = 'InvalidTokenAdminRegistryProposedAdmin' + readonly msg = 'An already owned registry can not be proposed' + constructor(readonly logs?: string[]) { + super('7022: An already owned registry can not be proposed') + } } export class SenderNotAllowed extends Error { - static readonly code = 7023; - readonly code = 7023; - readonly name = 'SenderNotAllowed'; - readonly msg = 'Sender not allowed for that destination chain'; - constructor(readonly logs?: string[]) { super('7023: Sender not allowed for that destination chain'); } + static readonly code = 7023 + readonly code = 7023 + readonly name = 'SenderNotAllowed' + readonly msg = 'Sender not allowed for that destination chain' + constructor(readonly logs?: string[]) { + super('7023: Sender not allowed for that destination chain') + } } export class InvalidCodeVersion extends Error { - static readonly code = 7024; - readonly code = 7024; - readonly name = 'InvalidCodeVersion'; - readonly msg = 'Invalid code version'; - constructor(readonly logs?: string[]) { super('7024: Invalid code version'); } + static readonly code = 7024 + readonly code = 7024 + readonly name = 'InvalidCodeVersion' + readonly msg = 'Invalid code version' + constructor(readonly logs?: string[]) { + super('7024: Invalid code version') + } } export class InvalidCcipVersionRollback extends Error { - static readonly code = 7025; - readonly code = 7025; - readonly name = 'InvalidCcipVersionRollback'; - readonly msg = 'Invalid rollback attempt on the CCIP version of the onramp to the destination chain'; - constructor(readonly logs?: string[]) { super('7025: Invalid rollback attempt on the CCIP version of the onramp to the destination chain'); } + static readonly code = 7025 + readonly code = 7025 + readonly name = 'InvalidCcipVersionRollback' + readonly msg = 'Invalid rollback attempt on the CCIP version of the onramp to the destination chain' + constructor(readonly logs?: string[]) { + super('7025: Invalid rollback attempt on the CCIP version of the onramp to the destination chain') + } } export class InvalidAccountListForPdaDerivation extends Error { - static readonly code = 7026; - readonly code = 7026; - readonly name = 'InvalidAccountListForPdaDerivation'; - readonly msg = 'Invalid account list for PDA derivation'; - constructor(readonly logs?: string[]) { super('7026: Invalid account list for PDA derivation'); } + static readonly code = 7026 + readonly code = 7026 + readonly name = 'InvalidAccountListForPdaDerivation' + readonly msg = 'Invalid account list for PDA derivation' + constructor(readonly logs?: string[]) { + super('7026: Invalid account list for PDA derivation') + } } export class InvalidDerivationStage extends Error { - static readonly code = 7027; - readonly code = 7027; - readonly name = 'InvalidDerivationStage'; - readonly msg = 'Unexpected account derivation stage'; - constructor(readonly logs?: string[]) { super('7027: Unexpected account derivation stage'); } + static readonly code = 7027 + readonly code = 7027 + readonly name = 'InvalidDerivationStage' + readonly msg = 'Unexpected account derivation stage' + constructor(readonly logs?: string[]) { + super('7027: Unexpected account derivation stage') + } } export class InvalidNonceVersion extends Error { - static readonly code = 7028; - readonly code = 7028; - readonly name = 'InvalidNonceVersion'; - readonly msg = 'Invalid version of the Nonce account'; - constructor(readonly logs?: string[]) { super('7028: Invalid version of the Nonce account'); } + static readonly code = 7028 + readonly code = 7028 + readonly name = 'InvalidNonceVersion' + readonly msg = 'Invalid version of the Nonce account' + constructor(readonly logs?: string[]) { + super('7028: Invalid version of the Nonce account') + } } export class InvalidTokenPoolAccountDerivationResponse extends Error { - static readonly code = 7029; - readonly code = 7029; - readonly name = 'InvalidTokenPoolAccountDerivationResponse'; - readonly msg = 'Token pool returned an unexpected derivation response'; - constructor(readonly logs?: string[]) { super('7029: Token pool returned an unexpected derivation response'); } + static readonly code = 7029 + readonly code = 7029 + readonly name = 'InvalidTokenPoolAccountDerivationResponse' + readonly msg = 'Token pool returned an unexpected derivation response' + constructor(readonly logs?: string[]) { + super('7029: Token pool returned an unexpected derivation response') + } } export class AccountDerivationResponseTooLarge extends Error { - static readonly code = 7030; - readonly code = 7030; - readonly name = 'AccountDerivationResponseTooLarge'; - readonly msg = "Can't fit account derivation response"; - constructor(readonly logs?: string[]) { super("7030: Can't fit account derivation response"); } + static readonly code = 7030 + readonly code = 7030 + readonly name = 'AccountDerivationResponseTooLarge' + readonly msg = "Can't fit account derivation response" + constructor(readonly logs?: string[]) { + super("7030: Can't fit account derivation response") + } } export class DefaultOwnerProposal extends Error { - static readonly code = 7031; - readonly code = 7031; - readonly name = 'DefaultOwnerProposal'; - readonly msg = 'Proposed owner is the default pubkey'; - constructor(readonly logs?: string[]) { super('7031: Proposed owner is the default pubkey'); } + static readonly code = 7031 + readonly code = 7031 + readonly name = 'DefaultOwnerProposal' + readonly msg = 'Proposed owner is the default pubkey' + constructor(readonly logs?: string[]) { + super('7031: Proposed owner is the default pubkey') + } } export function fromCode(code: number, logs?: string[]): CustomError | null { - switch (code) { - case 7000: return new Unauthorized(logs); - case 7001: return new InvalidRMNRemoteAddress(logs); - case 7002: return new InvalidInputsMint(logs); - case 7003: return new InvalidVersion(logs); - case 7004: return new FeeTokenMismatch(logs); - case 7005: return new RedundantOwnerProposal(logs); - case 7006: return new ReachedMaxSequenceNumber(logs); - case 7007: return new InvalidInputsTokenIndices(logs); - case 7008: return new InvalidInputsPoolAccounts(logs); - case 7009: return new InvalidInputsTokenAccounts(logs); - case 7010: return new InvalidInputsTokenAdminRegistryAccounts(logs); - case 7011: return new InvalidInputsLookupTableAccounts(logs); - case 7012: return new InvalidInputsLookupTableAccountWritable(logs); - case 7013: return new InvalidInputsTokenAmount(logs); - case 7014: return new InvalidInputsTransferAllAmount(logs); - case 7015: return new InvalidInputsAtaAddress(logs); - case 7016: return new InvalidInputsAtaWritable(logs); - case 7017: return new InvalidInputsChainSelector(logs); - case 7018: return new InsufficientLamports(logs); - case 7019: return new InsufficientFunds(logs); - case 7020: return new SourceTokenDataTooLarge(logs); - case 7021: return new InvalidTokenAdminRegistryInputsZeroAddress(logs); - case 7022: return new InvalidTokenAdminRegistryProposedAdmin(logs); - case 7023: return new SenderNotAllowed(logs); - case 7024: return new InvalidCodeVersion(logs); - case 7025: return new InvalidCcipVersionRollback(logs); - case 7026: return new InvalidAccountListForPdaDerivation(logs); - case 7027: return new InvalidDerivationStage(logs); - case 7028: return new InvalidNonceVersion(logs); - case 7029: return new InvalidTokenPoolAccountDerivationResponse(logs); - case 7030: return new AccountDerivationResponseTooLarge(logs); - case 7031: return new DefaultOwnerProposal(logs); - } - return null; + switch (code) { + case 7000: + return new Unauthorized(logs) + case 7001: + return new InvalidRMNRemoteAddress(logs) + case 7002: + return new InvalidInputsMint(logs) + case 7003: + return new InvalidVersion(logs) + case 7004: + return new FeeTokenMismatch(logs) + case 7005: + return new RedundantOwnerProposal(logs) + case 7006: + return new ReachedMaxSequenceNumber(logs) + case 7007: + return new InvalidInputsTokenIndices(logs) + case 7008: + return new InvalidInputsPoolAccounts(logs) + case 7009: + return new InvalidInputsTokenAccounts(logs) + case 7010: + return new InvalidInputsTokenAdminRegistryAccounts(logs) + case 7011: + return new InvalidInputsLookupTableAccounts(logs) + case 7012: + return new InvalidInputsLookupTableAccountWritable(logs) + case 7013: + return new InvalidInputsTokenAmount(logs) + case 7014: + return new InvalidInputsTransferAllAmount(logs) + case 7015: + return new InvalidInputsAtaAddress(logs) + case 7016: + return new InvalidInputsAtaWritable(logs) + case 7017: + return new InvalidInputsChainSelector(logs) + case 7018: + return new InsufficientLamports(logs) + case 7019: + return new InsufficientFunds(logs) + case 7020: + return new SourceTokenDataTooLarge(logs) + case 7021: + return new InvalidTokenAdminRegistryInputsZeroAddress(logs) + case 7022: + return new InvalidTokenAdminRegistryProposedAdmin(logs) + case 7023: + return new SenderNotAllowed(logs) + case 7024: + return new InvalidCodeVersion(logs) + case 7025: + return new InvalidCcipVersionRollback(logs) + case 7026: + return new InvalidAccountListForPdaDerivation(logs) + case 7027: + return new InvalidDerivationStage(logs) + case 7028: + return new InvalidNonceVersion(logs) + case 7029: + return new InvalidTokenPoolAccountDerivationResponse(logs) + case 7030: + return new AccountDerivationResponseTooLarge(logs) + case 7031: + return new DefaultOwnerProposal(logs) + } + return null } diff --git a/packages/ccip-svm/src/index.ts b/packages/ccip-svm/src/index.ts index 71671fd..6e5e147 100644 --- a/packages/ccip-svm/src/index.ts +++ b/packages/ccip-svm/src/index.ts @@ -1,3 +1,3 @@ -export * from './encoders/evmExtraArgsV2'; -export * from './encoders/svmExtraArgsV1'; -export * from './errors/custom'; +export * from './encoders/evmExtraArgsV2' +export * from './encoders/svmExtraArgsV1' +export * from './errors/custom' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 239d252..184e5bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -310,12 +310,39 @@ importers: specifier: ^2.37.6 version: 2.37.6(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.67) devDependencies: + '@eslint/js': + specifier: ^9.15.0 + version: 9.29.0 + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@typescript-eslint/eslint-plugin': + specifier: ^8.0.0 + version: 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.29.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.34.1(eslint@9.29.0(jiti@1.21.7))(typescript@5.8.3) + eslint: + specifier: ^9.15.0 + version: 9.29.0(jiti@1.21.7) + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.1.0(eslint@9.29.0(jiti@1.21.7)) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.5.0(eslint-config-prettier@9.1.0(eslint@9.29.0(jiti@1.21.7)))(eslint@9.29.0(jiti@1.21.7))(prettier@3.5.3) + jest: + specifier: ^29.7.0 + version: 29.7.0 + prettier: + specifier: ^3.0.0 + version: 3.5.3 + ts-jest: + specifier: ^29.2.5 + version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0)(typescript@5.8.3) typescript: specifier: ^5.6.3 version: 5.8.3 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(jsdom@24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.43.1) packages: @@ -342,10 +369,6 @@ packages: '@asamuzakjp/css-color@3.1.1': resolution: {integrity: sha512-hpRD68SV2OMcZCsrbdkccTw5FXjNDLo5OuqSHyHZfwweGsDWZwDJ2+gONyNAbazZclobMirACLw0lk8WVxIqxA==} - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -420,10 +443,6 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} @@ -3246,6 +3265,9 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -3546,9 +3568,6 @@ packages: '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@2.1.9': resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} peerDependencies: @@ -3560,47 +3579,21 @@ packages: vite: optional: true - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@2.1.9': resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@2.1.9': resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@2.1.9': resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@volar/language-core@1.11.1': resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} @@ -4810,9 +4803,6 @@ packages: es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -5120,10 +5110,6 @@ packages: resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} engines: {node: '>=12.0.0'} - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5185,15 +5171,6 @@ packages: picomatch: optional: true - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -6045,9 +6022,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -6243,9 +6217,6 @@ packages: loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -6794,9 +6765,6 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -6819,10 +6787,6 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -7575,9 +7539,6 @@ packages: std-env@3.8.1: resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} @@ -7677,9 +7638,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -7807,34 +7765,18 @@ packages: resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} - engines: {node: '>=14.0.0'} - tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -8265,11 +8207,6 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite-plugin-dts@3.9.1: resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8336,34 +8273,6 @@ packages: jsdom: optional: true - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} @@ -8703,12 +8612,6 @@ snapshots: '@csstools/css-tokenizer': 3.0.3 lru-cache: 10.4.3 - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.25.9 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -8827,8 +8730,6 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-option@7.27.1': {} @@ -9003,7 +8904,7 @@ snapshots: '@babel/types@7.26.10': dependencies: '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.27.1 '@babel/types@7.27.6': dependencies: @@ -10087,6 +9988,41 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.32 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.15.32)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3))': dependencies: '@jest/console': 29.7.0 @@ -12588,6 +12524,11 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -12946,14 +12887,6 @@ snapshots: chai: 5.2.0 tinyrainbow: 1.2.0 - '@vitest/expect@3.2.4': - dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.0 - tinyrainbow: 2.0.0 - '@vitest/mocker@2.1.9(vite@5.4.19(@types/node@20.19.1)(terser@5.43.1))': dependencies: '@vitest/spy': 2.1.9 @@ -12962,65 +12895,31 @@ snapshots: optionalDependencies: vite: 5.4.19(@types/node@20.19.1)(terser@5.43.1) - '@vitest/mocker@3.2.4(vite@5.4.19(@types/node@22.15.32)(terser@5.43.1))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 5.4.19(@types/node@22.15.32)(terser@5.43.1) - '@vitest/pretty-format@2.1.9': dependencies: tinyrainbow: 1.2.0 - '@vitest/pretty-format@3.2.4': - dependencies: - tinyrainbow: 2.0.0 - '@vitest/runner@2.1.9': dependencies: '@vitest/utils': 2.1.9 pathe: 1.1.2 - '@vitest/runner@3.2.4': - dependencies: - '@vitest/utils': 3.2.4 - pathe: 2.0.3 - strip-literal: 3.0.0 - '@vitest/snapshot@2.1.9': dependencies: '@vitest/pretty-format': 2.1.9 magic-string: 0.30.17 pathe: 1.1.2 - '@vitest/snapshot@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.17 - pathe: 2.0.3 - '@vitest/spy@2.1.9': dependencies: tinyspy: 3.0.2 - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.4 - '@vitest/utils@2.1.9': dependencies: '@vitest/pretty-format': 2.1.9 loupe: 3.1.3 tinyrainbow: 1.2.0 - '@vitest/utils@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 - '@volar/language-core@1.11.1': dependencies: '@volar/source-map': 1.11.1 @@ -14287,6 +14186,21 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 + create-jest@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.15.32)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)): dependencies: '@jest/types': 29.6.3 @@ -14681,8 +14595,6 @@ snapshots: es-module-lexer@1.6.0: {} - es-module-lexer@1.7.0: {} - es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -15232,8 +15144,6 @@ snapshots: expect-type@1.2.0: {} - expect-type@1.2.2: {} - expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -15293,10 +15203,6 @@ snapshots: optionalDependencies: picomatch: 4.0.2 - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -16221,6 +16127,25 @@ snapshots: - babel-plugin-macros - supports-color + jest-cli@29.7.0: + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0 + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.15.32)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@22.15.32)(typescript@5.8.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-cli@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)) @@ -16412,7 +16337,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.27.1 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -16567,6 +16492,18 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 + jest@29.7.0: + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3)) @@ -16599,8 +16536,6 @@ snapshots: js-tokens@4.0.0: {} - js-tokens@9.0.1: {} - js-yaml@3.14.1: dependencies: argparse: 1.0.10 @@ -16800,8 +16735,6 @@ snapshots: loupe@3.1.3: {} - loupe@3.2.1: {} - lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -17512,8 +17445,6 @@ snapshots: pathe@1.1.2: {} - pathe@2.0.3: {} - pathval@1.1.1: {} pathval@2.0.0: {} @@ -17533,8 +17464,6 @@ snapshots: picomatch@4.0.2: {} - picomatch@4.0.3: {} - pify@2.3.0: {} pify@3.0.0: {} @@ -18416,8 +18345,6 @@ snapshots: std-env@3.8.1: {} - std-env@3.9.0: {} - stream-shift@1.0.3: {} streamsearch@1.1.0: {} @@ -18536,10 +18463,6 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@3.0.0: - dependencies: - js-tokens: 9.0.1 - styled-jsx@5.1.1(@babel/core@7.27.4)(react@18.3.1): dependencies: client-only: 0.0.1 @@ -18705,23 +18628,12 @@ snapshots: fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - tinypool@1.0.2: {} - tinypool@1.1.1: {} - tinyrainbow@1.2.0: {} - tinyrainbow@2.0.0: {} - tinyspy@3.0.2: {} - tinyspy@4.0.4: {} - tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -18813,6 +18725,26 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.27.4) jest-util: 29.7.0 + ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0)(typescript@5.8.3): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.8.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.27.4 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.4) + jest-util: 29.7.0 + ts-node@10.9.2(@swc/core@1.12.1)(@types/node@20.19.1)(typescript@5.8.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -19175,24 +19107,6 @@ snapshots: - supports-color - terser - vite-node@3.2.4(@types/node@22.15.32)(terser@5.43.1): - dependencies: - cac: 6.7.14 - debug: 4.4.1(supports-color@8.1.1) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 5.4.19(@types/node@22.15.32)(terser@5.43.1) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vite-plugin-dts@3.9.1(@types/node@20.19.1)(rollup@4.36.0)(typescript@5.8.3)(vite@5.4.19(@types/node@20.19.1)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.43.0(@types/node@20.19.1) @@ -19220,16 +19134,6 @@ snapshots: fsevents: 2.3.3 terser: 5.43.1 - vite@5.4.19(@types/node@22.15.32)(terser@5.43.1): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.6 - rollup: 4.36.0 - optionalDependencies: - '@types/node': 22.15.32 - fsevents: 2.3.3 - terser: 5.43.1 - vitest@2.1.9(@types/node@20.19.1)(jsdom@24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.43.1): dependencies: '@vitest/expect': 2.1.9 @@ -19266,46 +19170,6 @@ snapshots: - supports-color - terser - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(jsdom@24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.43.1): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@5.4.19(@types/node@22.15.32)(terser@5.43.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.0 - debug: 4.4.1(supports-color@8.1.1) - expect-type: 1.2.2 - magic-string: 0.30.17 - pathe: 2.0.3 - picomatch: 4.0.2 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 5.4.19(@types/node@22.15.32)(terser@5.43.1) - vite-node: 3.2.4(@types/node@22.15.32)(terser@5.43.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 22.15.32 - jsdom: 24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - vlq@1.0.1: {} vue-template-compiler@2.7.16: From 60ec5c5ad1e377b7d4cc3c4001355501bf52e618 Mon Sep 17 00:00:00 2001 From: andrejrakic Date: Tue, 23 Sep 2025 22:53:28 +0200 Subject: [PATCH 5/6] feat: add hex utility functions --- .../src/__tests__/evmExtraArgsV2.test.ts | 5 ++- .../src/__tests__/svmExtraArgsV1.test.ts | 3 +- .../ccip-svm/src/encoders/evmExtraArgsV2.ts | 16 ++++---- .../ccip-svm/src/encoders/svmExtraArgsV1.ts | 1 + packages/ccip-svm/src/index.ts | 1 + packages/ccip-svm/src/utils/hex.ts | 38 +++++++++++++++++++ 6 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 packages/ccip-svm/src/utils/hex.ts diff --git a/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts index 3f48083..ad7279d 100644 --- a/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts +++ b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts @@ -1,5 +1,6 @@ import { encodeEVMExtraArgsV2, decodeEVMExtraArgsV2, EVM_EXTRA_ARGS_V2_TAG } from '../encoders/evmExtraArgsV2' import { keccak256, toBytes } from 'viem' +import { fromHexBuffer } from '../utils/hex' describe('EVM Extra Args V2', () => { let consoleSpy: jest.SpyInstance @@ -50,7 +51,7 @@ describe('EVM Extra Args V2', () => { if (shouldWarn) { expect(consoleSpy).toHaveBeenCalledWith( - 'Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.', + `Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations. More details: https://docs.chain.link/ccip/concepts/best-practices/evm#best-practices`, ) } else { expect(consoleSpy).not.toHaveBeenCalled() @@ -60,7 +61,7 @@ describe('EVM Extra Args V2', () => { expect(encoded.length).toBe(evmExtraArgsV2BytesLength) const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) expect(tag).toBe(EVM_EXTRA_ARGS_V2_TAG) - expect(encoded).toEqual(new Uint8Array(Buffer.from(expected.slice(2), 'hex'))) + expect(encoded).toEqual(fromHexBuffer(expected)) }) }) diff --git a/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts index 2a35310..c455f7f 100644 --- a/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts +++ b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts @@ -1,6 +1,7 @@ import { address } from '@solana/kit' import { encodeSVMExtraArgsV1, decodeSVMExtraArgsV1, SVM_EXTRA_ARGS_V1_TAG } from '../encoders/svmExtraArgsV1' import { keccak256, toBytes } from 'viem' +import { fromHexBuffer } from '../utils/hex' const MOCK_TOKEN_RECEIVER = address('11111111111111111111111111111112') // System Program ID (Devnet) const MOCK_ACCOUNT_1 = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') // Token Program ID (Devnet) @@ -68,7 +69,7 @@ describe('SVM Extra Args V1', () => { expect(tag).toBe(SVM_EXTRA_ARGS_V1_TAG) if (expectedHex) { - expect(encoded).toEqual(new Uint8Array(Buffer.from(expectedHex.slice(2), 'hex'))) + expect(encoded).toEqual(fromHexBuffer(expectedHex)) } }) }) diff --git a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts index 3fd8f32..7f3a4ba 100644 --- a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts +++ b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts @@ -1,4 +1,5 @@ import { encodeAbiParameters, parseAbiParameters, decodeAbiParameters } from 'viem' +import { toHex, fromHexBuffer } from '../utils/hex' /** * EVM Extra Args V2 tag: bytes4(keccak256("CCIP EVMExtraArgsV2")) @@ -12,13 +13,16 @@ export interface EVMExtraArgsV2 { /** * Creates properly encoded extraArgs buffer for EVM destinations + * More details: https://docs.chain.link/ccip/api-reference/svm/v0.1.1/messages#evmextraargsv2 * @param gasLimit - Gas limit for execution on destination chain (use 0 for token-only transfers) * @param allowOutOfOrderExecution - Whether messages can be executed out of order (default: true) * @returns Properly encoded extraArgs as Uint8Array */ export function encodeEVMExtraArgsV2({ gasLimit, allowOutOfOrderExecution = true }: EVMExtraArgsV2): Uint8Array { if (allowOutOfOrderExecution === false) { - console.warn(`Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations.`) + console.warn( + `Warning: Setting allowOutOfOrderExecution to false is not recommended for EVM destinations. More details: https://docs.chain.link/ccip/concepts/best-practices/evm#best-practices`, + ) } // bytes4(keccak256("CCIP EVMExtraArgsV2")) = 0x181dcf10 @@ -29,7 +33,7 @@ export function encodeEVMExtraArgsV2({ gasLimit, allowOutOfOrderExecution = true allowOutOfOrderExecution, ]) - const encodedExtraArgsBytes = new Uint8Array(Buffer.from(encodedExtraArgs.slice(2), 'hex')) + const encodedExtraArgsBytes = fromHexBuffer(encodedExtraArgs) // abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs) const result = new Uint8Array(tag.length + encodedExtraArgsBytes.length) @@ -59,15 +63,11 @@ export function decodeEVMExtraArgsV2(data: Uint8Array): EVMExtraArgsV2 { } const extraArgsData = data.slice(4) - const extraArgsHex = - '0x' + - Array.from(extraArgsData) - .map((b) => b.toString(16).padStart(2, '0')) - .join('') + const extraArgsHex = toHex(extraArgsData) const [gasLimit, allowOutOfOrderExecution] = decodeAbiParameters( parseAbiParameters('uint128 gasLimit, bool allowOutOfOrderExecution'), - extraArgsHex as `0x${string}`, + extraArgsHex, ) return { diff --git a/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts index f0526f2..6b976ac 100644 --- a/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts +++ b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts @@ -17,6 +17,7 @@ export interface SVMExtraArgsV1 { /** * Creates properly encoded extraArgs buffer for SVM destinations + * More details: https://docs.chain.link/ccip/api-reference/svm/v0.1.1/messages#svmextraargsv1 * @param options - SVM extra args configuration * @returns Properly encoded extraArgs as Uint8Array */ diff --git a/packages/ccip-svm/src/index.ts b/packages/ccip-svm/src/index.ts index 6e5e147..9401a38 100644 --- a/packages/ccip-svm/src/index.ts +++ b/packages/ccip-svm/src/index.ts @@ -1,3 +1,4 @@ export * from './encoders/evmExtraArgsV2' export * from './encoders/svmExtraArgsV1' export * from './errors/custom' +export * from './utils/hex' diff --git a/packages/ccip-svm/src/utils/hex.ts b/packages/ccip-svm/src/utils/hex.ts new file mode 100644 index 0000000..504f4ac --- /dev/null +++ b/packages/ccip-svm/src/utils/hex.ts @@ -0,0 +1,38 @@ +/** + * Converts a Uint8Array to a hex string with 0x prefix + * @param bytes - The bytes to convert + * @returns Hex string with 0x prefix + */ +export function toHex(bytes: Uint8Array): `0x${string}` { + return `0x${Array.from(bytes) + .map((b) => b.toString(16).padStart(2, '0')) + .join('')}` as `0x${string}` +} + +/** + * Converts a hex string (with or without 0x prefix) to Uint8Array + * @param hex - The hex string to convert + * @returns Uint8Array containing the bytes + */ +export function fromHex(hex: string): Uint8Array { + const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex + if (cleanHex.length % 2 !== 0) { + throw new Error('Invalid hex string: length must be even') + } + + const bytes = new Uint8Array(cleanHex.length / 2) + for (let i = 0; i < cleanHex.length; i += 2) { + bytes[i / 2] = parseInt(cleanHex.slice(i, i + 2), 16) + } + return bytes +} + +/** + * Converts a hex string to Uint8Array using Buffer (for compatibility) + * @param hex - The hex string to convert (with or without 0x prefix) + * @returns Uint8Array containing the bytes + */ +export function fromHexBuffer(hex: string): Uint8Array { + const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex + return new Uint8Array(Buffer.from(cleanHex, 'hex')) +} From 1143e792ac533df8d6c0d5a896be8e36ce58971b Mon Sep 17 00:00:00 2001 From: andrejrakic Date: Thu, 2 Oct 2025 15:10:46 +0200 Subject: [PATCH 6/6] refactor: introduce minimum length constants for EVM and SVM Extra Args, updating related tests and error messages --- .../src/__tests__/evmExtraArgsV2.test.ts | 15 +++++++----- .../src/__tests__/svmExtraArgsV1.test.ts | 24 ++++++++++--------- .../ccip-svm/src/encoders/evmExtraArgsV2.ts | 9 +++++-- .../ccip-svm/src/encoders/svmExtraArgsV1.ts | 10 +++++--- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts index ad7279d..d2f3227 100644 --- a/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts +++ b/packages/ccip-svm/src/__tests__/evmExtraArgsV2.test.ts @@ -1,10 +1,14 @@ -import { encodeEVMExtraArgsV2, decodeEVMExtraArgsV2, EVM_EXTRA_ARGS_V2_TAG } from '../encoders/evmExtraArgsV2' +import { + encodeEVMExtraArgsV2, + decodeEVMExtraArgsV2, + EVM_EXTRA_ARGS_V2_TAG, + EVM_EXTRA_ARGS_V2_MIN_LENGTH, +} from '../encoders/evmExtraArgsV2' import { keccak256, toBytes } from 'viem' import { fromHexBuffer } from '../utils/hex' describe('EVM Extra Args V2', () => { let consoleSpy: jest.SpyInstance - const evmExtraArgsV2BytesLength = 68 // 4 bytes tag + 32 bytes gasLimit + 32 bytes bool beforeEach(() => { consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}) @@ -58,7 +62,7 @@ describe('EVM Extra Args V2', () => { } expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(evmExtraArgsV2BytesLength) + expect(encoded.length).toBe(EVM_EXTRA_ARGS_V2_MIN_LENGTH) const tag = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, false) expect(tag).toBe(EVM_EXTRA_ARGS_V2_TAG) expect(encoded).toEqual(fromHexBuffer(expected)) @@ -78,13 +82,12 @@ describe('EVM Extra Args V2', () => { { name: 'data too short', data: new Uint8Array(10), - expectedError: - 'Invalid EVM Extra Args V2: data too short, expected at least 68 bytes (4 bytes tag + 32 bytes gasLimit + 32 bytes bool). Example (100_000 gasLimit and true): 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001', + expectedError: `Invalid EVM Extra Args V2: data too short, expected at least ${EVM_EXTRA_ARGS_V2_MIN_LENGTH} bytes (4 bytes tag + 32 bytes gasLimit + 32 bytes bool). Example (100_000 gasLimit and true): 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001`, }, { name: 'invalid tag', data: (() => { - const invalidData = new Uint8Array(evmExtraArgsV2BytesLength) + const invalidData = new Uint8Array(EVM_EXTRA_ARGS_V2_MIN_LENGTH) invalidData[0] = 0x12 invalidData[1] = 0x34 invalidData[2] = 0x56 diff --git a/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts index c455f7f..a5a4bab 100644 --- a/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts +++ b/packages/ccip-svm/src/__tests__/svmExtraArgsV1.test.ts @@ -1,5 +1,10 @@ import { address } from '@solana/kit' -import { encodeSVMExtraArgsV1, decodeSVMExtraArgsV1, SVM_EXTRA_ARGS_V1_TAG } from '../encoders/svmExtraArgsV1' +import { + encodeSVMExtraArgsV1, + decodeSVMExtraArgsV1, + SVM_EXTRA_ARGS_V1_TAG, + SVM_EXTRA_ARGS_V1_MIN_LENGTH, +} from '../encoders/svmExtraArgsV1' import { keccak256, toBytes } from 'viem' import { fromHexBuffer } from '../utils/hex' @@ -11,13 +16,11 @@ const MOCK_ACCOUNT_4 = address('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU') / const MOCK_ACCOUNT_5 = address('DzcwGnG1kM1i6zE9vR4YmzjL48mF8Eik1gC9CkJTQ7K1') // USDT Token Mint (Devnet) describe('SVM Extra Args V1', () => { - const svmExtraArgsV1MinBytesLength = 4 + 4 + 8 + 1 + 32 + 4 // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length - const testCases = [ { name: 'should encode with default values', input: {}, - expectedLength: svmExtraArgsV1MinBytesLength, + expectedLength: SVM_EXTRA_ARGS_V1_MIN_LENGTH, expectedHex: '0x1f3b3aba00000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000', }, @@ -29,14 +32,14 @@ describe('SVM Extra Args V1', () => { allowOutOfOrderExecution: false, accounts: [], }, - expectedLength: svmExtraArgsV1MinBytesLength, + expectedLength: SVM_EXTRA_ARGS_V1_MIN_LENGTH, expectedHex: '0x1f3b3aba00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', }, { name: 'should handle token-only transfers (computeUnits = 0)', input: { computeUnits: 0, allowOutOfOrderExecution: true }, - expectedLength: svmExtraArgsV1MinBytesLength, + expectedLength: SVM_EXTRA_ARGS_V1_MIN_LENGTH, }, { name: 'should encode SVM extra args with all parameters', @@ -47,14 +50,14 @@ describe('SVM Extra Args V1', () => { tokenReceiver: MOCK_TOKEN_RECEIVER, accounts: [MOCK_ACCOUNT_1, MOCK_ACCOUNT_2], }, - expectedLength: svmExtraArgsV1MinBytesLength + 2 * 32, // 53 + 64 bytes for 2 accounts + expectedLength: SVM_EXTRA_ARGS_V1_MIN_LENGTH + 2 * 32, // 53 + 64 bytes for 2 accounts }, { name: 'should encode with many accounts', input: { accounts: [MOCK_ACCOUNT_1, MOCK_ACCOUNT_2, MOCK_ACCOUNT_3, MOCK_ACCOUNT_4, MOCK_ACCOUNT_5], }, - expectedLength: svmExtraArgsV1MinBytesLength + 5 * 32, // 53 + 160 bytes for 5 accounts + expectedLength: SVM_EXTRA_ARGS_V1_MIN_LENGTH + 5 * 32, // 53 + 160 bytes for 5 accounts }, ] @@ -99,13 +102,12 @@ describe('SVM Extra Args V1', () => { { name: 'data too short', data: new Uint8Array(10), - expectedError: - 'Invalid SVM Extra Args V1: data too short, expected at least 53 bytes (4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length)', + expectedError: `Invalid SVM Extra Args V1: data too short, expected at least ${SVM_EXTRA_ARGS_V1_MIN_LENGTH} bytes (4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length)`, }, { name: 'invalid tag', data: (() => { - const invalidData = new Uint8Array(svmExtraArgsV1MinBytesLength) + const invalidData = new Uint8Array(SVM_EXTRA_ARGS_V1_MIN_LENGTH) invalidData[0] = 0x12 invalidData[1] = 0x34 invalidData[2] = 0x56 diff --git a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts index 7f3a4ba..9c81adf 100644 --- a/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts +++ b/packages/ccip-svm/src/encoders/evmExtraArgsV2.ts @@ -6,6 +6,11 @@ import { toHex, fromHexBuffer } from '../utils/hex' */ export const EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10 +/** + * Minimum byte length for EVM Extra Args V2: 4 bytes tag + 32 bytes gasLimit + 32 bytes bool + */ +export const EVM_EXTRA_ARGS_V2_MIN_LENGTH = 68 + export interface EVMExtraArgsV2 { gasLimit: bigint allowOutOfOrderExecution?: boolean @@ -49,9 +54,9 @@ export function encodeEVMExtraArgsV2({ gasLimit, allowOutOfOrderExecution = true * @returns Decoded EVMExtraArgsV2 object */ export function decodeEVMExtraArgsV2(data: Uint8Array): EVMExtraArgsV2 { - if (data.length < 68) { + if (data.length < EVM_EXTRA_ARGS_V2_MIN_LENGTH) { throw new Error( - 'Invalid EVM Extra Args V2: data too short, expected at least 68 bytes (4 bytes tag + 32 bytes gasLimit + 32 bytes bool). Example (100_000 gasLimit and true): 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001', + `Invalid EVM Extra Args V2: data too short, expected at least ${EVM_EXTRA_ARGS_V2_MIN_LENGTH} bytes (4 bytes tag + 32 bytes gasLimit + 32 bytes bool). Example (100_000 gasLimit and true): 0x181dcf1000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000001`, ) } diff --git a/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts index 6b976ac..8283e0c 100644 --- a/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts +++ b/packages/ccip-svm/src/encoders/svmExtraArgsV1.ts @@ -5,6 +5,11 @@ import { address, getAddressEncoder, getAddressDecoder } from '@solana/kit' */ export const SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba +/** + * Minimum byte length for SVM Extra Args V1: 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length + */ +export const SVM_EXTRA_ARGS_V1_MIN_LENGTH = 53 + export type Address = ReturnType export interface SVMExtraArgsV1 { @@ -78,10 +83,9 @@ export function encodeSVMExtraArgsV1({ * @returns Decoded SVMExtraArgsV1 object */ export function decodeSVMExtraArgsV1(data: Uint8Array): SVMExtraArgsV1 { - // 4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length - if (data.length < 4 + 4 + 8 + 1 + 32 + 4) { + if (data.length < SVM_EXTRA_ARGS_V1_MIN_LENGTH) { throw new Error( - 'Invalid SVM Extra Args V1: data too short, expected at least 53 bytes (4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length)', + `Invalid SVM Extra Args V1: data too short, expected at least ${SVM_EXTRA_ARGS_V1_MIN_LENGTH} bytes (4 bytes tag + 4 bytes computeUnits + 8 bytes bitmap + 1 byte bool + 32 bytes tokenReceiver + 4 bytes accounts length)`, ) }