diff --git a/README.md b/README.md index 5701672..499d895 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,18 @@ live AERE L1 (chain ID 2800): the DeFi, agentic-settlement, compliance, account-abstraction and post-quantum contract surface, the seedless hybrid PQC wallet, and AERE's native NIST post-quantum precompiles. -**SDK version:** 0.16.12 -**Last updated:** 2026-08-19 (consensus and network facts; the rest is as of 2026-07-16) +**SDK version:** 0.16.13 +**Last updated:** 2026-10-15 (network facts, state window, client selectors; the rest is as of 2026-07-16) + +**0.16.13, 2026-10-15: client selectors repaired.** Until this release about 130 hand-written function selectors in ten +clients (RaaS, Sink, Agent and x402 settlement, state channels, compliance, lending, the compliant pool) did not match +the deployed contracts, so calls through them reverted. Every selector is now the keccak of a signature in the compiled +ABI, checked by a gate on each change, and `src/test/selectors-live.ts` reads the deployed contracts through the shipped +clients. API changes: `AereSanctionsRegistryClient` gains `latestEpoch()`, `isSanctioned(epoch, ...)` and +`isSanctionedLatest(...)`, and `currentRoot(epoch?)` now reads the root of an epoch; `ChainalysisOracleWrapperClient` +gains `upstreamOracle()`; `AereTravelRuleHashRegistryClient.status` takes a commitment id; `AereInsuranceFundClient` +registers a market in two steps, `proposeMarket` then `registerMarket`; `AereRaaSFactoryClient.registerRollup` targets +the canonical `AereRaaSFactoryV2` and accepts an optional `defenseGrace`. **Network state at this release:** 35+ typed contract clients over a canonical address book of 130+ live contracts on chain 2800. @@ -18,10 +28,10 @@ address book of 130+ live contracts on chain 2800. |---|---| | Chain name | AERE Network | | Chain ID | 2800 (`0xAF0`) | -| Consensus | Hyperledger Besu QBFT, classical secp256k1 ECDSA | -| Validators | 7, Byzantine-fault tolerance f=2, commit quorum 5 of 7 | -| Operator / client diversity | Single operator (Foundation), single client (Besu) today | -| Block period | 0.5 s (500 ms) target, sub-second deterministic finality | +| Consensus | QBFT; per-block finality on classical secp256k1 ECDSA commit seals, with post-quantum validator seals on consensus messages and a post-quantum certificate under every 32nd block's hash (details on aere.network) | +| Validators | 10 since 2026-09-11, Byzantine-fault tolerance f=3, commit quorum 7 of 10 | +| Operator / client diversity | Single operator (the Foundation); two client implementations, Besu-derived and Nethermind-derived (one validator, since 2026-09-11) | +| Block period | 0.5 s target; measured 0.62 s (median of five 20,000-block windows, 2026-10-15); deterministic finality | | Native token | AERE (18 decimals) | | Max supply | 2,800,000,000 AERE (capped at genesis-v2, 2026-05-07) | | EVM ruleset | Pectra + Fusaka opcode and precompile set (Besu), plus five AERE post-quantum precompiles; not full parity with Ethereum mainnet (the beacon-chain system contracts, e.g. EIP-4788 beacon roots, have no code on chain 2800) | @@ -245,28 +255,31 @@ Options: $ aere chain AERE chain — https://rpc.aere.network Chain ID 2800 (0xaf0) - Block height 9520927 - Base fee 1.0 gwei - Gas price 1.0 gwei - Validators 7 - Peers 7 + Block height 22945693 + Base fee 1.0 gwei (1000000000 wei) + Gas price 1.0 gwei (1000000000 wei) + Validators 10 + - 0x38fb1510d4d11624564a74e6ebfd752e666849c3 + ... ``` +(Output of 2026-10-15. Since block 20,836,228 the validator list holds post-quantum validator ids, not ECDSA addresses.) ## State window on the public RPC (read before pinning a historical block) Both public endpoints are **pruning full nodes**, not archive nodes. - Blocks, transactions, receipts, logs and `qbft_getValidatorsByBlockNumber` go back to **block 0**. -- **World state is kept for only about the most recent 512 blocks**, roughly 4 minutes 25 seconds - at the measured 517 ms block interval. That bounds `eth_getBalance`, `eth_getCode`, +- **World state is kept for only about the most recent 512 blocks**, roughly 5 minutes 17 seconds + at the 0.62 s block interval measured on 2026-10-15. That bounds `eth_getBalance`, `eth_getCode`, `eth_getStorageAt`, `eth_call`, `eth_getProof` and `eth_getTransactionCount`. -Outside the window most methods say so: `eth_getBalance` / `eth_getCode` / `eth_getStorageAt` -return `null`, and `eth_getProof` returns `-32000 World state unavailable`. -**`eth_getTransactionCount` does not.** It returns `0x0`, which is well formed and -indistinguishable from an account that has never transacted. Reproduce it yourself: block -8,236,382 contains a transaction from `0xbeb33d20dfbbd49ec7ac1f617667f1f02dfd6465` with nonce -123,063, and the endpoint still answers `0x0` for that address at that block. +Outside the window both public endpoints now say so for every state method: measured on 2026-10-15, +`eth_getTransactionCount`, `eth_getBalance` and `eth_getProof` all return `-32000 World state unavailable`. +Until October 2026 they did not: `eth_getBalance` / `eth_getCode` / `eth_getStorageAt` returned `null`, and +**`eth_getTransactionCount` returned `0x0`**, well formed and indistinguishable from an account that has +never transacted (block 8,236,382 holds a transaction from `0xbeb33d20dfbbd49ec7ac1f617667f1f02dfd6465` with +nonce 123,063, and the endpoint answered `0x0` for that address at that block). Other endpoints and other +node versions may still answer that way, which is why the reader below keeps its guards. `StateWindowReader` returns a value or throws. It never returns a placeholder. @@ -301,11 +314,11 @@ pinning a historical block; and never test a nonce for zero at a historical bloc pass that signer into the SDK, or use the seedless PQC wallet above. - **Fiat rails are out of scope.** AERE is the on-chain settlement layer; fiat IBANs / cards / SEPA come from a BaaS partner (Striga, Baanx, Kulipa, etc.). -- **Decentralization.** 7 QBFT validators give f=2 (the chain keeps producing - blocks while up to 2 validators are down), commit quorum 5 of 7. All 7 are - operated by the Foundation on a single client (Besu), so this is - fault-tolerance, not yet operator or client diversity. Broader validator - operators and a second client are roadmap. +- **Decentralization.** 10 QBFT validators give f=3 (the chain keeps producing + blocks while up to 3 validators are down), commit quorum 7 of 10. All 10 are + operated by the Foundation; nine run the Besu-derived client and one, since + 2026-09-11, the Nethermind-derived client. That is fault tolerance and a second + code base, not yet operator diversity: independent validator operators are roadmap. - **Foundation key, not a multisig.** `0x0243A4f47D44b40b65D33f20329dE20D00c6f3C3` is a single-key EOA (zero bytecode on-chain), described here as a Foundation-controlled account. A real Safe-style threshold multisig is roadmap, diff --git a/package.json b/package.json index f63c2c4..4272c62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aere/sdk", - "version": "0.16.12", + "version": "0.16.13", "description": "Official AERE Network SDK: typed ethers v6 clients for AERE L1 (chain ID 2800). 35+ typed clients over a 130+ contract address book, covering core DeFi, agentic x402 settlement, compliance, ERC-4337 account abstraction, t-of-n post-quantum custody, the 5 live NIST PQC precompiles (Falcon-512/1024, ML-DSA-44, SLH-DSA-128s, SHAKE256), and a seedless hybrid PQC wallet.", "type": "module", "main": "./dist/index.js", @@ -24,7 +24,7 @@ ], "scripts": { "build": "tsc", - "test": "node --test dist/test/*.js", + "test": "node scripts/run-tests.mjs", "test:live": "node dist/test/state-window-live.js" }, "keywords": [ diff --git a/scripts/run-tests.mjs b/scripts/run-tests.mjs new file mode 100644 index 0000000..6af7126 --- /dev/null +++ b/scripts/run-tests.mjs @@ -0,0 +1,14 @@ +// Runs the offline test suite: every dist/test/*.js except the *-live.js proofs, which need the network +// (run those explicitly, e.g. `npm run test:live`). Portable: no shell globbing, works the same under cmd and sh. +import { readdirSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; + +const files = readdirSync('dist/test') + .filter((f) => f.endsWith('.js') && !/-live(\.test)?\.js$/.test(f)) + .map((f) => `dist/test/${f}`); +if (files.length === 0) { + console.error('no compiled tests in dist/test; run `npm run build` first'); + process.exit(2); +} +const r = spawnSync(process.execPath, ['--test', ...files], { stdio: 'inherit' }); +process.exit(r.status ?? 1); diff --git a/src/agentic/AereAgentClient.ts b/src/agentic/AereAgentClient.ts index 4621647..98431f3 100644 --- a/src/agentic/AereAgentClient.ts +++ b/src/agentic/AereAgentClient.ts @@ -8,14 +8,14 @@ export interface Eip1193Provider { // 4-byte selectors (precomputed; never call eth_sign just to derive them). const SEL = { - registerAgent: '0xa30b3acc', // registerAgent(address,bytes32,bytes32,uint128,uint128,uint128,string) - setSigner: '0x6c19e783', // setSigner(uint256,address) - setRateLimits: '0x44dec0e2', // setRateLimits(uint256,uint128,uint128,uint128) - setPaused: '0x8456db15', // setPaused(uint256,bool) - topUp: '0xc35a8e8b', // topUp(uint256,address,uint256) - withdraw: '0x069a89ec', // withdraw(uint256,address,uint256,address) - signerOf: '0x9f9eb5b1', // signerOf(uint256) view returns (address) - balances: '0xa7263000', // balances(uint256,address) view returns (uint256) + registerAgent: '0x8d471e06', // registerAgent(address,bytes32,bytes32,uint128,uint128,uint128,string) + setSigner: '0x85267907', // setSigner(uint256,address) + setRateLimits: '0x70c4e4df', // setRateLimits(uint256,uint128,uint128,uint128) + setPaused: '0xdaff97b5', // setPaused(uint256,bool) + topUp: '0x16645824', // topUp(uint256,address,uint256) + withdraw: '0x5f0f48bd', // withdraw(uint256,address,uint256,address) + signerOf: '0x5161fdf5', // signerOf(uint256) view returns (address) + balances: '0x1f320331', // balances(uint256,address) view returns (uint256) } as const; function pad32Hex(hex: string): string { @@ -175,7 +175,7 @@ export function buildAuthTypedData(opts: { }; } -const SEL_SETTLE = '0x0c0a40c3'; // settle(uint256,address,address,uint256,bytes32,uint256,uint256,bytes) +const SEL_SETTLE = '0xdf3beb47'; // settle(uint256,address,address,uint256,bytes32,uint256,uint256,bytes) export class AERE402SettlementClient { constructor(public readonly provider: Eip1193Provider, public readonly facilitator: string) {} diff --git a/src/agentic/NewAgenticClients.ts b/src/agentic/NewAgenticClients.ts index 3198646..effc43f 100644 --- a/src/agentic/NewAgenticClients.ts +++ b/src/agentic/NewAgenticClients.ts @@ -135,10 +135,10 @@ export class AereAIReputationClient { const INFER_SEL = { modelEpochCount: '0xf407005b', modelOf: '0x1548a0e9', - epochOf: '0xc1a5ae12', + epochOf: '0x033ee480', // epochOf(address,bytes32,uint64) registerModel: '0x703722ac', commitEpoch: '0x7f11fb10', - verifyInference: '0xa2a98a64', + verifyInference: '0x46a121a5', // verifyInference(address,bytes32,uint64,bytes32,bytes32,uint64,bytes32,bytes32[]) } as const; export class AereInferNetClient { diff --git a/src/channels/index.ts b/src/channels/index.ts index abe78b6..dbb8a08 100644 --- a/src/channels/index.ts +++ b/src/channels/index.ts @@ -51,15 +51,15 @@ function encDynBytes(hex: string): { offset: bigint; encoded: string } { } const SEL = { - CHALLENGE_WINDOW: '0xe9333e75', - channels: '0xb29a8140', // channels(bytes32) - open: '0x73c0d3a9', // open(address,address,address,uint256,uint256) - cooperativeClose: '0xa9fbe4b6', // cooperativeClose(ChannelState,bytes,bytes) - startChallenge: '0xd1e26000', // startChallenge(ChannelState,bytes,bytes) - overrideChallenge: '0x44b04beb', // overrideChallenge(ChannelState,bytes,bytes) - finalize: '0x6e3e98bf', // finalize(bytes32) - settleHtlc: '0xa84f3c0e', // settleHtlc(bytes32,bytes) - withdraw: '0x51cff8d9', // withdraw(bytes32) + CHALLENGE_WINDOW: '0xd62aad29', // CHALLENGE_WINDOW() + channels: '0x7a7ebd7b', // channels(bytes32) + open: '0xf9bff880', // open(address,address,address,uint256,uint256) + cooperativeClose: '0xeb8edd09', // cooperativeClose((bytes32,uint256,uint256,uint256,uint256,uint256,bytes32,uint256),bytes,bytes) + startChallenge: '0x1b6a88c1', // startChallenge((bytes32,uint256,uint256,uint256,uint256,uint256,bytes32,uint256),bytes,bytes) + overrideChallenge: '0x70c84312', // overrideChallenge((bytes32,uint256,uint256,uint256,uint256,uint256,bytes32,uint256),bytes,bytes) + finalize: '0x92584d80', // finalize(bytes32) + settleHtlc: '0x9be71c4d', // settleHtlc(bytes32,bytes) + withdraw: '0x8e19899e', // withdraw(bytes32) } as const; export class AereStateChannelsClient { diff --git a/src/compliance/NewComplianceClients.ts b/src/compliance/NewComplianceClients.ts index 8566f76..0a8ce9c 100644 --- a/src/compliance/NewComplianceClients.ts +++ b/src/compliance/NewComplianceClients.ts @@ -125,23 +125,23 @@ const POOL_SEL = { publishAssociationRoot: '0x9f92a5b1', withdraw: '0xb7433691', // R5/R6 additions - proposeAssociationRoot: '0x73fc3b6a', // proposeAssociationRoot(bytes32) + proposeAssociationRoot: '0xcca5d27b', // proposeAssociationRoot(bytes32) challengeAssociationRoot: '0x45cb9085', // challengeAssociationRoot(bytes32,string) — bond required dismissAssociationRootChallenge: '0x793819b9', - associationRootProposedAt: '0x68c46b72', - associationRootChallenged: '0x8a76c7c8', - associationRootDismissCount: '0x6c7560c8', - associationRootChallenger: '0xbb84a4c8', + associationRootProposedAt: '0xaff8a8bd', // associationRootProposedAt(bytes32) + associationRootChallenged: '0x7cb2674e', // associationRootChallenged(bytes32) + associationRootDismissCount: '0x5b2aa748', // associationRootDismissCount(bytes32) + associationRootChallenger: '0x957d8c14', // associationRootChallenger(bytes32) CHALLENGE_BOND: '0x9790ce71', - MAX_DISMISSALS_PER_ROOT: '0x9c9eea4f', - CHALLENGE_BOND_BURN: '0x9f3098ff', - BOND_TOKEN: '0x10ee36c5', - isKnownRoot: '0x2b7ac3f3', - getLastRoot: '0x4cf088d9', - ROOT_HISTORY_SIZE: '0xb0d3e9b4', - TREE_DEPTH: '0x7d9f6c6f', - nextLeafIndex: '0x3b15aabf', - ASSOCIATION_ROOT_CHALLENGE_WINDOW: '0x37dd9e6c', + MAX_DISMISSALS_PER_ROOT: '0xde2ccfa5', // MAX_DISMISSALS_PER_ROOT() + CHALLENGE_BOND_BURN: '0xf10dca34', // CHALLENGE_BOND_BURN() + BOND_TOKEN: '0xf21b52e5', // BOND_TOKEN() + isKnownRoot: '0x6d9833e3', // isKnownRoot(bytes32) + getLastRoot: '0xba70f757', // getLastRoot() + ROOT_HISTORY_SIZE: '0xcd87a3b4', // ROOT_HISTORY_SIZE() + TREE_DEPTH: '0xf1650536', // TREE_DEPTH() + nextLeafIndex: '0x0be4f422', // nextLeafIndex() + ASSOCIATION_ROOT_CHALLENGE_WINDOW: '0x03cf6363', // ASSOCIATION_ROOT_CHALLENGE_WINDOW() } as const; export class AereCompliancePoolClient { diff --git a/src/compliance/index.ts b/src/compliance/index.ts index e5b878b..ccf9fb0 100644 --- a/src/compliance/index.ts +++ b/src/compliance/index.ts @@ -35,27 +35,56 @@ function encodeDynamicBytes(hex: string): { offset: string; body: string } { } /* ===================== AereSanctionsRegistry ====================== */ +/* 2026-10-15 (D-512): this client called currentRoot() and an isSanctioned(address,uint16,bytes32[]) that the deployed + * contract does not have. The registry keeps one Merkle root per epoch: read latestEpoch(), the root and state of an epoch + * through snapshotStatus(uint256), and check an address with a Merkle proof against a given or the latest attested epoch. */ const SANCT_SEL = { - currentRoot: '0x6f29ed46', // currentRoot() - isSanctioned: '0x7ad8e02b', // isSanctioned(address,uint16,bytes32[]) - commitRoot: '0x9b30b9ae', // commitRoot(bytes32,uint16,string) — placeholder + latestEpoch: '0x9cb118bf', // latestEpoch() + snapshotStatus: '0x3d1146fa', // snapshotStatus(uint256) + isSanctioned: '0x7acc3cf1', // isSanctioned(uint256,address,uint16,bytes32[]) + isSanctionedLatest: '0xf6035837', // isSanctionedLatest(address,uint16,bytes32[]) } as const; +/** list ids as defined by the contract (LIST_OFAC_SDN = 1, ...). */ +export const SANCTIONS_LIST = { OFAC_SDN: 1, OFAC_SSI: 2, EU_CONSOLIDATED: 3, UK_HMT: 4, UN_CONSOLIDATED: 5 } as const; + +const encProof = (proof: string[]) => encUint(BigInt(proof.length)) + proof.map(encBytes32).join(''); + export class AereSanctionsRegistryClient { constructor(public readonly provider: RpcProvider, public readonly address: string) {} - async currentRoot(): Promise { - return await ethCall(this.provider, this.address, SANCT_SEL.currentRoot); + async latestEpoch(): Promise { + return BigInt(await ethCall(this.provider, this.address, SANCT_SEL.latestEpoch)); + } + + /** Root committed for `epoch` (default: the latest epoch). The registry holds one snapshot per epoch. */ + async currentRoot(epoch?: bigint): Promise { + const e = epoch ?? await this.latestEpoch(); + const res = await ethCall(this.provider, this.address, SANCT_SEL.snapshotStatus + encUint(e)); + return '0x' + res.replace(/^0x/, '').slice(0, 64); + } + + /** True iff the proof shows `addr` on `listId` in snapshot `epoch` AND that snapshot is attested. */ + async isSanctioned(epoch: bigint, addr: string, listId: number, proof: string[]): Promise { + const data = SANCT_SEL.isSanctioned + encUint(epoch) + encAddr(addr) + pad32(listId.toString(16)) + encUint(4n * 32n) + encProof(proof); + return BigInt(await ethCall(this.provider, this.address, data)) === 1n; + } + + /** Same check against the latest attested epoch; returns the verdict and that epoch (0 if none is attested). */ + async isSanctionedLatest(addr: string, listId: number, proof: string[]): Promise<{ sanctioned: boolean; attestedEpoch: bigint }> { + const data = SANCT_SEL.isSanctionedLatest + encAddr(addr) + pad32(listId.toString(16)) + encUint(3n * 32n) + encProof(proof); + const res = (await ethCall(this.provider, this.address, data)).replace(/^0x/, ''); + return { sanctioned: BigInt('0x' + res.slice(0, 64)) === 1n, attestedEpoch: BigInt('0x' + res.slice(64, 128)) }; } } /* ===================== ChainalysisOracleWrapper =================== */ const CHAIN_SEL = { - isSanctioned: '0xdf592f7d', // isSanctioned(address) - upstream: '0x6c0360eb', // upstream() - locked: '0xcf30901', // locked() + isSanctioned: '0xdf592f7d', // isSanctioned(address) + upstreamOracle: '0xff132a15', // upstreamOracle() + locked: '0xcf309012', // locked() } as const; export class ChainalysisOracleWrapperClient { @@ -70,32 +99,44 @@ export class ChainalysisOracleWrapperClient { return true; // fails closed } } + + /** The upstream oracle the wrapper forwards to; the zero address means none is configured and every call fails closed. */ + async upstreamOracle(): Promise { + return '0x' + (await ethCall(this.provider, this.address, CHAIN_SEL.upstreamOracle)).slice(-40); + } } /* ===================== AereTravelRuleHashRegistry ================= */ +/* 2026-10-15 (D-512): this client read committedHashes(bytes32) and status(bytes32), which the contract does not have. + * Commitments are numbered: read one by id through commitments(uint256). */ const TRR_SEL = { - committedHashes: '0x6c5e6e95', // committedHashes(bytes32) - status: '0x200d2ed2', // status(bytes32) + commitments: '0x49ce8997', // commitments(uint256) + nextCommitmentId: '0x5ac48e6d', // nextCommitmentId() } as const; export class AereTravelRuleHashRegistryClient { constructor(public readonly provider: RpcProvider, public readonly address: string) {} - /** State enum: 0=Unknown, 1=Committed, 2=Acknowledged, 3=Disputed. */ - async status(hash: string): Promise { - const res = await ethCall(this.provider, this.address, TRR_SEL.status + encBytes32(hash)); - return Number(BigInt(res)); + async nextCommitmentId(): Promise { + return BigInt(await ethCall(this.provider, this.address, TRR_SEL.nextCommitmentId)); + } + + /** State of commitment `id`: 0=Committed, 1=Acknowledged, 2=Disputed (the contract's State enum). */ + async status(id: bigint): Promise { + const res = (await ethCall(this.provider, this.address, TRR_SEL.commitments + encUint(id))).replace(/^0x/, ''); + // words: payloadHash, originatorVasp, beneficiaryVasp, threshold, committedAt, statusUpdatedAt, state, offset(disputeReason) + return Number(BigInt('0x' + res.slice(6 * 64, 7 * 64))); } } /* ===================== AereForensicEventRegistry ================== */ const FORENSIC_SEL = { - subjectAlertCount: '0x7c1d3d0a', // subjectAlertCount(address) - recentConfirmedAlertCount: '0x44e3a39d', // recentConfirmedAlertCount(address,uint8,uint256) - nextBotId: '0xf4cae09e', // nextBotId() - nextAlertId: '0xa0d49d3a', // nextAlertId() + subjectAlertCount: '0x3fa664d6', // subjectAlertCount(address) + recentConfirmedAlertCount: '0xd9f77c99', // recentConfirmedAlertCount(address,uint8,uint256) + nextBotId: '0x6fc0c79d', // nextBotId() + nextAlertId: '0x260be2a2', // nextAlertId() } as const; export class AereForensicEventRegistryClient { @@ -116,9 +157,9 @@ export class AereForensicEventRegistryClient { /* ===================== AereZKScreen =============================== */ const ZK_SEL = { - isCleared: '0xb6a5d7de', // isCleared(address,bytes32,uint256) - clearedAt: '0xc5f4dec0', // clearedAt(address,bytes32) - submitProof: '0x77a91da7', // submitProof(bytes32,bytes,bytes) + isCleared: '0x011f0451', // isCleared(address,bytes32,uint256) + clearedAt: '0xedddc71f', // clearedAt(address,bytes32) + submitProof: '0xeb90e2cb', // submitProof(bytes32,bytes,bytes) } as const; export class AereZKScreenClient { @@ -163,10 +204,10 @@ export class AereZKScreenClient { * proof of inference see AereZKMLVerifier. */ const AI_SEL = { - models: '0x88e98b22', // models(bytes32) - attestationCount: '0x8b2c3a99', // attestationCount(bytes32) - registerModel: '0xa9b5a9eb', // registerModel(bytes32,address,string) - anchor: '0x5b9babe0', // anchor(bytes32,bytes32,bytes32,bytes32,uint256,uint256,bytes) + models: '0xe236f46b', // models(bytes32) + attestationCount: '0xd65ddd52', // attestationCount(bytes32) + registerModel: '0x31c0953b', // registerModel(bytes32,address,string) + anchor: '0x10912ffc', // anchor(bytes32,bytes32,bytes32,bytes32,uint256,uint256,bytes) } as const; export class AereAIProofClient { diff --git a/src/lending/InsuranceFundClient.ts b/src/lending/InsuranceFundClient.ts index 6d000f5..5e8502b 100644 --- a/src/lending/InsuranceFundClient.ts +++ b/src/lending/InsuranceFundClient.ts @@ -21,15 +21,16 @@ async function ethSend(p: RpcProvider, from: string, to: string, data: string): } const SEL = { - FOUNDATION: '0x4d09f93e', // FOUNDATION() - COOLDOWN_SECONDS: '0xb47d5cf5', // COOLDOWN_SECONDS() - markets: '0x2c43cab8', // markets(address) + FOUNDATION: '0x65df2e51', // FOUNDATION() + COOLDOWN_SECONDS: '0x72b49d63', // COOLDOWN_SECONDS() + markets: '0x8e8f294b', // markets(address) balances: '0x27e235e3', // balances(address) - coverageRemaining: '0x1d29df0e', // coverageRemaining(address) - cooldownReady: '0x3a09a73e', // cooldownReady(address) - registerMarket: '0xc6da18d4', // registerMarket(address,uint256) - donate: '0xf14fcbc8', // donate(address,uint256) - coverBadDebt: '0xa90c1e1c', // coverBadDebt(address,address,uint256) + coverageRemaining: '0xb8046933', // coverageRemaining(address) + cooldownReady: '0xd881ceec', // cooldownReady(address) + proposeMarket: '0xe993ea12', // proposeMarket(address,uint256) + registerMarket: '0xa79b9ec9', // registerMarket(address) + donate: '0xe69d849d', // donate(address,uint256) + coverBadDebt: '0x71c8886f', // coverBadDebt(address,address,uint256) } as const; export class AereInsuranceFundClient { @@ -59,9 +60,18 @@ export class AereInsuranceFundClient { return ethSend(this.provider, from, this.address, SEL.donate + encAddr(token) + encUint(amount)); } - /** Foundation-only: register a market with a lifetime coverage cap. */ - async registerMarket(from: string, market: string, lifetimeCap: bigint): Promise { - return ethSend(this.provider, from, this.address, SEL.registerMarket + encAddr(market) + encUint(lifetimeCap)); + /** + * Foundation-only, step 1 of 2: propose a market with a lifetime coverage cap. The contract enforces + * REGISTRATION_DELAY before step 2. (2026-10-15: this client used to send a one-step + * registerMarket(address,uint256) that the contract does not have.) + */ + async proposeMarket(from: string, market: string, lifetimeCap: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.proposeMarket + encAddr(market) + encUint(lifetimeCap)); + } + + /** Foundation-only, step 2 of 2: register a proposed market once REGISTRATION_DELAY has passed. */ + async registerMarket(from: string, market: string): Promise { + return ethSend(this.provider, from, this.address, SEL.registerMarket + encAddr(market)); } /** Foundation-only: pay down `amount` of `borrower`'s debt on `market`. */ diff --git a/src/lending/LendingMarketClient.ts b/src/lending/LendingMarketClient.ts index 990276d..84a164f 100644 --- a/src/lending/LendingMarketClient.ts +++ b/src/lending/LendingMarketClient.ts @@ -21,47 +21,47 @@ export interface LendingMarketConfig { const SEL = { // immutable views - COLLATERAL: '0xfb4cd02f', // COLLATERAL() - DEBT_TOKEN: '0xea5b5ddf', // DEBT_TOKEN() - SINK: '0xf4f3b200', // SINK() - ORACLE: '0x7dc0d1d0', // ORACLE() - LTV_BPS: '0xcbc4cdc7', // LTV_BPS() - LIQ_THRESHOLD_BPS: '0xc0c5294d', // LIQ_THRESHOLD_BPS() - LIQ_BONUS_BPS: '0x3a86d9b3', // LIQ_BONUS_BPS() - BORROW_FEE_BPS: '0x47bf8b54', // BORROW_FEE_BPS() - MARKET_DEBT_CAP: '0xcca5e09e', // MARKET_DEBT_CAP() - COLLATERAL_DECIMALS: '0x99e9f4ad', // COLLATERAL_DECIMALS() - DEBT_DECIMALS: '0x4d9e7826', // DEBT_DECIMALS() + COLLATERAL: '0x24bbab8b', // COLLATERAL() + DEBT_TOKEN: '0x854db43c', // DEBT_TOKEN() + SINK: '0xae9fc205', // SINK() + ORACLE: '0x38013f02', // ORACLE() + LTV_BPS: '0xfae3706f', // LTV_BPS() + LIQ_THRESHOLD_BPS: '0xe3cd32d6', // LIQ_THRESHOLD_BPS() + LIQ_BONUS_BPS: '0xe3513531', // LIQ_BONUS_BPS() + BORROW_FEE_BPS: '0x185691da', // BORROW_FEE_BPS() + MARKET_DEBT_CAP: '0xfaac9ee3', // MARKET_DEBT_CAP() + COLLATERAL_DECIMALS: '0x4ddde78d', // COLLATERAL_DECIMALS() + DEBT_DECIMALS: '0xbad8a798', // DEBT_DECIMALS() // state views - collateralOf: '0x8aef7ddf', // collateralOf(address) - debtOf: '0xcc4d2f49', // debtOf(address) - supplierScaled: '0x9d7d6f0d', // supplierScaled(address) - supplierBalance: '0x9d9f3ff6', // supplierBalance(address) - debtBalance: '0x33ed81e4', // debtBalance(address) + collateralOf: '0x1aefb107', // collateralOf(address) + debtOf: '0xd283e75f', // debtOf(address) + supplierScaled: '0x68fdada6', // supplierScaled(address) + supplierBalance: '0x1de233e8', // supplierBalance(address) + debtBalance: '0x11005b07', // debtBalance(address) borrowIndex: '0xaa5af0fd', // borrowIndex() - supplyIndex: '0xb02f7d5e', // supplyIndex() - totalBorrowsScaled: '0xc0c44d6b', // totalBorrowsScaled() - totalSuppliedScaled: '0x7b0b3da7', // totalSuppliedScaled() - totalBorrowsCurrent: '0xc26c9116', // totalBorrowsCurrent() - totalSuppliedCurrent: '0x57e3c7fc', // totalSuppliedCurrent() - pendingSinkFee: '0x99c50fcb', // pendingSinkFee() - borrowRatePerSecond: '0x9e6dadce', // borrowRatePerSecond() - borrowPaused: '0x42b6e3bf', // borrowPaused() - depositPaused: '0x6a5db0fa', // depositPaused() - healthFactor: '0xe8b9c2dd', // healthFactor(address) + supplyIndex: '0x98f1bc12', // supplyIndex() + totalBorrowsScaled: '0x03647a88', // totalBorrowsScaled() + totalSuppliedScaled: '0x2785ce6d', // totalSuppliedScaled() + totalBorrowsCurrent: '0x73acee98', // totalBorrowsCurrent() + totalSuppliedCurrent: '0x9a1509b4', // totalSuppliedCurrent() + pendingSinkFee: '0x20a0e28c', // pendingSinkFee() + borrowRatePerSecond: '0x52609750', // borrowRatePerSecond() + borrowPaused: '0xbcb4bbea', // borrowPaused() + depositPaused: '0x02befd24', // depositPaused() + healthFactor: '0x6ad9f9df', // healthFactor(address) // write - accrueInterest: '0x59601e8a', // accrueInterest() - flushSinkFee: '0x71a5ba73', // flushSinkFee() - supply: '0xf2b9fdb8', // supply(uint256) + accrueInterest: '0xa6afed95', // accrueInterest() + flushSinkFee: '0x599fb8ac', // flushSinkFee() + supply: '0x35403023', // supply(uint256) redeem: '0xdb006a75', // redeem(uint256) - depositCollateral: '0x80aebec5', // depositCollateral(uint256) - withdrawCollateral: '0xeb0aaa4b', // withdrawCollateral(uint256) + depositCollateral: '0xbad4a01f', // depositCollateral(uint256) + withdrawCollateral: '0x6112fe2e', // withdrawCollateral(uint256) borrow: '0xc5ebeaec', // borrow(uint256) - repay: '0x0e752702', // repay(uint256) - repayOnBehalfOf: '0x8f1a3bf0', // repayOnBehalfOf(address,uint256) - liquidate: '0xf5e3c462', // liquidate(address,uint256) + repay: '0x371fd8e6', // repay(uint256) + repayOnBehalfOf: '0xede66a45', // repayOnBehalfOf(address,uint256) + liquidate: '0xbcbaf487', // liquidate(address,uint256) } as const; /* ----------------------------- helpers ---------------------------------- */ diff --git a/src/lending/RWATransferAdapterClient.ts b/src/lending/RWATransferAdapterClient.ts index 467b745..2d825ed 100644 --- a/src/lending/RWATransferAdapterClient.ts +++ b/src/lending/RWATransferAdapterClient.ts @@ -19,13 +19,13 @@ async function ethSend(p: RpcProvider, from: string, to: string, data: string): } const SEL = { - PAYOUT_TOKEN: '0x935ca8e6', // PAYOUT_TOKEN() - ORACLE: '0x7dc0d1d0', // ORACLE() - FOUNDATION: '0x4d09f93e', // FOUNDATION() - configs: '0x1d12be96', // configs(address) - states: '0xb95da3ec', // states(address) - periodRemaining: '0x3aff0d34', // periodRemaining(address) - payoutLiquidity: '0x8b8a55be', // payoutLiquidity() + PAYOUT_TOKEN: '0xe2f6f511', // PAYOUT_TOKEN() + ORACLE: '0x38013f02', // ORACLE() + FOUNDATION: '0x65df2e51', // FOUNDATION() + configs: '0xfce89878', // configs(address) + states: '0x02a22337', // states(address) + periodRemaining: '0x0a41950f', // periodRemaining(address) + payoutLiquidity: '0x875d90e6', // payoutLiquidity() redeem: '0x1e9a6950', // redeem(address,uint256) } as const; diff --git a/src/raas/RaaSClient.ts b/src/raas/RaaSClient.ts index 2c5bc0b..9344535 100644 --- a/src/raas/RaaSClient.ts +++ b/src/raas/RaaSClient.ts @@ -27,19 +27,19 @@ async function ethSend(p: RpcProvider, from: string, to: string, data: string, v const FAC_SEL = { // immutable views - BOND_TOKEN: '0x18b13f55', // BOND_TOKEN() - SINK: '0xf4f3b200', // SINK() - REGISTRATION_BOND: '0x4b094c4b', // REGISTRATION_BOND() - MIN_SINK_BPS: '0x9dabd040', // MIN_SINK_BPS() - DEFAULT_CHALLENGE_WINDOW: '0x8e8a6f3a', // DEFAULT_CHALLENGE_WINDOW() - FOUNDATION: '0x4d09f93e', // FOUNDATION() + BOND_TOKEN: '0xf21b52e5', // BOND_TOKEN() + SINK: '0xae9fc205', // SINK() + REGISTRATION_BOND: '0xc89d37de', // REGISTRATION_BOND() + MIN_SINK_BPS: '0x354ec275', // MIN_SINK_BPS() + DEFAULT_CHALLENGE_WINDOW: '0xda493740', // DEFAULT_CHALLENGE_WINDOW() + FOUNDATION: '0x65df2e51', // FOUNDATION() // state views - rollups: '0xdcecb8e3', // rollups(bytes32) - rollupCount: '0xff19a3a4', // rollupCount() + rollups: '0x59a181dc', // rollups(bytes32) + rollupCount: '0xf4e92675', // rollupCount() // writes - registerRollup: '0xbb4ab86c', // registerRollup(bytes32,address,address,address,uint16,uint16,uint16,uint256,uint64,string) - updateMetadata: '0xa1ae9b0e', // updateMetadata(bytes32,string) - exitRollup: '0x73db2bba', // exitRollup(bytes32,address,uint64,bytes) + registerRollup: '0x3878aaf1', // registerRollup(bytes32,address,address,address,uint16,uint16,uint16,uint256,uint64,uint64,string) (AereRaaSFactoryV2) + updateMetadata: '0xa375765e', // updateMetadata(bytes32,string) + exitRollup: '0x72fbb1bb', // exitRollup(bytes32,address,uint64,bytes) } as const; export class AereRaaSFactoryClient { @@ -73,7 +73,8 @@ export class AereRaaSFactoryClient { rollupBps: number; sequencerBps: number; minChallengeBond: bigint; - challengeWindow: number; + challengeWindow: number; // seconds; 0 = the factory's DEFAULT_CHALLENGE_WINDOW + defenseGrace?: number; // seconds; 0 or omitted = the factory's DEFAULT_DEFENSE_GRACE (AereRaaSFactoryV2) metadataUri: string; } ): Promise { @@ -88,7 +89,8 @@ export class AereRaaSFactoryClient { encUint(BigInt(opts.sequencerBps)) + encUint(opts.minChallengeBond) + encUint(BigInt(opts.challengeWindow)) + - encUint(0x140n); // 10 head slots * 32 = 320 = 0x140 + encUint(BigInt(opts.defenseGrace ?? 0)) + + encUint(0x160n); // 11 head slots * 32 = 352 = 0x160 const padded = Buffer.alloc(Math.ceil(meta.length / 32) * 32); meta.copy(padded); const dyn = encUint(BigInt(meta.length)) + padded.toString('hex'); @@ -100,28 +102,28 @@ export class AereRaaSFactoryClient { /* ============================ Settlement ============================= */ const SET_SEL = { - ROLLUP_ID: '0x7b0a4eb1', // ROLLUP_ID() - SINK: '0xf4f3b200', // SINK() - DEBT_TOKEN: '0xea5b5ddf', // DEBT_TOKEN() - CHALLENGE_WINDOW: '0x7a6a4abe', // CHALLENGE_WINDOW() - SINK_BPS: '0xb3f54bdd', // SINK_BPS() - ROLLUP_BPS: '0xc70aa727', // ROLLUP_BPS() - SEQUENCER_BPS: '0xe35d8b85', // SEQUENCER_BPS() - rollupOwner: '0x0e8d63c0', // rollupOwner() - sequencer: '0x42d1de27', // sequencer() - latestEpoch: '0x6f6db00f', // latestEpoch() - latestFinalisedEpoch:'0x3aac4670', // latestFinalisedEpoch() - isFinalised: '0x3a8a4a3e', // isFinalised(uint256) - totalRevenueSettled: '0xf3b6db95', // totalRevenueSettled() + ROLLUP_ID: '0xb5ed5559', // ROLLUP_ID() + SINK: '0xae9fc205', // SINK() + DEBT_TOKEN: '0x854db43c', // DEBT_TOKEN() + CHALLENGE_WINDOW: '0xd62aad29', // CHALLENGE_WINDOW() + SINK_BPS: '0x10dbc17d', // SINK_BPS() + ROLLUP_BPS: '0x7c103fdc', // ROLLUP_BPS() + SEQUENCER_BPS: '0x9d09bcd5', // SEQUENCER_BPS() + rollupOwner: '0x7c3a72a0', // rollupOwner() + sequencer: '0x5c1bba38', // sequencer() + latestEpoch: '0x9cb118bf', // latestEpoch() + latestFinalisedEpoch:'0x24b5e709', // latestFinalisedEpoch() + isFinalised: '0xc7a9b0ed', // isFinalised(uint256) + totalRevenueSettled: '0x335d152b', // totalRevenueSettled() - proposeStateRoot: '0x9e706b08', // proposeStateRoot(uint256,bytes32,bytes32) - advanceFinalisation: '0xb78f2ebd', // advanceFinalisation() - challenge: '0x8da548fa', // challenge(uint256,uint256,bytes32) - resolveChallenge: '0x437f2ba0', // resolveChallenge(uint256,bool) - resolveChallengeExpired: '0xb1cc5a3d', // resolveChallengeExpired(uint256) - settleRevenue: '0xc7c1aa8c', // settleRevenue(uint256) - setSequencer: '0xa6f9dae1', // setSequencer(address) - transferOwner: '0xc6dad082', // transferOwner(address) + proposeStateRoot: '0xe871f194', // proposeStateRoot(uint256,bytes32,bytes32) + advanceFinalisation: '0x51f09e3b', // advanceFinalisation() + challenge: '0xe0f7c5ae', // challenge(uint256,uint256,bytes32) + resolveChallenge: '0x94a754e0', // resolveChallenge(uint256,bool) + resolveChallengeExpired: '0x5454e09a', // resolveChallengeExpired(uint256) + settleRevenue: '0xe3c22c42', // settleRevenue(uint256) + setSequencer: '0x2547fa3e', // setSequencer(address) + transferOwner: '0x4fb2e45d', // transferOwner(address) } as const; export class AereRollupSettlementClient { diff --git a/src/sink/AereSinkClient.ts b/src/sink/AereSinkClient.ts index a08ee19..9c8f055 100644 --- a/src/sink/AereSinkClient.ts +++ b/src/sink/AereSinkClient.ts @@ -33,20 +33,20 @@ export interface AereSinkConfig { // 4-byte function selectors. Computed once at module load and held constant. // (Verified via cast sig-of ().) const SEL = { - AERE: '0x1cc59c1b', // AERE() - BURN_VAULT: '0x4e7eee76', // BURN_VAULT() - SAERE_VAULT: '0xc46aedd7', // SAERE_VAULT() - DEX_ROUTER: '0x3b1664c1', // DEX_ROUTER() - BURN_BPS: '0x9748d6c0', // BURN_BPS() - BUYBACK_BPS: '0xb56fcecf', // BUYBACK_BPS() - STAKER_YIELD_BPS: '0xc77b3402', // STAKER_YIELD_BPS() - MAX_SLIPPAGE_BPS: '0xfb6fb1f9', // MAX_SLIPPAGE_BPS() + AERE: '0xe3b11fda', // AERE() + BURN_VAULT: '0x25bad93c', // BURN_VAULT() + SAERE_VAULT: '0x4f0a5e94', // SAERE_VAULT() + DEX_ROUTER: '0x7af11ac0', // DEX_ROUTER() + BURN_BPS: '0xa37a9fc0', // BURN_BPS() + BUYBACK_BPS: '0x1a17db67', // BUYBACK_BPS() + STAKER_YIELD_BPS: '0x7a03922d', // STAKER_YIELD_BPS() + MAX_SLIPPAGE_BPS: '0xe229cd76', // MAX_SLIPPAGE_BPS() } as const; // flush(address,uint256) — for write -const FLUSH_SELECTOR = '0x6cd5c39b'; +const FLUSH_SELECTOR = '0x96fd1c42'; // sweepDust(address) — for write -const SWEEP_DUST_SELECTOR = '0xeb1a0a96'; +const SWEEP_DUST_SELECTOR = '0xac7ad478'; /* ----------------------------- helper functions ---------------------------- */ diff --git a/src/test/selectors-live.ts b/src/test/selectors-live.ts new file mode 100644 index 0000000..21b1e24 --- /dev/null +++ b/src/test/selectors-live.ts @@ -0,0 +1,93 @@ +/** + * selectors-live.ts — prove the repaired SDK clients against the REAL contracts on chain 2800 (D-512). + * + * On 2026-10-15 a site page showed "0 rollups" because the SDK's RaaS client called rollupCount() with a selector that + * no contract has; the call reverted and the caller printed a zero. The same day ~130 selectors across ten clients were + * found wrong, none of them even matching the signature written next to it. The offline gate + * (aerenew/scripts/deschise/sdk-selectori-corecti.cjs) now checks every selector against keccak and the compiled ABI; + * this file checks the other half: that the clients, as shipped, get an ANSWER from the deployed contracts. + * + * Every read below must return without a revert. Values are printed, not asserted, except where the chain fixes them + * (RaaS bond, sink floor, list of sanctions epochs). Negative control: the same RaaS read with the pre-repair selector + * must revert, otherwise this file could not tell a broken client from a working one. + * + * node dist/test/selectors-live.js [rpc] (network; not part of `npm test`) + * Read-only. No keys, no transactions. + */ +import { AereRaaSFactoryClient } from '../raas/index.js'; +import { AereSinkClient } from '../sink/AereSinkClient.js'; +import { LendingMarketClient, AereInsuranceFundClient } from '../lending/index.js'; +import { AereSanctionsRegistryClient, ChainalysisOracleWrapperClient, AereTravelRuleHashRegistryClient, AereForensicEventRegistryClient } from '../compliance/index.js'; +import { AereCompliancePoolClient } from '../compliance/NewComplianceClients.js'; + +const RPC = process.argv[2] ?? 'https://rpc.aere.network'; +const provider = { + async request({ method, params }: { method: string; params: unknown[] }): Promise { + const r = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); + const j = (await r.json()) as { result?: unknown; error?: { message: string } }; + if (j.error) throw new Error(j.error.message); + return j.result; + }, +}; + +let failures = 0; +async function check(label: string, f: () => Promise, expect?: (v: unknown) => boolean): Promise { + try { + const v = await f(); + const shown = typeof v === 'bigint' ? v.toString() : JSON.stringify(v, (_k, x) => (typeof x === 'bigint' ? x.toString() : x)); + if (expect && !expect(v)) { failures++; console.log(` FAIL ${label}: unexpected ${shown}`); } + else console.log(` PASS ${label}: ${shown}`); + } catch (e) { + failures++; + console.log(` FAIL ${label}: ${(e as Error).message}`); + } +} + +async function main(): Promise { + console.log(`selector live proof against ${RPC}\n`); + const raas = new AereRaaSFactoryClient(provider, '0xB7F8c754AC3155197d76f01857172bBd5a5F39Ab'); + await check('RaaS V2 rollupCount()', () => raas.rollupCount()); + await check('RaaS V2 REGISTRATION_BOND() = 1000 AERE', () => raas.getRegistrationBond(), (v) => v === 1000n * 10n ** 18n); + await check('RaaS V2 MIN_SINK_BPS() = 1000', () => raas.getMinSinkBps(), (v) => v === 1000); + await check('RaaS V2 SINK()', () => raas.getSink(), (v) => typeof v === 'string' && /^0x[0-9a-f]{40}$/i.test(v)); + + // negative control: the pre-repair rollupCount selector must revert, or this file proves nothing + await provider.request({ method: 'eth_call', params: [{ to: '0xB7F8c754AC3155197d76f01857172bBd5a5F39Ab', data: '0xff19a3a4' }, 'latest'] }).then( + () => { failures++; console.log(' FAIL negative control: the old selector 0xff19a3a4 answered; this proof cannot tell broken from working'); }, + () => console.log(' PASS negative control: the old selector 0xff19a3a4 reverts, as it did on the site'), + ); + + const sink = new AereSinkClient({ provider: provider as never, address: '0x69581B86A48161b067Ff4E01544780625B231676' }); + await check('AereSink readConfig()', () => sink.readConfig()); + + const market = new LendingMarketClient({ provider: provider as never, address: '0x2C2d39dB711C0A33De04Dc74b1E22f4760FD4bb0' }); + await check('AereLendingMarket getLtvBps()', () => market.getLtvBps()); + await check('AereLendingMarket totalSuppliedCurrent()', () => market.totalSuppliedCurrent()); + await check('AereLendingMarket borrowPaused()', () => market.borrowPaused()); + + const fund = new AereInsuranceFundClient(provider as never, '0x5Ab95C549c2A2b07913Df7edD4a16fd108B7CAAC'); + await check('AereInsuranceFund FOUNDATION()', () => fund.getFoundation()); + + const sanctions = new AereSanctionsRegistryClient(provider, '0xb7d235718D99560F6EA4Fc5eAea2F8a306A3Cacf'); + await check('AereSanctionsRegistry latestEpoch()', () => sanctions.latestEpoch(), (v) => typeof v === 'bigint' && v > 0n); + await check('AereSanctionsRegistry currentRoot()', () => sanctions.currentRoot(), (v) => typeof v === 'string' && /^0x[0-9a-f]{64}$/i.test(v)); + + const chainalysis = new ChainalysisOracleWrapperClient(provider, '0x1B7Be82C80f368f75Cb3807B1bc05E86A498f85c'); + await check('ChainalysisOracleWrapper upstreamOracle()', () => chainalysis.upstreamOracle()); + + const travel = new AereTravelRuleHashRegistryClient(provider, '0xcF0E2e010E6e4506672019b1e570874AEeBC4c84'); + await check('AereTravelRuleHashRegistry nextCommitmentId()', () => travel.nextCommitmentId()); + + const forensic = new AereForensicEventRegistryClient(provider, '0x4a7526A068e5DDE9788f6571E4A99095b14C6fff'); + await check('AereForensicEventRegistry subjectAlertCount()', () => forensic.subjectAlertCount('0x0000000000000000000000000000000000000001')); + + const pool = new AereCompliancePoolClient(provider as never, '0xB144c923572E5Ac1B6B961C4ccfec36917173465'); + await check('AereCompliancePoolV2 depositCount()', () => pool.depositCount()); + await check('AereCompliancePoolV2 nextLeafIndex()', () => pool.nextLeafIndex()); + await check('AereCompliancePoolV2 getLastRoot()', () => pool.getLastRoot()); + + console.log(`\n${failures === 0 ? 'RESULT: PASS' : `RESULT: FAIL, ${failures} check(s)`}`); + process.exit(failures === 0 ? 0 : 1); +} + +main().catch((e) => { console.error('live proof aborted:', e); process.exit(2); }); diff --git a/src/test/state-window-live.ts b/src/test/state-window-live.ts index cea2c7c..3fecbe3 100644 --- a/src/test/state-window-live.ts +++ b/src/test/state-window-live.ts @@ -6,8 +6,8 @@ * still behaves the way the author measured. This file closes that gap by * pointing the guard at production and asserting on what comes back. * - * It is deliberately NOT part of `npm test`: it needs the network, and a test - * suite that fails when the internet is down teaches people to ignore red. + * It is deliberately NOT part of `npm test` (the test script skips *-live.js): it needs the network, and a + * test suite that fails when the internet is down teaches people to ignore red. * * node dist/test/state-window-live.js # rpc.aere.network * node dist/test/state-window-live.js https://rpc2.aere.network @@ -18,7 +18,7 @@ * Structure, in this order, because a guard that has never been shown to fail * cannot be trusted: * - * STEP 1 PLANTED FAILURE. The raw call really does return a false zero. + * STEP 1 THE ENDPOINT. What the raw call returns today (a false zero until 2026-10, a refusal since). * STEP 2 NEGATIVE CONTROL. With the guards disabled, the reader hands that * false zero straight to the caller. * STEP 3 THE GUARD. With the guards on, the reader refuses. @@ -74,35 +74,38 @@ async function main(): Promise { console.log(''); // ------------------------------------------------------------------------- - console.log('STEP 1 PLANTED FAILURE: the raw call must still lie'); + console.log('STEP 1 WHAT THE ENDPOINT DOES TODAY outside the state window'); // ------------------------------------------------------------------------- - const rawNonce = await send('eth_getTransactionCount', [SIGNER, tag]); - if (rawNonce === '0x0') { - ok('eth_getTransactionCount returns a false zero', - `result "0x0" at block ${deepBlock}, where the block body proves ${trueNonce}`); - } else { - bad('eth_getTransactionCount returns a false zero', - `expected "0x0", got ${JSON.stringify(rawNonce)}. If this endpoint now answers honestly, ` + - `the published documentation must be updated, not this assertion.`); - } - - const rawBalance = await send('eth_getBalance', [SIGNER, tag]); - if (rawBalance === null) { - ok('eth_getBalance is honest at the same block', 'result null, which is a refusal and not a value'); - } else { - bad('eth_getBalance is honest at the same block', `expected null, got ${JSON.stringify(rawBalance)}`); - } - - await send('eth_getProof', [SIGNER, [], tag]).then( - () => bad('eth_getProof is honest at the same block', 'it answered; the state window may have changed'), - (e: Error) => ok('eth_getProof is honest at the same block', `error ${e.message}`), + // Until 2026-10 this endpoint returned a false "0x0" here. Measured on 2026-10-15 it refuses with + // "-32000 World state unavailable", which is honest. Either answer is recorded; a non-zero number would be a new hazard. + let endpointLies = false; + await send('eth_getTransactionCount', [SIGNER, tag]).then( + (raw) => { + if (raw === '0x0') { endpointLies = true; ok('the endpoint still returns a false zero (hazard real)', `"0x0" at block ${deepBlock}, the body proves ${trueNonce}`); } + else bad('eth_getTransactionCount answers outside the window', `got ${JSON.stringify(raw)}; neither a refusal nor the known false zero`); + }, + (e: Error) => ok('the endpoint refuses honestly outside the window', `error ${e.message}`), ); console.log(''); + // A lying endpoint, reproducing exactly the behaviour measured until 2026-10: state calls outside the window answer + // "0x0" (nonce) or null (balance) instead of an error. The guard must hold against THAT, whatever this endpoint does now. + const lying = async (method: string, params: unknown[]): Promise => { + try { return await send(method, params); } catch (e) { + if (/World state unavailable/.test(String(e))) { + if (method === 'eth_getTransactionCount') return '0x0'; + if (method === 'eth_getBalance' || method === 'eth_getCode' || method === 'eth_getStorageAt') return null; + } + throw e; + } + }; + if (!endpointLies) console.log(' the steps below run against a wrapper that reproduces the false zero, since this endpoint no longer produces it'); + if (!endpointLies) console.log(''); + // ------------------------------------------------------------------------- console.log('STEP 2 NEGATIVE CONTROL: with the guards off, the lie gets through'); // ------------------------------------------------------------------------- - const unguarded = new StateWindowReader(send, { + const unguarded = new StateWindowReader(endpointLies ? send : lying, { windowBlocks: 100_000_000, safetyMarginBlocks: 0, corroborateNonce: false, }); const leaked = await unguarded.getTransactionCount(SIGNER, deepBlock); @@ -116,7 +119,7 @@ async function main(): Promise { // ------------------------------------------------------------------------- console.log('STEP 3 THE GUARD: it refuses instead of guessing'); // ------------------------------------------------------------------------- - const reader = new StateWindowReader(send); + const reader = new StateWindowReader(endpointLies ? send : lying); await reader.getTransactionCount(SIGNER, deepBlock).then( (v) => bad('pre-flight refuses the deep nonce', `expected a throw, got ${v}`), @@ -127,7 +130,7 @@ async function main(): Promise { ); // Corroboration is the guard that survives a caller who races past pre-flight. - const racedPast = new StateWindowReader(send, { windowBlocks: 100_000_000, safetyMarginBlocks: 0 }); + const racedPast = new StateWindowReader(endpointLies ? send : lying, { windowBlocks: 100_000_000, safetyMarginBlocks: 0 }); await racedPast.getTransactionCount(SIGNER, deepBlock).then( (v) => bad('corroboration catches what pre-flight missed', `expected a throw, got ${v}`), (e: unknown) =>