From 709f8ceae25df6f87e7a24d9454f40ffb079daf0 Mon Sep 17 00:00:00 2001 From: zkasuran Date: Thu, 10 Sep 2026 17:43:07 +0530 Subject: [PATCH 1/2] refactor(apriori): move the aprMON ABI to the explorer tier The aprMON implementation is verified on MonadScan, so the ABI moves from the vendored tier to the explorer tier per ADR 0007. The full implementation ABI is fetched from the explorer and committed as typed as const via update:abis. An offline test asserts it is exact renderAbiModule output and a keyed online test cross-checks it against the verified implementation with compareDeployedAbi. The exported AprMonAbi now includes the viewRedeemRequest and getUserRequestData view helpers. --- .changeset/apriori-explorer-tier.md | 13 + packages/protocols/apriori/README.md | 56 +- packages/protocols/apriori/abis.json | 3 +- packages/protocols/apriori/package.json | 4 +- packages/protocols/apriori/scripts/abis.ts | 7 + .../protocols/apriori/scripts/update-abis.ts | 18 + .../protocols/apriori/src/abis/apriori.ts | 2614 ++++++++++++++++- packages/protocols/apriori/src/adapter.ts | 7 +- .../apriori/test-online/abi-explorer.test.ts | 79 +- packages/protocols/apriori/test/abis.test.ts | 40 + pnpm-lock.yaml | 3 + 11 files changed, 2711 insertions(+), 133 deletions(-) create mode 100644 .changeset/apriori-explorer-tier.md create mode 100644 packages/protocols/apriori/scripts/abis.ts create mode 100644 packages/protocols/apriori/scripts/update-abis.ts create mode 100644 packages/protocols/apriori/test/abis.test.ts diff --git a/.changeset/apriori-explorer-tier.md b/.changeset/apriori-explorer-tier.md new file mode 100644 index 00000000..85303318 --- /dev/null +++ b/.changeset/apriori-explorer-tier.md @@ -0,0 +1,13 @@ +--- +"@themoss/protocol-apriori": patch +--- + +Move the aprMON ABI to the explorer tier (ADR 0007). The EIP-1967 implementation +is now verified on MonadScan, so `src/abis/apriori.ts` is the full implementation +ABI fetched from the explorer and committed as typed `as const`, generated by +`update:abis` from the `scripts/abis.ts` source table. A keyed +`test-online/abi-explorer.test.ts` cross-checks the committed ABI against the +explorer-verified implementation with `compareDeployedAbi` and an offline +`test/abis.test.ts` asserts the committed module is exact `renderAbiModule` +output. The exported `AprMonAbi` now carries the full artifact, including the +`viewRedeemRequest` and `getUserRequestData` view helpers. diff --git a/packages/protocols/apriori/README.md b/packages/protocols/apriori/README.md index 2ea17212..44a25917 100644 --- a/packages/protocols/apriori/README.md +++ b/packages/protocols/apriori/README.md @@ -24,29 +24,39 @@ withdrawal queue: Canonical deployment source: [aPriori's official integration docs](https://apriori-docs.gitbook.io/apriori-docs/aprmon/smart-contract-integration), which publish the mainnet address and the exact function/event signatures. -## ABI provenance (ADR 0007, vendored tier) - -When this adapter landed the implementation contract was not explorer-verified, -so no explorer artifact existed to fetch or compare. The ABI in -`src/abis/apriori.ts` is vendored verbatim from the official docs, restricted to -entries whose signatures are machine-verifiable, and the derivation is -test-enforced rather than asserted. The implementation is verified on MonadScan -since 2026-09-05 and its explorer ABI agrees with every vendored signature; -moving the package to the explorer tier with a keyed `compareDeployedAbi` -cross-check is tracked in [#197](https://github.com/nishuzumi/moss/issues/197). - -- `abis.json` records the proxy/implementation pair. -- `test-online/abi-explorer.test.ts` (keyless, RPC-only) verifies on chain that - the proxy's EIP-1967 slot still resolves to the recorded implementation, that - both addresses have deployed bytecode, that **every** vendored function - selector and event topic hash recomputed from the artifact appears in the - implementation bytecode, that on-chain `name`/`symbol`/`decimals` match the - exported `APRMON_*` constants, and that `convertToShares`/`convertToAssets` - round-trip at a sane exchange rate. -- An aPriori upgrade flips the linkage test red, forcing human re-verification - of the vendored ABI. If aPriori verifies the implementation, this package - should switch to the keyed `fetchAbi` + `compareDeployedAbi` explorer - cross-check used by `@themoss/protocol-kuru`. +## ABI provenance (ADR 0007, explorer tier) + +The implementation is verified on MonadScan (contract `aprMON`, compiler +v0.8.28, exact bytecode match), so `src/abis/apriori.ts` is the full +implementation ABI fetched from the explorer and committed as typed `as const`, +generated by `renderAbiModule` and never hand-edited. This is the explorer tier +of ADR 0007, above the vendored tier the package used when the implementation +had no verified source. The full artifact also carries the `viewRedeemRequest` +and `getUserRequestData` view helpers the curated vendored ABI never included. + +Refresh the ABI from the repository root with: + +```bash +MONADSCAN_API_KEY=... pnpm --filter @themoss/protocol-apriori update:abis +``` + +- `scripts/abis.ts` records the source table (implementation address, export + name, module file); `update:abis` re-fetches and renders from it. +- `abis.json` pins the expected proxy/implementation pair and the reviewed + `allowedExplorerOnly` exceptions (none today). +- `test/abis.test.ts` (offline) re-derives the committed module from its own + embedded ABI and retrieval date and asserts byte-for-byte equality, so a + hand-edit or drift from `renderAbiModule` fails closed. +- `test-online/abi-explorer.test.ts` (keyed, needs `MONADSCAN_API_KEY`) verifies + on chain that the proxy's EIP-1967 slot still resolves to the recorded + implementation, that the proxy has deployed bytecode, that on-chain + `name`/`symbol`/`decimals` match the exported `APRMON_*` constants, that + `convertToShares`/`convertToAssets` round-trip at a sane exchange rate and that + the committed ABI is semantically identical to the ABI of the + explorer-verified implementation (`compareDeployedAbi`). A missing key fails + the suite rather than skipping. +- An aPriori upgrade flips the linkage and cross-check red, forcing human + re-verification of the ABI. ## Receipts diff --git a/packages/protocols/apriori/abis.json b/packages/protocols/apriori/abis.json index 634098ed..a6ae685d 100644 --- a/packages/protocols/apriori/abis.json +++ b/packages/protocols/apriori/abis.json @@ -1,6 +1,7 @@ { "aprMon": { "proxy": "0x0c65A0BC65a5D819235B71F554D210D3F80E0852", - "implementation": "0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2" + "implementation": "0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2", + "allowedExplorerOnly": [] } } diff --git a/packages/protocols/apriori/package.json b/packages/protocols/apriori/package.json index 66cb695d..06045178 100644 --- a/packages/protocols/apriori/package.json +++ b/packages/protocols/apriori/package.json @@ -17,7 +17,8 @@ "build": "tsup src/index.ts --format esm --dts --sourcemap --clean --target es2022", "typecheck": "tsc --noEmit", "test": "vitest run", - "test:abi:online": "vitest run --config vitest.online.config.ts" + "test:abi:online": "vitest run --config vitest.online.config.ts", + "update:abis": "tsx scripts/update-abis.ts" }, "dependencies": { "@themoss/core": "workspace:*", @@ -28,6 +29,7 @@ "@themoss/abi-tools": "workspace:*", "@themoss/simulator": "workspace:*", "@themoss/system": "workspace:*", + "tsx": "^4.19.0", "tsup": "^8.5.1", "typescript": "~5.9.0", "vitest": "^3.2.6" diff --git a/packages/protocols/apriori/scripts/abis.ts b/packages/protocols/apriori/scripts/abis.ts new file mode 100644 index 00000000..5b516cbf --- /dev/null +++ b/packages/protocols/apriori/scripts/abis.ts @@ -0,0 +1,7 @@ +export const SOURCES = [ + { + address: "0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2", + exportName: "AprMon", + file: "apriori.ts", + }, +] as const; diff --git a/packages/protocols/apriori/scripts/update-abis.ts b/packages/protocols/apriori/scripts/update-abis.ts new file mode 100644 index 00000000..1bb73afe --- /dev/null +++ b/packages/protocols/apriori/scripts/update-abis.ts @@ -0,0 +1,18 @@ +import { writeFileSync } from "node:fs"; +import { fetchAbi, renderAbiModule } from "@themoss/abi-tools"; +import { SOURCES } from "./abis.js"; + +const key = process.env.MONADSCAN_API_KEY; +if (!key) { + throw new Error( + "MONADSCAN_API_KEY is not set; create one at https://info.monadscan.com/myapikey/ and export it.", + ); +} + +const abisDir = new URL("../src/abis/", import.meta.url); +const retrievedAt = new Date(); +for (const { exportName, file, address } of SOURCES) { + const abi = await fetchAbi(address, key); + writeFileSync(new URL(file, abisDir), renderAbiModule({ exportName, address, abi, retrievedAt })); + console.log(`src/abis/${file}: ${abi.length} ABI entries from ${address}`); +} diff --git a/packages/protocols/apriori/src/abis/apriori.ts b/packages/protocols/apriori/src/abis/apriori.ts index 62384377..89ec1446 100644 --- a/packages/protocols/apriori/src/abis/apriori.ts +++ b/packages/protocols/apriori/src/abis/apriori.ts @@ -1,65 +1,2551 @@ -// ABI origin: vendored (ADR 0007) -// Source: https://apriori-docs.gitbook.io/apriori-docs/aprmon/smart-contract-integration -// (aPriori's official aprMON integration reference; signatures -// vendored verbatim) -// Retrieved: 2026-07-26 (UTC) -// Why not explorer (2026-07-26): the aprMON EIP-1967 implementation -// 0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2 had no verified source on -// MonadScan or Sourcify, npm had no aPriori SDK and the `Apriori-labs` -// GitHub organisation had no public repositories. -// Update 2026-09-05: the implementation is now verified on MonadScan -// (contract `aprMON`, compiler v0.8.28+commit.7893614a, optimizer on, exact -// bytecode match); the proxy's live EIP-1967 slot resolves to it, matching -// abis.json. Its explorer ABI agrees with every signature below, `indexed` -// layouts included. ADR 0007 now wants this package on the explorer tier with -// a keyed compareDeployedAbi cross-check; that switch is tracked in #197. -// https://monadscan.com/address/0x0c65A0BC65a5D819235B71F554D210D3F80E0852 -// https://monadscan.com/address/0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2 -// -// Derivation is test-enforced, not asserted (reproducible selector/topic -// record). `test-online/abi-explorer.test.ts` recomputes every selector and -// topic hash below from this artifact with viem and requires each one to -// appear in the deployed implementation bytecode, requires the proxy's -// EIP-1967 slot to resolve to the implementation recorded in abis.json, and -// checks name/symbol/decimals on chain. Live evidence for the event shapes: -// Deposit tx 0x0e949bd6cc0ccaf608c8b679459b4ef19e18a5a82f359c56e873986576f27327, -// RedeemRequest tx 0x68316b21056b865c5d54fd17808716693da59d83392bb6420c03d9f1615b810d, -// Redeem tx 0x7413c8200dbec7806270958c68619f6f1458f70411cff80c84d6fd4eb9ced13f. -// The Redeem transaction's own logs and call trace are also a test -// (`test/adapter.test.ts`): `indexed` never enters the event signature, so a -// topic hash cannot certify a layout, and a fixture encoded off the ABI under -// test can only fail by accident. The chain's bytes pin it. -// -// deposit(uint256,address) 0x6e553f65 payable, assets == msg.value -// requestRedeem(uint256,address,address) 0x7d41c86e (shares, controller, owner) -// redeem(uint256[],address) 0x492e47d2 no return value, batch by requestID -// convertToShares(uint256) 0xc6e6f592 view -// convertToAssets(uint256) 0x07a2d13a view -// name() 0x06fdde03 "aPriori Monad LST" -// symbol() 0x95d89b41 "aprMON" -// decimals() 0x313ce567 18 -// Deposit(address,address,uint256,uint256) -// 0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7 -// RedeemRequest(address,address,uint256,address,uint256,uint256) -// 0x110990b6c317a85848c161e269666a01fea23eb9e16150c2c46cae8c0faf4a9d -// Redeem(address,address,uint256,uint256,uint256,uint256) -// 0x8caf04742286d017f9ac3924388e188c73e6e5094311c5e59a61a7ef86dda8bf -// -// The docs also describe view helpers (viewRedeemRequest, getUserRequestData); -// they are not vendored here and enter with the explorer-tier artifact (#197). -// Every entry below is selector/topic-verified against the deployed bytecode. -import { parseAbi } from "viem"; +// ABI origin: explorer (ADR 0007) +// Source: https://monadscan.com/address/0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2 +// Endpoint: Etherscan V2 (chainid=143, module=contract, action=getabi) +// Retrieved: 2026-09-10 (UTC) -export const AprMonAbi = parseAbi([ - "function deposit(uint256 assets, address receiver) payable returns (uint256 shares)", - "function requestRedeem(uint256 shares, address controller, address owner) returns (uint256 requestId)", - "function redeem(uint256[] requestIDs, address receiver)", - "function convertToShares(uint256 assets) view returns (uint256)", - "function convertToAssets(uint256 shares) view returns (uint256)", - "function name() view returns (string)", - "function symbol() view returns (string)", - "function decimals() view returns (uint8)", - "event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)", - "event RedeemRequest(address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 shares, uint256 assets)", - "event Redeem(address indexed controller, address indexed receiver, uint256 indexed requestId, uint256 shares, uint256 assets, uint256 fee)", -]); +export const AprMonAbi = [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AlreadyClaimed", + "type": "error" + }, + { + "inputs": [], + "name": "BelowMinimumRedeemAmount", + "type": "error" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "ERC2612ExpiredSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC2612InvalidSigner", + "type": "error" + }, + { + "inputs": [], + "name": "EnforcedPause", + "type": "error" + }, + { + "inputs": [], + "name": "ExpectedPause", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentNonce", + "type": "uint256" + } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBlockNumber", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBurnableShares", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLastProcessedRequestId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRange", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRequestId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRewardFee", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTotalStaked", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidUtilisedPendingDeposit", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidWithdrawalFee", + "type": "error" + }, + { + "inputs": [], + "name": "NoActiveValidators", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyOracleOperatorAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfOrderOracleTx", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "RebalanceBatchNotWithdrawn", + "type": "error" + }, + { + "inputs": [], + "name": "RebalanceInProgressAlready", + "type": "error" + }, + { + "inputs": [], + "name": "RebalanceNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "RequestIdsArrayFull", + "type": "error" + }, + { + "inputs": [], + "name": "StakePrecompileClaimRewardsFailed", + "type": "error" + }, + { + "inputs": [], + "name": "StakePrecompileDelegateFailed", + "type": "error" + }, + { + "inputs": [], + "name": "StakePrecompileUndelegateFailed", + "type": "error" + }, + { + "inputs": [], + "name": "StakePrecompileWithdrawFailed", + "type": "error" + }, + { + "inputs": [], + "name": "TotalAssetsIsZero", + "type": "error" + }, + { + "inputs": [], + "name": "TransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "TxAlreadyProcessed", + "type": "error" + }, + { + "inputs": [], + "name": "UnauthorizedOperator", + "type": "error" + }, + { + "inputs": [], + "name": "ValidatorLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "WaitMoreTime", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "rewardFees", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawalFees", + "type": "uint256" + } + ], + "name": "ClaimedProtocolFees", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "epochLength", + "type": "uint256" + } + ], + "name": "EpochLengthUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardsDistributing", + "type": "uint256" + } + ], + "name": "EpochRewardsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "protocolFeeVault", + "type": "address" + } + ], + "name": "FeeVaultUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "minimumRedeem", + "type": "uint256" + } + ], + "name": "MinimumRedeemUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "OperatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalPendingDeposit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalStaked", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "burnableShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lastProcessedRequestId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardFeesAccumulated", + "type": "uint256" + } + ], + "name": "OracleDataUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "OracleOperatorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "rebalanceInProgress", + "type": "uint256" + } + ], + "name": "RebalanceStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "RedeemRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "rewardFee", + "type": "uint8" + } + ], + "name": "RewardFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "stakePrecompile", + "type": "address" + } + ], + "name": "StakePrecompileUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "validatorId", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isStaked", + "type": "bool" + } + ], + "name": "ValidatorRebalance", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "validatorsRegistry", + "type": "address" + } + ], + "name": "ValidatorsRegistryUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "withdrawalDelay", + "type": "uint64" + } + ], + "name": "WithdrawalDelayUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawalFee", + "type": "uint256" + } + ], + "name": "WithdrawalFeeUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DUST_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_SHARE_SUPPLY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "asset", + "outputs": [ + { + "internalType": "address", + "name": "assetTokenAddress", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "burnableShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimProtocolFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_totalWithdrawalAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_totalBurnableShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_nextRequestId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_pendingDeposit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_blockNumber", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "_rebalanceNeeded", + "type": "bool" + } + ], + "name": "claimValidatorRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "claimableRedeemRequest", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "convertToShares", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentWithdrawalBatchId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "batchSize", + "type": "uint256" + } + ], + "name": "getPendingWithdrawalAmounts", + "outputs": [ + { + "internalType": "uint256", + "name": "_totalWithdrawalAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_totalBurnableShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_nextRequestId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_pendingDeposit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "startIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pageSize", + "type": "uint256" + } + ], + "name": "getUserRequestData", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "claimed", + "type": "bool" + }, + { + "internalType": "bool", + "name": "claimable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "unlockEpoch", + "type": "uint64" + } + ], + "internalType": "struct aprMON.RequestData[]", + "name": "requestData", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getUserRequestDataCount", + "outputs": [ + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_validatorsRegistry", + "type": "address" + }, + { + "internalType": "address payable", + "name": "_stakePrecompile", + "type": "address" + }, + { + "internalType": "address", + "name": "_feeVault", + "type": "address" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_minimumRedeem", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_withdrawalFee", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "_rewardFee", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "_epochLength", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "_withdrawalDelay", + "type": "uint64" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastProcessedBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastProcessedRequestId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastProcessedWithdrawalBatchId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestUpdateBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "maxDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "maxAssets", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "maxMint", + "outputs": [ + { + "internalType": "uint256", + "name": "maxShares", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "maxRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "maxShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "maxWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "maxAssets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "midUpdateAdjustment", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minimumRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextRequestId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleOperator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleParams", + "outputs": [ + { + "internalType": "uint256", + "name": "totalWithdrawalAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalBurnableShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nextRequestId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pendingDeposit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "rebalanceNeeded", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum aprMON.OracleTxType", + "name": "", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "oracleUpdateTracking", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "pendingRedeemRequest", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewMint", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + } + ], + "name": "processDepositsAndWithdrawals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rebalanceBatchId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rebalanceBatchWithdrawn", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + } + ], + "name": "rebalanceDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rebalanceInProgress", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + } + ], + "name": "rebalanceUndelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "requestIDs", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "requestRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardFee", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardFeesAccumulated", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsDistributing", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epochLength", + "type": "uint256" + } + ], + "name": "setEpochLength", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_feeVault", + "type": "address" + } + ], + "name": "setFeeVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_minimumRedeem", + "type": "uint256" + } + ], + "name": "setMinimumRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oracleOperator", + "type": "address" + } + ], + "name": "setOracleOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "_rewardFee", + "type": "uint8" + } + ], + "name": "setRewardFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "_stakePrecompile", + "type": "address" + } + ], + "name": "setStakePrecompile", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_validatorsRegistry", + "type": "address" + } + ], + "name": "setValidatorsRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "_withdrawalDelay", + "type": "uint64" + } + ], + "name": "setWithdrawalDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_withdrawalFee", + "type": "uint256" + } + ], + "name": "setWithdrawalFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakePrecompile", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalPendingDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalStaked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_blockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_pendingDepositUtilisedForWithdrawals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_rewardsAfterProcessingWithdrawals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_totalStaked", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_burnableShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lastProcessedRequestId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_rewardFees", + "type": "uint256" + } + ], + "name": "updateOracleData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "validatorBatchIndices", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "validatorsRegistry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "viewRedeemRequest", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "claimed", + "type": "bool" + }, + { + "internalType": "bool", + "name": "claimable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "unlockEpoch", + "type": "uint64" + } + ], + "internalType": "struct aprMON.RequestData", + "name": "redeemRequest", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "withdrawalBatchUnlockEpochs", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalDelay", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalFeesAccumulated", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] as const; diff --git a/packages/protocols/apriori/src/adapter.ts b/packages/protocols/apriori/src/adapter.ts index 87c1c654..f04db71f 100644 --- a/packages/protocols/apriori/src/adapter.ts +++ b/packages/protocols/apriori/src/adapter.ts @@ -27,9 +27,10 @@ import type { ClaimOutcome, StakeOutcome, UnstakeOutcome } from "./types.js"; // Token"; upgrade history shows the current EIP-1967 implementation // 0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2 set at block 40,124,891): // https://monadscan.com/address/0x0c65A0BC65a5D819235B71F554D210D3F80E0852 -// The proxy linkage, bytecode, token metadata, and every vendored -// selector/topic are enforced on chain by test-online/abi-explorer.test.ts. -// ABI origin: vendored (ADR 0007) — see src/abis/apriori.ts +// The proxy linkage, bytecode, token metadata and the committed ABI are +// enforced on chain by test-online/abi-explorer.test.ts, which cross-checks the +// committed artifact against the explorer-verified implementation. +// ABI origin: explorer (ADR 0007), see src/abis/apriori.ts export const APRMON_ADDRESS: AddressValue = "0x0c65A0BC65a5D819235B71F554D210D3F80E0852" as const; // Static ERC-20 metadata for aprMON, verified on chain by the online suite. diff --git a/packages/protocols/apriori/test-online/abi-explorer.test.ts b/packages/protocols/apriori/test-online/abi-explorer.test.ts index 24c3e3ac..4f0fdd12 100644 --- a/packages/protocols/apriori/test-online/abi-explorer.test.ts +++ b/packages/protocols/apriori/test-online/abi-explorer.test.ts @@ -1,45 +1,50 @@ /** - * On-chain derivation checks for the vendored aPriori ABI (ADR 0007). + * Keyed online cross-check for the explorer-tier aPriori aprMON ABI (ADR 0007). * - * When this suite was written the aprMON EIP-1967 implementation was - * unverified on MonadScan and Sourcify, so there was no explorer artifact to - * fetch and compare and no API key involved. The ABI is vendored verbatim from - * aPriori's official integration docs (see src/abis/apriori.ts) and this suite - * enforces the derivation directly against Monad mainnet: + * Online and keyed on purpose: requires MONADSCAN_API_KEY plus Monad mainnet + * RPC and runs only via `pnpm test:abi:online`, never inside the offline + * `pnpm test` suite. A missing key FAILS this suite instead of skipping, so a + * misconfigured pipeline cannot stay green. * - * - the proxy address recorded in abis.json matches the adapter's constant; - * - the proxy's EIP-1967 slot still resolves to the implementation recorded - * in abis.json — an aPriori upgrade turns this suite red so a human - * re-verifies the vendored ABI before trusting it again; - * - proxy and implementation both have deployed bytecode; - * - every vendored function selector and event topic hash, recomputed from - * the artifact, appears in the deployed implementation bytecode; + * What it enforces: + * - the aprMON proxy recorded in abis.json is the one the adapter uses; + * - the proxy still points at the implementation recorded in abis.json + * (ERC-1967 slot read); an aPriori upgrade turns this suite red so a human + * re-verifies the ABI before trusting it again; * - on-chain name/symbol/decimals match the exported APRMON_* constants; - * - convertToShares/convertToAssets round-trip at a sane LST exchange rate. - * - * Requires Monad mainnet RPC; runs only via `pnpm test:abi:online`. - * The implementation is verified on MonadScan since 2026-09-05; replacing this - * with the keyed fetchAbi + compareDeployedAbi cross-check used by - * protocol-kuru is tracked in #197. + * - convertToShares/convertToAssets round-trip at a sane LST exchange rate; + * - the committed ABI is semantically identical to the ABI of the + * explorer-verified implementation: a second supply chain, independent of + * the committed artifact, that catches any drift. */ import { readFileSync } from "node:fs"; -import { ERC1967_IMPLEMENTATION_SLOT, erc1967ImplementationAddress } from "@themoss/abi-tools"; +import { + compareDeployedAbi, + ERC1967_IMPLEMENTATION_SLOT, + erc1967ImplementationAddress, + fetchAbi, +} from "@themoss/abi-tools"; import { createRuntime } from "@themoss/core"; -import { type Address, getAddress, toEventSelector, toFunctionSelector } from "viem"; +import { type Address, getAddress } from "viem"; import { describe, expect, it } from "vitest"; import { AprMonAbi } from "../src/abis/apriori.js"; import { APRMON_ADDRESS, APRMON_DECIMALS, APRMON_NAME, APRMON_SYMBOL } from "../src/index.js"; interface AbiManifest { - aprMon: { proxy: Address; implementation: Address }; + aprMon: { proxy: Address; implementation: Address; allowedExplorerOnly: string[] }; } const manifest = JSON.parse( readFileSync(new URL("../abis.json", import.meta.url), "utf8"), ) as AbiManifest; +const key = process.env.MONADSCAN_API_KEY; + +describe("aPriori ABI explorer cross-check", () => { + it("requires MONADSCAN_API_KEY", () => { + expect(key, "MONADSCAN_API_KEY must be set for pnpm test:abi:online").toBeTruthy(); + }); -describe("aPriori ABI on-chain derivation", () => { it("pins the aprMON proxy the adapter actually uses", () => { expect(getAddress(manifest.aprMon.proxy)).toBe(getAddress(APRMON_ADDRESS)); }); @@ -62,24 +67,6 @@ describe("aPriori ABI on-chain derivation", () => { ); }); - it("every vendored selector and topic hash appears in the implementation bytecode", { - timeout: 60_000, - }, async () => { - const runtime = await createRuntime(); - const code = await runtime.client.getCode({ address: manifest.aprMon.implementation }); - expect(code?.length ?? 0).toBeGreaterThan(2); - const haystack = (code ?? "0x").toLowerCase(); - for (const item of AprMonAbi) { - const needle = - item.type === "function" - ? toFunctionSelector(item).slice(2) - : toEventSelector(item).slice(2); - expect(haystack, `${item.type} ${item.name} (${needle}) missing from bytecode`).toContain( - needle.toLowerCase(), - ); - } - }); - it("matches on-chain token metadata against the exported constants", { timeout: 60_000, }, async () => { @@ -131,4 +118,14 @@ describe("aPriori ABI on-chain derivation", () => { const drift = roundTrip > one ? roundTrip - one : one - roundTrip; expect(drift).toBeLessThan(10n ** 15n); }); + + it("committed aprMON ABI matches the explorer-verified implementation", { + timeout: 120_000, + }, async () => { + const explorerAbi = await fetchAbi(manifest.aprMon.implementation, key ?? ""); + const issues = compareDeployedAbi(AprMonAbi, explorerAbi, { + allowedActualOnly: manifest.aprMon.allowedExplorerOnly, + }); + expect(issues).toEqual([]); + }); }); diff --git a/packages/protocols/apriori/test/abis.test.ts b/packages/protocols/apriori/test/abis.test.ts new file mode 100644 index 00000000..00d2509e --- /dev/null +++ b/packages/protocols/apriori/test/abis.test.ts @@ -0,0 +1,40 @@ +import { readFileSync } from "node:fs"; +import { type RenderAbiModuleOptions, renderAbiModule } from "@themoss/abi-tools"; +import { describe, expect, it } from "vitest"; +import { SOURCES } from "../scripts/abis.js"; + +describe("aPriori explorer ABI provenance", () => { + it("derives the committed ABI from the verified implementation source", () => { + const [source] = SOURCES; + expect(source).toEqual({ + address: "0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2", + exportName: "AprMon", + file: "apriori.ts", + }); + const committed = readFileSync(new URL("../src/abis/apriori.ts", import.meta.url), "utf8"); + const retrieved = /^\/\/ {3}Retrieved: (\d{4}-\d{2}-\d{2}) \(UTC\)$/m.exec(committed)?.[1]; + const literal = /^export const \w+Abi = (\[[\s\S]*\]) as const;$/m.exec(committed)?.[1]; + expect(retrieved).toBeDefined(); + expect(literal).toBeDefined(); + const abi = JSON.parse(literal as string) as RenderAbiModuleOptions["abi"]; + // The three events the Receipt parsers decode plus the two view helpers the + // explorer tier brings in that the vendored artifact never carried. + expect(abi).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "event", name: "Deposit" }), + expect.objectContaining({ type: "event", name: "RedeemRequest" }), + expect.objectContaining({ type: "event", name: "Redeem" }), + expect.objectContaining({ type: "function", name: "viewRedeemRequest" }), + expect.objectContaining({ type: "function", name: "getUserRequestData" }), + ]), + ); + expect(committed).toBe( + renderAbiModule({ + exportName: source?.exportName ?? "", + address: source?.address ?? "0x0000000000000000000000000000000000000000", + abi, + retrievedAt: new Date(`${retrieved}T00:00:00Z`), + }), + ); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f875f953..e048d699 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -300,6 +300,9 @@ importers: tsup: specifier: ^8.5.1 version: 8.5.1(postcss@8.5.23)(tsx@4.23.0)(typescript@5.9.3) + tsx: + specifier: ^4.19.0 + version: 4.23.0 typescript: specifier: ~5.9.0 version: 5.9.3 From c5d91a3197b45cca09e141664ed20d700e236a6f Mon Sep 17 00:00:00 2001 From: a Date: Wed, 16 Sep 2026 16:38:16 +0800 Subject: [PATCH 2/2] test(apriori): tie the source table to the pinned implementation; finish the explorer-tier wording - test/abis.test.ts asserts SOURCES fetches the implementation abis.json pins, so the offline derivation and the keyed cross-check cannot point at different contracts; drop the dead fallbacks. - test-online keeps the implementation bytecode check the deleted selector test used to carry. - vitest.online.config.ts describes the keyed suite; README states what the offline test does and does not defend. - Changeset minor: the exported AprMonAbi widens from 11 to 165 entries. --- .changeset/apriori-explorer-tier.md | 2 +- packages/protocols/apriori/README.md | 8 +++++--- .../apriori/test-online/abi-explorer.test.ts | 10 ++++++---- packages/protocols/apriori/test/abis.test.ts | 15 ++++++++++++--- .../protocols/apriori/vitest.online.config.ts | 9 +++------ 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.changeset/apriori-explorer-tier.md b/.changeset/apriori-explorer-tier.md index 85303318..2d9defa2 100644 --- a/.changeset/apriori-explorer-tier.md +++ b/.changeset/apriori-explorer-tier.md @@ -1,5 +1,5 @@ --- -"@themoss/protocol-apriori": patch +"@themoss/protocol-apriori": minor --- Move the aprMON ABI to the explorer tier (ADR 0007). The EIP-1967 implementation diff --git a/packages/protocols/apriori/README.md b/packages/protocols/apriori/README.md index 44a25917..58b25f76 100644 --- a/packages/protocols/apriori/README.md +++ b/packages/protocols/apriori/README.md @@ -44,9 +44,11 @@ MONADSCAN_API_KEY=... pnpm --filter @themoss/protocol-apriori update:abis name, module file); `update:abis` re-fetches and renders from it. - `abis.json` pins the expected proxy/implementation pair and the reviewed `allowedExplorerOnly` exceptions (none today). -- `test/abis.test.ts` (offline) re-derives the committed module from its own - embedded ABI and retrieval date and asserts byte-for-byte equality, so a - hand-edit or drift from `renderAbiModule` fails closed. +- `test/abis.test.ts` (offline) asserts the source table fetches the implementation + `abis.json` pins, and re-derives the committed module from its own embedded ABI + and retrieval date, so drift from `renderAbiModule`'s canonical rendering or from + the recorded address fails closed. ABI content itself is defended by the keyed + cross-check below and, for the three decoded events, by the real-log Receipt test. - `test-online/abi-explorer.test.ts` (keyed, needs `MONADSCAN_API_KEY`) verifies on chain that the proxy's EIP-1967 slot still resolves to the recorded implementation, that the proxy has deployed bytecode, that on-chain diff --git a/packages/protocols/apriori/test-online/abi-explorer.test.ts b/packages/protocols/apriori/test-online/abi-explorer.test.ts index 4f0fdd12..122c23ca 100644 --- a/packages/protocols/apriori/test-online/abi-explorer.test.ts +++ b/packages/protocols/apriori/test-online/abi-explorer.test.ts @@ -49,11 +49,13 @@ describe("aPriori ABI explorer cross-check", () => { expect(getAddress(manifest.aprMon.proxy)).toBe(getAddress(APRMON_ADDRESS)); }); - it("has deployed bytecode at the aprMON proxy address", { timeout: 60_000 }, async () => { + it("has deployed bytecode at the proxy and the recorded implementation", { + timeout: 60_000, + }, async () => { const runtime = await createRuntime(); - expect( - (await runtime.client.getCode({ address: manifest.aprMon.proxy }))?.length, - ).toBeGreaterThan(2); + for (const address of [manifest.aprMon.proxy, manifest.aprMon.implementation]) { + expect((await runtime.client.getCode({ address }))?.length, address).toBeGreaterThan(2); + } }); it("aprMON proxy still points at the recorded implementation", { timeout: 60_000 }, async () => { diff --git a/packages/protocols/apriori/test/abis.test.ts b/packages/protocols/apriori/test/abis.test.ts index 00d2509e..020b3855 100644 --- a/packages/protocols/apriori/test/abis.test.ts +++ b/packages/protocols/apriori/test/abis.test.ts @@ -3,11 +3,20 @@ import { type RenderAbiModuleOptions, renderAbiModule } from "@themoss/abi-tools import { describe, expect, it } from "vitest"; import { SOURCES } from "../scripts/abis.js"; +interface AbiManifest { + aprMon: { proxy: string; implementation: string; allowedExplorerOnly: string[] }; +} + describe("aPriori explorer ABI provenance", () => { it("derives the committed ABI from the verified implementation source", () => { const [source] = SOURCES; + // The fetch address is the implementation the online suite pins on chain, so + // the offline and keyed checks can never point at different contracts. + const manifest = JSON.parse( + readFileSync(new URL("../abis.json", import.meta.url), "utf8"), + ) as AbiManifest; expect(source).toEqual({ - address: "0x7D2F8dc5a67CA1911bb1A2429552CDf507d106F2", + address: manifest.aprMon.implementation, exportName: "AprMon", file: "apriori.ts", }); @@ -30,8 +39,8 @@ describe("aPriori explorer ABI provenance", () => { ); expect(committed).toBe( renderAbiModule({ - exportName: source?.exportName ?? "", - address: source?.address ?? "0x0000000000000000000000000000000000000000", + exportName: source.exportName, + address: source.address, abi, retrievedAt: new Date(`${retrieved}T00:00:00Z`), }), diff --git a/packages/protocols/apriori/vitest.online.config.ts b/packages/protocols/apriori/vitest.online.config.ts index e8b490af..64def681 100644 --- a/packages/protocols/apriori/vitest.online.config.ts +++ b/packages/protocols/apriori/vitest.online.config.ts @@ -3,12 +3,9 @@ import { defineConfig } from "vitest/config"; const src = (path: string) => fileURLToPath(new URL(path, import.meta.url)); -// The online ABI derivation suite (pnpm test:abi:online). Keyless: written -// while the aprMON implementation was unverified on MonadScan, so the vendored -// ABI is enforced directly against mainnet RPC (EIP-1967 linkage, bytecode -// selector/topic presence, token metadata) instead of a keyed explorer fetch. -// The keyed cross-check is tracked in #197. Kept apart from the offline -// default `pnpm test`. +// The online explorer cross-check suite (pnpm test:abi:online). Kept apart +// from the offline default so a missing MONADSCAN_API_KEY fails loudly here +// without ever gating `pnpm test`. export default defineConfig({ esbuild: { target: "es2022" }, test: { include: ["test-online/**/*.test.ts"] },