diff --git a/packages/cre-sdk/src/sdk/don-info.ts b/packages/cre-sdk/src/sdk/don-info.ts new file mode 100644 index 00000000..a1c02224 --- /dev/null +++ b/packages/cre-sdk/src/sdk/don-info.ts @@ -0,0 +1,20 @@ +export type Environment = { + chainSelector: bigint + registryAddress: string +} + +export type Zone = { + environment: Environment + donID: number +} + +export function productionEnvironment(): Environment { + return { + chainSelector: 5009297550715157269n, + registryAddress: '0x76c9cf548b4179F8901cda1f8623568b58215E62', + } +} + +export function zoneFromEnvironment(environment: Environment, donID: number): Zone { + return { environment, donID } +} diff --git a/packages/cre-sdk/src/sdk/errors.ts b/packages/cre-sdk/src/sdk/errors.ts index 9dab9a28..02fa2186 100644 --- a/packages/cre-sdk/src/sdk/errors.ts +++ b/packages/cre-sdk/src/sdk/errors.ts @@ -1,4 +1,4 @@ -import { Mode, type SecretRequest } from '@cre/generated/sdk/v1alpha/sdk_pb' +import type { SecretRequest } from '@cre/generated/sdk/v1alpha/sdk_pb' export class DonModeError extends Error { constructor() { @@ -25,3 +25,55 @@ export class SecretsError extends Error { this.name = 'SecretsError' } } + +export class NullReportError extends Error { + constructor() { + super('null report') + this.name = 'NullReportError' + } +} + +export class WrongSignatureCountError extends Error { + constructor() { + super('wrong number of signatures') + this.name = 'WrongSignatureCountError' + } +} + +export class ParseSignatureError extends Error { + constructor() { + super('failed to parse signature') + this.name = 'ParseSignatureError' + } +} + +export class RecoverSignerError extends Error { + constructor() { + super('failed to recover signer address from signature') + this.name = 'RecoverSignerError' + } +} + +export class UnknownSignerError extends Error { + constructor() { + super('invalid signature') + this.name = 'UnknownSignerError' + } +} + +export class DuplicateSignerError extends Error { + constructor() { + super('duplicate signer') + this.name = 'DuplicateSignerError' + } +} + +export class RawReportTooShortError extends Error { + constructor( + public readonly need: number, + public readonly got: number, + ) { + super(`raw report too short to contain metadata header: need ${need} bytes, got ${got}`) + this.name = 'RawReportTooShortError' + } +} diff --git a/packages/cre-sdk/src/sdk/index.ts b/packages/cre-sdk/src/sdk/index.ts index 65d659dc..9ca2643e 100644 --- a/packages/cre-sdk/src/sdk/index.ts +++ b/packages/cre-sdk/src/sdk/index.ts @@ -1,4 +1,6 @@ export * from './cre' +export * from './don-info' +export * from './errors' export * from './report' export type * from './runtime' export * from './runtime' diff --git a/packages/cre-sdk/src/sdk/report-internals.ts b/packages/cre-sdk/src/sdk/report-internals.ts new file mode 100644 index 00000000..01e5fc13 --- /dev/null +++ b/packages/cre-sdk/src/sdk/report-internals.ts @@ -0,0 +1,13 @@ +import type { Address } from 'viem' + +// Do not re-export in index.ts to avoid exposing it to the public API. +// This is public to allow the cache to be cleared in the tests. + +export type DonInfo = { + f: number + signers: Map +} + +// Do not re-export in index.ts to avoid exposing it to the public API. +// donInfoCache exists outside Report to allow testing to clear the cache. +export var donInfoCache = new Map() diff --git a/packages/cre-sdk/src/sdk/report.test.ts b/packages/cre-sdk/src/sdk/report.test.ts new file mode 100644 index 00000000..e2b3c5b3 --- /dev/null +++ b/packages/cre-sdk/src/sdk/report.test.ts @@ -0,0 +1,600 @@ +import { beforeEach, describe, expect } from 'bun:test' +import { create } from '@bufbuild/protobuf' +import { AnySchema } from '@bufbuild/protobuf/wkt' +import { newTestRuntime, registerTestCapability, test } from '@cre/sdk/test' +import { hexToBytes } from 'viem' +import { + DuplicateSignerError, + type Environment, + ParseSignatureError, + productionEnvironment, + RawReportTooShortError, + RecoverSignerError, + Report, + UnknownSignerError, + WrongSignatureCountError, + zoneFromEnvironment, +} from '.' +import { donInfoCache } from './report-internals' + +// From a real report +const reportContext = decodeHex( + '000e8ce31db48e5e44619d24d9dadfc5f22a34db8205b2b25cd831eab02244c500000000000000000000000000000000000000000000000000000001612bcb000000000000000000000000000000000000000000000000000000000000000000', +) +const rawReport = decodeHex( + '010d46e9e05b28a322cd96c94f1f60c236a1b52bd446d98396f1a84afae388836a69c96faa000000010000000100a3bb3b17a1053fe409147c2b170af4866c182070905e5a0f314be04c8296a336613263393832646464b0f2d38245dd6d397ebbdb5a814b753d56c30715000148656c6c6f2c20576f726c6421', +) + +const reportBody = 'Hello, World!' +const reportExecID = '0d46e9e05b28a322cd96c94f1f60c236a1b52bd446d98396f1a84afae388836a' +const reportSeqNr = 0n +const reportDigest = reportContext.subarray(0, 32) +const reportDONID = 1 +const reportDONCfgVer = 1 +const reportWfID = '00a3bb3b17a1053fe409147c2b170af4866c182070905e5a0f314be04c8296a3' +const reportWfName = '6a2c982ddd' +const reportWfOwner = 'b0f2d38245dd6d397ebbdb5a814b753d56c30715' +const reportTimestampUnix = 1774809002 +const reportReportID = '0001' +const reportVersion = 1 + +const reportSigs = sigsToBytes([ + '452d9e1fef501946b5e0a52e32dd4ec48a796c8c30a4f62332006a1fb5a7d4b578b6ad0b7a15f3c4ef46bffb891884b50ac23f5fcfa5cf75e7a7329d6fed7a6400', + '3b48aa3db3d6398f1d69f649e266d4388d3d176b76ff58de72ae4ca30e27559b5789a491b8f94578f793bc5781ad5c50dbf48bb79e61b929f625162a4c79172300', + 'a0fa2428e44e7ffa9452433b3275bd79819db0620b7d2090fea21065a1dfed1a7c5135540c6e44c20be4d36d18c1859253eca53476f45ea3f8a54ddc97d4b42c01', + 'f868744afd82e657110ec1e214fe91e6abc10ea84edcf69b5076ee5bf9126e056ba50eb078d1b594140ddaac11aa0b52fe9ca4f629b334b243e38a345e0d519501', +]) + +const extraValidSig = decodeHex( + 'c6ad1f99caadc9438d3ac14a821d523f2716c908286224e0a558e864cfa51a07aa3d8707818c47bd324e4053c29ea046b0073343d48186ae3bc71f5ee85a6401', +) +const notASignerSig = decodeHex( + 'ec644867408acbfbc51f30731e1ebfb6395a943c500f824c23db57aec71642777b1a143319ad3b5b9d2c8bda616ea98cd3c6c469e76da9482abb6afb0d3b06c200', +) + +const reportSigIds = [6, 1, 5, 8] + +// callReplay holds the expected request and canned response for one capability call. +interface CallReplay { + RequestValue: Uint8Array + ResponseValue: Uint8Array +} + +// workflowReplay holds a sequence of expected calls for one chain. +// The mock advances through Calls in order, matching each by RequestValue. +interface WorkflowReplay { + Calls: CallReplay[] + ChainSelector: bigint +} + +const productionEnvironmentReplay: WorkflowReplay = { + Calls: [ + { + RequestValue: decodeHex( + '120e0a010310ffffffffffffffffff010a3c121476c9cf548b4179f8901cda1f8623568b58215e621a24235374050000000000000000000000000000000000000000000000000000000000000001', + ), + ResponseValue: decodeHex( + '0ae018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000a2f2ef1df488df1fa0481e5e9029b140e06b8b451fd50f63af35e78daa8e77a9b1b151301a950a6e7349df023c6a61c2f836425b4219d819781d59930205314d379b4573667de33b549e999c1318fa183a8965ecb29aa6f9b5076470a097da30c43de846c124a064722dbfe87260bcf7f268bb8b6a95812c00316ba58ace153c9274238b4c0afa52061cc0bac29b3002bc90568e002426fb697658d70bf3f10fe79e8ca1a676b07039ac3e1f5fa70d89e4a58487a3b54ecbb53900f5f2d85afb20ebd0742865703034f8a357db566837dde300357e0a6b3a0982d622495bdb2bb044432b7c8ee9d14059569595a1020a3b4598574aa3a502708a6d96a1881cc0c196ea7403b385b02634647717a096a71fb4d577a11bc37bc3794fb2d599b3c5d4ec86a31d8865abe7704e2059a58ec1d5e177325b0fc09c5ca9b2ff6fc5158710000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000067a6f6e652d6100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011776f726b666c6f775f315f7a6f6e652d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000007e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000004500acd080aca080a08456e636c6176657312bd082aba081299042296040a170a11456e636c6176654175746848656164657212020a000a160a10456e636c61766545787472614461746112021a000a2f0a09456e636c617665494412221a2066338ab3ebe1d246feb5849330e92022b9d66b0c0753b1f2ac6e356acbff20fc0a160a0b456e636c6176655479706512070a054e4954524f0a3d0a0a456e636c61766555524c122f0a2d68747470733a2f2f636f6e666964656e7469616c2d687474702e656e636c617665732e636861696e2e6c696e6b0ada020a0d5472757374656456616c75657312c8022ac50212c2021abf027b2270637230223a22336130646135323235666439323134323666633864613065376139316534323631623431333137336239626530303237616462326666626231616661653430616462613662663431626133663066653537386339303032646134653565613237222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22643938306538363964333066303333353130356563373236623234393430633636633130383130376362386636653661363638363561323562646464303537303363393231373964303334386661323936636166336332663633313036393631227d129b042298040a160a10456e636c61766545787472614461746112021a000a2f0a09456e636c617665494412221a206401f4177859d87613cdc14bd3841049840abaf6d0163cf77080227b8866ebe20a160a0b456e636c6176655479706512070a054e4954524f0a3f0a0a456e636c61766555524c12310a2f68747470733a2f2f636f6e666964656e7469616c2d687474702d322e656e636c617665732e636861696e2e6c696e6b0ada020a0d5472757374656456616c75657312c8022ac50212c2021abf027b2270637230223a22336130646135323235666439323134323666633864613065376139316534323631623431333137336239626530303237616462326666626231616661653430616462613662663431626133663066653537386339303032646134653565613237222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22643938306538363964333066303333353130356563373236623234393430633636633130383130376362386636653661363638363561323562646464303537303363393231373964303334386661323936636166336332663633313036393631227d0a170a11456e636c6176654175746848656164657212020a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000000000002400100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000000000000024001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000000000000024001000000000000000000000000000000000000000000000000000000000000', + ), + }, + { + RequestValue: decodeHex( + '120e0a010310ffffffffffffffffff010a9d03121476c9cf548b4179f8901cda1f8623568b58215e621a840305a519660000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a2f2ef1df488df1fa0481e5e9029b140e06b8b451fd50f63af35e78daa8e77a9b1b151301a950a6e7349df023c6a61c2f836425b4219d819781d59930205314d379b4573667de33b549e999c1318fa183a8965ecb29aa6f9b5076470a097da30c43de846c124a064722dbfe87260bcf7f268bb8b6a95812c00316ba58ace153c9274238b4c0afa52061cc0bac29b3002bc90568e002426fb697658d70bf3f10fe79e8ca1a676b07039ac3e1f5fa70d89e4a58487a3b54ecbb53900f5f2d85afb20ebd0742865703034f8a357db566837dde300357e0a6b3a0982d622495bdb2bb044432b7c8ee9d14059569595a1020a3b4598574aa3a502708a6d96a1881cc0c196ea7403b385b02634647717a096a71fb4d577a11bc37bc3794fb2d599b3c5d4ec86a31d8865abe7704e2059a58ec1d5e177325b0fc09c5ca9b2ff6fc515871', + ), + ResponseValue: decodeHex( + '0a80440000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000e40000000000000000000000000000000000000000000000000000000000000118000000000000000000000000000000000000000000000000000000000000014c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001b400000000000000000000000000000000000000000000000000000000000001e80000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001ff9b062fccb2f042311343048b9518068370f8370000000000000000000000002f2ef1df488df1fa0481e5e9029b140e06b8b451fd50f63af35e78daa8e77a9b3ab22f16cd99e40a0961c1fe754957995ad9342876935903f98748b4679ad14461faf5857337f5bd2286b73385d5a14d2fa7a06cb412a60c48c0ab8b6a48d29500000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001e55fcaf921e76c6bbcf9415bba12b1236f07b0c30000000000000000000000001b151301a950a6e7349df023c6a61c2f836425b4219d819781d59930205314d38fd8e4f29d73296f22a7b3f5fc9202575ce22f0f2d00958535933317e10bed7931d7553b6a9b0f19ad9b2135005e17ac43b2d64156fbc40189016a02edca0e2600000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000014d6cfd44f94408a39fb1af94a53c107a730ba16100000000000000000000000079b4573667de33b549e999c1318fa183a8965ecb29aa6f9b5076470a097da30cdcef5ac750843e491047b36a967b21cd1808b89150b649fb52026f9d4fbd3d7d42f5c6de0835861a4851a6a28b9701bb6b7f3334dba723723766faeb9179587000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001de5cd1dd4300a0b4854f8223add60d20e1dfe21b00000000000000000000000043de846c124a064722dbfe87260bcf7f268bb8b6a95812c00316ba58ace153c9bf27f741368396516a3eddadb25d6155aa826f49a803339a8d26ad414011ae10fd55f6f19ddac740310fc1e11df087af1204edb720d8087b1ac81ed7526305c200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001f3baa9a99b5ad64f50779f449bac83baac8bfdb6000000000000000000000000274238b4c0afa52061cc0bac29b3002bc90568e002426fb697658d70bf3f10fec2d55147082f081d11eeec32a377e78d61413e8ed3324fc580f908159a6f8d2f44191e378823030493e5005bfb9eab10d36f480d7aab96a3a7342cd582e870aa00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001d7f22fb5382ff477d2ff5c702cab0ef8abf1823300000000000000000000000079e8ca1a676b07039ac3e1f5fa70d89e4a58487a3b54ecbb53900f5f2d85afb2062ba100224f908adf57d63a93833df318624d88d8a196e3944be333e35c4033638ae4ff0cf5a5b1644363fa9c2cdbd6dcc68385b6e743a4fc35428106ec48c200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001cdf20f8ffd41b02c680988b20e68735cc8c1ca170000000000000000000000000ebd0742865703034f8a357db566837dde300357e0a6b3a0982d622495bdb2bb0204c67585b82a51088c1f206e9cf70d915b5216b19002805ac2ab0a8e8a36688d1df6c27959c6526fd563063ad3257736f965df9c1001262a5aeaadee20a9fb00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000014d7d71c7e584cfa1f5c06275e5d283b9d3176924000000000000000000000000044432b7c8ee9d14059569595a1020a3b4598574aa3a502708a6d96a1881cc0c642af6c11171c08a776eadcf58582f4cd7e47d9d14677cc46e2809db3fc4dd64ea95a3cd860cac9afa6833ef3c690fb0000bb95ddf5690471c0602d99b54858f00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000011a89c98e75983ec384ad8e83eaf7d0176eeaf155000000000000000000000000196ea7403b385b02634647717a096a71fb4d577a11bc37bc3794fb2d599b3c5d236ad77c8868ef28a0cfc7911f93b0dbfa2bfb0aea0aede56a84cffc7ecdee421ad7ab8dfda546fe13432afdddec94ff44d18c5307bd8e95dddccef8a8671d0200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000014f99b550623e77b807df7cbed9c79d55e1163b480000000000000000000000004ec86a31d8865abe7704e2059a58ec1d5e177325b0fc09c5ca9b2ff6fc515871eeeb764510e9f4f6159bcbb8750e99f7ef8d8611c503adb79a415adea3c6ef68924645a472462338ba1d39a83bb08be8de8cb63716e0f54caae9475208f3619400000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000', + ), + }, + ], + ChainSelector: 5009297550715157269n, +} + +const otherEnvironmentReplay: WorkflowReplay = { + Calls: [ + { + RequestValue: decodeHex( + '120e0a010310ffffffffffffffffff010a3c12147f3191eaf73429177bab3bac5c36ed2d5e39985f1a24235374050000000000000000000000000000000000000000000000000000000000000001', + ), + ResponseValue: decodeHex( + '0a803c0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000717d46e26bc3ff8f63ee9de35237e545a44371b08506fe5ef271e94916a4dc63e7c396cae7f777e6619a187d797f7f3636818777d88bbac532782ebc1a44baccd439c4912deb85b489723f72e58e5ba50ac39f8225b30dd8ab9dd5521d867702f5886de6eb4c1c4283dacbd678a1da430ea8bf1b7da7e767afb2ecd2ea6d930dbba27b0482262260c9c377c666405b0cdbecfc54c1a59b2ba9ecff04c9ed3a6c5df142f783baab355fdc201e24d999fa3f65ee2f7461b3a944e0c0c742599018a578a1b91f504453e42470255b6adffe81462809c5b7d83cb3a7b453ff9f41ed50000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000067a6f6e652d6100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011776f726b666c6f775f315f7a6f6e652d610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000ae000000000000000000000000000000000000000000000000000000000000011a00000000000000000000000000000000000000000000000000000000000001260000000000000000000000000000000000000000000000000000000000000192000000000000000000000000000000000000000000000000000000000000019e000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000009760af1120aee120a08456e636c6176657312e1122ade1212ac0922a9090a170a11456e636c6176654175746848656164657212020a000a160a10456e636c61766545787472614461746112021a000a2f0a09456e636c617665494412221a2066338ab3ebe1d246feb5849330e92022b9d66b0c0753b1f2ac6e356acbff20fc0a160a0b456e636c6176655479706512070a054e4954524f0a460a0a456e636c61766555524c12380a3668747470733a2f2f656e636c6176652d312e707269766163792e73746167652e65787465726e616c2e67726964646c652e73683a38300ae4070a0d5472757374656456616c75657312d2072acf0712c2021abf027b2270637230223a22353533383935656138373939373638373830346337666437363533303462613839663661643163656135623935333066393036616466616562373634376239326436623566623732656333633261303638356632333134623134323138623138222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22663361646365393734363332373531646362643131363532623035656630633666383133663931616638346562363430353033313336396530396435616464636139373436333431313162643039353563336637643237343939316364316638227d12c2021abf027b2270637230223a22376237643532376134336636363164396334633634623964393432333762623937383332343332373832633235633031633661343435333935303363303330383766613562616230323165396436663433643062663634396665653437343864222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22313363333837353839396431636239373136346464386132383165373431646538313966616236353337366262306139346335313636656631343930376339623166316161373433326234653639303138346339306433623738666663633161227d12c2021abf027b2270637230223a22336130646135323235666439323134323666633864613065376139316534323631623431333137336239626530303237616462326666626231616661653430616462613662663431626133663066653537386339303032646134653565613237222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22643938306538363964333066303333353130356563373236623234393430633636633130383130376362386636653661363638363561323562646464303537303363393231373964303334386661323936636166336332663633313036393631227d12ac0922a9090a170a11456e636c6176654175746848656164657212020a000a160a10456e636c61766545787472614461746112021a000a2f0a09456e636c617665494412221a206401f4177859d87613cdc14bd3841049840abaf6d0163cf77080227b8866ebe20a160a0b456e636c6176655479706512070a054e4954524f0a460a0a456e636c61766555524c12380a3668747470733a2f2f656e636c6176652d322e707269766163792e73746167652e65787465726e616c2e67726964646c652e73683a38300ae4070a0d5472757374656456616c75657312d2072acf0712c2021abf027b2270637230223a22353533383935656138373939373638373830346337666437363533303462613839663661643163656135623935333066393036616466616562373634376239326436623566623732656333633261303638356632333134623134323138623138222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22663361646365393734363332373531646362643131363532623035656630633666383133663931616638346562363430353033313336396530396435616464636139373436333431313162643039353563336637643237343939316364316638227d12c2021abf027b2270637230223a22376237643532376134336636363164396334633634623964393432333762623937383332343332373832633235633031633661343435333935303363303330383766613562616230323165396436663433643062663634396665653437343864222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22313363333837353839396431636239373136346464386132383165373431646538313966616236353337366262306139346335313636656631343930376339623166316161373433326234653639303138346339306433623738666663633161227d12c2021abf027b2270637230223a22336130646135323235666439323134323666633864613065376139316534323631623431333137336239626530303237616462326666626231616661653430616462613662663431626133663066653537386339303032646134653565613237222c2270637231223a22346234643562333636316233656663313239323039303063383065313236653463653738336335323264653663303261326135626637616633613262393332376238363737366631383865346265316331633430346131323964626461343933222c2270637232223a22643938306538363964333066303333353130356563373236623234393430633636633130383130376362386636653661363638363561323562646464303537303363393231373964303334386661323936636166336332663633313036393631227d400100000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000060a40014a850c0a0b5f5f64656661756c745f5f12f50b0a3a0114000a9dadc131e4e73c69a68881faf8e6a308cb3d8a0520008a138f84becd0d0b3cffd3e5c07f835d6fd6ec1c73c8897e566ee8e51e23a1100a3a0114004c691603d19278baf83a6fca9d6a19fb5fdbd4ec05200057a758ef576b2bc759d9e4099a9a2fdd9f36e38816594492c45b3621f698cf700a3a011400591be078e128b2393e748226659795281de0e8a705200026b2b8455ff0a7085a2629b8307cc22de5085e7b52a30a1bae61f91e2ae055c00a3a011400e75c048f6d663668a7973838fa2711221e50af94052000ebe25babaa9004d9c7395d4bb77c53de0914c769b50b1092a817c1789b2141450a3a0114002e93310b6b97e77e9ae25e4c365687c79f19ea1c05200053db2212eb20f2a03d440449ec86a92c6c943ef79ee30d7b74ca51f8358fcc2c0a3a0114004887166c424a781d043a2200db75e5d749c6020e052000794e4c8fddc426c659c0bd75a86322315ac83faf6958fb08f3ca89efcf50663e0a3a0114000369e9dd2c77dda3327b6f5809608105e9d3b4ca052000b6a50858ffd29bcdfafb0beb0e6a9591008789734c2176aa3ca33ab6ce79bb5a1214cb3549ac73ad2c1b551777cc9420c3d9ca9bcfbe12142f0287694abc561fe516106a3af22a317274d4ff1214ecdc973d36282505eabe963e45eca1d370a49f28121479936119db3bca54a6202efcf5eae306c12ffdab1214207574c9879bf6b3ffe30fe92dc21b64b5d1f493121411ff98c6da070010affb54d708047bbf92f0403b121436fdca6a6655a64db2f0e24dca32d978fd8694981802281e32ae07c80180a8d6b907d00180e497d012d80180cab5ee01e0018084af5fe80180d88ee16ff0010afa0101078202208af5e968a270655ff374d829e5e1bf88ff61b2a2945e15799d01ccbc1a7a045e8202204600a42ce908bb7c6d756165fef5db1e3152a83256e3783a525de103d09d7b888202201d6c9ae9a3058f3e653c71576cf33df497c688f3e461f931e907d749afd1b4608202208f81cd5b79c6f249e03866ed61aec09fe9b488d97012a5ceb8f1269843c68a768202204e9ce42151dd26209e2fca5dd4c3a580f7aa7a07ea82e6705357f10895abaf8b82022064413b48eba168c6667d27d2090a8e0728fb488638a5fcc945b14bc663e6fc8f82022062a0f487830c32db10c802e63df02967981ec504046003a36e1c892b7ab510c08a0234313244334b6f6f57425250524c3442466f73694644763172383333516177725537376f4e653942326d446171346f75697a5353528a0234313244334b6f6f574a42485758574b70504d6d784b354c6f414372776b6f65476545386d3356457a7352347864503334375157598a0234313244334b6f6f57454e48686f42525271676a764e776e66514d54435a683432316565586d78587a4164505a4b626157774b514a8a0234313244334b6f6f57466d774b6f4b695a5a466d687a42566842676d726e555a6e39467a59443432707051375456714d5a346747328a0234313244334b6f6f574e4d3336714c6d384b474e3674647a526361694750574e735a6458455871457935316246566f526f794c7a478a0234313244334b6f6f57517141774c325037516555516a637359573139734769466b6e356868786233516f41316b6d64564561764d508a0234313244334b6f6f574669356e486d4844366646594535486837575a71726d746832445064736e61484d77316632726a6e5a50544192021c0880804010808040188080c002208080c00228e80738901c42020828980280cab5ee01a00280cab5ee01a80280cab5ee01b00280cab5ee01ba02c2010a20e4f689f966fce6c5c7e4865310420bb4aa8fdee813050f00b9ba6c84b4a7fe721220f2246aaa3942288f1752820ebdf4e9783a71d894746e53992409bd6586b15ea31a10c9ed7748fbeda59af34a99ac7c963a371a109f4d4a36fd9af719d9dcbd24854093af1a1020e18ae075ca840807c3c68cf5e581081a10c3b6de0de744c5eb37aa2c74536be0421a10a56f33982b187226f17658861b06d2611a102680ffceddc5f45271a917ced0ddbbaf1a10fb2218a8ef6e42e17626a0cddcc82f5ac00280cab5ee01c8028094ebdc0338050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060740014a820c0a0b5f5f64656661756c745f5f12f20b0a3a0114000a9dadc131e4e73c69a68881faf8e6a308cb3d8a0520008a138f84becd0d0b3cffd3e5c07f835d6fd6ec1c73c8897e566ee8e51e23a1100a3a0114004c691603d19278baf83a6fca9d6a19fb5fdbd4ec05200057a758ef576b2bc759d9e4099a9a2fdd9f36e38816594492c45b3621f698cf700a3a011400591be078e128b2393e748226659795281de0e8a705200026b2b8455ff0a7085a2629b8307cc22de5085e7b52a30a1bae61f91e2ae055c00a3a011400e75c048f6d663668a7973838fa2711221e50af94052000ebe25babaa9004d9c7395d4bb77c53de0914c769b50b1092a817c1789b2141450a3a0114002e93310b6b97e77e9ae25e4c365687c79f19ea1c05200053db2212eb20f2a03d440449ec86a92c6c943ef79ee30d7b74ca51f8358fcc2c0a3a0114004887166c424a781d043a2200db75e5d749c6020e052000794e4c8fddc426c659c0bd75a86322315ac83faf6958fb08f3ca89efcf50663e0a3a0114000369e9dd2c77dda3327b6f5809608105e9d3b4ca052000b6a50858ffd29bcdfafb0beb0e6a9591008789734c2176aa3ca33ab6ce79bb5a1214cb3549ac73ad2c1b551777cc9420c3d9ca9bcfbe12142f0287694abc561fe516106a3af22a317274d4ff1214ecdc973d36282505eabe963e45eca1d370a49f28121479936119db3bca54a6202efcf5eae306c12ffdab1214207574c9879bf6b3ffe30fe92dc21b64b5d1f493121411ff98c6da070010affb54d708047bbf92f0403b121436fdca6a6655a64db2f0e24dca32d978fd8694981802281e32ab07c80180a8d6b907d00180e497d012e0018084af5fe80180d88ee16ff0010afa0101078202208af5e968a270655ff374d829e5e1bf88ff61b2a2945e15799d01ccbc1a7a045e8202204600a42ce908bb7c6d756165fef5db1e3152a83256e3783a525de103d09d7b888202201d6c9ae9a3058f3e653c71576cf33df497c688f3e461f931e907d749afd1b4608202208f81cd5b79c6f249e03866ed61aec09fe9b488d97012a5ceb8f1269843c68a768202204e9ce42151dd26209e2fca5dd4c3a580f7aa7a07ea82e6705357f10895abaf8b82022064413b48eba168c6667d27d2090a8e0728fb488638a5fcc945b14bc663e6fc8f82022062a0f487830c32db10c802e63df02967981ec504046003a36e1c892b7ab510c08a0234313244334b6f6f57425250524c3442466f73694644763172383333516177725537376f4e653942326d446171346f75697a5353528a0234313244334b6f6f574a42485758574b70504d6d784b354c6f414372776b6f65476545386d3356457a7352347864503334375157598a0234313244334b6f6f57454e48686f42525271676a764e776e66514d54435a683432316565586d78587a4164505a4b626157774b514a8a0234313244334b6f6f57466d774b6f4b695a5a466d687a42566842676d726e555a6e39467a59443432707051375456714d5a346747328a0234313244334b6f6f574e4d3336714c6d384b474e3674647a526361694750574e735a6458455871457935316246566f526f794c7a478a0234313244334b6f6f57517141774c325037516555516a637359573139734769466b6e356868786233516f41316b6d64564561764d508a0234313244334b6f6f574669356e486d4844366646594535486837575a71726d746832445064736e61484d77316632726a6e5a5054419202200880804010808040188080c002208080c002280130904e38c0843d4203088407980280cab5ee01a00280cab5ee01a80280cab5ee01b00280cab5ee01ba02c2010a20e4f689f966fce6c5c7e4865310420bb4aa8fdee813050f00b9ba6c84b4a7fe721220f2246aaa3942288f1752820ebdf4e9783a71d894746e53992409bd6586b15ea31a10c9ed7748fbeda59af34a99ac7c963a371a109f4d4a36fd9af719d9dcbd24854093af1a1020e18ae075ca840807c3c68cf5e581081a10c3b6de0de744c5eb37aa2c74536be0421a10a56f33982b187226f17658861b06d2611a102680ffceddc5f45271a917ced0ddbbaf1a10fb2218a8ef6e42e17626a0cddcc82f5ac00280cab5ee01c8028094ebdc03380200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000000000000024001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000000000000024001000000000000000000000000000000000000000000000000000000000000', + ), + }, + { + RequestValue: decodeHex( + '120e0a010310ffffffffffffffffff010abd0212147f3191eaf73429177bab3bac5c36ed2d5e39985f1aa40205a519660000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000717d46e26bc3ff8f63ee9de35237e545a44371b08506fe5ef271e94916a4dc63e7c396cae7f777e6619a187d797f7f3636818777d88bbac532782ebc1a44baccd439c4912deb85b489723f72e58e5ba50ac39f8225b30dd8ab9dd5521d867702f5886de6eb4c1c4283dacbd678a1da430ea8bf1b7da7e767afb2ecd2ea6d930dbba27b0482262260c9c377c666405b0cdbecfc54c1a59b2ba9ecff04c9ed3a6c5df142f783baab355fdc201e24d999fa3f65ee2f7461b3a944e0c0c742599018a578a1b91f504453e42470255b6adffe81462809c5b7d83cb3a7b453ff9f41ed5', + ), + ResponseValue: decodeHex( + '0aa03a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000008e00000000000000000000000000000000000000000000000000000000000000ce000000000000000000000000000000000000000000000000000000000000010e000000000000000000000000000000000000000000000000000000000000014e000000000000000000000000000000000000000000000000000000000000018e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000010a9dadc131e4e73c69a68881faf8e6a308cb3d8a00000000000000000000000017d46e26bc3ff8f63ee9de35237e545a44371b08506fe5ef271e94916a4dc63e896803ee748795154f4d43c781f6ff91c23d9e10697ff8fdb195581b6a2db8524240b57854dd1f21c10353ea458eecd8593624d0e0a7cca07c62a4b58df8c258000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000186f6666636861696e5f7265706f7274696e6740312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000014c691603d19278baf83a6fca9d6a19fb5fdbd4ec0000000000000000000000007c396cae7f777e6619a187d797f7f3636818777d88bbac532782ebc1a44baccd69b6edd515be8ca5acc9c94515b55bf6c635f6fafc452df3fc26098f7c179f0407d5475a1d4e14340fa6c94bcc81e77717314630d469689370f35dc7517dbed8000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000186f6666636861696e5f7265706f7274696e6740312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001591be078e128b2393e748226659795281de0e8a7000000000000000000000000439c4912deb85b489723f72e58e5ba50ac39f8225b30dd8ab9dd5521d867702f9d2d902db7c2d75ffc3dd4cff4a70f93e7ace5f8f9f4fff6f9e512239a2a29351c2d617ecc06b241b89dea2eaeeafe9da9b19a80e8c03e8e9c0b9c732b48255a000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000186f6666636861696e5f7265706f7274696e6740312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001e75c048f6d663668a7973838fa2711221e50af940000000000000000000000005886de6eb4c1c4283dacbd678a1da430ea8bf1b7da7e767afb2ecd2ea6d930dbf32436e8554e1ad99f3751682bea0c7058c3079295df2fc252c122c6a6256566099865d05106cee289724c3bdaefd435238dc39194493aab978ff3ee59d62d58000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000186f6666636861696e5f7265706f7274696e6740312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000012e93310b6b97e77e9ae25e4c365687c79f19ea1c000000000000000000000000ba27b0482262260c9c377c666405b0cdbecfc54c1a59b2ba9ecff04c9ed3a6c5deb8e63fb276addfcb987956da035d97b2f2279c8b367c349fe8174530a1822095263cf5c220fad97babc2967b311f7ba463353d56e78c410d3d0e8fcc798343000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000186f6666636861696e5f7265706f7274696e6740312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000014887166c424a781d043a2200db75e5d749c6020e000000000000000000000000df142f783baab355fdc201e24d999fa3f65ee2f7461b3a944e0c0c742599018aaf546e571c7ffa311382a263a6ba92a908d2e0b918010079bbce610a859da12c57335620009298b5705b7c1bb9b346cdc01e39b8241d6fda6163deecaea58721000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000186f6666636861696e5f7265706f7274696e6740312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000010369e9dd2c77dda3327b6f5809608105e9d3b4ca000000000000000000000000578a1b91f504453e42470255b6adffe81462809c5b7d83cb3a7b453ff9f41ed59dcbb68288a52c3439afd223c4414b678733b7667ff20e21d6261d1be5bc8c61837ec6a79c0e790244c4b14c93adb988db6dfc3e2042886785c3ae5871f43d68000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000186f6666636861696e5f7265706f7274696e6740312e302e300000000000000000000000000000000000000000000000000000000000000000000000000000001263726f6e2d7472696767657240312e302e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d616374696f6e7340312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000018687474702d7472696767657240312e302e302d616c70686100000000000000000000000000000000000000000000000000000000000000000000000000000015636f6e73656e73757340312e302e302d616c7068610000000000000000000000000000000000000000000000000000000000000000000000000000000000001d636f6e666964656e7469616c2d6874747040312e302e302d616c706861000000000000000000000000000000000000000000000000000000000000000000000d646f6e74696d6540312e302e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + ), + }, + ], + ChainSelector: 16015286601757825753n, +} + +function decodeHex(s: string): Uint8Array { + return hexToBytes(`0x${s}`) +} + +function sigsToBytes(sigsHex: string[]): Uint8Array[] { + return sigsHex.map((s) => decodeHex(s)) +} + +function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { + return a.length === b.length && a.every((v, i) => v === b[i]) +} + +const CALL_CONTRACT_REQUEST_TYPE_URL = + 'type.googleapis.com/capabilities.blockchain.evm.v1alpha.CallContractRequest' +const CALL_CONTRACT_REPLY_TYPE_URL = + 'type.googleapis.com/capabilities.blockchain.evm.v1alpha.CallContractReply' + +function setupDonSettingRead(replay: WorkflowReplay): { actualCalls: number } { + const state = { actualCalls: 0 } + const capId = `evm:ChainSelector:${replay.ChainSelector}@1.0.0` + registerTestCapability(capId, (req) => { + const callIdx = state.actualCalls++ + if (req.payload.typeUrl !== CALL_CONTRACT_REQUEST_TYPE_URL) { + return { + response: { + case: 'error', + value: `expected type url ${CALL_CONTRACT_REQUEST_TYPE_URL} got ${req.payload.typeUrl}`, + }, + } + } + if (callIdx >= replay.Calls.length) { + return { + response: { + case: 'error', + value: `unexpected call ${callIdx} (only ${replay.Calls.length} calls expected)`, + }, + } + } + const expected = replay.Calls[callIdx] + if (!bytesEqual(expected.RequestValue, req.payload.value)) { + return { + response: { + case: 'error', + value: `call ${callIdx}: request bytes mismatch`, + }, + } + } + return { + response: { + case: 'payload', + value: create(AnySchema, { + typeUrl: CALL_CONTRACT_REPLY_TYPE_URL, + value: expected.ResponseValue, + }), + }, + } + }) + return state +} + +// isErrorType mirrors Go's errors.Is: checks if err or any error in an +// AggregateError is an instance of the given class. +function isErrorType( + err: unknown, + cls: abstract new (...args: unknown[]) => T, +): boolean { + if (err instanceof cls) return true + if (err instanceof AggregateError) return err.errors.some((e) => isErrorType(e, cls)) + return false +} + +async function getReport(): Promise { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const sigsWithExtra: Uint8Array[] = [] + sigsWithExtra.push(reportSigs[0]) + sigsWithExtra.push(notASignerSig) + sigsWithExtra.push(...reportSigs.slice(1)) + sigsWithExtra.push(extraValidSig) + return Report.parse(runtime, rawReport, sigsWithExtra, reportContext) +} + +beforeEach(() => { + donInfoCache.clear() +}) + +test('SeqNr', async () => { + const report = await getReport() + expect(report.seqNr()).toBe(reportSeqNr) +}) + +test('ConfigDigest', async () => { + const report = await getReport() + expect(report.configDigest()).toEqual(reportDigest) +}) + +test('ReportContext', async () => { + const report = await getReport() + expect(report.reportContext()).toEqual(reportContext) +}) + +test('Version', async () => { + const report = await getReport() + expect(report.version()).toBe(reportVersion) +}) + +test('ExecutionID', async () => { + const report = await getReport() + expect(report.executionId()).toBe(reportExecID) +}) + +test('Timestamp', async () => { + const report = await getReport() + expect(report.timestamp()).toBe(reportTimestampUnix) +}) + +test('DONID', async () => { + const report = await getReport() + expect(report.donId()).toBe(reportDONID) +}) + +test('DONConfigVersion', async () => { + const report = await getReport() + expect(report.donConfigVersion()).toBe(reportDONCfgVer) +}) + +test('WorkflowID', async () => { + const report = await getReport() + expect(report.workflowId()).toBe(reportWfID) +}) + +test('WorkflowName', async () => { + const report = await getReport() + expect(report.workflowName()).toBe(reportWfName) +}) + +test('WorkflowOwner', async () => { + const report = await getReport() + expect(report.workflowOwner()).toBe(reportWfOwner) +}) + +test('ReportID', async () => { + const report = await getReport() + expect(report.reportId()).toBe(reportReportID) +}) + +test('Body', async () => { + const report = await getReport() + expect(new TextDecoder().decode(report.body())).toBe(reportBody) +}) + +test('Signatures includes signatures and ids of F+1 valid entries', async () => { + const report = await getReport() + const actual = report.x_generatedCodeOnly_unwrap().sigs + for (let i = 0; i < reportSigs.length; i++) { + expect(actual[i].signature).toEqual(reportSigs[i]) + expect(actual[i].signerId).toEqual(reportSigIds[i]) + } +}) + +describe('Report Parse', () => { + test('valid report', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const report = await Report.parse(runtime, rawReport, reportSigs, reportContext) + expect(report).not.toBeNull() + }) + + test('DON signatures are cached', async () => { + const mock = setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + await Report.parse(runtime, rawReport, reportSigs, reportContext) + await Report.parse(runtime, rawReport, reportSigs, reportContext) + // Two capability calls (getDON + getNodesByP2PIds) on the first parse; + // the second parse should hit the cache and make zero additional calls. + expect(mock.actualCalls).toBe(2) + }) + + test('valid report on specific DON', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const config = { + acceptedZones: [zoneFromEnvironment(productionEnvironment(), reportDONID)], + } + + const report = await Report.parse(runtime, rawReport, reportSigs, reportContext, config) + + expect(report).not.toBeNull() + }) + + test('invalid DON does not fetch signatures on chain', async () => { + const runtime = newTestRuntime() + const config = { + acceptedZones: [zoneFromEnvironment(productionEnvironment(), reportDONID + 1)], + } + await expect( + Report.parse(runtime, rawReport, reportSigs, reportContext, config), + ).rejects.toThrow(/DON ID 1 is not in accepted zones/) + }) + + test('zones not fetched across environments', async () => { + setupDonSettingRead(otherEnvironmentReplay) + const runtime = newTestRuntime() + const config = { + acceptedZones: [ + zoneFromEnvironment(productionEnvironment(), reportDONID + 1), + { + environment: { + chainSelector: otherEnvironmentReplay.ChainSelector, + registryAddress: '0x7f3191EaF73429177bAB3bAc5c36Ed2D5E39985f', + }, + donID: reportDONID, + }, + ], + } + + let err: unknown + try { + await Report.parse(runtime, rawReport, reportSigs, reportContext, config) + } catch (e) { + err = e + } + + expect(isErrorType(err, UnknownSignerError)).toBe(true) + }) + + test('DON id in multiple environments', async () => { + setupDonSettingRead(otherEnvironmentReplay) + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const config = { + acceptedZones: [ + { + environment: { + chainSelector: otherEnvironmentReplay.ChainSelector, + registryAddress: '0x7f3191EaF73429177bAB3bAc5c36Ed2D5E39985f', + }, + donID: reportDONID, + }, + zoneFromEnvironment(productionEnvironment(), reportDONID), + ], + } + const report = await Report.parse(runtime, rawReport, reportSigs, reportContext, config) + expect(report).not.toBeNull() + }) + + test('All fetch errors surfaced if zones cannot be checked', async () => { + setupDonSettingRead(otherEnvironmentReplay) + // Unexpected input will simulate a failure from the read: + // same Calls as otherEnvironmentReplay but different ChainSelector, + // so the request bytes won't match and the mock returns an error. + const unexpectedInput: WorkflowReplay = { + ...otherEnvironmentReplay, + ChainSelector: 17015286601757825753n, + } + setupDonSettingRead(unexpectedInput) + // Production (5009...) is intentionally NOT registered. + const runtime = newTestRuntime() + const config = { + acceptedZones: [ + { + environment: { + chainSelector: otherEnvironmentReplay.ChainSelector, + registryAddress: '0x7f3191EaF73429177bAB3bAc5c36Ed2D5E39985f', + }, + donID: reportDONID, + }, + { + environment: { + chainSelector: unexpectedInput.ChainSelector, + registryAddress: '0x89c9cf548b4179F8901cda1f8623568b58215E62', + }, + donID: reportDONID, + }, + { + environment: { + chainSelector: 18015286601757825753n, + registryAddress: '0x99c9cf548b4179F8901cda1f8623568b58215E62', + }, + // not included, not intended to read from + donID: reportDONID + 1, + }, + ], + acceptedEnvironments: [productionEnvironment()], + } + let err: unknown + try { + await Report.parse(runtime, rawReport, reportSigs, reportContext, config) + } catch (e) { + err = e + } + const msg = err instanceof Error ? err.message : String(err) + expect(msg).toContain('17015286601757825753') + expect(msg).toContain('0x89c9cf548b4179F8901cda1f8623568b58215E62') + expect(msg).toContain('5009297550715157269') + expect(msg).toContain('0x76c9cf548b4179F8901cda1f8623568b58215E62') + expect(msg).not.toContain('18015286601757825753') + }) + + describe('DON id in zone and environment', () => { + const cases: Array<{ + name: string + environment: Environment + zone: { environment: Environment; donID: number } + }> = [ + { + name: 'zone', + environment: { + chainSelector: otherEnvironmentReplay.ChainSelector, + registryAddress: '0x76c9cf548b4179F8901cda1f8623568b58215E62', + }, + zone: zoneFromEnvironment(productionEnvironment(), reportDONID), + }, + { + name: 'environment', + environment: productionEnvironment(), + zone: { + environment: { + chainSelector: otherEnvironmentReplay.ChainSelector, + registryAddress: '0x76c9cf548b4179F8901cda1f8623568b58215E62', + }, + donID: reportDONID, + }, + }, + ] + + for (const tt of cases) { + test(`Report in ${tt.name}`, async () => { + setupDonSettingRead(otherEnvironmentReplay) + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const config = { + acceptedEnvironments: [tt.environment], + acceptedZones: [tt.zone], + } + const report = await Report.parse(runtime, rawReport, reportSigs, reportContext, config) + expect(report).not.toBeNull() + }) + } + }) + + test('too few signatures fails validation', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + await expect( + Report.parse(runtime, rawReport, reportSigs.slice(0, 2), reportContext), + ).rejects.toThrow(WrongSignatureCountError) + }) + + test('invalid signature length fails validation', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const invalidSigs = [...reportSigs] + invalidSigs[1] = new Uint8Array(32) + let err: unknown + try { + await Report.parse(runtime, rawReport, invalidSigs, reportContext) + } catch (e) { + err = e + } + expect(isErrorType(err, ParseSignatureError)).toBe(true) + }) + + test('unrecoverable signature fails validation', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const invalidSigs = [...reportSigs] + invalidSigs[1] = new Uint8Array(65) + let err: unknown + try { + await Report.parse(runtime, rawReport, invalidSigs, reportContext) + } catch (e) { + err = e + } + expect(isErrorType(err, RecoverSignerError)).toBe(true) + }) + + test('invalid signatures fails validation', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const invalidSigs = [...reportSigs] + invalidSigs[1] = notASignerSig + let err: unknown + try { + await Report.parse(runtime, rawReport, invalidSigs, reportContext) + } catch (e) { + err = e + } + expect(isErrorType(err, UnknownSignerError)).toBe(true) + }) + + test('duplicated signatures fails validation', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const invalidSigs = [...reportSigs] + invalidSigs[2] = invalidSigs[1] + let err: unknown + try { + await Report.parse(runtime, rawReport, invalidSigs, reportContext) + } catch (e) { + err = e + } + expect(isErrorType(err, DuplicateSignerError)).toBe(true) + }) + + test('multiple signature failures all reported', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + // sigs[0]: too short → ParseSignatureError + // sigs[1]: valid + // sigs[2]: 65 zeros → RecoverSignerError + // sigs[3]: valid + // Only 2 valid sigs, need 4 (f+1), so all skip errors must be surfaced. + const invalidSigs = [...reportSigs] + invalidSigs[0] = new Uint8Array(32) // wrong length + invalidSigs[2] = new Uint8Array(65) // unrecoverable + let err: unknown + try { + await Report.parse(runtime, rawReport, invalidSigs, reportContext) + } catch (e) { + err = e + } + expect(isErrorType(err, ParseSignatureError)).toBe(true) + expect(isErrorType(err, RecoverSignerError)).toBe(true) + }) + + describe('invalid report format returns errors without reading from chain', () => { + for (const skipSignatureVerification of [false, true]) { + test(`skipSignatureVerification=${skipSignatureVerification}`, async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const config = { + acceptedEnvironments: [productionEnvironment()], + skipSignatureVerification, + } + await expect( + Report.parse( + runtime, + new TextEncoder().encode('Not a report'), + reportSigs, + new Uint8Array(0), + config, + ), + ).rejects.toThrow(RawReportTooShortError) + }) + } + }) + + test('skip signature validation invalid signatures', async () => { + setupDonSettingRead(productionEnvironmentReplay) + const runtime = newTestRuntime() + const invalidSigs = [...reportSigs] + invalidSigs[1] = new Uint8Array(32) + invalidSigs[1].set(reportSigs[1].subarray(0, 32)) + invalidSigs[1][2] = 0 + const config = { skipSignatureVerification: true } + const report = await Report.parse(runtime, rawReport, invalidSigs, reportContext, config) + expect(report).not.toBeNull() + }) + + test('invalid registry address returns an error', async () => { + const runtime = newTestRuntime() + const invalidAddress = 'Not real' + const config = { + acceptedEnvironments: [ + { + chainSelector: 1324n, + registryAddress: invalidAddress, + } satisfies Environment, + ], + } + let err: unknown + try { + await Report.parse(runtime, rawReport, reportSigs, reportContext, config) + } catch (e) { + err = e + } + const allMessages = + err instanceof AggregateError + ? err.errors.map((e) => (e instanceof Error ? e.message : String(e))).join(' ') + : err instanceof Error + ? err.message + : String(err) + expect(allMessages).toContain(`invalid registry address`) + expect(allMessages).toContain(invalidAddress) + }) +}) diff --git a/packages/cre-sdk/src/sdk/report.ts b/packages/cre-sdk/src/sdk/report.ts index d07ca1da..92719b9e 100644 --- a/packages/cre-sdk/src/sdk/report.ts +++ b/packages/cre-sdk/src/sdk/report.ts @@ -1,18 +1,654 @@ -import { fromJson } from '@bufbuild/protobuf' +import { create, fromJson } from '@bufbuild/protobuf' +import { BinaryReader, WireType } from '@bufbuild/protobuf/wire' +import { AnySchema } from '@bufbuild/protobuf/wkt' import { + AttributedSignatureSchema, + AwaitCapabilitiesRequestSchema, + CapabilityRequestSchema, type ReportResponse, type ReportResponseJson, ReportResponseSchema, } from '@cre/generated/sdk/v1alpha/sdk_pb' +import type { Address } from 'viem' +import { concatHex, getAddress, hexToBytes, keccak256, recoverAddress, toHex } from 'viem' +import { type Environment, productionEnvironment, type Zone } from './don-info' +import { + DuplicateSignerError, + NullReportError, + ParseSignatureError, + RawReportTooShortError, + RecoverSignerError, + UnknownSignerError, + WrongSignatureCountError, +} from './errors' +import { type DonInfo, donInfoCache } from './report-internals' +import type { BaseRuntime } from './runtime' + +const GET_DON_SELECTOR = new Uint8Array([0x23, 0x53, 0x74, 0x05]) +const GET_NODES_BY_P2P_IDS_SELECTOR = new Uint8Array([0x05, 0xa5, 0x19, 0x66]) + +function cacheKey(env: Environment, donID: number): string { + return `${env.chainSelector.toString()}:${donID}` +} + +function normalizeRegistryHex(addr: string): string { + return addr + .trim() + .replace(/^0[xX]/, '') + .toLowerCase() +} + +function isProductionEnvironmentForReport(env: Environment): boolean { + const pe = productionEnvironment() + return ( + env.chainSelector === pe.chainSelector && + normalizeRegistryHex(env.registryAddress) === normalizeRegistryHex(pe.registryAddress) + ) +} + +function decodeRegistryAddress(registryAddress: string): Uint8Array { + const hex = normalizeRegistryHex(registryAddress) + if (hex.length !== 40) { + throw new Error(`invalid registry address ${JSON.stringify(registryAddress)}`) + } + return hexToBytes(`0x${hex}`) +} + +function padUint256(v: bigint | number): Uint8Array { + const n = typeof v === 'bigint' ? v : BigInt(v) + const b = new Uint8Array(32) + const view = new DataView(b.buffer) + view.setBigUint64(24, n, false) + return b +} + +function bytesToBigIntBE(word: Uint8Array): bigint { + let x = 0n + for (let i = 0; i < word.length; i++) { + x = (x << 8n) | BigInt(word[i]) + } + return x +} + +function readUint256AsInt(word: Uint8Array): number { + const b = bytesToBigIntBE(word) + if (b > BigInt(Number.MAX_SAFE_INTEGER)) { + throw new Error('ABI uint256 value too large for Number') + } + return Number(b) +} + +function protoVarint(v: number | bigint): number[] { + const out: number[] = [] + let n = typeof v === 'bigint' ? v : BigInt(v) + while (n >= 128n) { + out.push(Number(n & 0x7fn) | 0x80) + n >>= 7n + } + out.push(Number(n)) + return out +} + +function protoTag(field: number, wireType: 0 | 2): number[] { + return protoVarint((field << 3) | wireType) +} + +function protoLenBytes(data: Uint8Array): number[] { + return [...protoVarint(data.length), ...data] +} + +function buildCallContractRequestBytes(registryAddr: Uint8Array, callData: Uint8Array): Uint8Array { + const callMsg = new Uint8Array([ + ...protoTag(2, 2), + ...protoLenBytes(registryAddr), + ...protoTag(3, 2), + ...protoLenBytes(callData), + ]) + const bigInt = new Uint8Array([ + ...protoTag(1, 2), + ...protoLenBytes(new Uint8Array([0x03])), + ...protoTag(2, 0), + ...protoVarint(0xffffffffffffffffn), + ]) + return new Uint8Array([ + ...protoTag(2, 2), + ...protoLenBytes(bigInt), + ...protoTag(1, 2), + ...protoLenBytes(callMsg), + ]) +} + +function decodeCallContractReplyData(bytes: Uint8Array): Uint8Array { + const reader = new BinaryReader(bytes) + while (reader.pos < reader.len) { + const [fieldNo, wireType] = reader.tag() + if (fieldNo === 1 && wireType === WireType.LengthDelimited) { + return reader.bytes() + } + reader.skip(wireType) + } + throw new Error('data field not found in CallContractReply') +} + +const CALL_CONTRACT_REQUEST_TYPE_URL = + 'type.googleapis.com/capabilities.blockchain.evm.v1alpha.CallContractRequest' + +function callContract( + runtime: BaseRuntime, + capID: string, + registryAddr: Uint8Array, + callData: Uint8Array, +): Uint8Array { + const reqBytes = buildCallContractRequestBytes(registryAddr, callData) + const anyPayload = create(AnySchema, { + typeUrl: CALL_CONTRACT_REQUEST_TYPE_URL, + value: reqBytes, + }) + // biome-ignore lint/suspicious/noExplicitAny: intentional bypass of typed callCapability to avoid EVM schema imports + const rt = runtime as any + const callbackId: number = rt.nextCallId++ + const req = create(CapabilityRequestSchema, { + id: capID, + method: 'CallContract', + payload: anyPayload, + callbackId, + }) + + if (!rt.helpers.call(req)) { + throw new Error(`EVM capability '${capID}' not found`) + } + + // Must use create() — the real WASM bridge serializes this as a protobuf message and + // requires a proper typed message (with $typeName). A plain { ids: [...] } object works + // in the test runtime but silently breaks in simulation. + const awaitResp = rt.helpers.await( + create(AwaitCapabilitiesRequestSchema, { ids: [callbackId] }), + rt.maxResponseSize, + ) + const capResp = awaitResp?.responses?.[callbackId] + if (!capResp) { + throw new Error(`no response from EVM capability '${capID}'`) + } + const response = capResp.response + if (response.case === 'error') { + throw new Error(response.value as string) + } + if (response.case !== 'payload') { + throw new Error(`unexpected response '${response.case}' from EVM capability '${capID}'`) + } + return decodeCallContractReplyData((response.value as { value: Uint8Array }).value) +} + +function encodeGetDONCalldata(donID: number): Uint8Array { + const padded = new Uint8Array(32) + const view = new DataView(padded.buffer) + view.setUint32(28, donID >>> 0, false) + const out = new Uint8Array(4 + 32) + out.set(GET_DON_SELECTOR, 0) + out.set(padded, 4) + return out +} + +function concatBytes(parts: readonly Uint8Array[]): Uint8Array { + const total = parts.reduce((n, p) => n + p.length, 0) + const r = new Uint8Array(total) + let o = 0 + for (const p of parts) { + r.set(p, o) + o += p.length + } + return r +} + +function encodeGetNodesByP2PIdsCalldata(p2pIds: Uint8Array[]): Uint8Array { + const chunks: Uint8Array[] = [ + GET_NODES_BY_P2P_IDS_SELECTOR, + padUint256(32), + padUint256(p2pIds.length), + ] + for (const id of p2pIds) { + if (id.length !== 32) { + throw new Error('p2p id must be 32 bytes') + } + chunks.push(new Uint8Array(id)) + } + return concatBytes(chunks) +} + +const NODE_TUPLE_HEAD_SIZE = 288 + +function fetchDONInfo(runtime: BaseRuntime, env: Environment, donID: number): DonInfo { + const key = cacheKey(env, donID) + const hit = donInfoCache.get(key) + if (hit) { + return hit + } + + const registryAddr = decodeRegistryAddress(env.registryAddress) + const capID = `evm:ChainSelector:${env.chainSelector.toString()}@1.0.0` + + const getDONABI = callContract(runtime, capID, registryAddr, encodeGetDONCalldata(donID)) + + if (getDONABI.length < 224) { + throw new Error(`getDON ABI response too short: ${getDONABI.length} bytes`) + } + + const f = readUint256AsInt(getDONABI.subarray(96, 128)) + + const tupleStart = 32 + const nodeP2PIdsPtr = readUint256AsInt(getDONABI.subarray(192, 224)) + const nodeCountOff = tupleStart + nodeP2PIdsPtr + if (nodeCountOff + 32 > getDONABI.length) { + throw new Error('getDON ABI: nodeP2PIds pointer out of range') + } + const nodeCount = readUint256AsInt(getDONABI.subarray(nodeCountOff, nodeCountOff + 32)) + if (nodeCountOff + 32 + nodeCount * 32 > getDONABI.length) { + throw new Error('getDON ABI: nodeP2PIds data out of range') + } + + const nodeP2PIds: Uint8Array[] = [] + for (let i = 0; i < nodeCount; i++) { + const start = nodeCountOff + 32 + i * 32 + nodeP2PIds.push(getDONABI.subarray(start, start + 32)) + } + + if (nodeCount === 0) { + const info: DonInfo = { f, signers: new Map() } + donInfoCache.set(key, info) + return info + } + + const getNodesABI = callContract( + runtime, + capID, + registryAddr, + encodeGetNodesByP2PIdsCalldata(nodeP2PIds), + ) + + if (getNodesABI.length < 64) { + throw new Error(`getNodesByP2PIds ABI response too short: ${getNodesABI.length} bytes`) + } + + const outerPtr = readUint256AsInt(getNodesABI.subarray(0, 32)) + if (outerPtr + 32 > getNodesABI.length) { + throw new Error('getNodesByP2PIds ABI: outer pointer out of range') + } + const returnedCount = readUint256AsInt(getNodesABI.subarray(outerPtr, outerPtr + 32)) + + const signers = new Map() + for (let i = 0; i < returnedCount; i++) { + const elemPtrOff = outerPtr + 32 + i * 32 + if (elemPtrOff + 32 > getNodesABI.length) { + break + } + const elemPtr = readUint256AsInt(getNodesABI.subarray(elemPtrOff, elemPtrOff + 32)) + const tupleBase = outerPtr + 32 + elemPtr + if (tupleBase + NODE_TUPLE_HEAD_SIZE > getNodesABI.length) { + break + } + const nodeOperatorId = Number( + bytesToBigIntBE(getNodesABI.subarray(tupleBase, tupleBase + 32)) & 0xffffffffn, + ) + const signerSlot = tupleBase + 3 * 32 + const addrBytes = getNodesABI.subarray(signerSlot, signerSlot + 20) + const addr = getAddress(toHex(addrBytes)) + signers.set(addr, nodeOperatorId) + } + + const info: DonInfo = { f, signers } + donInfoCache.set(key, info) + return info +} + +function computeReportHash(rawReport: Uint8Array, reportContext: Uint8Array): `0x${string}` { + const innerHash = keccak256(toHex(rawReport)) + return keccak256(concatHex([innerHash, toHex(reportContext)])) +} + +function addressKeyNo0x(addr: `0x${string}`): string { + return addr.slice(2).toLowerCase() +} + +async function verifySigs( + report: ReportResponse, + f: number, + signers: Map, +): Promise { + const required = f + 1 + const sigs = report.sigs + if (sigs.length < required) { + throw new WrongSignatureCountError() + } + + const reportHash = computeReportHash(report.rawReport, report.reportContext) + const seen = new Set() + const accepted: typeof sigs = [] + const skipErrs: Error[] = [] + + for (let i = 0; i < sigs.length; i++) { + if (accepted.length === required) { + break + } + + const attrSig = sigs[i] + const sigBytes = new Uint8Array(attrSig.signature) + + if (sigBytes.length !== 65) { + skipErrs.push(new ParseSignatureError()) + continue + } + + const normalized = new Uint8Array(sigBytes) + if (normalized[64] === 27 || normalized[64] === 28) { + normalized[64] -= 27 + } + + let recovered: `0x${string}` + try { + recovered = await recoverAddress({ + hash: reportHash, + signature: toHex(normalized), + }) + } catch { + skipErrs.push(new RecoverSignerError()) + continue + } + + const key = addressKeyNo0x(recovered) + if (seen.has(key)) { + skipErrs.push(new DuplicateSignerError()) + continue + } + seen.add(key) + + const nodeOperatorId = signers.get(key) + if (nodeOperatorId === undefined) { + skipErrs.push(new UnknownSignerError()) + continue + } + + attrSig.signerId = nodeOperatorId + accepted.push(attrSig) + } + + if (accepted.length < required) { + if (skipErrs.length > 0) { + throw new AggregateError(skipErrs) + } + throw new WrongSignatureCountError() + } + + report.sigs = accepted +} + +export type ReportParseConfig = { + acceptedZones?: Zone[] + acceptedEnvironments?: Environment[] + skipSignatureVerification?: boolean +} + +function mergeReportParseConfig( + overrides?: ReportParseConfig, +): Required< + Pick +> { + return { + acceptedZones: overrides?.acceptedZones ?? [], + acceptedEnvironments: + overrides !== undefined ? (overrides.acceptedEnvironments ?? []) : [productionEnvironment()], + skipSignatureVerification: overrides?.skipSignatureVerification ?? false, + } +} + +function normalizeDonSigners(signers: Map): Map { + const out = new Map() + for (const [addr, id] of signers) { + out.set(addr.slice(2).toLowerCase(), id) + } + return out +} + +export const REPORT_METADATA_HEADER_LENGTH = 109 + +const REPORT_METADATA_OFFSETS = { + version: 0, + versionSize: 1, + executionId: 1, + executionIdSize: 32, + timestamp: 33, + timestampSize: 4, + donId: 37, + donIdSize: 4, + donConfigVersion: 41, + donConfigVersionSize: 4, + workflowId: 45, + workflowIdSize: 32, + workflowName: 77, + workflowNameSize: 10, + workflowOwner: 87, + workflowOwnerSize: 20, + reportId: 107, + reportIdSize: 2, + bodyStart: 109, +} as const + +export type ReportMetadataHeader = { + version: number + executionId: string + timestamp: number + donId: number + donConfigVersion: number + workflowId: string + workflowName: string + workflowOwner: string + reportId: string + body: Uint8Array +} + +function encodeHexLower(bytes: Uint8Array): string { + let out = '' + for (let i = 0; i < bytes.length; i++) { + out += bytes[i].toString(16).padStart(2, '0') + } + return out +} + +function readUint32BE(raw: Uint8Array, offset: number): number { + return new DataView(raw.buffer, raw.byteOffset + offset, 4).getUint32(0, false) +} + +function parseReportMetadataHeader(raw: Uint8Array | null | undefined): ReportMetadataHeader { + if (raw === undefined || raw === null) { + throw new NullReportError() + } + if (raw.length < REPORT_METADATA_HEADER_LENGTH) { + throw new RawReportTooShortError(REPORT_METADATA_HEADER_LENGTH, raw.length) + } + + const o = REPORT_METADATA_OFFSETS + const workflowNameBytes = raw.subarray(o.workflowName, o.workflowName + o.workflowNameSize) + + return { + version: raw[o.version], + executionId: encodeHexLower(raw.subarray(o.executionId, o.executionId + o.executionIdSize)), + timestamp: readUint32BE(raw, o.timestamp), + donId: readUint32BE(raw, o.donId), + donConfigVersion: readUint32BE(raw, o.donConfigVersion), + workflowId: encodeHexLower(raw.subarray(o.workflowId, o.workflowId + o.workflowIdSize)), + workflowName: new TextDecoder('utf-8', { fatal: false }).decode(workflowNameBytes), + workflowOwner: encodeHexLower( + raw.subarray(o.workflowOwner, o.workflowOwner + o.workflowOwnerSize), + ), + reportId: encodeHexLower(raw.subarray(o.reportId, o.reportId + o.reportIdSize)), + body: raw.subarray(REPORT_METADATA_HEADER_LENGTH), + } +} export class Report { private readonly report: ReportResponse + private cachedHeader: ReportMetadataHeader | undefined + public constructor(report: ReportResponse | ReportResponseJson) { this.report = (report as unknown as { $typeName?: string }).$typeName ? (report as ReportResponse) : fromJson(ReportResponseSchema, report as ReportResponseJson) } + public static async parse( + runtime: BaseRuntime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, + ): Promise { + const configDigest = + reportContext.length >= 32 ? reportContext.slice(0, 32) : new Uint8Array(32) + const seqNr = + reportContext.length >= 40 + ? new DataView(reportContext.buffer, reportContext.byteOffset + 32, 8).getBigUint64( + 0, + false, + ) + : 0n + const reportResponse = create(ReportResponseSchema, { + configDigest, + seqNr, + reportContext, + rawReport, + sigs: signatures.map((signature) => + create(AttributedSignatureSchema, { signature, signerId: 0 }), + ), + }) + + const merged = mergeReportParseConfig(config) + const report = new Report(reportResponse) + if (merged.skipSignatureVerification) { + report.donId() + return report + } + await report.verifySignaturesWithConfig(runtime, merged) + return report + } + + private parseHeader(): ReportMetadataHeader { + if (this.cachedHeader !== undefined) { + return this.cachedHeader + } + this.cachedHeader = parseReportMetadataHeader(this.report.rawReport) + return this.cachedHeader + } + + private async verifySignaturesWithConfig( + runtime: BaseRuntime, + config: Required< + Pick< + ReportParseConfig, + 'acceptedZones' | 'acceptedEnvironments' | 'skipSignatureVerification' + > + >, + ): Promise { + const donId = this.donId() + const candidates: Environment[] = [] + for (const z of config.acceptedZones) { + if (z.donID === donId) { + candidates.push(z.environment) + } + } + candidates.push(...config.acceptedEnvironments) + + if (candidates.length === 0) { + throw new Error(`DON ID ${donId} is not in accepted zones`) + } + + const fetchFailures: Error[] = [] + let lastVerifyErr: Error | null = null + + for (const env of candidates) { + let info: DonInfo + try { + info = fetchDONInfo(runtime, env, donId) + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + fetchFailures.push( + new Error( + `could not read from chain ${env.chainSelector} contract ${env.registryAddress}: ${msg}`, + ), + ) + continue + } + + try { + await verifySigs(this.report, info.f, normalizeDonSigners(info.signers)) + return + } catch (err) { + lastVerifyErr = err instanceof Error ? err : new Error(String(err)) + } + } + + if (fetchFailures.length > 0) { + throw new AggregateError(fetchFailures, fetchFailures.map((e) => e.message).join('\n')) + } + if (lastVerifyErr !== null) { + throw lastVerifyErr + } + } + + public seqNr(): bigint { + return this.report.seqNr + } + + public configDigest(): Uint8Array { + return this.report.configDigest + } + + public reportContext(): Uint8Array { + return this.report.reportContext + } + + public rawReport(): Uint8Array { + return this.report.rawReport + } + + public version(): number { + return this.parseHeader().version + } + + public executionId(): string { + return this.parseHeader().executionId + } + + public timestamp(): number { + return this.parseHeader().timestamp + } + + public donId(): number { + return this.parseHeader().donId + } + + public donConfigVersion(): number { + return this.parseHeader().donConfigVersion + } + + public workflowId(): string { + return this.parseHeader().workflowId + } + + public workflowName(): string { + return this.parseHeader().workflowName + } + + public workflowOwner(): string { + return this.parseHeader().workflowOwner + } + + public reportId(): string { + return this.parseHeader().reportId + } + + public body(): Uint8Array { + return this.parseHeader().body + } + // x_generatedCodeOnly_unwrap is meant to be used by the code generator only. x_generatedCodeOnly_unwrap(): ReportResponse { return this.report diff --git a/packages/cre-sdk/src/sdk/testutils/test-runtime.ts b/packages/cre-sdk/src/sdk/testutils/test-runtime.ts index 01844874..94e94882 100644 --- a/packages/cre-sdk/src/sdk/testutils/test-runtime.ts +++ b/packages/cre-sdk/src/sdk/testutils/test-runtime.ts @@ -9,6 +9,7 @@ import { create, toBinary } from '@bufbuild/protobuf' import type { Any } from '@bufbuild/protobuf/wkt' import { anyPack, anyUnpack } from '@bufbuild/protobuf/wkt' import type { + AwaitCapabilitiesRequest, AwaitCapabilitiesResponse, AwaitSecretsResponse, CapabilityResponse, @@ -237,7 +238,18 @@ function createTestRuntimeHelpers( return true }, - await(request: { ids: number[] }, maxResponseSizeBytes: bigint): AwaitCapabilitiesResponse { + await( + request: AwaitCapabilitiesRequest, + maxResponseSizeBytes: bigint, + ): AwaitCapabilitiesResponse { + if ( + (request as unknown as { $typeName?: string }).$typeName !== + 'sdk.v1alpha.AwaitCapabilitiesRequest' + ) { + throw new Error( + 'await: expected a typed AwaitCapabilitiesRequest (created via create(AwaitCapabilitiesRequestSchema, ...)); got a plain object. The real WASM bridge serializes this to binary and will fail with a plain object.', + ) + } const responses: Record = {} for (const id of request.ids) { const resp = pendingCalls.get(id)