diff --git a/.changeset/apriori-explorer-tier.md b/.changeset/apriori-explorer-tier.md new file mode 100644 index 00000000..2d9defa2 --- /dev/null +++ b/.changeset/apriori-explorer-tier.md @@ -0,0 +1,13 @@ +--- +"@themoss/protocol-apriori": minor +--- + +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..58b25f76 100644 --- a/packages/protocols/apriori/README.md +++ b/packages/protocols/apriori/README.md @@ -24,29 +24,41 @@ 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) 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 + `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..122c23ca 100644 --- a/packages/protocols/apriori/test-online/abi-explorer.test.ts +++ b/packages/protocols/apriori/test-online/abi-explorer.test.ts @@ -1,54 +1,61 @@ /** - * 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)); }); - 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 () => { @@ -62,24 +69,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 +120,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..020b3855 --- /dev/null +++ b/packages/protocols/apriori/test/abis.test.ts @@ -0,0 +1,49 @@ +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"; + +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: manifest.aprMon.implementation, + 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, + 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"] }, 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