diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 5afaab642c..0adf701ae2 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -39,6 +39,7 @@ jobs: filters: | src: - 'contracts/src/**' + - 'contracts/shanghai/**' foundry: - 'foundry.toml' test: @@ -104,6 +105,18 @@ jobs: - name: Verify storage layout interop between src/ and src/legacy/ run: python3 contracts/scripts/verify-storage-layout.py + - name: forge build (shanghai profile) + run: FOUNDRY_PROFILE=shanghai forge build --silent contracts/shanghai/variants/BoundlessMarket.sol contracts/shanghai/legacy/BoundlessMarketLegacy.sol + + - name: Verify shanghai legacy/ bytecode matches deployed Taiko impl + run: BOUNDLESS_OUT_DIR=out-shanghai BOUNDLESS_LEGACY_SNAPSHOT_DIR=contracts/shanghai/legacy python3 contracts/scripts/verify-legacy-bytecode.py + + - name: Verify shanghai market/legacy storage layout interop + run: BOUNDLESS_OUT_DIR=out-shanghai python3 contracts/scripts/verify-storage-layout.py + + - name: Run the contract suite under the shanghai profile + run: FOUNDRY_PROFILE=shanghai forge test --isolate + upgradability: runs-on: ubuntu-latest needs: contracts-changed diff --git a/contracts/scripts/Deploy.s.sol b/contracts/scripts/Deploy.s.sol index 1e76198695..1d51a2f26f 100644 --- a/contracts/scripts/Deploy.s.sol +++ b/contracts/scripts/Deploy.s.sol @@ -18,8 +18,8 @@ import {BoundlessRouter} from "../src/router/BoundlessRouter.sol"; import {ControlID} from "../src/blake3-groth16/ControlID.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; -import {BoundlessMarket} from "../src/BoundlessMarket.sol"; -import {BoundlessMarket as BoundlessMarketLegacy} from "../src/legacy/BoundlessMarketLegacy.sol"; +import {BoundlessMarket} from "boundless-market/BoundlessMarket.sol"; +import {BoundlessMarket as BoundlessMarketLegacy} from "boundless-market-legacy/BoundlessMarketLegacy.sol"; import {HitPoints} from "../src/HitPoints.sol"; import {BoundlessScriptBase} from "./BoundlessScript.s.sol"; diff --git a/contracts/scripts/Manage.s.sol b/contracts/scripts/Manage.s.sol index e3acca0ab1..6ce58ac4f1 100644 --- a/contracts/scripts/Manage.s.sol +++ b/contracts/scripts/Manage.s.sol @@ -9,8 +9,8 @@ import {Script} from "forge-std/Script.sol"; import {console2} from "forge-std/console2.sol"; import {Strings} from "openzeppelin/contracts/utils/Strings.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {BoundlessMarket} from "../src/BoundlessMarket.sol"; -import {BoundlessMarket as BoundlessMarketLegacy} from "../src/legacy/BoundlessMarketLegacy.sol"; +import {BoundlessMarket} from "boundless-market/BoundlessMarket.sol"; +import {BoundlessMarket as BoundlessMarketLegacy} from "boundless-market-legacy/BoundlessMarketLegacy.sol"; import {BoundlessRouter} from "../src/router/BoundlessRouter.sol"; import {BoundlessMarketLib} from "../src/libraries/BoundlessMarketLib.sol"; import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; diff --git a/contracts/scripts/verify-legacy-bytecode.py b/contracts/scripts/verify-legacy-bytecode.py index 0317d0b25f..cefd66be83 100755 --- a/contracts/scripts/verify-legacy-bytecode.py +++ b/contracts/scripts/verify-legacy-bytecode.py @@ -24,14 +24,21 @@ """ import json +import os import re import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[2] -ARTIFACT = REPO_ROOT / "out" / "BoundlessMarketLegacy.sol" / "BoundlessMarket.json" -DEPLOYED_HEX = REPO_ROOT / "contracts" / "test" / "legacy" / "deployed-bytecode.hex" -META_TOML = REPO_ROOT / "contracts" / "test" / "legacy" / "deployed-bytecode.meta.toml" +# Defaults check the Cancun/Base legacy. Override via env to check the Taiko legacy compiled under +# FOUNDRY_PROFILE=shanghai: +# BOUNDLESS_OUT_DIR=out-shanghai +# BOUNDLESS_LEGACY_SNAPSHOT_DIR=contracts/shanghai/legacy +_OUT_DIR = REPO_ROOT / os.environ.get("BOUNDLESS_OUT_DIR", "out") +_SNAPSHOT_DIR = REPO_ROOT / os.environ.get("BOUNDLESS_LEGACY_SNAPSHOT_DIR", "contracts/test/legacy") +ARTIFACT = _OUT_DIR / "BoundlessMarketLegacy.sol" / "BoundlessMarket.json" +DEPLOYED_HEX = _SNAPSHOT_DIR / "deployed-bytecode.hex" +META_TOML = _SNAPSHOT_DIR / "deployed-bytecode.meta.toml" def strip0x(s: str) -> str: diff --git a/contracts/scripts/verify-storage-layout.py b/contracts/scripts/verify-storage-layout.py index 754c4953b1..ac7b15f9ab 100755 --- a/contracts/scripts/verify-storage-layout.py +++ b/contracts/scripts/verify-storage-layout.py @@ -25,13 +25,17 @@ """ import json +import os import re import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[2] -NEW_ARTIFACT = REPO_ROOT / "out" / "BoundlessMarket.sol" / "BoundlessMarket.json" -LEGACY_ARTIFACT = REPO_ROOT / "out" / "BoundlessMarketLegacy.sol" / "BoundlessMarket.json" +# Default profile writes to ./out; the shanghai profile writes to ./out-shanghai. Point the script +# at the latter with `BOUNDLESS_OUT_DIR=out-shanghai` to verify the Taiko market/legacy pair. +_OUT_DIR = REPO_ROOT / os.environ.get("BOUNDLESS_OUT_DIR", "out") +NEW_ARTIFACT = _OUT_DIR / "BoundlessMarket.sol" / "BoundlessMarket.json" +LEGACY_ARTIFACT = _OUT_DIR / "BoundlessMarketLegacy.sol" / "BoundlessMarket.json" # Top-level storage variables shared between the two contracts. Order + # expected slot are part of the contract — any change here is a real diff --git a/contracts/shanghai/src/compat/Bytes.sol b/contracts/shanghai/compat/Bytes.sol similarity index 100% rename from contracts/shanghai/src/compat/Bytes.sol rename to contracts/shanghai/compat/Bytes.sol diff --git a/contracts/shanghai/src/BoundlessMarket.sol b/contracts/shanghai/legacy/BoundlessMarketLegacy.sol similarity index 99% rename from contracts/shanghai/src/BoundlessMarket.sol rename to contracts/shanghai/legacy/BoundlessMarketLegacy.sol index 9408e97612..adf91c6dcf 100644 --- a/contracts/shanghai/src/BoundlessMarket.sol +++ b/contracts/shanghai/legacy/BoundlessMarketLegacy.sol @@ -24,8 +24,8 @@ import { } from "risc0/IRiscZeroVerifier.sol"; import {IRiscZeroSetVerifier} from "risc0/IRiscZeroSetVerifier.sol"; -import {IBoundlessMarket} from "./IBoundlessMarket.sol"; -import {IBoundlessMarketCallback} from "./IBoundlessMarketCallback.sol"; +import {IBoundlessMarket} from "./IBoundlessMarketLegacy.sol"; +import {IBoundlessMarketCallback} from "./IBoundlessMarketCallbackLegacy.sol"; import {Account} from "./types/Account.sol"; import {AssessorJournal} from "./types/AssessorJournal.sol"; import {AssessorCallback} from "./types/AssessorCallback.sol"; diff --git a/contracts/shanghai/src/IBoundlessMarketCallback.sol b/contracts/shanghai/legacy/IBoundlessMarketCallbackLegacy.sol similarity index 100% rename from contracts/shanghai/src/IBoundlessMarketCallback.sol rename to contracts/shanghai/legacy/IBoundlessMarketCallbackLegacy.sol diff --git a/contracts/shanghai/src/IBoundlessMarket.sol b/contracts/shanghai/legacy/IBoundlessMarketLegacy.sol similarity index 100% rename from contracts/shanghai/src/IBoundlessMarket.sol rename to contracts/shanghai/legacy/IBoundlessMarketLegacy.sol diff --git a/contracts/shanghai/legacy/LEGACY-FROZEN.md b/contracts/shanghai/legacy/LEGACY-FROZEN.md new file mode 100644 index 0000000000..9d322ffca7 --- /dev/null +++ b/contracts/shanghai/legacy/LEGACY-FROZEN.md @@ -0,0 +1,54 @@ +# `contracts/shanghai/legacy/` — frozen audited tree (Taiko) + +This subtree is a frozen copy of the pre-router `BoundlessMarket` and its transitive dependencies +**as deployed on Taiko mainnet**, compiled under the Shanghai EVM (`FOUNDRY_PROFILE=shanghai`). It +exists so the new router-based shanghai market can forward its pre-router legacy ABI to the audited +bytecode at the existing implementation address via a `fallback() + delegatecall` shim, without +re-introducing the legacy bodies into the new market's bytecode. + +It is the Taiko/Shanghai counterpart of `contracts/src/legacy/` (which freezes the Base/Cancun +deployment). The two are separate because they reproduce different deployed bytecode: Taiko's impl +was compiled for the Shanghai EVM (no `tstore`/`mcopy`), so it uses the `sstore`/`sload` +`FulfillmentContext` and the `compat/Bytes` shim rather than the Cancun originals. + +## Provenance + +The sources here mirror the pre-router shanghai market suite (`contracts/shanghai/src/`) as it was +at the Taiko deployment. The only diffs from those sources are file-basename + import renames +(`BoundlessMarket.sol` → `BoundlessMarketLegacy.sol`, `IBoundlessMarket*.sol` → +`IBoundlessMarket*Legacy.sol`) so Forge writes artifacts to distinct `out-shanghai/` directories; the +contract **symbols** stay `BoundlessMarket` / `IBoundlessMarket*` so the build (with +`bytecode_hash = none`) is byte-identical to the deployed audited code. + +The on-chain identity that ultimately matters is the deployed bytecode at the Taiko mainnet +`BoundlessMarket` proxy's pre-upgrade implementation address: + +- proxy: `0xb3f5c7b4379052eade8c7f3fa6da37fb871da28b` +- impl: `0x6c2d2c33e9a7cd0e1b39dc218f472e4bf534523b` + +The bytecode-parity invariant under `deployed-bytecode.hex` + `deployed-bytecode.meta.toml` (in this +directory) is the load-bearing check. + +## Verification + +```bash +# Bytecode parity: this tree compiles to the deployed Taiko impl (modulo immutables). +FOUNDRY_PROFILE=shanghai forge build contracts/shanghai/legacy/BoundlessMarketLegacy.sol +BOUNDLESS_OUT_DIR=out-shanghai BOUNDLESS_LEGACY_SNAPSHOT_DIR=contracts/shanghai/legacy \ + uv run contracts/scripts/verify-legacy-bytecode.py + +# Storage-layout interop: the new shanghai market can safely delegatecall this legacy impl. +FOUNDRY_PROFILE=shanghai forge build \ + contracts/shanghai/variants/BoundlessMarket.sol contracts/shanghai/legacy/BoundlessMarketLegacy.sol +BOUNDLESS_OUT_DIR=out-shanghai uv run contracts/scripts/verify-storage-layout.py +``` + +(Each command is a single line; the `\` continuations above are for documentation only — paste them +as one line.) + +## Freeze policy + +Do not modify any file in this tree. Any drift between the frozen source and the deployed audited +bytecode is a real issue — fix the drift, do not edit the verification script or this snapshot to +mask it. The deployed implementation is reused directly as the new market's `LEGACY_IMPL`; this tree +is the source-of-record + the interop safety check, not a redeployment target. diff --git a/contracts/shanghai/legacy/compat/Bytes.sol b/contracts/shanghai/legacy/compat/Bytes.sol new file mode 100644 index 0000000000..bbd67276b3 --- /dev/null +++ b/contracts/shanghai/legacy/compat/Bytes.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +// Shanghai-compatible replacement for OpenZeppelin's Bytes.sol (v5.4.0) +// Replaces mcopy with a manual memory copy loop for pre-Cancun EVM compatibility. +pragma solidity ^0.8.24; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +library Bytes { + function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return indexOf(buffer, s, 0); + } + + function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + uint256 length = buffer.length; + for (uint256 i = pos; i < length; ++i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) { + return i; + } + } + return type(uint256).max; + } + + function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return lastIndexOf(buffer, s, type(uint256).max); + } + + function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + uint256 length = buffer.length; + for (uint256 i = Math.min(Math.saturatingAdd(pos, 1), length); i > 0; --i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) { + return i - 1; + } + } + return type(uint256).max; + } + } + + function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { + return slice(buffer, start, buffer.length); + } + + function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { + uint256 length = buffer.length; + end = Math.min(end, length); + start = Math.min(start, end); + + uint256 len = end - start; + bytes memory result = new bytes(len); + // Manual word-by-word copy (Shanghai-compatible, no mcopy) + assembly ("memory-safe") { + let src := add(add(buffer, 0x20), start) + let dst := add(result, 0x20) + let remaining := len + for {} iszero(lt(remaining, 0x20)) {} { + mstore(dst, mload(src)) + src := add(src, 0x20) + dst := add(dst, 0x20) + remaining := sub(remaining, 0x20) + } + if remaining { + let mask := sub(shl(shl(3, remaining), 1), 1) + mstore(dst, or(and(mload(dst), mask), and(mload(src), not(mask)))) + } + } + return result; + } + + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + assembly ("memory-safe") { + value := mload(add(add(buffer, 0x20), offset)) + } + } +} diff --git a/contracts/shanghai/legacy/deployed-bytecode.hex b/contracts/shanghai/legacy/deployed-bytecode.hex new file mode 100644 index 0000000000..de07e8f760 --- /dev/null +++ b/contracts/shanghai/legacy/deployed-bytecode.hex @@ -0,0 +1 @@ +0x6080806040526004361015610012575f80fd5b5f905f3560e01c90816301ffc9a71461223a5750806308c84e70146121f65780630b7ae1a71461216957806315d7a2401461214e5780631ce0302414612130578063248a9ca3146121115780632abff1f2146120065780632e107a9014611f845780632e1a7d4d14611f665780632f2ff15d14611f3457806336568abe14611eef57806341451f9414611e3e57806341d3ab6914611e23578063444161da14611de857806345bc4d1014611a7a5780634cefb7cf14611a535780634f1ef2861461186e57806352d1902d14611807578063553c0248146117eb5780635b07fdd8146117c85780635d704b331461171757806360dfd4a91461167f5780636112fe2e1461151e578063612bee0c146114fd57806370a08231146114ba5780637136a7f3146114a257806375b238fc146112405780637870d4811461148157806381bf6c241461143857806384b0196e1461131057806391d14854146112ba578063956b09601461129d5780639f04f420146112805780639fe9428c14611245578063a217fddf14611240578063ad2fa6c8146111b8578063ad3cb1cc1461116f578063ae7330f1146110d1578063afe171fd1461108d578063b09c980b14611047578063b760faf914610fc1578063bad4a01f14610fa2578063c515c15f14610f1d578063c64067a214610f05578063cb74db1114610edc578063cdc9712314610de6578063d0e30db014610dd2578063d4bd257b14610d35578063d547741f14610cfa578063df2e670614610c88578063eba2ecc814610c4a578063ece510a514610c05578063ef1ae1c814610bc0578063f2800f1a14610b69578063f399e22e1461059e578063fd737ea8146104e5578063ff1214a5146102ba5763ffa1ad741461029c575f80fd5b346102b757806003193601126102b757602060405160018152f35b80fd5b50346102b75760603660031901126102b7576004356001600160401b0381116104e157610160816004019160031990360301126104e1576024356001600160401b0381116104dd576103109036906004016122e2565b916044356001600160401b0381116104d9576103309036906004016122e2565b61033a833561443d565b9161034787878488614787565b60405191959161035860608261261b565b6021815260208101907f4c6f636b526571756573742850726f6f66526571756573742072657175657374825260408101602960f81b9052610397615324565b906103a061536e565b6103a86153b3565b6103b0615471565b6103b86154be565b916103c1615545565b9360405196879660208801998a915180926103db92612412565b8701815191826020830191602001916103f392612412565b0160200180825160208194019161040992612412565b0180825160208194019161041c92612412565b0180825160208194019161042f92612412565b0180825160208194019161044292612412565b0180825160208194019161045592612412565b0103601f1981018252610468908261261b565b51902090604051906020820192835260408201526040815261048b60608261261b565b519020610496615af3565b906104a091615ba8565b9136906104ac92612657565b6104b591615bc5565b6104c191959295615bff565b6104ca85614db8565b966104d6989196614f58565b80f35b8480fd5b8280fd5b5080fd5b50346102b75760c03660031901126102b7576104ff6122b8565b6024358260643560ff811681036104e1577f000000000000000000000000c284a781072442cc1882a8db4573990b7b49dac46001600160a01b0316803b156104dd5760405163d505accf60e01b815291839183918290849082906105749060a43590608435906044358d303360048901612d8e565b03925af1610589575b50506104d69133614565565b816105939161261b565b6104dd57825f61057d565b50346102b75760403660031901126102b7576105b86122b8565b906024356001600160401b0381116104e1576105d89036906004016122e2565b5f80516020615f3c833981519152939193549060ff8260401c1615916001600160401b03811680159081610b61575b6001149081610b57575b159081610b4e575b50610b3f5767ffffffffffffffff1981166001175f80516020615f3c8339815191525582610b13575b506001600160a01b03831615610b045761065a615b7d565b610662615b7d565b6040928351610671858261261b565b601081526f12509bdd5b991b195cdcd3585c9ad95d60821b602082015284519061069b868361261b565b60018252603160f81b60208301526106b1615b7d565b6106b9615b7d565b8051906001600160401b038211610af0576106e15f80516020615e7c8339815191525461286f565b601f8111610a81575b50602090601f8311600114610a055761071a92918991836108f7575b50508160011b915f199060031b1c19161790565b5f80516020615e7c833981519152555b8051906001600160401b0382116109f1576107525f80516020615e9c8339815191525461286f565b601f8111610982575b50602090601f831160011461090257918061078f926107c495948a926108f75750508160011b915f199060031b1c19161790565b5f80516020615e9c833981519152555b855f80516020615ebc83398151915255855f80516020615f5c83398151915255613ec8565b506001600160401b0381116108e3576107e7816107e260025461286f565b6128a7565b83601f82116001146108745781908596610816949596926108695750508160011b915f199060031b1c19161790565b6002555b610822575080f35b5f80516020615f3c833981519152805460ff60401b1916905551600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a180f35b013590505f80610706565b60028552601f198216955f80516020615e5c83398151915291865b8881106108cb575083600195969798106108b2575b505050811b0160025561081a565b01355f19600384901b60f8161c191690555f80806108a4565b9092602060018192868601358155019401910161088f565b634e487b7160e01b84526041600452602484fd5b015190505f80610706565b5f80516020615e9c83398151915288528188209190601f198416895b81811061096a57509160019391856107c497969410610952575b505050811b015f80516020615e9c8339815191525561079f565b01515f1960f88460031b161c191690555f8080610938565b9293602060018192878601518155019501930161091e565b5f80516020615e9c83398151915288527f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c810191602085106109e7575b601f0160051c01905b8181106109dc575061075b565b8881556001016109cf565b90915081906109c6565b634e487b7160e01b87526041600452602487fd5b5f80516020615e7c83398151915289528189209190601f1984168a5b818110610a695750908460019594939210610a51575b505050811b015f80516020615e7c8339815191525561072a565b01515f1960f88460031b161c191690555f8080610a37565b92936020600181928786015181550195019301610a21565b5f80516020615e7c83398151915289527f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f840160051c81019160208510610ae6575b601f0160051c01905b818110610adb57506106ea565b898155600101610ace565b9091508190610ac5565b634e487b7160e01b88526041600452602488fd5b63267eaa8160e21b8452600484fd5b68ffffffffffffffffff191668010000000000000001175f80516020615f3c833981519152555f610642565b63f92ee8a960e01b8552600485fd5b9050155f610619565b303b159150610611565b849150610607565b50346102b75760203660031901126102b75760043590610b888261392a565b15610bae576040816020936001600160401b039352808452205460a01c16604051908152f35b60249163d2be005d60e01b8252600452fd5b50346102b757806003193601126102b7576040517f000000000000000000000000c284a781072442cc1882a8db4573990b7b49dac46001600160a01b03168152602090f35b50346102b757806003193601126102b7576040517f000000000000000000000000607d196b43abc5d9be3c7fb8e336ca82fec18c456001600160a01b03168152602090f35b50346102b7576104d6610c5c3661275e565b91610c67813561443d565b90610c7485858386614787565b50610c7e84614db8565b9690953395614f58565b507fc354af001adff0e8c35481c5ce3df3edee370c71572514d281e884c8cb552203610cb33661275e565b9291909234610ced575b610ce760405192839260408452610cd76040850183613b8b565b91848303602086015235966127b1565b0390a280f35b610cf5613ad1565b610cbd565b50346102b75760403660031901126102b757610d31600435610d1a6122a2565b90610d2c610d2782612851565b613e82565b613ff5565b5080f35b50346102b757610d443661250c565b969095919490936001600160a01b039092169190823b156104d95791610d85939185809460405196879586948593636691f64760e01b8552600485016127d1565b03925af18015610dc757610db2575b610dae610da28686866127fc565b60405191829182612458565b0390f35b610dbd82809261261b565b6102b75780610d94565b6040513d84823e3d90fd5b50806003193601126102b7576104d6613ad1565b50346102b757806003193601126102b757604051908060025490610e098261286f565b8085529160018116908115610eb55750600114610e6b575b610dae84610e318186038261261b565b6040519182917f6c5a03c0785e91bc0ad0db486004116010680a03af4e712bcca3188e566941008352604060208401526040830190612433565b600281525f80516020615e5c833981519152939250905b808210610e9b57509091508101602001610e3182610e21565b919260018160209254838588010152019101909291610e82565b60ff191660208087019190915292151560051b85019092019250610e319150839050610e21565b50346102b75760203660031901126102b7576020610efb60043561392a565b6040519015158152f35b50346102b7576104d6610f173661275e565b91613890565b50346102b75760203660031901126102b757604060e091600435815280602052208054906001600160601b0360026001830154920154916040519360018060a01b03811685526001600160401b038160a01c16602086015262ffffff81871c16604086015260f81c6060850152818116608085015260601c1660a083015260c0820152f35b50346102b75760203660031901126102b7576104d66004353333614565565b5060203660031901126102b757610fd66122b8565b610fdf34614534565b9060018060a01b03169081835260016020526001600160601b0361100a604085209282845416612d23565b166001600160601b03198254161790557fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c6020604051348152a280f35b50346102b75760203660031901126102b7576020906001600160601b03906040906001600160a01b036110786122b8565b16815260018452205460601c16604051908152f35b50346102b757806003193601126102b75760206040516001600160401b037f0000000000000000000000000000000000000000000000000000000069bb16e3168152f35b50346102b75760603660031901126102b757806110ec6122b8565b6044356001600160401b03811161116b5761110b9036906004016122e2565b6001600160a01b0390921691823b156111665761114492849283604051809681958294636691f64760e01b8452602435600485016127d1565b03925af18015610dc7576111555750f35b8161115f9161261b565b6102b75780f35b505050fd5b5050fd5b50346102b757806003193601126102b75750610dae60405161119260408261261b565b60058152640352e302e360dc1b6020820152604051918291602083526020830190612433565b50346102b7576111c73661233f565b9a93969297909960018060a09b949b9897981b031691823b156104d9579161120a939185809460405196879586948593636691f64760e01b8552600485016127d1565b03925af18015610dc75761122b575b610dae610da28a8a8a8a8a8a8a6137b9565b61123682809261261b565b6102b75780611219565b612744565b50346102b757806003193601126102b75760206040517f6c5a03c0785e91bc0ad0db486004116010680a03af4e712bcca3188e566941008152f35b50346102b757806003193601126102b757602060405161c3508152f35b50346102b757806003193601126102b75760206040516113888152f35b50346102b75760403660031901126102b75760406112d66122a2565b9160043581525f80516020615f1c833981519152602052209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b50346102b757806003193601126102b7575f80516020615ebc833981519152541580611422575b156113e55761138990611348613957565b90611351613a24565b90602061139760405193611365838661261b565b8385525f368137604051968796600f60f81b885260e08589015260e0880190612433565b908682036040880152612433565b904660608601523060808601528260a086015284820360c08601528080855193848152019401925b8281106113ce57505050500390f35b8351855286955093810193928101926001016113bf565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b505f80516020615f5c8339815191525415611337565b50346102b75760203660031901126102b757611475602091604061145d60043561443d565b6001600160a01b039091168352600185529120614486565b90506040519015158152f35b50346102b757610dae610da2611496366126ab565b959490949391936137b9565b50346102b7576104d66114b4366124b7565b91612efc565b50346102b75760203660031901126102b7576020906001600160601b03906040906001600160a01b036114eb6122b8565b16815260018452205416604051908152f35b50346102b757610dae610da2611512366126ab565b95949094939193612e09565b50346102b75760203660031901126102b75760043533825260016020526001600160601b03604083205460601c166001600160601b0361155d83614534565b161161166c5761159361156f82614534565b33845260016020526001600160601b03604085209181835460601c16031690612d43565b60405163a9059cbb60e01b815233600482015260248101829052602081604481867f000000000000000000000000c284a781072442cc1882a8db4573990b7b49dac46001600160a01b03165af1908115611661578391611632575b5015611623576040519081527fa315121c7f539fd811176ad2735d5d3981237b261889ec13ae4d617ad06e39bc60203392a280f35b6312171d8360e31b8252600482fd5b611654915060203d60201161165a575b61164c818361261b565b810190612d76565b5f6115ee565b503d611642565b6040513d85823e3d90fd5b63112fed8b60e31b825233600452602482fd5b50346102b75760203660031901126102b75760046060604060209383358152808552206002604051916116b18361259b565b805460018060a01b03811684526001600160401b038160a01c168785015262ffffff8160e01c16604085015260f81c848401526001600160601b0360018201548181166080860152851c1660a0840152015460c082015201511615156040519015158152f35b50346102b75760a03660031901126102b7576004358160443560ff811681036104e1577f000000000000000000000000c284a781072442cc1882a8db4573990b7b49dac46001600160a01b0316803b156104dd5760405163d505accf60e01b8152918391839182908490829061179e9060843590606435906024358d303360048901612d8e565b03925af16117b3575b506104d6823333614565565b816117bd9161261b565b6104e157815f6117a7565b50346102b757806003193601126102b75760206117e3615af3565b604051908152f35b50346102b757806003193601126102b757602090604051908152f35b50346102b757806003193601126102b7577f0000000000000000000000006c2d2c33e9a7cd0e1b39dc218f472e4bf534523b6001600160a01b0316300361185f5760206040515f80516020615efc8339815191528152f35b63703e46dd60e11b8152600490fd5b5060403660031901126102b7576118836122b8565b906024356001600160401b0381116104e1576118a390369060040161268d565b6001600160a01b037f0000000000000000000000006c2d2c33e9a7cd0e1b39dc218f472e4bf534523b16308114908115611a31575b50611a22576118e5613e46565b6040516352d1902d60e01b8152926001600160a01b0381169190602085600481865afa809585966119ee575b5061192a57634c9c8ce360e01b84526004839052602484fd5b9091845f80516020615efc83398151915281036119dc5750813b156119ca575f80516020615efc83398151915280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8480a281518390156119b05780836020610d3195519101845af46119aa613d45565b91615dfd565b505050346119bb5780f35b63b398979f60e01b8152600490fd5b634c9c8ce360e01b8452600452602483fd5b632a87526960e21b8552600452602484fd5b9095506020813d602011611a1a575b81611a0a6020938361261b565b810103126104d95751945f611911565b3d91506119fd565b63703e46dd60e11b8252600482fd5b5f80516020615efc833981519152546001600160a01b0316141590505f6118d8565b50346102b75760403660031901126102b7576104d6611a706122b8565b6024359033614565565b50346102b75760203660031901126102b757600435611abb611a9b8261443d565b6001600160a01b0390911680855260016020526040852090929190614486565b5015611dd457818352826020526040832060405190611ad98261259b565b805460018060a01b03811683526001600160401b038160a01c16602084015262ffffff8160e01c16604084015260f81c60608301526001810154600260808401926001600160601b03831684526001600160601b0360a086019360601c168352015460c08401526004606084015116611dc0576001606084015116611dac576001600160401b03611b69846140b1565b16421115611d835784865260208690526040862080546001600160f81b03811660f891821c60041790911b6001600160f81b0319161781558690600101556001600160601b038151166113888102908082046113881490151715611d6f57611be66001600160601b039392612710611beb93049485915116612a2c565b614534565b936002606060018060a01b038651169501511615155f14611d0b57505060018060a01b03821685526001602052611c3c60408620611c36856001600160601b03835460601c16612d23565b90612d43565b60405163a9059cbb60e01b815261dead60048201526024810182905291602083604481897f000000000000000000000000c284a781072442cc1882a8db4573990b7b49dac46001600160a01b03165af18015611d00577f79ca7c80cf57b513ffdf8aa37ec70e40757f5e0d35219241860bb4b4c2fa7616946060946001600160601b0392611ce3575b5060405193845216602083015260018060a01b03166040820152a280f35b611cfb9060203d60201161165a5761164c818361261b565b611cc5565b6040513d88823e3d90fd5b9092506001600160601b0330933088526001602052611d3760408920611c368885835460601c16612d23565b511690865260016020526001600160601b03611d5a604088209282845416612d23565b166001600160601b0319825416179055611c3c565b634e487b7160e01b87526011600452602487fd5b6044866001600160401b0387611d98876140b1565b9063079c66ab60e41b845260045216602452fd5b631cfdeebb60e01b86526004859052602486fd5b633231064d60e11b86526004859052602486fd5b63d2be005d60e01b83526004829052602483fd5b50346102b757806003193601126102b75760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b50346102b757610dae610da2611e38366124b7565b91612a39565b50346102b75760203660031901126102b75760043590611e5d8261392a565b15610bae57604081602093611ede935280845220600260405191611e808361259b565b805460018060a01b03811684526001600160401b038160a01c168685015262ffffff8160e01c16604085015260f81c60608401526001600160601b036001820154818116608086015260601c1660a0840152015460c08201526140b1565b6001600160401b0360405191168152f35b50346102b75760403660031901126102b757611f096122a2565b336001600160a01b03821603611f2557610d3190600435613ff5565b63334bd91960e11b8252600482fd5b50346102b75760403660031901126102b757610d31600435611f546122a2565b90611f61610d2782612851565b613f51565b50346102b75760203660031901126102b7576104d660043533613d74565b50346102b757611f933661250c565b969095919490936001600160a01b039092169190823b156104d95791611fd4939185809460405196879586948593636691f64760e01b8552600485016127d1565b03925af18015610dc757611ff1575b610dae610da2868686612a39565b611ffc82809261261b565b6102b75780611fe3565b50346102b75760203660031901126102b7576004356001600160401b0381116104e1576120379036906004016122e2565b612042929192613e46565b6001600160401b0381116120fd5761205f816107e260025461286f565b81601f8211600114612092578190839461208c94926108695750508160011b915f199060031b1c19161790565b60025580f35b60028352601f198216935f80516020615e5c83398151915291845b8681106120e557508360019596106120cc575b505050811b0160025580f35b01355f19600384901b60f8161c191690555f80806120c0565b909260206001819286860135815501940191016120ad565b634e487b7160e01b82526041600452602482fd5b50346102b75760203660031901126102b75760206117e3600435612851565b50346102b757806003193601126102b7576020604051620186a08152f35b50346102b757610dae610da2612163366124b7565b916127fc565b346121f2576121773661233f565b97999598909691959294929091906001600160a01b0316803b156121f2576121b99a5f80946040519d8e9586948593636691f64760e01b8552600485016127d1565b03925af19687156121e757610dae98610da2986121d7575b50612e09565b5f6121e19161261b565b5f6121d1565b6040513d5f823e3d90fd5b5f80fd5b346121f2575f3660031901126121f2576040517f000000000000000000000000607d196b43abc5d9be3c7fb8e336ca82fec18c456001600160a01b03168152602090f35b346121f25760203660031901126121f2576004359063ffffffff60e01b82168092036121f257602091637965db0b60e01b811490811561227c575b5015158152f35b6301ffc9a760e01b14905083612275565b35906001600160e01b0319821682036121f257565b602435906001600160a01b03821682036121f257565b600435906001600160a01b03821682036121f257565b35906001600160a01b03821682036121f257565b9181601f840112156121f2578235916001600160401b0383116121f257602083818601950101116121f257565b9181601f840112156121f2578235916001600160401b0383116121f2576020808501948460051b0101116121f257565b60e06003198201126121f2576004356001600160a01b03811681036121f25791602435916044356001600160401b0381116121f25781612381916004016122e2565b929092916064356001600160401b0381116121f257816123a39160040161230f565b929092916084356001600160401b0381116121f257816123c59160040161230f565b9290929160a4356001600160401b0381116121f257816123e79160040161230f565b9290929160c435906001600160401b0382116121f25760809082900360031901126121f25760040190565b5f5b8381106124235750505f910152565b8181015183820152602001612414565b9060209161244c81518092818552858086019101612412565b601f01601f1916010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b83831061248a57505050505090565b90919293946020806124a8600193603f198682030187528951612433565b9701930193019193929061247b565b60406003198201126121f2576004356001600160401b0381116121f257816124e19160040161230f565b92909291602435906001600160401b0382116121f25760809082900360031901126121f25760040190565b60a06003198201126121f2576004356001600160a01b03811681036121f25791602435916044356001600160401b0381116121f2578161254e916004016122e2565b929092916064356001600160401b0381116121f257816125709160040161230f565b92909291608435906001600160401b0382116121f25760809082900360031901126121f25760040190565b60e081019081106001600160401b038211176125b657604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b038211176125b657604052565b604081019081106001600160401b038211176125b657604052565b606081019081106001600160401b038211176125b657604052565b90601f801991011681019081106001600160401b038211176125b657604052565b6001600160401b0381116125b657601f01601f191660200190565b9291926126638261263c565b91612671604051938461261b565b8294818452818301116121f2578281602093845f960137010152565b9080601f830112156121f2578160206126a893359101612657565b90565b60806003198201126121f2576004356001600160401b0381116121f257816126d59160040161230f565b929092916024356001600160401b0381116121f257816126f79160040161230f565b929092916044356001600160401b0381116121f257816127199160040161230f565b92909291606435906001600160401b0382116121f25760809082900360031901126121f25760040190565b346121f2575f3660031901126121f25760206040515f8152f35b9060406003198301126121f2576004356001600160401b0381116121f25761016081840360031901126121f25760040191602435906001600160401b0382116121f2576127ad916004016122e2565b9091565b908060209392818452848401375f828201840152601f01601f1916010190565b6040906126a89492815281602082015201916127b1565b356001600160a01b03811681036121f25790565b8260609261280c92959495612a39565b92016001600160a01b0361281f826127e8565b165f5260016020526001600160601b0360405f2054168061283e575050565b61284a61284f926127e8565b613d74565b565b5f525f80516020615f1c833981519152602052600160405f20015490565b90600182811c9216801561289d575b602083101461288957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161287e565b601f81116128b3575050565b60025f5260205f20906020601f840160051c830193106128ed575b601f0160051c01905b8181106128e2575050565b5f81556001016128d7565b90915081906128ce565b6001600160401b0381116125b65760051b60200190565b903590601e19813603018212156121f257018035906001600160401b0382116121f2576020019160608202360383136121f257565b9190811015612953576060020190565b634e487b7160e01b5f52603260045260245ffd5b3561ffff811681036121f25790565b8051156129535760200190565b80518210156129535760209160051b010190565b91908110156129535760051b8101359060be19813603018212156121f2570190565b600211156129c357565b634e487b7160e01b5f52602160045260245ffd5b903590601e19813603018212156121f257018035906001600160401b0382116121f2576020019181360383136121f257565b601f19810191908211612a1857565b634e487b7160e01b5f52601160045260245ffd5b91908203918211612a1857565b929192612a47848383612efc565b612a50826128f7565b93612a5e604051958661261b565b828552601f19612a6d846128f7565b015f5b818110612d1257505084612a83846128f7565b612a90604051918261261b565b848152601f19612a9f866128f7565b013660208301376020830194612ab5868561290e565b90505f5b818110612cd35750505f5b818110612ad45750505050505050565b612adf818388612997565b90612af5612aef606088016127e8565b836140d3565b90612b008388612983565b52612cca57612b0f8185612983565b5180612b22575b50600191505b01612ac4565b606083013560028110156121f257600190612b3c816129b9565b03612cbb57612b4e60808401846129d7565b50926040840135840191612b628b8a61290e565b90915f19810191908211612a1857612b7992612943565b916040612b88602085016127e8565b930135926001600160601b0384168094036121f257612baa60a08401846129d7565b9290915a603f810290808204603f1490151715612a1857869060061c10612cac576001600160a01b031694853b156121f25760205f8760019a612c338397612c21996040519a8b998a98899663a12da43f60e01b8852013560048701526060602487015260648601906040602082013591016127b1565b848103600319016044860152916127b1565b0393f19081612c9c575b50612c95577f5c5960582bfc7a494183b4e9a66bfe8ecffc07a83a48d136e732400f7b98bf5090612c6c613d45565b92612c8b60405192839283526040602084015235946040830190612433565b0390a25b5f612b16565b5050612c8f565b5f612ca69161261b565b5f612c3d565b6307099c5360e21b5f5260045ffd5b63b90a25b160e01b5f5260045ffd5b60019150612b1c565b612ce781612ce18a8961290e565b90612943565b9060018101808211612a1857612d0b61ffff612d04600195612967565b1687612983565b5201612ab9565b806060602080938a01015201612a70565b906001600160601b03809116911601906001600160601b038211612a1857565b80546bffffffffffffffffffffffff60601b191660609290921b6bffffffffffffffffffffffff60601b16919091179055565b908160209103126121f2575180151581036121f25790565b9360c095919897969360ff9360e087019a60018060a01b0316875260018060a01b031660208701526040860152606085015216608083015260a08201520152565b91908110156129535760051b8101359061015e19813603018212156121f2570190565b90821015612953576127ad9160051b8101906129d7565b919695949392905f5b818110612e2857505050506126a89394506127fc565b80612e458a610f178387612e3f600197898c612dcf565b93612df2565b01612e12565b903590601e19813603018212156121f257018035906001600160401b0382116121f257602001918160061b360383136121f257565b91908110156129535760061b0190565b6020815260406020612eac845183838601526060850190612433565b93015191015290565b359061ffff821682036121f257565b35906001600160601b03821682036121f257565b90612ef290604093969594966060845260608401916127b1565b9460208201520152565b61ffff82116137a057612f0e826128f7565b90612f1c604051928361261b565b828252601f19612f2b846128f7565b01366020840137612f3b836128f7565b90612f49604051928361261b565b838252601f19612f58856128f7565b013660208401376040850193612f6e8587612e4b565b90505f5b8181106136e65750505f5b8181106133745750505050612f919061468a565b612faa612fa1602085018561290e565b91909385612e4b565b612fb9606087969396016127e8565b9160405193608085018581106001600160401b038211176125b657604052612fe0816128f7565b91612fee604051938461261b565b81835260606020840192028101903682116121f257915b81831061332357505050835261301a816128f7565b94613028604051968761261b565b818652602086019160061b8101903682116121f257915b8183106132e4575050506020820193845260408201928352606082019060018060a01b031681526040519260208401946020865260c08501935193608060408701528451809152602060e087019501905f5b81811061329f575050505192603f19858203016060860152602080855192838152019401905f5b81811061326f5750509051608085015250516001600160a01b031660a0830152819003601f19810182526020925f92613106926130f5908261261b565b604051928392839251928391612412565b8101039060025afa156121e7575f517f000000000000000000000000607d196b43abc5d9be3c7fb8e336ca82fec18c456001600160a01b03169161314a81806129d7565b843b156121f25760405163ab750e7560e01b8152915f91839182916131969188917f6c5a03c0785e91bc0ad0db486004116010680a03af4e712bcca3188e566941009160048601612ed8565b0381875afa908161325f575b5061325a576001600160401b037f0000000000000000000000000000000000000000000000000000000069bb16e316421161324b57806131e1916129d7565b919092803b156121f257613230935f936040519586948593849363ab750e7560e01b85527f00000000000000000000000000000000000000000000000000000000000000009160048601612ed8565b03915afa80156121e7576132415750565b5f61284f9161261b565b63439cc0cd60e01b5f5260045ffd5b505050565b5f6132699161261b565b5f6131a2565b8251805161ffff1687526020908101516001600160e01b03191681880152604090960195909201916001016130b8565b8251805161ffff1688526020818101516001600160a01b0316818a01526040918201516001600160601b03169189019190915260609097019690920191600101613091565b6040833603126121f257602060409182516132fe816125e5565b61330786612eb5565b815261331483870161228d565b8382015281520192019161303f565b6060833603126121f257602060609160405161333e81612600565b61334786612eb5565b81526133548387016122ce565b8382015261336460408701612ec4565b6040820152815201920191613005565b61337f818385612997565b9060c0823603126121f2576040519160c083018381106001600160401b038211176125b65760405280358084526020820135806020860152604083013591826040870152606084013560028110156121f2576060870190815260808501356001600160401b0381116121f2576133f8903690870161268d565b906080880191825260a086019788356001600160401b0381116121f25761342460a09136908a0161268d565b9101525190613432826129b9565b61343b826129b9565b5161347b60216040518093602082019560ff60f81b9060f81b16865261346a8151809260208686019101612412565b81010301601f19810183528261261b565b519020916040519261348c846125ca565b8684526020840192835260408401918252606084018581526080850191825260a09060746040516134bd848261261b565b818152736c66696c6c6d656e74446174614469676573742960601b608060208301927f4173736573736f72436f6d6d69746d656e742875696e7432353620696e64657884527f2c75696e743235362069642c627974657333322072657175657374446967657360408201527f742c6279746573333220636c61696d4469676573742c6279746573333220667560608201520152209551945193519051925193604051956020870197885260408701526060860152608085015283015260c082015260c0815261358d60e08261261b565b51902061359a848a612983565b526135a58388612983565b51613655576135f9937f000000000000000000000000607d196b43abc5d9be3c7fb8e336ca82fec18c456001600160a01b0316926135e391906129d7565b9490604051956135f2876125e5565b3691612657565b84526020840152803b156121f257613628925f916040518080968194631599ead560e01b835260048301612e90565b039161c350fa9182156121e757600192613645575b505b01612f7d565b5f61364f9161261b565b5f61363d565b61368e937f000000000000000000000000607d196b43abc5d9be3c7fb8e336ca82fec18c456001600160a01b0316926135e391906129d7565b84526020840152803b156121f2576136bd925f916040518080968194631599ead560e01b835260048301612e90565b03915afa9182156121e7576001926136d6575b5061363f565b5f6136e09161261b565b5f6136d0565b60206136fc826136f68a8c612e4b565b90612e80565b013563ffffffff60e01b81168091036121f25761374161373761ffff61372f61372a866136f68f8f90612e4b565b612967565b168688612997565b60a08101906129d7565b6004929192116121f257600161377961ffff61377261372a878f978f6136f69163ffffffff60e01b90351699612e4b565b1689612983565b5281810361378b575050600101612f72565b632e2ce35360e21b5f5260045260245260445ffd5b506377e4aa5360e11b5f5260045261ffff60245260445ffd5b919695949392905f5b8181106137d857505050506126a8939450612a39565b806137ef8a610f178387612e3f600197898c612dcf565b016137c2565b35906001600160401b03821682036121f257565b359063ffffffff821682036121f257565b91908260e09103126121f2576040516138328161259b565b60c08082948035845260208101356020850152613851604082016137f5565b604085015261386260608201613809565b606085015261387360808201613809565b608085015261388460a08201613809565b60a08501520135910152565b916138a991833560201c6001600160a01b031684614787565b509060406138e8611be66138d86138bf85614db8565b90506001600160401b034291161094608036910161381a565b6001600160401b03421690614e54565b6001600160601b038251916138fc83612600565b60018352602083018590521691018190526001607f1b9115613924576001607e1b5b17179055565b5f61391e565b6139366139539161443d565b6001600160a01b039091165f908152600160205260409020614486565b5090565b604051905f825f80516020615e7c83398151915254916139768361286f565b8083529260018116908115613a05575060011461399a575b61284f9250038361261b565b505f80516020615e7c8339815191525f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b8183106139e957505090602061284f9282010161398e565b60209193508060019154838589010152019101909184926139d1565b6020925061284f94915060ff191682840152151560051b82010161398e565b604051905f825f80516020615e9c8339815191525491613a438361286f565b8083529260018116908115613a055750600114613a665761284f9250038361261b565b505f80516020615e9c8339815191525f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b818310613ab557505090602061284f9282010161398e565b6020919350806001915483858901015201910190918492613a9d565b613ada34614534565b335f5260016020526001600160601b03613afb60405f209282845416612d23565b166001600160601b03198254161790556040513481527fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c60203392a2565b9035603e19823603018112156121f2570190565b9060038210156129c35752565b9035601e19823603018112156121f25701602081359101916001600160401b0382116121f25781360383136121f257565b90813581526020820135607e19833603018112156121f257610160602083015282016001600160a01b03613bbe826122ce565b166101608301526001600160601b03613bd960208301612ec4565b16610180830152613bed6040820182613b39565b9060806101a084015281359160038310156121f257613c25613c3891613c1b613c71956101e0880190613b4d565b6020810190613b5a565b60406102008701526102208601916127b1565b906001600160e01b031990613c4f9060600161228d565b166101c0840152613c636040850185613b5a565b9084830360408601526127b1565b613c7e6060840184613b39565b8282036060840152803560028110156121f257610140926040613cb5859484613ca9613cc5966129b9565b84526020810190613b5a565b91909281602082015201916127b1565b936080810135608085015260a081013560a08501526001600160401b03613cee60c083016137f5565b1660c085015263ffffffff613d0560e08301613809565b1660e085015263ffffffff613d1d6101008301613809565b1661010085015263ffffffff613d366101208301613809565b16610120850152013591015290565b3d15613d6f573d90613d568261263c565b91613d64604051938461261b565b82523d5f602084013e565b606090565b9060018060a01b03821691825f5260016020526001600160601b0360405f2054166001600160601b03613da684614534565b1611613e33575f8080848194613dbb82614534565b88845260016020526001600160601b03806040862092818454160316166001600160601b03198254161790555af1613df1613d45565b5015613e245760207f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6591604051908152a2565b6312171d8360e31b5f5260045ffd5b8263112fed8b60e31b5f5260045260245ffd5b335f9081525f80516020615edc833981519152602052604090205460ff1615613e6b57565b63e2517d3f60e01b5f52336004525f60245260445ffd5b5f8181525f80516020615f1c8339815191526020908152604080832033845290915290205460ff1615613eb25750565b63e2517d3f60e01b5f523360045260245260445ffd5b6001600160a01b0381165f9081525f80516020615edc833981519152602052604090205460ff16613f4c576001600160a01b03165f8181525f80516020615edc83398151915260205260408120805460ff191660011790553391907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d8180a4600190565b505f90565b5f8181525f80516020615f1c833981519152602090815260408083206001600160a01b038616845290915290205460ff16613fef575f8181525f80516020615f1c833981519152602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9080a4600190565b50505f90565b5f8181525f80516020615f1c833981519152602090815260408083206001600160a01b038616845290915290205460ff1615613fef575f8181525f80516020615f1c833981519152602090815260408083206001600160a01b0395909516808452949091528120805460ff19169055339291907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4600190565b906001600160401b03809116911601906001600160401b038211612a1857565b6126a89062ffffff60406001600160401b036020840151169201511690614091565b90916060925f928035906140e68261443d565b969060018060a01b0381165f5260016020526141058860405f20614486565b91819991936040516141168161259b565b5f81525f60208201525f60408201525f828201525f60808201525f60a08201525f60c08201529a6143c1575b506020850135996141516155b2565b508a54945f8c556141606155b2565b506040516001607f1b8716151561417682612600565b8082526001600160601b03604060208401936001607e1b8b161515855201981688525f1461436e57516142fe5791878995949288945b156142e65760208101516001600160401b031642116142c9576141cf9750615966565b955b865161428b575b604051906020825283602083015260408201526040820135606082015260608201359160028310156121f2576142868291846142347faf1db8f86d3f32029a484ff54c7ac1d7ef8f038ab050fc065af9e82eb9b850ca966129b9565b608084015261426861425d61424c6080840184613b5a565b60c060a088015260e08701916127b1565b9160a0810190613b5a565b848303601f190160c08601526001600160a01b0390981697906127b1565b0390a3565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb356460405160208152806142c1602082018b612433565b0390a16141d8565b9291906001600160601b036142e09851169361570c565b956141d1565b5050906001600160601b036142e096511691886155d0565b5050505050505092505091506040519063873fd26b60e01b602083015260248201526024815261432f60448261261b565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb356460405160208152806143656020820185612433565b0390a190600190565b5080806143b4575b156143a157614384826140b1565b6001600160401b03429116106142fe5791878995949288946141ac565b8763c274d3e360e01b5f5260045260245ffd5b508b60c083015114614376565b909950855f525f602052600260405f206001600160601b03604051936143e68561259b565b825460018060a01b03811686526001600160401b038160a01c16602087015262ffffff8160e01c16604087015260f81c8186015260018301549082821660808701521c1660a0840152015460c0820152985f614142565b906001600160c11b0319821661446557602082901c6001600160a01b03169163ffffffff1690565b6341abc80160e01b5f5260045ffd5b63020000008210156129535701905f90565b63ffffffff8216919060208310156144d8576401fffffffe905460c01c9160011b169180830460021490151715612a18576001600160401b03906003831b1616901c9060026001831615159216151590565b916144e39150612a09565b908160011b9180830460021481151715612a185760ff916145139160071c6001600160f81b031690600101614474565b90549060031b1c9116906003821b16901c9060026001831615159216151590565b6001600160601b03811161454e576001600160601b031690565b6306dfcc6560e41b5f52606060045260245260445ffd5b6040516323b872dd60e01b81526001600160a01b039182166004820152306024820152604481018490529192917f000000000000000000000000c284a781072442cc1882a8db4573990b7b49dac4909116906020905f9060649082855af19081601f3d1160015f511416151661467d575b50156146415760208161463861460c7ff645c19720906ca336d36d26058a9489c6c757fe35843b75a74e3b8aa972ecf594614534565b9460018060a01b031694855f5260018452611c3660405f20916001600160601b03835460601c16612d23565b604051908152a2565b60405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606490fd5b3b153d171590505f6145d6565b80511561446557600181511461477e5780515b600181116146b357506146af90612976565b5190565b60018101808211612a185760011c905f5b8160011c811061471257506001808216146146e0575b5061469d565b5f198101908111612a18576146f59083612983565b515f198201828111612a185761470b9084612983565b525f6146da565b600181901b906001600160ff1b0381168103612a18576147328286612983565b5160018301809311612a185761474a60019387612983565b51908181101561476f575f5260205260405f205b6147688287612983565b52016146c4565b905f5260205260405f2061475e565b6146af90612976565b91939290610160833603126121f2576040516147a2816125ca565b83359384825260208101356001600160401b0381116121f25781019081360391608083126121f25760408051936147d885612600565b126121f2576040516147e9816125e5565b6147f2826122ce565b815261480060208301612ec4565b6020820152835260408101356001600160401b0381116121f25781016040813603126121f25760405191614833836125e5565b813560038110156121f25783526020820135926001600160401b0384116121f2576148666060936148769536910161268d565b602082015260208601520161228d565b60408301526020830191825260408101356001600160401b0381116121f257810136601f820112156121f2576148b3903690602081359101612657565b906040840191825260608101356001600160401b0381116121f2578101906040823603126121f257604051916148e8836125e5565b803560028110156121f257835260208101356001600160401b0381116121f2576149149136910161268d565b60208301526060850191825261492e90369060800161381a565b906080850191825261493e6154be565b614946615324565b61494e61536e565b906149576153b3565b61495f615471565b614967615545565b91604051948594602086019788815160208193019161498592612412565b86018151918260208301916020019161499d92612412565b016020018082516020819401916149b392612412565b018082516020819401916149c692612412565b018082516020819401916149d992612412565b018082516020819401916149ec92612412565b0103601f19810182526149ff908261261b565b51902094519351614a0e615545565b614a16615324565b614a1e615471565b906040519182916020830194858151602081930191614a3c92612412565b830181519182602083019160200191614a5492612412565b01602001808251602081940191614a6a92612412565b0103601f1981018252614a7d908261261b565b519020908051614a8b615324565b8051906020012090600160a01b6001900381511690602001516001600160601b031660405191602083019384526040830152606082015260608152614ad160808261261b565b519020906020810151614ae2615471565b805190602001209080519060038210156129c3576020015160208151910120614b1960405192602084019485526040840190613b4d565b606082015260608152614b2d60808261261b565b51902090604063ffffffff60e01b9101511690604051926020840194855260408401526060830152608082015260808152614b6960a08261261b565b5190209251602081519101209051614b7f61536e565b60208151910120906020815191614b95836129b9565b0151602081519101206040519160208301938452614bb2816129b9565b6040830152606082015260608152614bcb60808261261b565b5190209151614bd86153b3565b604051614bf56020828161346a8183019687815193849201612412565b519020908051906020810151906001600160401b0360408201511663ffffffff60608301511663ffffffff6080840151169160c063ffffffff60a08601511694015194604051966020880198895260408801526060870152608086015260a085015260c084015260e08301526101008201526101008152614c786101208261261b565b51902092604051946020860196875260408601526060850152608084015260a083015260c082015260c08152614caf60e08261261b565b51902094614cc486614cbf615af3565b615ba8565b93600160c01b1615614d815791602091614cf593604051809581948293630b135d3f60e11b845289600485016127d1565b03916001600160a01b0316620186a0fa9081156121e7575f91614d3e575b506001600160e01b0319166374eca2c160e11b01614d2f579190565b638baa579f60e01b5f5260045ffd5b90506020813d602011614d79575b81614d596020938361261b565b810103126121f257516001600160e01b0319811681036121f2575f614d13565b3d9150614d4c565b614d93614d9991614da2943691612657565b84615bc5565b90939193615bff565b6001600160a01b03908116911603614d2f579190565b614dc690608036910161381a565b9081516020830151106144655763ffffffff606083015116608083019063ffffffff825116106144655763ffffffff90511660a083019063ffffffff8251161061446557614e339063ffffffff6001600160401b036040614e2687615b5a565b9601511691511690614091565b9162ffffff6001600160401b03614e4a8386614f38565b1611614465579190565b9060408201906001600160401b0380835116911690811115614f32576001600160401b03614e8184615b5a565b168111614f2b576001600160401b03825116906001600160401b03614eb2606086019363ffffffff85511690614091565b16811115614ec4575050506020015190565b614ef1906001600160401b0363ffffffff614ee56020880151885190612a2c565b94511694511690612a2c565b925192818102918183041490151715612a18578115614f1757048101809111612a185790565b634e487b7160e01b5f52601260045260245ffd5b5050505f90565b50505190565b906001600160401b03809116911603906001600160401b038211612a1857565b9590929796949360018060a01b031697885f526001602052614f7d8560405f20614486565b90615310576152fc576001600160401b038616988942116152e457614fab611be66138d83660808c0161381a565b96815f52600160205260405f20996001600160601b038b5416946001600160601b038a16938487106152d2575060018060a01b031698895f52600160205260405f20906001600160601b03825460601c16966101408d01358098106152bf57918d6001600160601b0380615051946150569897960316166001600160601b03198254161790556001600160601b0361504289614534565b81835460601c16031690612d43565b614f38565b926001600160401b03841662ffffff81116152a8575061507590614534565b604051936150828561259b565b88855260208086019c8d5262ffffff90911660408087019182525f60608801818152608089019687526001600160601b0390951660a0808a0191825260c08a019889528e35808452958390529290912097519e51925194519290911b67ffffffffffffffff60a01b166001600160a01b039e909e169d909d1760e09390931b62ffffff60e01b169290921760f89290921b6001600160f81b031916919091178455996001840191516001600160601b03166001600160601b03166001600160601b0319835416178255516001600160601b031661515e91612d43565b51906002015563ffffffff831692602084105f14615219576401fffffffe9060011b169280840460021490151715612a185785546001600160c01b038116600190941b6001600160401b031660c091821c17901b6001600160c01b031916929092179094557fe5e43c93dc0ec595ed3b122bdc6d39a480e9d17fb6812e0f90cfc4ba33b0969e93615214915b6152066040519586958652606060208701526060860190613b8b565b9184830360408601526127b1565b0390a2565b509161522490612a09565b918260011b9583870460021484151715612a18577fe5e43c93dc0ec595ed3b122bdc6d39a480e9d17fb6812e0f90cfc4ba33b0969e96615214946152a39260ff916001916152809160071c6001600160f81b0316908301614474565b929093161b82548260031b1c179082549060031b91821b915f19901b1916179055565b6151ea565b6306dfcc6560e41b5f52601860045260245260445ffd5b8b63112fed8b60e31b5f5260045260245ffd5b63112fed8b60e31b5f5260045260245ffd5b898863cfe6a8fd60e01b5f523560045260245260445ffd5b86631cfdeebb60e01b5f523560045260245ffd5b8763a905765160e01b5f523560045260245ffd5b6040519061533360608361261b565b60268252654c696d69742960d01b6040837f43616c6c6261636b286164647265737320616464722c75696e7439362067617360208201520152565b6040519061537d60608361261b565b60218252602960f81b6040837f496e7075742875696e743820696e707574547970652c6279746573206461746160208201520152565b604051906153c260c08361261b565b60888252676c61746572616c2960c01b60a0837f4f666665722875696e74323536206d696e50726963652c75696e74323536206d60208201527f617850726963652c75696e7436342072616d70557053746172742c75696e743360408201527f322072616d705570506572696f642c75696e743332206c6f636b54696d656f7560608201527f742c75696e7433322074696d656f75742c75696e74323536206c6f636b436f6c60808201520152565b6040519061548060608361261b565b602982526874657320646174612960b81b6040837f5072656469636174652875696e743820707265646963617465547970652c627960208201520152565b604051906154cd60808361261b565b605a82527f6c2c496e70757420696e7075742c4f66666572206f66666572290000000000006060837f50726f6f66526571756573742875696e743235362069642c526571756972656d60208201527f656e747320726571756972656d656e74732c737472696e6720696d616765557260408201520152565b6040519061555460808361261b565b60438252626f722960e81b6060837f526571756972656d656e74732843616c6c6261636b2063616c6c6261636b2c5060208201527f7265646963617465207072656469636174652c6279746573342073656c65637460408201520152565b604051906155bf82612600565b5f6040838281528260208201520152565b96949591929390966060966156bf575f80516020615f7c83398151915260209596979860018060a01b031693845f526001875261561160405f209687615ce8565b6040519387013584526001600160a01b0316958693a36001600160601b03825416906001600160601b038516821061569357506001600160601b038481920316166001600160601b03198254161790555f5260016020526001600160601b0361568160405f209282845416612d23565b166001600160601b0319825416179055565b949550505050506040519063112fed8b60e31b60208301526024820152602481526126a860448261261b565b955050505050915060405190631cfdeebb60e01b60208301526024820152602481526126a860448261261b565b906001600160601b03809116911603906001600160601b038211612a1857565b9395979692949094606098600160608701511615158015615956575b61592757156158d6575b50506001600160a01b03165f908152600160205260408120608093909301516001600160601b0386811696959294911685818811156158a35781615775916156ec565b906001600160601b03835416906001600160601b038316821061587e575b5082546bffffffffffffffffffffffff19169190036001600160601b03161790555b5f90815260208190526040902080546affffffffffffffffffffff60a01b81166001600160a01b0384169081176001600160a01b0319929092161760f890811c600217901b6001600160f81b03191617905560018060a01b03165f52600160205260405f206001600160601b0361582f8482845416612d23565b166001600160601b0319825416179055615847575050565b6001600160601b039192935060405192636008fdcb60e01b60208501526024840152166044820152604481526126a860648261261b565b96509450506001600160601b0380615897868098612d23565b96600196915091615793565b6158b86158c1916001600160601b03936156ec565b82845416612d23565b166001600160601b03198254161790556157b5565b6001600160a01b0383165f9081526001602052604090206158f79190615ce8565b60405160209182013581526001600160a01b0384169186915f80516020615f7c8339815191529190a35f80615732565b5050505050509192505060405190631cfdeebb60e01b60208301526024820152602481526126a860448261261b565b5060026060870151161515615728565b9391909296959496606097600160608701511615158015615ae3575b615ab55715615a6a575b505082516001600160a01b039485169416841480159190615a56575b50615a2c5760a061284f93926001600160601b03925f525f6020525f6001604082208160f81b828060f81b03825416178155015582608082015116845f526001602052836159fd60405f209282845416612d23565b168419825416179055015116905f526001602052611c3660405f20916001600160601b03835460601c16612d23565b92935050506040519063a905765160e01b60208301526024820152602481526126a860448261261b565b9050602060c084015191013514155f6159a8565b615a869160018060a01b03165f52600160205260405f20615ce8565b60405160208281013582526001600160a01b0386169184915f80516020615f7c83398151915291a35f8061598c565b50505050929350505060405190631cfdeebb60e01b60208301526024820152602481526126a860448261261b565b5060026060870151161515615982565b615afb615c5f565b615b03615cb6565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152615b5460c08261261b565b51902090565b6126a89063ffffffff60806001600160401b036040840151169201511690614091565b60ff5f80516020615f3c8339815191525460401c1615615b9957565b631afcd79f60e31b5f5260045ffd5b6042916040519161190160f01b8352600283015260228201522090565b8151919060418303615bf557615bee9250602082015190606060408401519301515f1a90615d85565b9192909190565b50505f9160029190565b60048110156129c35780615c11575050565b60018103615c285763f645eedf60e01b5f5260045ffd5b60028103615c43575063fce698f760e01b5f5260045260245ffd5b600314615c4d5750565b6335e2f38360e21b5f5260045260245ffd5b615c67613957565b8051908115615c77576020012090565b50505f80516020615ebc833981519152548015615c915790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b615cbe613a24565b8051908115615cce576020012090565b50505f80516020615f5c833981519152548015615c915790565b9063ffffffff8116906020821015615d45576401fffffffe9060011b169080820460021490151715612a185781546001600160c01b038116600290921b6001600160401b031660c091821c17901b6001600160c01b031916179055565b50615d4f90612a09565b8060011b9080820460021481151715612a185761284f9260ff916002916152809160071c6001600160f81b031690600101614474565b91906fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411615df2579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156121e7575f516001600160a01b03811615615de857905f905f90565b505f906001905f90565b5050505f9160039190565b90615e215750805115615e1257602081519101fd5b63d6bda27560e01b5f5260045ffd5b81511580615e52575b615e32575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15615e2a56fe405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acea16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100b7db2dd08fcb62d0c9e08c51941cae53c267786a0b75803fb7960902fc8ef97d360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101120ea8d7610aa46e4a31b254c5d07489ebe8f1a93dc7bbbe60eaf3db2c62c0cca164736f6c634300081a000a diff --git a/contracts/shanghai/legacy/deployed-bytecode.meta.toml b/contracts/shanghai/legacy/deployed-bytecode.meta.toml new file mode 100644 index 0000000000..982a7e0f60 --- /dev/null +++ b/contracts/shanghai/legacy/deployed-bytecode.meta.toml @@ -0,0 +1,29 @@ +# Reference snapshot of the BoundlessMarket implementation deployed at the Taiko +# mainnet proxy (the pre-router market, compiled under FOUNDRY_PROFILE=shanghai). +# Used by scripts/verify-legacy-bytecode.py to assert that contracts/shanghai/legacy/ +# compiles to the same bytecode (modulo immutable slots baked in at deploy time). +# +# Refresh procedure: +# cast code 0x6c2d2c33e9a7cd0e1b39dc218f472e4bf534523b \ +# --rpc-url https://rpc.mainnet.taiko.xyz \ +# > contracts/shanghai/legacy/deployed-bytecode.hex +# and update `fetched_at_block` below. + +network = "taiko-mainnet" +chain_id = 167000 +proxy = "0xb3f5c7b4379052eade8c7f3fa6da37fb871da28b" +impl = "0x6c2d2c33e9a7cd0e1b39dc218f472e4bf534523b" +fetched_at_block = 7982471 + +# Constructor immutables baked into the deployed bytecode. The compiled +# shanghai/legacy/ artifact has zeros at these positions; the verification +# script masks them out for the body-match check and then re-extracts each +# baked-in value and asserts it matches the expected value below. + +[immutables] +VERIFIER = "0x607d196b43abc5d9BE3c7Fb8e336Ca82fec18C45" +ASSESSOR_ID = "0x6c5a03c0785e91bc0ad0db486004116010680a03af4e712bcca3188e56694100" +COLLATERAL_TOKEN_CONTRACT = "0xC284A781072442cc1882a8Db4573990B7B49DaC4" +DEPRECATED_ASSESSOR_ID = "0x0000000000000000000000000000000000000000000000000000000000000000" +DEPRECATED_ASSESSOR_EXPIRES_AT = 1773868771 +APPLICATION_VERIFIER = "0x607d196b43abc5d9BE3c7Fb8e336Ca82fec18C45" diff --git a/contracts/shanghai/src/libraries/BoundlessMarketLib.sol b/contracts/shanghai/legacy/libraries/BoundlessMarketLib.sol similarity index 100% rename from contracts/shanghai/src/libraries/BoundlessMarketLib.sol rename to contracts/shanghai/legacy/libraries/BoundlessMarketLib.sol diff --git a/contracts/shanghai/src/libraries/MerkleProofish.sol b/contracts/shanghai/legacy/libraries/MerkleProofish.sol similarity index 97% rename from contracts/shanghai/src/libraries/MerkleProofish.sol rename to contracts/shanghai/legacy/libraries/MerkleProofish.sol index a0edc9d596..eb2c10c076 100644 --- a/contracts/shanghai/src/libraries/MerkleProofish.sol +++ b/contracts/shanghai/legacy/libraries/MerkleProofish.sol @@ -4,7 +4,7 @@ // as found in the LICENSE-BSL file. pragma solidity ^0.8.26; -import {IBoundlessMarket} from "../IBoundlessMarket.sol"; +import {IBoundlessMarket} from "../IBoundlessMarketLegacy.sol"; library MerkleProofish { // Compute the root of the Merkle tree given all of its leaves. diff --git a/contracts/shanghai/src/types/Account.sol b/contracts/shanghai/legacy/types/Account.sol similarity index 100% rename from contracts/shanghai/src/types/Account.sol rename to contracts/shanghai/legacy/types/Account.sol diff --git a/contracts/shanghai/src/types/AssessorCallback.sol b/contracts/shanghai/legacy/types/AssessorCallback.sol similarity index 100% rename from contracts/shanghai/src/types/AssessorCallback.sol rename to contracts/shanghai/legacy/types/AssessorCallback.sol diff --git a/contracts/shanghai/src/types/AssessorCommitment.sol b/contracts/shanghai/legacy/types/AssessorCommitment.sol similarity index 100% rename from contracts/shanghai/src/types/AssessorCommitment.sol rename to contracts/shanghai/legacy/types/AssessorCommitment.sol diff --git a/contracts/shanghai/src/types/AssessorJournal.sol b/contracts/shanghai/legacy/types/AssessorJournal.sol similarity index 100% rename from contracts/shanghai/src/types/AssessorJournal.sol rename to contracts/shanghai/legacy/types/AssessorJournal.sol diff --git a/contracts/shanghai/src/types/AssessorReceipt.sol b/contracts/shanghai/legacy/types/AssessorReceipt.sol similarity index 100% rename from contracts/shanghai/src/types/AssessorReceipt.sol rename to contracts/shanghai/legacy/types/AssessorReceipt.sol diff --git a/contracts/shanghai/src/types/Callback.sol b/contracts/shanghai/legacy/types/Callback.sol similarity index 100% rename from contracts/shanghai/src/types/Callback.sol rename to contracts/shanghai/legacy/types/Callback.sol diff --git a/contracts/shanghai/src/types/Fulfillment.sol b/contracts/shanghai/legacy/types/Fulfillment.sol similarity index 100% rename from contracts/shanghai/src/types/Fulfillment.sol rename to contracts/shanghai/legacy/types/Fulfillment.sol diff --git a/contracts/shanghai/src/types/FulfillmentContext.sol b/contracts/shanghai/legacy/types/FulfillmentContext.sol similarity index 100% rename from contracts/shanghai/src/types/FulfillmentContext.sol rename to contracts/shanghai/legacy/types/FulfillmentContext.sol diff --git a/contracts/shanghai/src/types/FulfillmentData.sol b/contracts/shanghai/legacy/types/FulfillmentData.sol similarity index 100% rename from contracts/shanghai/src/types/FulfillmentData.sol rename to contracts/shanghai/legacy/types/FulfillmentData.sol diff --git a/contracts/shanghai/src/types/Input.sol b/contracts/shanghai/legacy/types/Input.sol similarity index 100% rename from contracts/shanghai/src/types/Input.sol rename to contracts/shanghai/legacy/types/Input.sol diff --git a/contracts/shanghai/src/types/LockRequest.sol b/contracts/shanghai/legacy/types/LockRequest.sol similarity index 100% rename from contracts/shanghai/src/types/LockRequest.sol rename to contracts/shanghai/legacy/types/LockRequest.sol diff --git a/contracts/shanghai/src/types/Offer.sol b/contracts/shanghai/legacy/types/Offer.sol similarity index 99% rename from contracts/shanghai/src/types/Offer.sol rename to contracts/shanghai/legacy/types/Offer.sol index 975073420f..547a09eff9 100644 --- a/contracts/shanghai/src/types/Offer.sol +++ b/contracts/shanghai/legacy/types/Offer.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.26; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {IBoundlessMarket} from "../IBoundlessMarket.sol"; +import {IBoundlessMarket} from "../IBoundlessMarketLegacy.sol"; using OfferLibrary for Offer global; diff --git a/contracts/shanghai/src/types/Predicate.sol b/contracts/shanghai/legacy/types/Predicate.sol similarity index 100% rename from contracts/shanghai/src/types/Predicate.sol rename to contracts/shanghai/legacy/types/Predicate.sol diff --git a/contracts/shanghai/src/types/ProofRequest.sol b/contracts/shanghai/legacy/types/ProofRequest.sol similarity index 100% rename from contracts/shanghai/src/types/ProofRequest.sol rename to contracts/shanghai/legacy/types/ProofRequest.sol diff --git a/contracts/shanghai/src/types/RequestId.sol b/contracts/shanghai/legacy/types/RequestId.sol similarity index 97% rename from contracts/shanghai/src/types/RequestId.sol rename to contracts/shanghai/legacy/types/RequestId.sol index ea6e10055d..09e6114094 100644 --- a/contracts/shanghai/src/types/RequestId.sol +++ b/contracts/shanghai/legacy/types/RequestId.sol @@ -4,7 +4,7 @@ // as found in the LICENSE-BSL file. pragma solidity ^0.8.26; -import {IBoundlessMarket} from "../IBoundlessMarket.sol"; +import {IBoundlessMarket} from "../IBoundlessMarketLegacy.sol"; type RequestId is uint256; diff --git a/contracts/shanghai/src/types/RequestLock.sol b/contracts/shanghai/legacy/types/RequestLock.sol similarity index 100% rename from contracts/shanghai/src/types/RequestLock.sol rename to contracts/shanghai/legacy/types/RequestLock.sol diff --git a/contracts/shanghai/src/types/Requirements.sol b/contracts/shanghai/legacy/types/Requirements.sol similarity index 100% rename from contracts/shanghai/src/types/Requirements.sol rename to contracts/shanghai/legacy/types/Requirements.sol diff --git a/contracts/shanghai/src/types/Selector.sol b/contracts/shanghai/legacy/types/Selector.sol similarity index 100% rename from contracts/shanghai/src/types/Selector.sol rename to contracts/shanghai/legacy/types/Selector.sol diff --git a/contracts/shanghai/scripts/BoundlessScript.s.sol b/contracts/shanghai/scripts/BoundlessScript.s.sol deleted file mode 100644 index 1f3486d3ea..0000000000 --- a/contracts/shanghai/scripts/BoundlessScript.s.sol +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.26; - -import {Script, console2} from "forge-std/Script.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; - -library BoundlessScript { - /// @notice Validates that an address value is not zero, with descriptive error message - function requireLib(address value, string memory label) internal pure returns (address) { - if (value == address(0)) { - console2.log("address value %s is required", label); - require(false, "required address value not set"); - } - return value; - } - - /// @notice Validates that a bytes32 value is not zero, with descriptive error message - function requireLib(bytes32 value, string memory label) internal pure returns (bytes32) { - if (value == bytes32(0)) { - console2.log("bytes32 value %s is required", label); - require(false, "required bytes32 value not set"); - } - return value; - } - - /// @notice Validates that a string value is not empty, with descriptive error message - function requireLib(string memory value, string memory label) internal pure returns (string memory) { - if (bytes(value).length == 0) { - console2.log("string value %s is required", label); - require(false, "required string value not set"); - } - return value; - } - - /// @notice Helper to convert string to lowercase for display - function _toLowerCase(string memory str) internal pure returns (string memory) { - bytes memory strBytes = bytes(str); - for (uint256 i = 0; i < strBytes.length; i++) { - if (strBytes[i] >= 0x41 && strBytes[i] <= 0x5A) { - strBytes[i] = bytes1(uint8(strBytes[i]) + 32); - } - } - return string(strBytes); - } -} - -/// @notice Base contract for Boundless scripts with shared functionality -abstract contract BoundlessScriptBase is Script { - using BoundlessScript for address; - using BoundlessScript for bytes32; - using BoundlessScript for string; - - // Path to deployment config file, relative to the project root. - string constant CONFIG = "contracts/deployment.toml"; - - /// @notice Gets the current git commit hash - function getCurrentCommit() internal view returns (string memory) { - return vm.envOr("CURRENT_COMMIT", string("unknown")); - } - - /// @notice Displays warning for uncommitted changes - function checkUncommittedChangesWarning(string memory actionType) internal view { - string memory hasUnstaged = vm.envOr("HAS_UNSTAGED_CHANGES", string("")); - string memory hasStaged = vm.envOr("HAS_STAGED_CHANGES", string("")); - if (bytes(hasUnstaged).length > 0 || bytes(hasStaged).length > 0) { - console2.log(""); - console2.log("================================================================="); - console2.log(string.concat("WARNING: ", actionType, " was done with uncommitted changes!")); - console2.log(string.concat("- The ", actionType, " commit hash may not reflect actual code state")); - console2.log( - string.concat( - "- Consider committing changes before production ", BoundlessScript._toLowerCase(actionType), "s" - ) - ); - console2.log("================================================================="); - } - } - - /// @notice Gets the deployer address from private key or env var - function getDeployer() internal returns (address) { - uint256 privateKey = vm.envOr("DEPLOYER_PRIVATE_KEY", uint256(0)); - if (privateKey != 0) { - vm.rememberKey(privateKey); - return vm.addr(privateKey); - } - - address deployer = vm.envOr("DEPLOYER_ADDRESS", address(0)); - require(deployer != address(0), "env var DEPLOYER_ADDRESS or DEPLOYER_PRIVATE_KEY required"); - return deployer; - } - - /// @notice Reads a 32-byte image ID from a .bin file using r0vm --id - function readImageIdFromFile(string memory filename) internal returns (bytes32) { - string memory filePath = string.concat(vm.projectRoot(), "/crates/povw/elfs/", filename); - - string[] memory args = new string[](4); - args[0] = "r0vm"; - args[1] = "--id"; - args[2] = "--elf"; - args[3] = filePath; - - try vm.ffi(args) returns (bytes memory result) { - return abi.decode(result, (bytes32)); - } catch { - console2.log("Failed to read image ID from .bin file: %s", filename); - return bytes32(0); - } - } - - /// @notice Updates a specific field in deployment.toml via FFI - /// @param key The field name to update (e.g., "admin", "admin-2") - /// @param value The address value to set - function _updateDeploymentConfig(string memory key, address value) internal { - string[] memory args = new string[](4); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = string.concat("--", key); - args[3] = Strings.toHexString(value); - - vm.ffi(args); - } - - /// @notice Removes an admin from deployment.toml by clearing the matching admin field - /// @param adminField1 First admin field to check (e.g., "admin") - /// @param adminField2 Second admin field to check (e.g., "admin-2") - /// @param removedAdmin The admin address being removed - /// @dev Only clears the TOML field that contains the specific admin address being removed - function _removeAdminFromToml(string memory adminField1, string memory adminField2, address removedAdmin) internal { - // Load current deployment config to check which field contains the removed admin - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - // Clear the field that matches the removed admin address - if (deploymentConfig.admin == removedAdmin) { - _updateDeploymentConfig(adminField1, address(0)); - console2.log("Cleared %s field in deployment.toml", adminField1); - } else if (deploymentConfig.admin2 == removedAdmin) { - _updateDeploymentConfig(adminField2, address(0)); - console2.log("Cleared %s field in deployment.toml", adminField2); - } else { - console2.log( - "Admin address %s not found in TOML fields, no update needed", Strings.toHexString(removedAdmin) - ); - } - } - - /// @notice Print Gnosis Safe transaction information for manual upgrades - /// @param proxyAddress The proxy contract address (target for Gnosis Safe) - /// @param newImpl The new implementation address - /// @param initializerData The initializer call data (if any) - function _printGnosisSafeInfo(address proxyAddress, address newImpl, bytes memory initializerData) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE UPGRADE INFO ==="); - console2.log("Target Address (To): ", proxyAddress); - - if (initializerData.length > 0) { - // For upgradeToAndCall - bytes memory callData = abi.encodeWithSignature("upgradeToAndCall(address,bytes)", newImpl, initializerData); - console2.log("Function: upgradeToAndCall(address,bytes)"); - console2.log("New Implementation: ", newImpl); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("Expected Events on Successful Execution:"); - console2.log("1. Upgraded(address indexed implementation)"); - console2.log(" - implementation: ", newImpl); - } else { - // For upgradeTo - bytes memory callData = abi.encodeWithSignature("upgradeTo(address)", newImpl); - console2.log("Function: upgradeTo(address)"); - console2.log("New Implementation: ", newImpl); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("Expected Events on Successful Execution:"); - console2.log("1. Upgraded(address indexed implementation)"); - console2.log(" - implementation: ", newImpl); - } - console2.log("================================"); - } -} diff --git a/contracts/shanghai/scripts/Config.s.sol b/contracts/shanghai/scripts/Config.s.sol deleted file mode 100644 index c27564d3cf..0000000000 --- a/contracts/shanghai/scripts/Config.s.sol +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Vm} from "forge-std/Vm.sol"; -import {console2, stdToml} from "forge-std/Test.sol"; - -struct DeploymentConfig { - string name; - uint256 chainId; - address admin; - address admin2; - address verifier; - address applicationVerifier; - address setVerifier; - address boundlessMarket; - address boundlessMarketImpl; - address boundlessMarketOldImpl; - address collateralToken; - bytes32 assessorImageId; - string assessorGuestUrl; - uint32 deprecatedAssessorDuration; - // PoVW contract addresses - address povwAccounting; - address povwAccountingImpl; - address povwAccountingOldImpl; - address povwAccountingAdmin; - string povwAccountingDeploymentCommit; - address povwMint; - address povwMintImpl; - address povwMintOldImpl; - address povwMintAdmin; - string povwMintDeploymentCommit; - // PoVW image IDs - bytes32 povwLogUpdaterId; - bytes32 povwMintCalculatorId; - // ZKC contract addresses - address zkc; - address vezkc; -} - -library ConfigLoader { - /// Reference the vm address without needing to inherit from Script. - Vm private constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - function loadConfig(string memory configFilePath) - internal - view - returns (string memory config, string memory deployKey) - { - // Load the config file - config = VM.readFile(configFilePath); - - // Get the config profile from the environment variable, or leave it empty - string memory chainKey = VM.envOr("CHAIN_KEY", string("")); - string memory stackTag = VM.envOr("STACK_TAG", string("")); - if (bytes(stackTag).length == 0) { - deployKey = chainKey; - } else if (bytes(chainKey).length != 0) { - deployKey = string.concat(chainKey, "-", stackTag); - } - - // If no profile is set, select the default one based on the chainId - if (bytes(deployKey).length == 0) { - string[] memory deployKeys = VM.parseTomlKeys(config, ".deployment"); - for (uint256 i = 0; i < deployKeys.length; i++) { - if (stdToml.readUint(config, string.concat(".deployment.", deployKeys[i], ".id")) == block.chainid) { - if (bytes(deployKey).length != 0) { - console2.log("Multiple entries found with chain ID %s", block.chainid); - require(false, "multiple entries found with same chain ID"); - } - deployKey = deployKeys[i]; - } - } - } - - console2.log("Using chain deployment key: %s", deployKey); - - return (config, deployKey); - } - - function loadDeploymentConfig(string memory configFilePath) internal view returns (DeploymentConfig memory) { - (string memory config, string memory deployKey) = loadConfig(configFilePath); - return ConfigParser.parseConfig(config, deployKey); - } -} - -library ConfigParser { - function parseConfig(string memory config, string memory deployKey) - internal - view - returns (DeploymentConfig memory) - { - DeploymentConfig memory deploymentConfig; - - string memory chain = string.concat(".deployment.", deployKey); - - deploymentConfig.name = stdToml.readString(config, string.concat(chain, ".name")); - deploymentConfig.chainId = stdToml.readUint(config, string.concat(chain, ".id")); - deploymentConfig.admin = stdToml.readAddressOr(config, string.concat(chain, ".admin"), address(0)); - deploymentConfig.admin2 = stdToml.readAddressOr(config, string.concat(chain, ".admin-2"), address(0)); - deploymentConfig.verifier = stdToml.readAddressOr(config, string.concat(chain, ".verifier"), address(0)); - deploymentConfig.applicationVerifier = - stdToml.readAddressOr(config, string.concat(chain, ".application-verifier"), address(0)); - deploymentConfig.setVerifier = stdToml.readAddressOr(config, string.concat(chain, ".set-verifier"), address(0)); - deploymentConfig.boundlessMarket = - stdToml.readAddressOr(config, string.concat(chain, ".boundless-market"), address(0)); - deploymentConfig.boundlessMarketImpl = - stdToml.readAddressOr(config, string.concat(chain, ".boundless-market-impl"), address(0)); - deploymentConfig.boundlessMarketOldImpl = - stdToml.readAddressOr(config, string.concat(chain, ".boundless-market-old-impl"), address(0)); - deploymentConfig.collateralToken = - stdToml.readAddressOr(config, string.concat(chain, ".collateral-token"), address(0)); - deploymentConfig.assessorImageId = stdToml.readBytes32(config, string.concat(chain, ".assessor-image-id")); - deploymentConfig.assessorGuestUrl = stdToml.readString(config, string.concat(chain, ".assessor-guest-url")); - deploymentConfig.deprecatedAssessorDuration = - uint32(stdToml.readUint(config, string.concat(chain, ".deprecated-assessor-duration"))); - - // PoVW contract addresses - deploymentConfig.povwAccounting = - stdToml.readAddressOr(config, string.concat(chain, ".povw-accounting"), address(0)); - deploymentConfig.povwAccountingImpl = - stdToml.readAddressOr(config, string.concat(chain, ".povw-accounting-impl"), address(0)); - deploymentConfig.povwAccountingOldImpl = - stdToml.readAddressOr(config, string.concat(chain, ".povw-accounting-old-impl"), address(0)); - deploymentConfig.povwAccountingAdmin = - stdToml.readAddressOr(config, string.concat(chain, ".povw-accounting-admin"), address(0)); - deploymentConfig.povwAccountingDeploymentCommit = - stdToml.readStringOr(config, string.concat(chain, ".povw-accounting-deployment-commit"), ""); - deploymentConfig.povwMint = stdToml.readAddressOr(config, string.concat(chain, ".povw-mint"), address(0)); - deploymentConfig.povwMintImpl = - stdToml.readAddressOr(config, string.concat(chain, ".povw-mint-impl"), address(0)); - deploymentConfig.povwMintOldImpl = - stdToml.readAddressOr(config, string.concat(chain, ".povw-mint-old-impl"), address(0)); - deploymentConfig.povwMintAdmin = - stdToml.readAddressOr(config, string.concat(chain, ".povw-mint-admin"), address(0)); - deploymentConfig.povwMintDeploymentCommit = - stdToml.readStringOr(config, string.concat(chain, ".povw-mint-deployment-commit"), ""); - - // PoVW image IDs - deploymentConfig.povwLogUpdaterId = - stdToml.readBytes32Or(config, string.concat(chain, ".povw-log-updater-id"), bytes32(0)); - deploymentConfig.povwMintCalculatorId = - stdToml.readBytes32Or(config, string.concat(chain, ".povw-mint-calculator-id"), bytes32(0)); - - // ZKC contract addresses - deploymentConfig.zkc = stdToml.readAddressOr(config, string.concat(chain, ".zkc"), address(0)); - deploymentConfig.vezkc = stdToml.readAddressOr(config, string.concat(chain, ".vezkc"), address(0)); - - return deploymentConfig; - } -} diff --git a/contracts/shanghai/scripts/Deploy.PoVW.s.sol b/contracts/shanghai/scripts/Deploy.PoVW.s.sol deleted file mode 100644 index f726dbf97f..0000000000 --- a/contracts/shanghai/scripts/Deploy.PoVW.s.sol +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.26; - -import {console2} from "forge-std/Script.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {IRiscZeroSelectable} from "risc0/IRiscZeroSelectable.sol"; -import {RiscZeroVerifierRouter} from "risc0/RiscZeroVerifierRouter.sol"; -import {RiscZeroSetVerifier} from "risc0/RiscZeroSetVerifier.sol"; -import {RiscZeroCheats} from "risc0/test/RiscZeroCheats.sol"; -import {PovwAccounting} from "../src/povw/PovwAccounting.sol"; -import {PovwMint} from "../src/povw/PovwMint.sol"; -import {IZKC} from "zkc/interfaces/IZKC.sol"; -import {IRewards as IZKCRewards} from "zkc/interfaces/IRewards.sol"; -import {MockZKC, MockZKCRewards} from "../test/MockZKC.sol"; -import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {BoundlessScriptBase, BoundlessScript} from "./BoundlessScript.s.sol"; - -contract DeployPoVW is BoundlessScriptBase, RiscZeroCheats { - struct DeployedContracts { - address verifier; - address zkc; - address vezkc; - address povwAccountingImpl; - address povwAccountingAddress; - address povwMintImpl; - address povwMintAddress; - bytes32 logUpdaterId; - bytes32 mintCalculatorId; - } - - /// @notice Updates deployment.toml with deployed contract addresses and image IDs - function updateDeploymentToml(DeployedContracts memory contracts) internal { - console2.log("Updating deployment.toml with PoVW contract addresses and image IDs"); - - // Get current git commit hash - string memory currentCommit = getCurrentCommit(); - - string[] memory args = new string[](28); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--povw-accounting"; - args[3] = vm.toString(contracts.povwAccountingAddress); - args[4] = "--povw-accounting-impl"; - args[5] = vm.toString(contracts.povwAccountingImpl); - args[6] = "--povw-mint"; - args[7] = vm.toString(contracts.povwMintAddress); - args[8] = "--povw-mint-impl"; - args[9] = vm.toString(contracts.povwMintImpl); - args[10] = "--povw-mint-old-impl"; - args[11] = vm.toString(address(0)); - args[12] = "--povw-accounting-old-impl"; - args[13] = vm.toString(address(0)); - args[14] = "--povw-log-updater-id"; - args[15] = vm.toString(contracts.logUpdaterId); - args[16] = "--povw-mint-calculator-id"; - args[17] = vm.toString(contracts.mintCalculatorId); - args[18] = "--povw-accounting-deployment-commit"; - args[19] = currentCommit; - args[20] = "--povw-mint-deployment-commit"; - args[21] = currentCommit; - args[22] = "--zkc"; - args[23] = vm.toString(contracts.zkc); - args[24] = "--vezkc"; - args[25] = vm.toString(contracts.vezkc); - args[26] = "--verifier"; - args[27] = vm.toString(contracts.verifier); - vm.ffi(args); - } - - function run() external { - // load ENV variables first - uint256 deployerKey = vm.envOr("DEPLOYER_PRIVATE_KEY", uint256(0)); - require( - deployerKey != 0, - "No deployer key provided. Please set the env var DEPLOYER_PRIVATE_KEY. Ensure private key prefixed with 0x" - ); - vm.rememberKey(deployerKey); - - console2.log("Deploying PoVW contracts (admins will be loaded from deployment.toml)"); - - // Read and log the chainID - uint256 chainId = block.chainid; - console2.log("You are deploying on ChainID %d", chainId); - - // Load the deployment config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - // Validate admin addresses are set (use deployment config instead of env var) - address povwAccountingAdmin = - BoundlessScript.requireLib(deploymentConfig.povwAccountingAdmin, "PovwAccounting admin"); - address povwMintAdmin = BoundlessScript.requireLib(deploymentConfig.povwMintAdmin, "PovwMint admin"); - - IRiscZeroVerifier verifier; - bool devMode = bytes(vm.envOr("RISC0_DEV_MODE", string(""))).length > 0; - - if (!devMode) { - verifier = IRiscZeroVerifier(BoundlessScript.requireLib(deploymentConfig.verifier, "Verifier")); - console2.log("Using IRiscZeroVerifier at", address(verifier)); - } - - vm.startBroadcast(); - - if (devMode) { - // Deploy verifier in dev mode - RiscZeroVerifierRouter verifierRouter = new RiscZeroVerifierRouter(povwAccountingAdmin); - console2.log("Deployed RiscZeroVerifierRouter to", address(verifierRouter)); - - IRiscZeroVerifier _verifier = deployRiscZeroVerifier(); - IRiscZeroSelectable selectable = IRiscZeroSelectable(address(_verifier)); - bytes4 selector = selectable.SELECTOR(); - verifierRouter.addVerifier(selector, _verifier); - - // Deploy set verifier for dev mode - string memory setBuilderPath = - "/target/riscv-guest/guest-set-builder/set-builder/riscv32im-risc0-zkvm-elf/release/set-builder.bin"; - string memory cwd = vm.envString("PWD"); - string memory setBuilderGuestUrl = string.concat("file://", cwd, setBuilderPath); - console2.log("Set builder URI", setBuilderGuestUrl); - - string[] memory argv = new string[](4); - argv[0] = "r0vm"; - argv[1] = "--id"; - argv[2] = "--elf"; - argv[3] = string.concat(".", setBuilderPath); - bytes32 setBuilderImageId = abi.decode(vm.ffi(argv), (bytes32)); - - RiscZeroSetVerifier setVerifier = - new RiscZeroSetVerifier(IRiscZeroVerifier(verifierRouter), setBuilderImageId, setBuilderGuestUrl); - console2.log("Deployed RiscZeroSetVerifier to", address(setVerifier)); - verifierRouter.addVerifier(setVerifier.SELECTOR(), setVerifier); - - verifier = IRiscZeroVerifier(verifierRouter); - console2.log("Dev mode: Deployed RiscZeroVerifier at", address(verifier)); - } - - // Determine ZKC contracts to use - deploy mocks only in RISC0_DEV_MODE - address zkcAddress; - address vezkcAddress; - - if (devMode) { - // Deploy mock ZKC contracts only in dev mode - MockZKC mockZkc = new MockZKC(); - MockZKCRewards mockZkcRewards = new MockZKCRewards(); - - zkcAddress = address(mockZkc); - vezkcAddress = address(mockZkcRewards); - - console2.log("In DEV MODE. Redeploying Mock ZKC and Mock ZKCRewards"); - console2.log("Deployed MockZKC to", zkcAddress); - console2.log("Deployed MockZKCRewards to", vezkcAddress); - } else { - // Use existing ZKC contracts - zkcAddress = BoundlessScript.requireLib(deploymentConfig.zkc, "ZKC"); - vezkcAddress = BoundlessScript.requireLib(deploymentConfig.vezkc, "veZKC"); - console2.log("Using existing ZKC at", zkcAddress); - console2.log("Using existing veZKC at", vezkcAddress); - } - - // PoVW image IDs (use mock values in dev mode) - bytes32 logUpdaterId; - bytes32 mintCalculatorId; - - if (devMode) { - // Use mock image IDs when in dev mode - logUpdaterId = bytes32(uint256(0x1111111111111111111111111111111111111111111111111111111111111111)); - mintCalculatorId = bytes32(uint256(0x2222222222222222222222222222222222222222222222222222222222222222)); - console2.log("Using mock PoVW image IDs for dev mode"); - } else { - // Check if environment variables are set first - bytes32 envLogUpdater = vm.envOr("POVW_LOG_UPDATER_ID", bytes32(0)); - bytes32 envMintCalculator = vm.envOr("POVW_MINT_CALCULATOR_ID", bytes32(0)); - - if (envLogUpdater != bytes32(0) && envMintCalculator != bytes32(0)) { - // Use environment variables if both are set - logUpdaterId = envLogUpdater; - mintCalculatorId = envMintCalculator; - console2.log("Using PoVW image IDs from environment variables"); - } else { - // Use .bin files as default - logUpdaterId = readImageIdFromFile("boundless-povw-log-updater.bin"); - mintCalculatorId = readImageIdFromFile("boundless-povw-mint-calculator.bin"); - console2.log("Using PoVW image IDs from .bin files"); - } - - // Require that we have valid image IDs - logUpdaterId = BoundlessScript.requireLib(logUpdaterId, "Log Updater ID"); - mintCalculatorId = BoundlessScript.requireLib(mintCalculatorId, "Mint Calculator ID"); - } - - console2.log("Log Updater ID: %s", vm.toString(logUpdaterId)); - console2.log("Mint Calculator ID: %s", vm.toString(mintCalculatorId)); - - // Deploy PovwAccounting - bytes32 salt = bytes32(vm.envOr("SALT", uint256(0))); - address povwAccountingImpl = address(new PovwAccounting{salt: salt}(verifier, IZKC(zkcAddress), logUpdaterId)); - address povwAccountingAddress = address( - new ERC1967Proxy{salt: salt}( - povwAccountingImpl, abi.encodeCall(PovwAccounting.initialize, (povwAccountingAdmin)) - ) - ); - - console2.log("Deployed PovwAccounting impl to", povwAccountingImpl); - console2.log("Deployed PovwAccounting proxy to", povwAccountingAddress); - console2.log("PovwAccounting admin:", povwAccountingAdmin); - - // Deploy PovwMint - address povwMintImpl = address( - new PovwMint{salt: salt}( - verifier, - PovwAccounting(povwAccountingAddress), - mintCalculatorId, - IZKC(zkcAddress), - IZKCRewards(vezkcAddress) - ) - ); - address povwMintAddress = - address(new ERC1967Proxy{salt: salt}(povwMintImpl, abi.encodeCall(PovwMint.initialize, (povwMintAdmin)))); - - console2.log("Deployed PovwMint impl to", povwMintImpl); - console2.log("Deployed PovwMint proxy to", povwMintAddress); - console2.log("PovwMint admin:", povwMintAdmin); - - vm.stopBroadcast(); - - // Update deployment.toml with contract addresses and image IDs - DeployedContracts memory deployedContracts = DeployedContracts({ - verifier: address(verifier), - zkc: zkcAddress, - vezkc: vezkcAddress, - povwAccountingImpl: povwAccountingImpl, - povwAccountingAddress: povwAccountingAddress, - povwMintImpl: povwMintImpl, - povwMintAddress: povwMintAddress, - logUpdaterId: logUpdaterId, - mintCalculatorId: mintCalculatorId - }); - updateDeploymentToml(deployedContracts); - - console2.log("PoVW contracts deployed successfully!"); - console2.log("ZKC:", zkcAddress); - console2.log("veZKC:", vezkcAddress); - console2.log("PovwAccounting:", povwAccountingAddress); - console2.log("PovwMint:", povwMintAddress); - - if (devMode) { - console2.log(""); - console2.log("================================================================="); - console2.log("WARNING: RISC0_DEV_MODE was enabled!"); - console2.log("- Deployed with mock verifier, ZKC contracts, and test image IDs"); - console2.log("- deployment.toml was updated with mock addresses"); - console2.log("- These contracts are NOT suitable for production use"); - console2.log("================================================================="); - } - - // Check for uncommitted changes warning - checkUncommittedChangesWarning("Deployment"); - } -} diff --git a/contracts/shanghai/scripts/Deploy.s.sol b/contracts/shanghai/scripts/Deploy.s.sol deleted file mode 100644 index f03fb2dc52..0000000000 --- a/contracts/shanghai/scripts/Deploy.s.sol +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {console2} from "forge-std/Script.sol"; -import {Strings} from "openzeppelin/contracts/utils/Strings.sol"; -import {IRiscZeroSelectable} from "risc0/IRiscZeroSelectable.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {RiscZeroSetVerifier} from "risc0/RiscZeroSetVerifier.sol"; -import {RiscZeroVerifierRouter} from "risc0/RiscZeroVerifierRouter.sol"; -import {RiscZeroCheats} from "risc0/test/RiscZeroCheats.sol"; -import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; -import {Blake3Groth16Verifier} from "../src/blake3-groth16/Blake3Groth16Verifier.sol"; -import {ControlID} from "../src/blake3-groth16/ControlID.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; -import {BoundlessMarket} from "../src/BoundlessMarket.sol"; -import {HitPoints} from "../src/HitPoints.sol"; -import {BoundlessScriptBase} from "./BoundlessScript.s.sol"; - -contract Deploy is BoundlessScriptBase, RiscZeroCheats { - // Path to deployment config file, relative to the project root. - string constant CONFIG_FILE = "contracts/deployment.toml"; - - IRiscZeroVerifier verifier; - IRiscZeroVerifier applicationVerifier; - address boundlessMarketAddress; - bytes32 assessorImageId; - address stakeToken; - - function run() external { - string memory assessorGuestUrl = ""; - - // load ENV variables first - uint256 deployerKey = vm.envOr("DEPLOYER_PRIVATE_KEY", uint256(0)); - require(deployerKey != 0, "No deployer key provided. Please set the env var DEPLOYER_PRIVATE_KEY."); - vm.rememberKey(deployerKey); - - address boundlessMarketOwner = vm.envAddress("BOUNDLESS_MARKET_OWNER"); - console2.log("BoundlessMarket Owner:", boundlessMarketOwner); - - // Read and log the chainID - uint256 chainId = block.chainid; - console2.log("You are deploying on ChainID %d", chainId); - - // Load the deployment config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG_FILE)); - - // Assign parsed config values to the variables - verifier = IRiscZeroVerifier(deploymentConfig.verifier); - applicationVerifier = IRiscZeroVerifier(deploymentConfig.applicationVerifier); - assessorImageId = deploymentConfig.assessorImageId; - assessorGuestUrl = deploymentConfig.assessorGuestUrl; - - if (assessorImageId == bytes32(0)) { - revert("assessor image ID must be set in deployment.toml"); - } - - vm.startBroadcast(deployerKey); - - // Deploy the verifier, if dev mode is enabled. - if (bytes(vm.envOr("RISC0_DEV_MODE", string(""))).length > 0) { - RiscZeroVerifierRouter verifierRouter = new RiscZeroVerifierRouter(boundlessMarketOwner); - console2.log("Deployed RiscZeroVerifierRouter to", address(verifierRouter)); - - IRiscZeroVerifier _verifier = deployRiscZeroVerifier(); - IRiscZeroSelectable selectable = IRiscZeroSelectable(address(_verifier)); - bytes4 selector = selectable.SELECTOR(); - verifierRouter.addVerifier(selector, _verifier); - console2.log("Added Groth16 verifier to router with selector"); - console2.logBytes4(selector); - - IRiscZeroVerifier _blake3G16Verifier = deployBlake3Verifier(); - IRiscZeroSelectable blake3G16Selectable = IRiscZeroSelectable(address(_blake3G16Verifier)); - bytes4 blake3G16Selector = blake3G16Selectable.SELECTOR(); - verifierRouter.addVerifier(blake3G16Selector, _blake3G16Verifier); - console2.log("Added Blake3 Groth16 verifier to router with selector"); - console2.logBytes4(blake3G16Selector); - // TODO: Create a more robust way of getting a URI for guests, and ensure that it is - // in-sync with the configured image ID. - string memory setBuilderPath = - "/target/riscv-guest/guest-set-builder/set-builder/riscv32im-risc0-zkvm-elf/release/set-builder.bin"; - string memory cwd = vm.envString("PWD"); - string memory setBuilderGuestUrl = string.concat("file://", cwd, setBuilderPath); - console2.log("Set builder URI", setBuilderGuestUrl); - - string[] memory argv = new string[](4); - argv[0] = "r0vm"; - argv[1] = "--id"; - argv[2] = "--elf"; - argv[3] = string.concat(".", setBuilderPath); - bytes32 setBuilderImageId = abi.decode(vm.ffi(argv), (bytes32)); - - string memory assessorPath = - "/target/riscv-guest/guest-assessor/assessor-guest/riscv32im-risc0-zkvm-elf/release/assessor-guest.bin"; - assessorGuestUrl = string.concat("file://", cwd, assessorPath); - console2.log("Assessor URI", assessorGuestUrl); - - argv[3] = string.concat(".", assessorPath); - assessorImageId = abi.decode(vm.ffi(argv), (bytes32)); - - RiscZeroSetVerifier setVerifier = - new RiscZeroSetVerifier(IRiscZeroVerifier(verifierRouter), setBuilderImageId, setBuilderGuestUrl); - console2.log("Deployed RiscZeroSetVerifier to", address(setVerifier)); - verifierRouter.addVerifier(setVerifier.SELECTOR(), setVerifier); - - verifier = IRiscZeroVerifier(verifierRouter); - applicationVerifier = verifier; - } - - if (address(verifier) == address(0)) { - revert("verifier must be specified in deployment.toml"); - } else { - console2.log("Using IRiscZeroVerifier deployed at", address(verifier)); - } - - if (address(applicationVerifier) == address(0)) { - revert("application verifier must be specified in deployment.toml"); - } else { - console2.log("Using application IRiscZeroVerifier deployed at", address(applicationVerifier)); - } - - bool deployedNewCollateralToken; - if (deploymentConfig.collateralToken == address(0) || deploymentConfig.collateralToken.code.length == 0) { - // Deploy the HitPoints contract - stakeToken = address(new HitPoints(boundlessMarketOwner)); - HitPoints(stakeToken).grantMinterRole(boundlessMarketOwner); - console2.log("Deployed HitPoints collateral token to", stakeToken); - deployedNewCollateralToken = true; - } else { - stakeToken = deploymentConfig.collateralToken; - console2.log("Using collateral token deployed at", stakeToken); - } - - // Deploy the Boundless market - bytes32 salt = vm.envOr("SALT", keccak256(abi.encodePacked("salt"))); - address newImplementation = address( - new BoundlessMarket{salt: salt}(verifier, applicationVerifier, assessorImageId, bytes32(0), 0, stakeToken) - ); - console2.log("Deployed new BoundlessMarket implementation at", newImplementation); - boundlessMarketAddress = address( - new ERC1967Proxy{salt: salt}( - newImplementation, abi.encodeCall(BoundlessMarket.initialize, (boundlessMarketOwner, assessorGuestUrl)) - ) - ); - console2.log("Deployed BoundlessMarket (proxy) to", boundlessMarketAddress); - - if (deployedNewCollateralToken) { - HitPoints(stakeToken).grantAuthorizedTransferRole(boundlessMarketAddress); - console2.log( - "Granted AUTHORIZED_TRANSFER role to BoundlessMarket on HitPoints collateral token", stakeToken - ); - } - - vm.stopBroadcast(); - - // Update deployment.toml with deployment information - string memory currentCommit = getCurrentCommit(); - - string[] memory args = new string[](8); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--boundless-market"; - args[3] = Strings.toHexString(boundlessMarketAddress); - args[4] = "--boundless-market-impl"; - args[5] = Strings.toHexString(newImplementation); - args[6] = "--boundless-market-deployment-commit"; - args[7] = currentCommit; - - vm.ffi(args); - console2.log("Updated BoundlessMarket deployment commit: %s", currentCommit); - - // Also update collateral token if we deployed it - if (deployedNewCollateralToken) { - string[] memory tokenArgs = new string[](4); - tokenArgs[0] = "python3"; - tokenArgs[1] = "contracts/update_deployment_toml.py"; - tokenArgs[2] = "--collateral-token"; - tokenArgs[3] = Strings.toHexString(stakeToken); - vm.ffi(tokenArgs); - console2.log("Updated collateral token address: %s", stakeToken); - } - - // Check for uncommitted changes warning - checkUncommittedChangesWarning("Deployment"); - } - - /// @notice Deploy either a test or fully verifying `Blake3Groth16Verifier` depending on `devMode()`. - function deployBlake3Verifier() internal returns (IRiscZeroVerifier) { - if (devMode()) { - // NOTE: Using a fixed selector of 0xFFFF0000 for the selector of the mock verifier. - IRiscZeroVerifier _verifier = new RiscZeroMockVerifier(bytes4(0xFFFF0000)); - console2.log("Deployed RiscZeroMockVerifier to", address(_verifier)); - return _verifier; - } else { - IRiscZeroVerifier _verifier = new Blake3Groth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); - console2.log("Deployed Blake3Groth16Verifier to", address(_verifier)); - return _verifier; - } - } -} diff --git a/contracts/shanghai/scripts/HitPointsOperator.md b/contracts/shanghai/scripts/HitPointsOperator.md deleted file mode 100644 index b19327a001..0000000000 --- a/contracts/shanghai/scripts/HitPointsOperator.md +++ /dev/null @@ -1,135 +0,0 @@ -# HitPoints Operator Guide - -This guide explains how to use the provided [Bash script](./hp) to interact with the **HitPoints** smart contract. It covers prerequisites, environment variables, and usage for the various commands. - ---- - -## 1. Overview - -The Bash script offers a way to manage the **HitPoints** ERC20 token by calling specific functions on the deployed smart contract via **cast** (from the Foundry suite). Its main functions include: - -- Minting tokens -- Granting and revoking roles (MINTER and AUTHORIZED_TRANSFER) -- Checking token balances - ---- - -## 2. Prerequisites - -1. **Foundry / cast**\ - Make sure you have [Foundry](https://book.getfoundry.sh/) installed, which includes the `cast` tool for interacting with Ethereum contracts. - ---- - -## 3. Environment Variables - -Before you run the script, you must set the following environment variables in your shell session. If these variables are not set, the script will not run and will display an error. - -| Variable | Description | -| -------------------- | ---------------------------------------------------------------------------------------------------- | -| `PRIVATE_KEY` | The private key of the account that will send transactions (contract owner/admin or authorized). | -| `RPC_URL` | The RPC endpoint of the Ethereum network you’re interacting with (e.g., Infura or Alchemy endpoint). | -| `HIT_POINTS_ADDRESS` | The contract address where the **HitPoints** token has been deployed. | - -Example of setting them in a Unix shell: - -```bash -export PRIVATE_KEY=0x1234567890... -export RPC_URL=https://rpc.sepolia.org -export HIT_POINTS_ADDRESS=0xe5321cF13B07Bf6f6dD621E85E45C8e28adedCc9 -``` - -## 4. Usage - -### 4.1 Available Commands - -#### mint - -Calls the mint(address, uint256) function to mint HP tokens to target_address. - -```bash -./hp mint [amount] -``` - -Parameters: - -- target_address: The address to receive the newly minted tokens. -- amount (optional): The amount of tokens to mint. If omitted, the script defaults to DEFAULT_MINT_AMOUNT (100 tokens by default; 1 token = 1e18 for an 18-decimal token). - -Example: - -```console -./hp mint 0xRecipientAddress 100000000000000000000 -``` - -This will mint 100 tokens (1 token = 1e18 for an 18-decimal token). - -#### grant-minter-role - -Calls grantMinterRole(address) on the contract to give the MINTER role to target_address. Addresses with this role can mint tokens. - -```bash -./hp grant-minter-role -``` - -Example: - -```console -./hp grant-minter-role 0xMinterAddress -``` - -#### revoke-minter-role - -Calls revokeMinterRole(address) on the contract to remove the MINTER role from target_address. - -```bash -./hp revoke-minter-role -``` - -Example: - -```console -./hp revoke-minter-role 0xMinterAddress -``` - -#### grant-auth-transfer-role - -Calls grantAuthorizedTransferRole(address) to give the AUTHORIZED_TRANSFER role to target_address. Addresses with this role can bypass restricted transfer rules. - -```bash -./hp grant-auth-transfer-role -``` - -Example: - -```console -./hp grant-auth-transfer-role 0xAuthTransferAddress -``` - -#### revoke-auth-transfer-role - -Calls revokeAuthorizedTransferRole(address) to remove the AUTHORIZED_TRANSFER role from target_address. - -```bash -./hp revoke-auth-transfer-role -``` - -Example: - -```console -./hp revoke-auth-transfer-role 0xAuthTransferAddress -``` - -#### Check balance - -Calls balanceOf(address) to retrieve the HP token balance of target_address. - -```bash -./hp balance -``` - -Example: - -```console -./hp balance 0xRecipientAddress -``` diff --git a/contracts/shanghai/scripts/Manage.PoVW.s.sol b/contracts/shanghai/scripts/Manage.PoVW.s.sol deleted file mode 100644 index 292d8bbf9c..0000000000 --- a/contracts/shanghai/scripts/Manage.PoVW.s.sol +++ /dev/null @@ -1,388 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {console2} from "forge-std/console2.sol"; -import {Strings} from "openzeppelin/contracts/utils/Strings.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {PovwAccounting} from "../src/povw/PovwAccounting.sol"; -import {PovwMint} from "../src/povw/PovwMint.sol"; -import {IZKC} from "zkc/interfaces/IZKC.sol"; -import {IRewards as IZKCRewards} from "zkc/interfaces/IRewards.sol"; -import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; -import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; -import {Options as UpgradeOptions} from "openzeppelin-foundry-upgrades/Options.sol"; -import {BoundlessScriptBase, BoundlessScript} from "./BoundlessScript.s.sol"; - -/// @notice Upgrade script for the PovwAccounting contract. -/// @dev Set values in deployment.toml to configure the upgrade. -contract UpgradePoVWAccounting is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - // Get PoVW proxy address from deployment.toml - address povwAccountingAddress = BoundlessScript.requireLib(deploymentConfig.povwAccounting, "povw-accounting"); - - // Get current admin from the proxy contract - PovwAccounting povwAccounting = PovwAccounting(povwAccountingAddress); - address currentAdmin = povwAccounting.owner(); - - address currentImplementation = Upgrades.getImplementationAddress(povwAccountingAddress); - - // Get constructor arguments for PovwAccounting - IRiscZeroVerifier verifier = - IRiscZeroVerifier(BoundlessScript.requireLib(deploymentConfig.verifier, "verifier")); - - // Handle ZKC address - if zero address, don't upgrade (production should have real ZKC) - address zkcAddress = BoundlessScript.requireLib(deploymentConfig.zkc, "zkc"); - IZKC zkc = IZKC(zkcAddress); - - // Get the latest log updater ID dynamically - bytes32 logUpdaterId; - bool devMode = bytes(vm.envOr("RISC0_DEV_MODE", string(""))).length > 0; - - if (devMode) { - // Use mock ID in dev mode - logUpdaterId = bytes32(uint256(0x1111111111111111111111111111111111111111111111111111111111111111)); - console2.log("Using mock PoVW log updater ID for dev mode"); - } else { - // Try environment variable first - bytes32 envLogUpdater = vm.envOr("POVW_LOG_UPDATER_ID", bytes32(0)); - if (envLogUpdater != bytes32(0)) { - logUpdaterId = envLogUpdater; - console2.log("Using PoVW log updater ID from environment variable"); - } else { - // Try reading from .bin file - logUpdaterId = readImageIdFromFile("boundless-povw-log-updater.bin"); - if (logUpdaterId == bytes32(0)) { - // Fall back to config as last resort - logUpdaterId = deploymentConfig.povwLogUpdaterId; - console2.log("Using PoVW log updater ID from deployment config (fallback)"); - } else { - console2.log("Using PoVW log updater ID from .bin file"); - } - } - - // Require that we have a valid log updater ID - logUpdaterId = BoundlessScript.requireLib(logUpdaterId, "Log Updater ID"); - } - - console2.log("Log Updater ID: %s", vm.toString(logUpdaterId)); - - UpgradeOptions memory opts; - opts.referenceContract = "build-info-reference:PovwAccounting"; - opts.referenceBuildInfoDir = "contracts/build-info-reference"; - opts.constructorData = abi.encode(verifier, zkc, logUpdaterId); - - // Check if safety checks should be skipped - bool skipSafetyChecks = vm.envOr("SKIP_SAFETY_CHECKS", false); - if (skipSafetyChecks) { - console2.log("WARNING: Skipping all upgrade safety checks (SKIP_SAFETY_CHECKS=true)"); - opts.unsafeSkipAllChecks = true; - } - - vm.startBroadcast(currentAdmin); - Upgrades.upgradeProxy(povwAccountingAddress, "PovwAccounting.sol:PovwAccounting", "", opts, currentAdmin); - vm.stopBroadcast(); - - // Verify the upgrade - address newImplementation = Upgrades.getImplementationAddress(povwAccountingAddress); - require(newImplementation != currentImplementation, "PovwAccounting implementation was not upgraded"); - require(povwAccounting.owner() == currentAdmin, "PovwAccounting admin changed during upgrade"); - - console2.log("Upgraded PovwAccounting admin is %s", currentAdmin); - console2.log("Upgraded PovwAccounting proxy contract at %s", povwAccountingAddress); - console2.log("Upgraded PovwAccounting impl from %s to %s", currentImplementation, newImplementation); - - // Get current git commit hash - string memory currentCommit = getCurrentCommit(); - - string[] memory args = new string[](10); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--povw-accounting-impl"; - args[3] = Strings.toHexString(newImplementation); - args[4] = "--povw-accounting-old-impl"; - args[5] = Strings.toHexString(currentImplementation); - args[6] = "--povw-accounting-deployment-commit"; - args[7] = currentCommit; - args[8] = "--povw-log-updater-id"; - args[9] = vm.toString(logUpdaterId); - - vm.ffi(args); - console2.log("Updated PovwAccounting deployment commit: %s", currentCommit); - console2.log("Updated PoVW log updater ID: %s", vm.toString(logUpdaterId)); - - // Check for uncommitted changes warning - checkUncommittedChangesWarning("Upgrade"); - } -} - -/// @notice Upgrade script for the PovwMint contract. -/// @dev Set values in deployment.toml to configure the upgrade. -contract UpgradePoVWMint is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - // Get PoVW proxy address from deployment.toml - address povwMintAddress = BoundlessScript.requireLib(deploymentConfig.povwMint, "povw-mint"); - - // Get current admin from the proxy contract - PovwMint povwMint = PovwMint(povwMintAddress); - console2.log("Getting admin"); - address currentAdmin = povwMint.owner(); - console2.log("Current PovwMint admin: %s", currentAdmin); - - console2.log("Getting impl"); - address currentImplementation = Upgrades.getImplementationAddress(povwMintAddress); - - console2.log("Current PovwMint implementation: %s", currentImplementation); - - // Get constructor arguments for PovwMint - IRiscZeroVerifier verifier = - IRiscZeroVerifier(BoundlessScript.requireLib(deploymentConfig.verifier, "verifier")); - PovwAccounting povwAccounting = - PovwAccounting(BoundlessScript.requireLib(deploymentConfig.povwAccounting, "povw-accounting")); - - bytes32 mintCalculatorId; - bool devMode = bytes(vm.envOr("RISC0_DEV_MODE", string(""))).length > 0; - - if (devMode) { - // Use mock ID in dev mode - mintCalculatorId = bytes32(uint256(0x2222222222222222222222222222222222222222222222222222222222222222)); - console2.log("Using mock PoVW mint calculator ID for dev mode"); - } else { - // Try environment variable first - bytes32 envMintCalculator = vm.envOr("POVW_MINT_CALCULATOR_ID", bytes32(0)); - if (envMintCalculator != bytes32(0)) { - mintCalculatorId = envMintCalculator; - console2.log("Using PoVW mint calculator ID from environment variable"); - } else { - // Try reading from .bin file - mintCalculatorId = readImageIdFromFile("boundless-povw-mint-calculator.bin"); - if (mintCalculatorId == bytes32(0)) { - // Fall back to config as last resort - mintCalculatorId = deploymentConfig.povwMintCalculatorId; - console2.log("Using PoVW mint calculator ID from deployment config (fallback)"); - } else { - console2.log("Using PoVW mint calculator ID from .bin file"); - } - } - - // Require that we have a valid mint calculator ID - mintCalculatorId = BoundlessScript.requireLib(mintCalculatorId, "Mint Calculator ID"); - } - - console2.log("Mint Calculator ID: %s", vm.toString(mintCalculatorId)); - - // Handle ZKC addresses - if zero address, don't upgrade (production should have real ZKC) - address zkcAddress = BoundlessScript.requireLib(deploymentConfig.zkc, "zkc"); - address vezkcAddress = BoundlessScript.requireLib(deploymentConfig.vezkc, "vezkc"); - - IZKC zkc = IZKC(zkcAddress); - IZKCRewards vezkc = IZKCRewards(vezkcAddress); - - UpgradeOptions memory opts; - opts.referenceContract = "build-info-reference:PovwMint"; - opts.referenceBuildInfoDir = "contracts/build-info-reference"; - opts.constructorData = abi.encode(verifier, povwAccounting, mintCalculatorId, zkc, vezkc); - - // Check if safety checks should be skipped - bool skipSafetyChecks = vm.envOr("SKIP_SAFETY_CHECKS", false); - if (skipSafetyChecks) { - console2.log("WARNING: Skipping all upgrade safety checks (SKIP_SAFETY_CHECKS=true)"); - opts.unsafeSkipAllChecks = true; - } - - vm.startBroadcast(currentAdmin); - Upgrades.upgradeProxy(povwMintAddress, "PovwMint.sol:PovwMint", "", opts, currentAdmin); - vm.stopBroadcast(); - - // Verify the upgrade - address newImplementation = Upgrades.getImplementationAddress(povwMintAddress); - require(newImplementation != currentImplementation, "PovwMint implementation was not upgraded"); - require(povwMint.owner() == currentAdmin, "PovwMint admin changed during upgrade"); - - console2.log("Upgraded PovwMint admin is %s", currentAdmin); - console2.log("Upgraded PovwMint proxy contract at %s", povwMintAddress); - console2.log("Upgraded PovwMint impl from %s to %s", currentImplementation, newImplementation); - - // Get current git commit hash - string memory currentCommit = getCurrentCommit(); - - string[] memory args = new string[](10); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--povw-mint-impl"; - args[3] = Strings.toHexString(newImplementation); - args[4] = "--povw-mint-old-impl"; - args[5] = Strings.toHexString(currentImplementation); - args[6] = "--povw-mint-deployment-commit"; - args[7] = currentCommit; - args[8] = "--povw-mint-calculator-id"; - args[9] = vm.toString(mintCalculatorId); - - vm.ffi(args); - console2.log("Updated PovwMint deployment commit: %s", currentCommit); - console2.log("Updated PoVW mint calculator ID: %s", vm.toString(mintCalculatorId)); - - // Check for uncommitted changes warning - checkUncommittedChangesWarning("Upgrade"); - } -} - -/// @notice Script for transferring ownership of the PoVW contracts. -/// @dev Transfer will be from the current owner to the NEW_ADMIN environment variable -contract TransferPoVWOwnership is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address newAdmin = BoundlessScript.requireLib(vm.envOr("NEW_ADMIN", address(0)), "NEW_ADMIN"); - address povwAccountingAddress = BoundlessScript.requireLib(deploymentConfig.povwAccounting, "povw-accounting"); - address povwMintAddress = BoundlessScript.requireLib(deploymentConfig.povwMint, "povw-mint"); - - PovwAccounting povwAccounting = PovwAccounting(povwAccountingAddress); - PovwMint povwMint = PovwMint(povwMintAddress); - - address currentAccountingAdmin = povwAccounting.owner(); - address currentMintAdmin = povwMint.owner(); - - require(newAdmin != currentAccountingAdmin, "current and new PovwAccounting admin address are the same"); - require(newAdmin != currentMintAdmin, "current and new PovwMint admin address are the same"); - - vm.startBroadcast(currentAccountingAdmin); - povwAccounting.transferOwnership(newAdmin); - vm.stopBroadcast(); - - vm.startBroadcast(currentMintAdmin); - povwMint.transferOwnership(newAdmin); - vm.stopBroadcast(); - - // check owners of each contract - require(povwAccounting.owner() == newAdmin, "PovwAccounting owner is not the new admin"); - require(povwMint.owner() == newAdmin, "PovwMint owner is not the new admin"); - - console2.log("Transferred ownership of PovwAccounting contract from %s to %s", currentAccountingAdmin, newAdmin); - console2.log("Transferred ownership of PovwMint contract from %s to %s", currentMintAdmin, newAdmin); - - // Update deployment.toml with new admin addresses - string[] memory args = new string[](6); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--povw-accounting-admin"; - args[3] = Strings.toHexString(newAdmin); - args[4] = "--povw-mint-admin"; - args[5] = Strings.toHexString(newAdmin); - vm.ffi(args); - - console2.log("Updated deployment.toml with new admin addresses"); - } -} - -/// @notice Rollback script for the PovwAccounting contract. -/// @dev Set values in deployment.toml to configure the rollback. -contract RollbackPoVWAccounting is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address povwAccountingAddress = BoundlessScript.requireLib(deploymentConfig.povwAccounting, "povw-accounting"); - address oldImplementation = - BoundlessScript.requireLib(deploymentConfig.povwAccountingOldImpl, "povw-accounting-old-impl"); - - // Get current admin from the proxy contract - PovwAccounting povwAccounting = PovwAccounting(povwAccountingAddress); - address currentAdmin = povwAccounting.owner(); - - require(oldImplementation != address(0), "old implementation address is not set"); - console2.log( - "\nWARNING: This will rollback the PovwAccounting contract to this address: %s\n", oldImplementation - ); - - // Rollback the proxy contract - vm.startBroadcast(currentAdmin); - - // For PovwAccounting, we don't need a reinitializer call like BoundlessMarket - bytes memory rollbackUpgradeData = abi.encodeWithSignature("upgradeTo(address)", oldImplementation); - (bool success, bytes memory returnData) = povwAccountingAddress.call(rollbackUpgradeData); - require(success, string(returnData)); - - vm.stopBroadcast(); - - // Verify the rollback - address currentImplementation = Upgrades.getImplementationAddress(povwAccountingAddress); - require(currentImplementation == oldImplementation, "PovwAccounting rollback failed"); - require(povwAccounting.owner() == currentAdmin, "PovwAccounting admin changed during rollback"); - console2.log("Rollback successful. PovwAccounting implementation is now %s", currentImplementation); - - // Update deployment.toml to swap impl and old-impl addresses - string[] memory args = new string[](6); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--povw-accounting-impl"; - args[3] = Strings.toHexString(currentImplementation); - args[4] = "--povw-accounting-old-impl"; - args[5] = Strings.toHexString(deploymentConfig.povwAccountingImpl); - - vm.ffi(args); - console2.log("Updated deployment.toml with rollback addresses"); - } -} - -/// @notice Rollback script for the PovwMint contract. -/// @dev Set values in deployment.toml to configure the rollback. -contract RollbackPoVWMint is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address povwMintAddress = BoundlessScript.requireLib(deploymentConfig.povwMint, "povw-mint"); - address oldImplementation = BoundlessScript.requireLib(deploymentConfig.povwMintOldImpl, "povw-mint-old-impl"); - - // Get current admin from the proxy contract - PovwMint povwMint = PovwMint(povwMintAddress); - address currentAdmin = povwMint.owner(); - - require(oldImplementation != address(0), "old implementation address is not set"); - console2.log("\nWARNING: This will rollback the PovwMint contract to this address: %s\n", oldImplementation); - - // Rollback the proxy contract - vm.startBroadcast(currentAdmin); - - // For PovwMint, we don't need a reinitializer call like BoundlessMarket - bytes memory rollbackUpgradeData = abi.encodeWithSignature("upgradeTo(address)", oldImplementation); - (bool success, bytes memory returnData) = povwMintAddress.call(rollbackUpgradeData); - require(success, string(returnData)); - - vm.stopBroadcast(); - - // Verify the rollback - address currentImplementation = Upgrades.getImplementationAddress(povwMintAddress); - require(currentImplementation == oldImplementation, "PovwMint rollback failed"); - require(povwMint.owner() == currentAdmin, "PovwMint admin changed during rollback"); - console2.log("Rollback successful. PovwMint implementation is now %s", currentImplementation); - - // Update deployment.toml to swap impl and old-impl addresses - string[] memory args = new string[](6); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--povw-mint-impl"; - args[3] = Strings.toHexString(currentImplementation); - args[4] = "--povw-mint-old-impl"; - args[5] = Strings.toHexString(deploymentConfig.povwMintImpl); - - vm.ffi(args); - console2.log("Updated deployment.toml with rollback addresses"); - } -} diff --git a/contracts/shanghai/scripts/Manage.s.sol b/contracts/shanghai/scripts/Manage.s.sol deleted file mode 100644 index 32386197bb..0000000000 --- a/contracts/shanghai/scripts/Manage.s.sol +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Script} from "forge-std/Script.sol"; -import {console2} from "forge-std/console2.sol"; -import {Strings} from "openzeppelin/contracts/utils/Strings.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {BoundlessMarket} from "../src/BoundlessMarket.sol"; -import {BoundlessMarketLib} from "../src/libraries/BoundlessMarketLib.sol"; -import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; -import {Options as UpgradeOptions} from "openzeppelin-foundry-upgrades/Options.sol"; -import {BoundlessScriptBase} from "./BoundlessScript.s.sol"; - -library RequireLib { - function required(address value, string memory label) internal pure returns (address) { - if (value == address(0)) { - console2.log("address value %s is required", label); - require(false, "required address value not set"); - } - console2.log("Using %s = %s", label, value); - return value; - } - - function required(bytes32 value, string memory label) internal pure returns (bytes32) { - if (value == bytes32(0)) { - console2.log("bytes32 value %s is required", label); - require(false, "required bytes32 value not set"); - } - console2.log("Using %s = %x", label, uint256(value)); - return value; - } - - function required(string memory value, string memory label) internal pure returns (string memory) { - if (bytes(value).length == 0) { - console2.log("string value %s is required", label); - require(false, "required string value not set"); - } - console2.log("Using %s = %s", label, value); - return value; - } -} - -using RequireLib for address; -using RequireLib for string; -using RequireLib for bytes32; - -// This is the EIP-1967 implementation slot: -bytes32 constant IMPLEMENTATION_SLOT = 0x360894A13BA1A3210667C828492DB98DCA3E2076CC3735A920A3CA505D382BBC; - -/// @notice Deployment script for the market deployment. -/// @dev Set values in deployment.toml to configure the deployment. -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/tutorials/solidity-scripting -contract DeployBoundlessMarket is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address admin = deploymentConfig.admin.required("admin"); - address verifier = deploymentConfig.verifier.required("verifier"); - address applicationVerifier = deploymentConfig.applicationVerifier.required("application-verifier"); - bytes32 assessorImageId = deploymentConfig.assessorImageId.required("assessor-image-id"); - string memory assessorGuestUrl = deploymentConfig.assessorGuestUrl.required("assessor-guest-url"); - address collateralToken = deploymentConfig.collateralToken.required("collateral-token"); - - vm.startBroadcast(getDeployer()); - // Deploy the proxy contract and initialize the contract - bytes32 salt = bytes32(0); - address newImplementation = address( - new BoundlessMarket{salt: salt}( - IRiscZeroVerifier(verifier), - IRiscZeroVerifier(applicationVerifier), - assessorImageId, - bytes32(0), - 0, - collateralToken - ) - ); - address marketAddress = address( - new ERC1967Proxy{salt: salt}( - newImplementation, abi.encodeCall(BoundlessMarket.initialize, (admin, assessorGuestUrl)) - ) - ); - - vm.stopBroadcast(); - - // Verify the deployment - BoundlessMarket market = BoundlessMarket(marketAddress); - require(market.VERIFIER() == IRiscZeroVerifier(deploymentConfig.verifier), "verifier does not match"); - require( - market.APPLICATION_VERIFIER() == IRiscZeroVerifier(deploymentConfig.applicationVerifier), - "application verifier does not match" - ); - (bytes32 assessorId, string memory guestUrl) = market.imageInfo(); - require(assessorId == deploymentConfig.assessorImageId, "assessor image ID does not match"); - require( - keccak256(bytes(guestUrl)) == keccak256(bytes(deploymentConfig.assessorGuestUrl)), - "assessor guest URL does not match" - ); - require( - market.COLLATERAL_TOKEN_CONTRACT() == deploymentConfig.collateralToken, "collateral token does not match" - ); - require( - market.hasRole(market.ADMIN_ROLE(), deploymentConfig.admin), "market admin role does not match the admin" - ); - - console2.log("BoundlessMarket admin is %s", deploymentConfig.admin); - console2.log("BoundlessMarket stake token contract at %s", deploymentConfig.collateralToken); - console2.log("BoundlessMarket verifier contract at %s", deploymentConfig.verifier); - console2.log("BoundlessMarket application verifier contract at %s", deploymentConfig.applicationVerifier); - console2.log("BoundlessMarket assessor image ID %s", Strings.toHexString(uint256(assessorId), 32)); - console2.log("BoundlessMarket assessor guest URL %s", guestUrl); - - address boundlessMarketImpl = address(uint160(uint256(vm.load(marketAddress, IMPLEMENTATION_SLOT)))); - console2.log( - "Deployed BoundlessMarket proxy contract at %s with impl at %s", marketAddress, boundlessMarketImpl - ); - - // Get current git commit hash - string memory currentCommit = getCurrentCommit(); - - string[] memory args = new string[](10); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--boundless-market"; - args[3] = Strings.toHexString(marketAddress); - args[4] = "--boundless-market-impl"; - args[5] = Strings.toHexString(boundlessMarketImpl); - args[6] = "--boundless-market-old-impl"; - args[7] = Strings.toHexString(address(0)); // Old impl is not set at deployment time - args[8] = "--boundless-market-deployment-commit"; - args[9] = currentCommit; - - vm.ffi(args); - console2.log("Updated BoundlessMarket deployment commit: %s", currentCommit); - - // Check for uncommitted changes warning - checkUncommittedChangesWarning("Deployment"); - } -} - -/// @notice Deployment script for the market contract upgrade. -/// @dev Set values in deployment.toml to configure the deployment. -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/tutorials/solidity-scripting -contract UpgradeBoundlessMarket is BoundlessScriptBase { - function run() external { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - bool skipSafetyChecks = vm.envOr("SKIP_SAFETY_CHECKS", false); - - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address marketAddress = deploymentConfig.boundlessMarket.required("boundless-market"); - address collateralToken = deploymentConfig.collateralToken.required("collateral-token"); - address verifier = deploymentConfig.verifier.required("verifier"); - address applicationVerifier = deploymentConfig.applicationVerifier.required("application-verifier"); - address currentImplementation = address(uint160(uint256(vm.load(marketAddress, IMPLEMENTATION_SLOT)))); - uint32 deprecatedAssessorDuration = deploymentConfig.deprecatedAssessorDuration; - - // Get the current assessor image ID and guest URL - BoundlessMarket market = BoundlessMarket(marketAddress); - (bytes32 deprecatedAssessorImageId,) = market.imageInfo(); - - // Use the assessor image ID recorded in deployment.toml - bytes32 assessorImageId = deploymentConfig.assessorImageId.required("assessor-image-id"); - string memory assessorGuestUrl = deploymentConfig.assessorGuestUrl.required("assessor-guest-url"); - - // Upgrade requires build info from the currently deployed version. - // You can get this build info with the following process. - // Check the `deployment.toml` for the deployed commit. - // - // ```sh - // git worktree add ../boundless-reference ${DEPLOYED_COMMIT:?} - // cd ../boundless-reference - // forge build - // cp -R out/build-info ../boundless/contracts/build-info-reference - // ``` - UpgradeOptions memory opts; - opts.constructorData = BoundlessMarketLib.encodeConstructorArgs( - IRiscZeroVerifier(verifier), - IRiscZeroVerifier(applicationVerifier), - assessorImageId, - deprecatedAssessorImageId, - deprecatedAssessorDuration, - collateralToken - ); - - if (skipSafetyChecks) { - console2.log("WARNING: Skipping all upgrade safety checks and reference build!"); - opts.unsafeSkipAllChecks = true; - } else { - // Only set reference contract when doing safety checks - opts.referenceContract = "build-info-reference:BoundlessMarket"; - opts.referenceBuildInfoDir = "contracts/build-info-reference"; - } - - address newImpl = address(0); - bytes memory initializerData = abi.encodeCall(BoundlessMarket.setImageUrl, (assessorGuestUrl)); - - vm.startBroadcast(getDeployer()); - if (gnosisExecute) { - console2.log("GNOSIS_EXECUTE=true: Deploying new implementation for Safe upgrade"); - console2.log("Target proxy address: ", marketAddress); - console2.log("Current implementation: ", currentImplementation); - - // Use prepareUpgrade for validation + deployment - newImpl = Upgrades.prepareUpgrade("BoundlessMarket.sol:BoundlessMarket", opts); - console2.log("New implementation deployed: ", newImpl); - - // Print Gnosis Safe transaction info - _printGnosisSafeInfo(marketAddress, newImpl, initializerData); - } else { - console2.log("Upgrading Boundless Market at: ", marketAddress); - console2.log("Current implementation: ", currentImplementation); - - // Perform upgrade with optional initializer - if (initializerData.length > 0) { - Upgrades.upgradeProxy(marketAddress, "BoundlessMarket.sol:BoundlessMarket", initializerData, opts); - } else { - Upgrades.upgradeProxy(marketAddress, "BoundlessMarket.sol:BoundlessMarket", "", opts); - } - - newImpl = Upgrades.getImplementationAddress(marketAddress); - console2.log("Upgraded Boundless Market implementation to: ", newImpl); - - // Verify the upgrade - BoundlessMarket upgradedMarket = BoundlessMarket(marketAddress); - require( - upgradedMarket.VERIFIER() == IRiscZeroVerifier(deploymentConfig.verifier), - "upgraded market verifier does not match" - ); - require( - upgradedMarket.APPLICATION_VERIFIER() == IRiscZeroVerifier(deploymentConfig.applicationVerifier), - "upgraded market application verifier does not match" - ); - (bytes32 assessorId, string memory upgradedGuestUrl) = upgradedMarket.imageInfo(); - require(assessorId == deploymentConfig.assessorImageId, "upgraded market assessor image ID does not match"); - require( - keccak256(bytes(upgradedGuestUrl)) == keccak256(bytes(deploymentConfig.assessorGuestUrl)), - "upgraded market assessor guest URL does not match" - ); - require( - upgradedMarket.COLLATERAL_TOKEN_CONTRACT() == deploymentConfig.collateralToken, - "upgraded market stake token does not match" - ); - require( - upgradedMarket.hasRole(upgradedMarket.ADMIN_ROLE(), deploymentConfig.admin2), - "upgraded market admin does not match the admin" - ); - address boundlessMarketImpl = address(uint160(uint256(vm.load(marketAddress, IMPLEMENTATION_SLOT)))); - console2.log("Upgraded BoundlessMarket admin is %s", deploymentConfig.admin); - console2.log("Upgraded BoundlessMarket proxy contract at %s", marketAddress); - console2.log("Upgraded BoundlessMarket impl contract at %s", boundlessMarketImpl); - console2.log("Upgraded BoundlessMarket collateral token contract at %s", deploymentConfig.collateralToken); - console2.log("Upgraded BoundlessMarket verifier contract at %s", deploymentConfig.verifier); - console2.log( - "Upgraded BoundlessMarket application verifier contract at %s", deploymentConfig.applicationVerifier - ); - console2.log("Upgraded BoundlessMarket assessor image ID %s", Strings.toHexString(uint256(assessorId), 32)); - console2.log("Upgraded BoundlessMarket assessor guest URL %s", upgradedGuestUrl); - } - vm.stopBroadcast(); - - string[] memory args = new string[](6); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--boundless-market-impl"; - args[3] = Strings.toHexString(newImpl); - args[4] = "--boundless-market-old-impl"; - args[5] = Strings.toHexString(currentImplementation); - - vm.ffi(args); - } -} - -/// @notice Deployment script for the market contract rollback. -/// @dev Set values in deployment.toml to configure the deployment. -contract RollbackBoundlessMarket is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address admin = deploymentConfig.admin.required("admin"); - address marketAddress = deploymentConfig.boundlessMarket.required("boundless-market"); - string memory assessorGuestUrl = deploymentConfig.assessorGuestUrl.required("assessor-guest-url"); - address oldImplementation = deploymentConfig.boundlessMarketOldImpl.required("boundless-market-old-impl"); - - require(oldImplementation != address(0), "old implementation address is not set"); - console2.log( - "\nWARNING: This will rollback the BoundlessMarket contract to this address: %s\n", oldImplementation - ); - - // Rollback the proxy contract. - vm.startBroadcast(admin); - - bytes memory initializer = abi.encodeCall(BoundlessMarket.setImageUrl, (assessorGuestUrl)); - bytes memory rollbackUpgradeData = - abi.encodeWithSignature("upgradeToAndCall(address,bytes)", oldImplementation, initializer); - - (bool success, bytes memory returnData) = marketAddress.call(rollbackUpgradeData); - require(success, string(returnData)); - - vm.stopBroadcast(); - - // Verify the upgrade - BoundlessMarket upgradedMarket = BoundlessMarket(marketAddress); - require( - upgradedMarket.VERIFIER() == IRiscZeroVerifier(deploymentConfig.verifier), - "upgraded market verifier does not match" - ); - require( - upgradedMarket.APPLICATION_VERIFIER() == IRiscZeroVerifier(deploymentConfig.applicationVerifier), - "upgraded market application verifier does not match" - ); - (bytes32 assessorId, string memory upgradedGuestUrl) = upgradedMarket.imageInfo(); - require(assessorId == deploymentConfig.assessorImageId, "upgraded market assessor image ID does not match"); - require( - keccak256(bytes(upgradedGuestUrl)) == keccak256(bytes(deploymentConfig.assessorGuestUrl)), - "upgraded market assessor guest URL does not match" - ); - require( - upgradedMarket.COLLATERAL_TOKEN_CONTRACT() == deploymentConfig.collateralToken, - "upgraded market stake token does not match" - ); - require( - upgradedMarket.hasRole(upgradedMarket.ADMIN_ROLE(), deploymentConfig.admin), - "upgraded market admin does not match the admin" - ); - - console2.log("Upgraded BoundlessMarket admin is %s", deploymentConfig.admin); - console2.log("Upgraded BoundlessMarket proxy contract at %s", marketAddress); - console2.log("Upgraded BoundlessMarket collateral token contract at %s", deploymentConfig.collateralToken); - console2.log("Upgraded BoundlessMarket verifier contract at %s", deploymentConfig.verifier); - console2.log( - "Upgraded BoundlessMarket application verifier contract at %s", deploymentConfig.applicationVerifier - ); - console2.log("Upgraded BoundlessMarket assessor image ID %s", Strings.toHexString(uint256(assessorId), 32)); - console2.log("Upgraded BoundlessMarket assessor guest URL %s", upgradedGuestUrl); - - address currentImplementation = address(uint160(uint256(vm.load(marketAddress, IMPLEMENTATION_SLOT)))); - require( - currentImplementation == oldImplementation, - "current implementation address does not match the old implementation address" - ); - console2.log("Rollback successful. Current implementation address is now %s", currentImplementation); - - string[] memory args = new string[](4); - args[0] = "python3"; - args[1] = "contracts/update_deployment_toml.py"; - args[2] = "--boundless-market-impl"; - args[3] = Strings.toHexString(currentImplementation); - - vm.ffi(args); - } -} - -/// @notice Script for adding admin role to a new address on the BoundlessMarket contract. -/// @dev Grants ADMIN_ROLE to the address specified in ADMIN_TO_ADD environment variable -/// -/// Sample Usage: -/// export CHAIN_KEY="anvil" -/// export ADMIN_TO_ADD="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" -/// forge script contracts/scripts/Manage.s.sol:AddBoundlessMarketAdmin \ -/// --private-key \ -/// --broadcast \ -/// --rpc-url -contract AddBoundlessMarketAdmin is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address adminToAdd = vm.envAddress("ADMIN_TO_ADD"); - require(adminToAdd != address(0), "ADMIN_TO_ADD environment variable not set"); - - address marketAddress = deploymentConfig.boundlessMarket.required("boundless-market"); - BoundlessMarket market = BoundlessMarket(marketAddress); - - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - bytes32 adminRole = market.ADMIN_ROLE(); - - if (gnosisExecute) { - console2.log("GNOSIS_EXECUTE=true: Preparing grantRole calldata for Safe execution"); - console2.log("BoundlessMarket Contract: ", marketAddress); - console2.log("Admin to Add: ", adminToAdd); - console2.log("Role: ADMIN_ROLE"); - - // Print Gnosis Safe transaction info for grantRole - bytes memory grantRoleCallData = - abi.encodeWithSignature("grantRole(bytes32,address)", adminRole, adminToAdd); - console2.log("================================"); - console2.log("=== GNOSIS SAFE GRANT ROLE INFO ==="); - console2.log("Target Address (To): ", marketAddress); - console2.log("Function: grantRole(bytes32,address)"); - console2.log("Role: "); - console2.logBytes32(adminRole); - console2.log("Account: ", adminToAdd); - console2.log("Calldata:"); - console2.logBytes(grantRoleCallData); - console2.log("====================================="); - console2.log("BoundlessMarket Admin Grant Role Calldata Ready"); - console2.log("Transaction NOT executed - use Gnosis Safe to execute"); - } else { - // Get current admin with ADMIN_ROLE - use deployer as they should have admin role - address currentAdmin = getDeployer(); - require(market.hasRole(market.ADMIN_ROLE(), currentAdmin), "deployer does not have admin role"); - - vm.broadcast(currentAdmin); - market.grantRole(adminRole, adminToAdd); - - // Sanity checks - console2.log("BoundlessMarket Contract: ", marketAddress); - console2.log("New BoundlessMarket Admin: ", adminToAdd); - console2.log("ADMIN_ROLE granted: ", market.hasRole(adminRole, adminToAdd)); - console2.log("Other admin: ", currentAdmin); - console2.log("Other admin still active: ", market.hasRole(adminRole, currentAdmin)); - console2.log("================================================"); - console2.log("BoundlessMarket Admin Role Updated Successfully"); - } - - _updateDeploymentConfig("admin-2", adminToAdd); - } -} - -/// @notice Script for removing admin role from an address on the BoundlessMarket contract. -/// @dev Revokes ADMIN_ROLE from the address specified in ADMIN_TO_REMOVE environment variable -/// -/// Sample Usage: -/// export CHAIN_KEY="anvil" -/// export ADMIN_TO_REMOVE="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" -/// forge script contracts/scripts/Manage.s.sol:RemoveBoundlessMarketAdmin \ -/// --private-key \ -/// --broadcast \ -/// --rpc-url -contract RemoveBoundlessMarketAdmin is BoundlessScriptBase { - function run() external { - // Load the config - DeploymentConfig memory deploymentConfig = - ConfigLoader.loadDeploymentConfig(string.concat(vm.projectRoot(), "/", CONFIG)); - - address adminToRemove = vm.envAddress("ADMIN_TO_REMOVE"); - require(adminToRemove != address(0), "ADMIN_TO_REMOVE environment variable not set"); - - address marketAddress = deploymentConfig.boundlessMarket.required("boundless-market"); - BoundlessMarket market = BoundlessMarket(marketAddress); - - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - bytes32 adminRole = market.ADMIN_ROLE(); - - // Safety check: Ensure at least one other admin will remain - address otherAdmin = - (adminToRemove == deploymentConfig.admin) ? deploymentConfig.admin2 : deploymentConfig.admin; - - require( - otherAdmin != address(0) && market.hasRole(adminRole, otherAdmin), - "Cannot remove admin: would leave BoundlessMarket without any admins" - ); - - if (gnosisExecute) { - console2.log("GNOSIS_EXECUTE=true: Preparing revokeRole calldata for Safe execution"); - console2.log("BoundlessMarket Contract: ", marketAddress); - console2.log("Admin to Remove: ", adminToRemove); - console2.log("Role: ADMIN_ROLE"); - - // Print Gnosis Safe transaction info for revokeRole - bytes memory revokeRoleCallData = - abi.encodeWithSignature("revokeRole(bytes32,address)", adminRole, adminToRemove); - console2.log("================================"); - console2.log("=== GNOSIS SAFE REVOKE ROLE INFO ==="); - console2.log("Target Address (To): ", marketAddress); - console2.log("Function: revokeRole(bytes32,address)"); - console2.log("Role: "); - console2.logBytes32(adminRole); - console2.log("Account: ", adminToRemove); - console2.log("Calldata:"); - console2.logBytes(revokeRoleCallData); - console2.log("====================================="); - console2.log("BoundlessMarket Admin Revoke Role Calldata Ready"); - console2.log("Transaction NOT executed - use Gnosis Safe to execute"); - } else { - // Get current admin with ADMIN_ROLE - use deployer as they should have admin role - address currentAdmin = getDeployer(); - require(market.hasRole(market.ADMIN_ROLE(), currentAdmin), "deployer does not have admin role"); - - vm.broadcast(currentAdmin); - market.revokeRole(adminRole, adminToRemove); - - // Sanity checks - console2.log("BoundlessMarket Contract: ", marketAddress); - console2.log("Removed BoundlessMarket Admin: ", adminToRemove); - console2.log("Other admin: ", otherAdmin); - console2.log("Other Admin still active: ", market.hasRole(adminRole, otherAdmin)); - console2.log("ADMIN_ROLE revoked: ", !market.hasRole(adminRole, adminToRemove)); - console2.log("================================================"); - console2.log("BoundlessMarket Admin Role Removed Successfully"); - } - - _removeAdminFromToml("admin", "admin-2", adminToRemove); - } -} diff --git a/contracts/shanghai/scripts/ManageVerifier.s.sol b/contracts/shanghai/scripts/ManageVerifier.s.sol deleted file mode 100644 index cbe28bed35..0000000000 --- a/contracts/shanghai/scripts/ManageVerifier.s.sol +++ /dev/null @@ -1,1630 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.9; - -import {Script} from "forge-std/Script.sol"; -import {console2} from "forge-std/console2.sol"; -import {Strings} from "openzeppelin/contracts/utils/Strings.sol"; -import {TimelockController} from "openzeppelin/contracts/governance/TimelockController.sol"; -import {RiscZeroVerifierRouter} from "../src/verifier/RiscZeroVerifierRouter.sol"; -import {VerifierLayeredRouter} from "../src/verifier/VerifierLayeredRouter.sol"; -import {RiscZeroVerifierEmergencyStop} from "risc0/RiscZeroVerifierEmergencyStop.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {IRiscZeroSelectable} from "risc0/IRiscZeroSelectable.sol"; -import {Blake3Groth16Verifier} from "../src/blake3-groth16/Blake3Groth16Verifier.sol"; -import {ControlID} from "../src/blake3-groth16/ControlID.sol"; -import {ControlID as Groth16ControlID, RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; -import {RiscZeroSetVerifier, RiscZeroSetVerifierLib} from "risc0/RiscZeroSetVerifier.sol"; -import {ConfigLoader, Deployment, DeploymentLib, VerifierDeployment} from "../src/config/VerifierConfig.sol"; - -// Default salt used with CREATE2 for deterministic deployment addresses. -bytes32 constant CREATE2_SALT = hex"b00d1e59"; - -/// @notice Compare strings for equality. -function stringEq(string memory a, string memory b) pure returns (bool) { - return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); -} - -/// @notice Return the role code for the given named role -function timelockControllerRole(TimelockController timelockController, string memory roleStr) view returns (bytes32) { - if (stringEq(roleStr, "proposer")) { - return timelockController.PROPOSER_ROLE(); - } else if (stringEq(roleStr, "executor")) { - return timelockController.EXECUTOR_ROLE(); - } else if (stringEq(roleStr, "canceller")) { - return timelockController.CANCELLER_ROLE(); - } else { - revert(); - } -} - -/// @notice Base contract for the scripts below, providing common context and functions. -contract RiscZeroManagementScript is Script { - using DeploymentLib for Deployment; - - Deployment internal deployment; - TimelockController internal _timelockController; - VerifierLayeredRouter internal _verifierRouter; - RiscZeroVerifierRouter internal _parentRouter; - RiscZeroVerifierEmergencyStop internal _verifierEstop; - IRiscZeroVerifier internal _verifier; - - // RISC Zero stack (upstream verifier infrastructure) - TimelockController internal _risc0TimelockController; - RiscZeroVerifierRouter internal _risc0Router; - RiscZeroVerifierEmergencyStop internal _risc0VerifierEstop; - - function loadConfig() internal { - string memory configPath = - vm.envOr("DEPLOYMENT_CONFIG", string.concat(vm.projectRoot(), "/", "contracts/deployment_verifier.toml")); - console2.log("Loading deployment config from %s", configPath); - ConfigLoader.loadDeploymentConfig(configPath).copyTo(deployment); - - // Wrap the control addresses with their respective contract implementations. - // NOTE: These addresses may be zero, so this does not guarantee contracts are deployed. - _timelockController = TimelockController(payable(deployment.timelockController)); - _verifierRouter = VerifierLayeredRouter(deployment.router); - _parentRouter = RiscZeroVerifierRouter(deployment.parentRouter); - _risc0TimelockController = TimelockController(payable(deployment.risc0TimelockController)); - _risc0Router = RiscZeroVerifierRouter(deployment.risc0Router); - } - - modifier withConfig() { - loadConfig(); - _; - } - - /// @notice Returns the address of the deployer, set in the DEPLOYER_ADDRESS env var. - function deployerAddress() internal returns (address) { - address deployer = vm.envAddress("DEPLOYER_ADDRESS"); - uint256 deployerKey = vm.envOr("DEPLOYER_PRIVATE_KEY", uint256(0)); - if (deployerKey != 0) { - require(vm.addr(deployerKey) == deployer, "DEPLOYER_ADDRESS and DEPLOYER_PRIVATE_KEY are inconsistent"); - vm.rememberKey(deployerKey); - } - return deployer; - } - - /// @notice Returns the address of the contract admin, set in the ADMIN_ADDRESS env var. - /// @dev This admin address will be set as the owner of the estop contracts, and the proposer - /// of for the timelock controller. Note that it is not the "admin" on the timelock. - function adminAddress() internal view returns (address) { - return vm.envOr("ADMIN_ADDRESS", deployment.admin); - } - - /// @notice Returns the timelock-delay, set in the MIN_DELAY env var. - function timelockDelay() internal view returns (uint256) { - return vm.envOr("MIN_DELAY", deployment.timelockDelay); - } - - /// @notice Determines the contract address of TimelockController from the environment. - /// @dev Uses the TIMELOCK_CONTROLLER environment variable. - function timelockController() internal returns (TimelockController) { - if (address(_timelockController) != address(0)) { - return _timelockController; - } - _timelockController = TimelockController(payable(vm.envAddress("TIMELOCK_CONTROLLER"))); - console2.log("Using TimelockController at address", address(_timelockController)); - return _timelockController; - } - - /// @notice Determines the contract address of VerifierLayeredRouter from the environment. - /// @dev Uses the VERIFIER_ROUTER environment variable. - function verifierRouter() internal returns (VerifierLayeredRouter) { - if (address(_verifierRouter) != address(0)) { - return _verifierRouter; - } - _verifierRouter = VerifierLayeredRouter(vm.envAddress("VERIFIER_ROUTER")); - console2.log("Using VerifierLayeredRouter at address", address(_verifierRouter)); - return _verifierRouter; - } - - function parentRouter() internal returns (RiscZeroVerifierRouter) { - if (address(_parentRouter) != address(0)) { - return _parentRouter; - } - _parentRouter = RiscZeroVerifierRouter(vm.envAddress("PARENT_VERIFIER_ROUTER")); - console2.log("Using Parent RiscZeroVerifierRouter at address", address(_parentRouter)); - return _parentRouter; - } - - /// @notice Determines the contract address of RiscZeroVerifierRouter from the environment. - /// @dev Uses the VERIFIER_ESTOP environment variable. - function verifierEstop() internal returns (RiscZeroVerifierEmergencyStop) { - if (address(_verifierEstop) != address(0)) { - return _verifierEstop; - } - // Use the address set in the VERIFIER_ESTOP environment variable if it is set. - _verifierEstop = RiscZeroVerifierEmergencyStop(vm.envOr("VERIFIER_ESTOP", address(0))); - if (address(_verifierEstop) != address(0)) { - console2.log("Using RiscZeroVerifierEmergencyStop at address", address(_verifierEstop)); - return _verifierEstop; - } - bytes4 selector = bytes4(vm.envBytes("VERIFIER_SELECTOR")); - for (uint256 i = 0; i < deployment.verifiers.length; i++) { - if (deployment.verifiers[i].selector == selector) { - _verifierEstop = RiscZeroVerifierEmergencyStop(deployment.verifiers[i].estop); - break; - } - } - console2.log( - "Using RiscZeroVerifierEmergencyStop at address %s and selector %x", - address(_verifierEstop), - uint256(bytes32(selector)) - ); - return _verifierEstop; - } - - /// @notice Determines the contract address of IRiscZeroVerifier from the environment. - /// @dev Uses the VERIFIER_ESTOP environment variable, and gets the proxied verifier. - function verifier() internal returns (IRiscZeroVerifier) { - if (address(_verifier) != address(0)) { - return _verifier; - } - _verifier = verifierEstop().verifier(); - console2.log("Using IRiscZeroVerifier at address", address(_verifier)); - return _verifier; - } - - /// @notice Determines the contract address of IRiscZeroSelectable from the environment. - /// @dev Uses the VERIFIER_ESTOP environment variable, and gets the proxied selectable. - function selectable() internal returns (IRiscZeroSelectable) { - return IRiscZeroSelectable(address(verifier())); - } - - /// @notice Simulates a call to check if it will succeed, given the current EVM state. - function simulate(address dest, bytes memory data) internal { - console2.log("Simulating call to", dest); - console2.logBytes(data); - uint256 snapshot = vm.snapshot(); - vm.prank(address(timelockController())); - (bool success,) = dest.call(data); - require(success, "simulation of transaction to schedule failed"); - vm.revertTo(snapshot); - console2.log("Simulation successful"); - } - - /// @notice Returns the RISC Zero stack TimelockController. - function risc0TimelockController() internal returns (TimelockController) { - if (address(_risc0TimelockController) != address(0)) { - return _risc0TimelockController; - } - _risc0TimelockController = TimelockController(payable(vm.envAddress("RISC0_TIMELOCK_CONTROLLER"))); - console2.log("Using RISC Zero TimelockController at address", address(_risc0TimelockController)); - return _risc0TimelockController; - } - - /// @notice Returns the RISC Zero stack RiscZeroVerifierRouter (= parentRouter). - function risc0Router() internal returns (RiscZeroVerifierRouter) { - if (address(_risc0Router) != address(0)) { - return _risc0Router; - } - _risc0Router = RiscZeroVerifierRouter(vm.envAddress("RISC0_ROUTER")); - console2.log("Using RISC Zero RiscZeroVerifierRouter at address", address(_risc0Router)); - return _risc0Router; - } - - /// @notice Returns a verifier estop from the risc0Verifiers config by VERIFIER_SELECTOR. - function risc0VerifierEstop() internal returns (RiscZeroVerifierEmergencyStop) { - if (address(_risc0VerifierEstop) != address(0)) { - return _risc0VerifierEstop; - } - // Use the address set in the RISC0_VERIFIER_ESTOP environment variable if it is set. - _risc0VerifierEstop = RiscZeroVerifierEmergencyStop(vm.envOr("RISC0_VERIFIER_ESTOP", address(0))); - if (address(_risc0VerifierEstop) != address(0)) { - console2.log("Using RISC Zero RiscZeroVerifierEmergencyStop at address", address(_risc0VerifierEstop)); - return _risc0VerifierEstop; - } - bytes4 selector = bytes4(vm.envBytes("VERIFIER_SELECTOR")); - for (uint256 i = 0; i < deployment.risc0Verifiers.length; i++) { - if (deployment.risc0Verifiers[i].selector == selector) { - _risc0VerifierEstop = RiscZeroVerifierEmergencyStop(deployment.risc0Verifiers[i].estop); - break; - } - } - console2.log( - "Using RISC Zero RiscZeroVerifierEmergencyStop at address %s and selector %x", - address(_risc0VerifierEstop), - uint256(bytes32(selector)) - ); - return _risc0VerifierEstop; - } - - /// @notice Returns the timelock-delay for the RISC Zero stack, set in the RISC0_MIN_DELAY env var. - function risc0TimelockDelay() internal view returns (uint256) { - return vm.envOr("RISC0_MIN_DELAY", deployment.risc0TimelockDelay); - } - - /// @notice Simulates a call as the RISC Zero timelock to check if it will succeed. - function risc0Simulate(address dest, bytes memory data) internal { - console2.log("Simulating call to", dest); - console2.logBytes(data); - uint256 snapshot = vm.snapshot(); - vm.prank(address(risc0TimelockController())); - (bool success,) = dest.call(data); - require(success, "simulation of transaction to schedule failed"); - vm.revertTo(snapshot); - console2.log("Simulation successful"); - } -} - -/// @notice Deployment script for the timelocked router. -/// @dev Use the following environment variable to control the deployment: -/// * MIN_DELAY minimum delay in seconds for operations -/// * PROPOSER address of proposer -/// * EXECUTOR address of executor -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract DeployTimelockRouter is RiscZeroManagementScript { - function run() external withConfig { - // initial minimum delay in seconds for operations - uint256 minDelay = timelockDelay(); - console2.log("minDelay:", minDelay); - - // accounts to be granted proposer and canceller roles - address[] memory proposers = new address[](1); - proposers[0] = vm.envOr("PROPOSER", adminAddress()); - console2.log("proposers:", proposers[0]); - - // accounts to be granted executor role - address[] memory executors = new address[](1); - executors[0] = vm.envOr("EXECUTOR", adminAddress()); - console2.log("executors:", executors[0]); - - // NOTE: This functionality is unused in our process. The admin is not subject to the timelock - // delay, which is useful e.g. for initial setup, but should not be used in production. - // - // optional account to be granted admin role; disable with zero address - // When the admin is unset, the contract is self-administered. - //address admin = vm.envOr("ADMIN", address(0)); - //console2.log("admin:", admin); - - // Deploy new contracts - vm.broadcast(deployerAddress()); - _timelockController = new TimelockController{salt: CREATE2_SALT}(minDelay, proposers, executors, address(0)); - console2.log("Deployed TimelockController to", address(timelockController())); - - vm.broadcast(deployerAddress()); - _verifierRouter = new VerifierLayeredRouter{salt: CREATE2_SALT}(address(timelockController()), parentRouter()); - console2.log("Deployed VerifierLayeredRouter to", address(verifierRouter())); - } -} - -/// @notice Deployment script for the RISC Zero verifier with Emergency Stop mechanism. -/// @dev Use the following environment variable to control the deployment: -/// * CHAIN_KEY key of the target chain -/// * VERIFIER_ESTOP_OWNER owner of the emergency stop contract -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract DeployEstopBlake3Groth16Verifier is RiscZeroManagementScript { - function run() external withConfig { - string memory chainKey = vm.envString("CHAIN_KEY"); - console2.log("chainKey:", chainKey); - address verifierEstopOwner = vm.envOr("VERIFIER_ESTOP_OWNER", adminAddress()); - console2.log("verifierEstopOwner:", verifierEstopOwner); - - // Deploy new contracts - vm.broadcast(deployerAddress()); - Blake3Groth16Verifier blake3Groth16Verifier = - new Blake3Groth16Verifier{salt: CREATE2_SALT}(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); - _verifier = blake3Groth16Verifier; - - vm.broadcast(deployerAddress()); - _verifierEstop = - new RiscZeroVerifierEmergencyStop{salt: CREATE2_SALT}(blake3Groth16Verifier, verifierEstopOwner); - - // Print in TOML format - console2.log(""); - console2.log("[[chains.%s.verifiers]]", chainKey); - console2.log("name = \"Blake3Groth16Verifier\""); - console2.log("version = \"%s\"", blake3Groth16Verifier.VERSION()); - console2.log("selector = \"%s\"", Strings.toHexString(uint256(uint32(blake3Groth16Verifier.SELECTOR())), 4)); - console2.log("verifier = \"%s\"", address(verifier())); - console2.log("estop = \"%s\"", address(verifierEstop())); - console2.log("unroutable = true # remove when added to the router"); - } -} - -/// @notice Schedule addition of verifier to router. -/// @dev Use the following environment variable to control the deployment: -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// * VERIFIER_ROUTER contract address of RiscZeroVerifierRouter -/// * VERIFIER_ESTOP contract address of RiscZeroVerifierEmergencyStop -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleAddVerifier is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - // Schedule the 'addVerifier()' request - bytes4 selector = selectable().SELECTOR(); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", timelockController().getMinDelay()); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory data = abi.encodeCall(verifierRouter().addVerifier, (selector, verifierEstop())); - address dest = address(verifierRouter()); - simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), dest, selector, data, scheduleDelay); - return; - } - vm.broadcast(adminAddress()); - timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - /// @param timelockAddress The timelock controller address (target for Gnosis Safe) - /// @param dest The destination address for the scheduled operation - /// @param selector The verifier selector being added - /// @param data The calldata for the scheduled operation - /// @param scheduleDelay The minimum delay in seconds for the scheduled action - function _printGnosisSafeInfo( - address timelockAddress, - address dest, - bytes4 selector, - bytes memory data, - uint256 scheduleDelay - ) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE ADD VERIFIER INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Verifier Router Address (dest): ", dest); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", dest, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish addition of verifier to router. -/// @dev Use the following environment variable to control the deployment: -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// * VERIFIER_ROUTER contract address of RiscZeroVerifierRouter -/// * VERIFIER_ESTOP contract address of RiscZeroVerifierEmergencyStop -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishAddVerifier is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - // Execute the 'addVerifier()' request - bytes4 selector = selectable().SELECTOR(); - console2.log("selector:"); - console2.logBytes4(selector); - - bytes memory data = abi.encodeCall(verifierRouter().addVerifier, (selector, verifierEstop())); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), address(verifierRouter()), selector, data); - return; - } - - vm.broadcast(adminAddress()); - timelockController().execute(address(verifierRouter()), 0, data, 0, 0); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - /// @param timelockAddress The timelock controller address (target for Gnosis Safe) - /// @param dest The destination address for the scheduled operation - /// @param selector The verifier selector being added - /// @param data The calldata for the scheduled operation - function _printGnosisSafeInfo(address timelockAddress, address dest, bytes4 selector, bytes memory data) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE ADD VERIFIER INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Verifier Router Address (dest): ", dest); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", dest, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Schedule removal of a verifier from the router. -/// @dev Use the following environment variable to control the deployment: -/// * VERIFIER_SELECTOR the selector associated with this verifier -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// * VERIFIER_ROUTER contract address of RiscZeroVerifierRouter -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleRemoveVerifier is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - bytes4 selector = bytes4(vm.envBytes("VERIFIER_SELECTOR")); - console2.log("selector:"); - console2.logBytes4(selector); - - // Schedule the 'removeVerifier()' request - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", timelockController().getMinDelay()); - console2.log("scheduleDelay:", scheduleDelay); - - bytes memory data = abi.encodeCall(verifierRouter().removeVerifier, selector); - address dest = address(verifierRouter()); - simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), dest, selector, data, scheduleDelay); - return; - } - - vm.broadcast(adminAddress()); - timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - /// @param timelockAddress The timelock controller address (target for Gnosis Safe) - /// @param dest The destination address for the scheduled operation - /// @param selector The verifier selector being removed - /// @param data The calldata for the scheduled operation - /// @param scheduleDelay The minimum delay in seconds for the scheduled action - function _printGnosisSafeInfo( - address timelockAddress, - address dest, - bytes4 selector, - bytes memory data, - uint256 scheduleDelay - ) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE REMOVE VERIFIER INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Verifier Router Address (dest): ", dest); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", dest, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish removal of a verifier from the router. -/// @dev Use the following environment variable to control the deployment: -/// * VERIFIER_SELECTOR the selector associated with this verifier -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// * VERIFIER_ROUTER contract address of RiscZeroVerifierRouter -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishRemoveVerifier is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - bytes4 selector = bytes4(vm.envBytes("VERIFIER_SELECTOR")); - console2.log("selector:"); - console2.logBytes4(selector); - - // Execute the 'removeVerifier()' request - bytes memory data = abi.encodeCall(verifierRouter().removeVerifier, selector); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), address(verifierRouter()), selector, data); - return; - } - - vm.broadcast(adminAddress()); - timelockController().execute(address(verifierRouter()), 0, data, 0, 0); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - /// @param timelockAddress The timelock controller address (target for Gnosis Safe) - /// @param dest The destination address for the scheduled operation - /// @param selector The verifier selector being removed - /// @param data The calldata for the scheduled operation - function _printGnosisSafeInfo(address timelockAddress, address dest, bytes4 selector, bytes memory data) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE REMOVE VERIFIER INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Verifier Router Address (dest): ", dest); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", dest, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Schedule an update of the minimum timelock delay. -/// @dev Use the following environment variable to control the deployment: -/// * MIN_DELAY minimum delay in seconds for operations -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleUpdateDelay is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - uint256 minDelay = vm.envUint("MIN_DELAY"); - console2.log("minDelay:", minDelay); - - // Schedule the 'updateDelay()' request - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", timelockController().getMinDelay()); - console2.log("scheduleDelay:", scheduleDelay); - - bytes memory data = abi.encodeCall(timelockController().updateDelay, minDelay); - address dest = address(timelockController()); - simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), minDelay, data, scheduleDelay); - return; - } - - vm.broadcast(adminAddress()); - timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - /// @param timelockAddress The timelock controller address (target for Gnosis Safe) - /// @param minDelay The new minimum delay - /// @param data The calldata for the scheduled operation - /// @param scheduleDelay The minimum delay in seconds for the scheduled action - function _printGnosisSafeInfo(address timelockAddress, uint256 minDelay, bytes memory data, uint256 scheduleDelay) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE MIN DELAY INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("New min delay: ", minDelay); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", timelockAddress, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish an update of the minimum timelock delay. -/// @dev Use the following environment variable to control the deployment: -/// * MIN_DELAY minimum delay in seconds for operations -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishUpdateDelay is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - uint256 minDelay = vm.envUint("MIN_DELAY"); - console2.log("minDelay:", minDelay); - - // Execute the 'updateDelay()' request - bytes memory data = abi.encodeCall(timelockController().updateDelay, minDelay); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), minDelay, data); - return; - } - - vm.broadcast(adminAddress()); - timelockController().execute(address(timelockController()), 0, data, 0, 0); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - /// @param timelockAddress The timelock controller address (target for Gnosis Safe) - /// @param minDelay The new minimum delay - /// @param data The calldata for the scheduled operation - function _printGnosisSafeInfo(address timelockAddress, uint256 minDelay, bytes memory data) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE MIN DELAY INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("New min delay: ", minDelay); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", timelockAddress, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -// TODO: Add this command to the README.md -/// @notice Cancel a pending operation on the timelock controller -/// @dev Use the following environment variable to control the script: -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// * OPERATION_ID identifier for the operation to cancel -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract CancelOperation is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - bytes32 operationId = vm.envBytes32("OPERATION_ID"); - console2.log("operationId:", uint256(operationId)); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), operationId); - return; - } - - // Execute the 'cancel()' request - vm.broadcast(adminAddress()); - timelockController().cancel(operationId); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo(address timelockAddress, bytes32 operationId) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE CANCEL OPERATION INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Operation ID: ", uint256(operationId)); - - bytes memory callData = abi.encodeWithSignature("cancel(bytes32)", operationId); - console2.log("Function: cancel(bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Schedule grant role. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be granted -/// * ACCOUNT the account to be granted the role -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleGrantRole is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Schedule the 'grantRole()' request - bytes32 role = timelockControllerRole(timelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", timelockController().getMinDelay()); - console2.log("scheduleDelay:", scheduleDelay); - - bytes memory data = abi.encodeCall(timelockController().grantRole, (role, account)); - address dest = address(timelockController()); - simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), role, account, data, scheduleDelay); - return; - } - - vm.broadcast(adminAddress()); - timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo( - address timelockAddress, - bytes32 role, - address account, - bytes memory data, - uint256 scheduleDelay - ) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE GRANT ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", timelockAddress, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish grant role. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be granted -/// * ACCOUNT the account to be granted the role -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishGrantRole is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Execute the 'grantRole()' request - bytes32 role = timelockControllerRole(timelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - bytes memory data = abi.encodeCall(timelockController().grantRole, (role, account)); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), role, account, data); - return; - } - - vm.broadcast(adminAddress()); - timelockController().execute(address(timelockController()), 0, data, 0, 0); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo(address timelockAddress, bytes32 role, address account, bytes memory data) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE GRANT ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", timelockAddress, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Schedule revoke role. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be revoked -/// * ACCOUNT the account to be revoked of the role -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleRevokeRole is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Schedule the 'grantRole()' request - bytes32 role = timelockControllerRole(timelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", timelockController().getMinDelay()); - console2.log("scheduleDelay:", scheduleDelay); - - bytes memory data = abi.encodeCall(timelockController().revokeRole, (role, account)); - address dest = address(timelockController()); - simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), role, account, data, scheduleDelay); - return; - } - - vm.broadcast(adminAddress()); - timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo( - address timelockAddress, - bytes32 role, - address account, - bytes memory data, - uint256 scheduleDelay - ) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE REVOKE ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", timelockAddress, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish revoke role. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be revoked -/// * ACCOUNT the account to be revoked of the role -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishRevokeRole is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Execute the 'grantRole()' request - bytes32 role = timelockControllerRole(timelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - bytes memory data = abi.encodeCall(timelockController().revokeRole, (role, account)); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(timelockController()), role, account, data); - return; - } - - vm.broadcast(adminAddress()); - timelockController().execute(address(timelockController()), 0, data, 0, 0); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo(address timelockAddress, bytes32 role, address account, bytes memory data) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE REVOKE ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", timelockAddress, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Renounce role. -/// @dev Use the following environment variable to control the deployment: -/// * RENOUNCE_ADDRESS the address to send the renounce transaction -/// * RENOUNCE_ROLE the role to be renounced -/// * TIMELOCK_CONTROLLER contract address of TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract RenounceRole is RiscZeroManagementScript { - function run() external withConfig { - address renouncer = vm.envAddress("RENOUNCE_ADDRESS"); - string memory roleStr = vm.envString("RENOUNCE_ROLE"); - console2.log("renouncer:", renouncer); - console2.log("roleStr:", roleStr); - - console2.log("msg.sender:", msg.sender); - - // Renounce the role - bytes32 role = timelockControllerRole(timelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - vm.broadcast(renouncer); - timelockController().renounceRole(role, msg.sender); - } -} - -/// @notice Schedule grant role on the RISC Zero timelock controller. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be granted -/// * ACCOUNT the account to be granted the role -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * RISC0_TIMELOCK_CONTROLLER contract address of RISC Zero TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleGrantRisc0Role is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Schedule the 'grantRole()' request - bytes32 role = timelockControllerRole(risc0TimelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", risc0TimelockController().getMinDelay()); - console2.log("scheduleDelay:", scheduleDelay); - - bytes memory data = abi.encodeCall(risc0TimelockController().grantRole, (role, account)); - address dest = address(risc0TimelockController()); - risc0Simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), role, account, data, scheduleDelay); - return; - } - - vm.broadcast(adminAddress()); - risc0TimelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo( - address timelockAddress, - bytes32 role, - address account, - bytes memory data, - uint256 scheduleDelay - ) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE GRANT RISC0 ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", timelockAddress, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish grant role on the RISC Zero timelock controller. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be granted -/// * ACCOUNT the account to be granted the role -/// * RISC0_TIMELOCK_CONTROLLER contract address of RISC Zero TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishGrantRisc0Role is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Execute the 'grantRole()' request - bytes32 role = timelockControllerRole(risc0TimelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - bytes memory data = abi.encodeCall(risc0TimelockController().grantRole, (role, account)); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), role, account, data); - return; - } - - vm.broadcast(adminAddress()); - risc0TimelockController().execute(address(risc0TimelockController()), 0, data, 0, 0); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo(address timelockAddress, bytes32 role, address account, bytes memory data) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE GRANT RISC0 ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", timelockAddress, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Schedule revoke role on the RISC Zero timelock controller. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be revoked -/// * ACCOUNT the account to be revoked of the role -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * RISC0_TIMELOCK_CONTROLLER contract address of RISC Zero TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleRevokeRisc0Role is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Schedule the 'revokeRole()' request - bytes32 role = timelockControllerRole(risc0TimelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", risc0TimelockController().getMinDelay()); - console2.log("scheduleDelay:", scheduleDelay); - - bytes memory data = abi.encodeCall(risc0TimelockController().revokeRole, (role, account)); - address dest = address(risc0TimelockController()); - risc0Simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), role, account, data, scheduleDelay); - return; - } - - vm.broadcast(adminAddress()); - risc0TimelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo( - address timelockAddress, - bytes32 role, - address account, - bytes memory data, - uint256 scheduleDelay - ) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE REVOKE RISC0 ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", timelockAddress, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish revoke role on the RISC Zero timelock controller. -/// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be revoked -/// * ACCOUNT the account to be revoked of the role -/// * RISC0_TIMELOCK_CONTROLLER contract address of RISC Zero TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishRevokeRisc0Role is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - string memory roleStr = vm.envString("ROLE"); - console2.log("roleStr:", roleStr); - - address account = vm.envAddress("ACCOUNT"); - console2.log("account:", account); - - // Execute the 'revokeRole()' request - bytes32 role = timelockControllerRole(risc0TimelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - bytes memory data = abi.encodeCall(risc0TimelockController().revokeRole, (role, account)); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), role, account, data); - return; - } - - vm.broadcast(adminAddress()); - risc0TimelockController().execute(address(risc0TimelockController()), 0, data, 0, 0); - } - - /// @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo(address timelockAddress, bytes32 role, address account, bytes memory data) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE REVOKE RISC0 ROLE INFO ==="); - console2.log("Target Timelock Controller Address (To): ", timelockAddress); - console2.log("Role: ", uint256(role)); - console2.log("Account: ", account); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", timelockAddress, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Renounce role on the RISC Zero timelock controller. -/// @dev Use the following environment variable to control the deployment: -/// * RENOUNCE_ADDRESS the address to send the renounce transaction -/// * RENOUNCE_ROLE the role to be renounced -/// * RISC0_TIMELOCK_CONTROLLER contract address of RISC Zero TimelockController -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract RenounceRisc0Role is RiscZeroManagementScript { - function run() external withConfig { - address renouncer = vm.envAddress("RENOUNCE_ADDRESS"); - string memory roleStr = vm.envString("RENOUNCE_ROLE"); - console2.log("renouncer:", renouncer); - console2.log("roleStr:", roleStr); - - console2.log("msg.sender:", msg.sender); - - // Renounce the role - bytes32 role = timelockControllerRole(risc0TimelockController(), roleStr); - console2.log("role: "); - console2.logBytes32(role); - - vm.broadcast(renouncer); - risc0TimelockController().renounceRole(role, msg.sender); - } -} - -/// @notice Activate an Emergency Stop mechanism. -/// @dev Use the following environment variable to control the deployment: -/// * VERIFIER_ESTOP contract address of RiscZeroVerifierEmergencyStop -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ActivateEstop is RiscZeroManagementScript { - function run() external withConfig { - // Check for deployment mode flags - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - // Locate contracts - console2.log("Using RiscZeroVerifierEmergencyStop at address", address(verifierEstop())); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(verifierEstop())); - return; - } - // Activate the emergency stop - vm.broadcast(adminAddress()); - verifierEstop().estop(); - require(verifierEstop().paused(), "verifier is not stopped after calling estop"); - } - - // @notice Print Gnosis Safe transaction information for manual submissions - function _printGnosisSafeInfo(address estopAddress) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE ACTIVATE EMERGENCY STOP INFO ==="); - console2.log("RiscZeroVerifierEmergencyStop Address (To): ", estopAddress); - bytes memory callData = abi.encodeWithSignature("estop()"); - console2.log("Function: estop()"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -// ============================================================================ -// RISC Zero Stack Deployment Scripts -// ============================================================================ -// These scripts deploy the upstream RISC Zero verifier infrastructure -// (TimelockController + RiscZeroVerifierRouter + Groth16Verifier + SetVerifier) -// on chains where RISC Zero hasn't deployed their stack yet. - -/// @notice Deploy the RISC Zero TimelockController and RiscZeroVerifierRouter. -/// @dev Use the following environment variables: -/// * MIN_DELAY (optional) minimum delay in seconds for operations (defaults to risc0-timelock-delay) -/// * PROPOSER (optional) address of proposer (defaults to ADMIN_ADDRESS) -/// * EXECUTOR (optional) address of executor (defaults to ADMIN_ADDRESS) -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract DeployRisc0TimelockRouter is RiscZeroManagementScript { - function run() external withConfig { - // initial minimum delay in seconds for operations - uint256 minDelay = risc0TimelockDelay(); - console2.log("minDelay:", minDelay); - - // accounts to be granted proposer and canceller roles - address[] memory proposers = new address[](1); - proposers[0] = vm.envOr("PROPOSER", adminAddress()); - console2.log("proposers:", proposers[0]); - - // accounts to be granted executor role - address[] memory executors = new address[](1); - executors[0] = vm.envOr("EXECUTOR", adminAddress()); - console2.log("executors:", executors[0]); - - // Deploy new contracts - vm.broadcast(deployerAddress()); - _risc0TimelockController = - new TimelockController{salt: CREATE2_SALT}(minDelay, proposers, executors, address(0)); - console2.log("Deployed RISC Zero TimelockController to", address(risc0TimelockController())); - - vm.broadcast(deployerAddress()); - _risc0Router = new RiscZeroVerifierRouter{salt: CREATE2_SALT}(address(risc0TimelockController())); - console2.log("Deployed RiscZeroVerifierRouter to", address(risc0Router())); - - // Print TOML snippet - string memory chainKey = vm.envString("CHAIN_KEY"); - console2.log(""); - console2.log("# Add to [chains.%s] in deployment_verifier.toml:", chainKey); - console2.log("risc0-router = \"%s\"", address(risc0Router())); - console2.log("risc0-timelock-controller = \"%s\"", address(risc0TimelockController())); - console2.log("risc0-timelock-delay = %d", minDelay); - console2.log("parent-router = \"%s\"", address(risc0Router())); - } -} - -/// @notice Deploy the RiscZeroGroth16Verifier with Emergency Stop mechanism. -/// @dev Use the following environment variables: -/// * CHAIN_KEY key of the target chain -/// * VERIFIER_ESTOP_OWNER (optional) owner of the emergency stop contract (defaults to ADMIN_ADDRESS) -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract DeployEstopGroth16Verifier is RiscZeroManagementScript { - function run() external withConfig { - string memory chainKey = vm.envString("CHAIN_KEY"); - console2.log("chainKey:", chainKey); - address verifierEstopOwner = vm.envOr("VERIFIER_ESTOP_OWNER", adminAddress()); - console2.log("verifierEstopOwner:", verifierEstopOwner); - - // Deploy new contracts - vm.broadcast(deployerAddress()); - RiscZeroGroth16Verifier groth16Verifier = new RiscZeroGroth16Verifier{salt: CREATE2_SALT}( - Groth16ControlID.CONTROL_ROOT, Groth16ControlID.BN254_CONTROL_ID - ); - - vm.broadcast(deployerAddress()); - RiscZeroVerifierEmergencyStop estop = - new RiscZeroVerifierEmergencyStop{salt: CREATE2_SALT}(groth16Verifier, verifierEstopOwner); - - // Print in TOML format - console2.log(""); - console2.log("[[chains.%s.risc0-verifiers]]", chainKey); - console2.log("name = \"RiscZeroGroth16Verifier\""); - console2.log("version = \"%s\"", groth16Verifier.VERSION()); - console2.log("selector = \"%s\"", Strings.toHexString(uint256(uint32(groth16Verifier.SELECTOR())), 4)); - console2.log("verifier = \"%s\"", address(groth16Verifier)); - console2.log("estop = \"%s\"", address(estop)); - console2.log("unroutable = true # remove when added to the router"); - } -} - -/// @notice Deploy the RiscZeroSetVerifier with Emergency Stop mechanism. -/// @dev Use the following environment variables: -/// * CHAIN_KEY key of the target chain -/// * VERIFIER_ESTOP_OWNER (optional) owner of the emergency stop contract (defaults to ADMIN_ADDRESS) -/// * SET_BUILDER_IMAGE_ID image ID of the SetBuilder guest -/// * SET_BUILDER_GUEST_URL URL of the SetBuilder guest -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract DeployEstopSetVerifier is RiscZeroManagementScript { - function run() external withConfig { - string memory chainKey = vm.envString("CHAIN_KEY"); - console2.log("chainKey:", chainKey); - address verifierEstopOwner = vm.envOr("VERIFIER_ESTOP_OWNER", adminAddress()); - console2.log("verifierEstopOwner:", verifierEstopOwner); - - bytes32 SET_BUILDER_IMAGE_ID = vm.envBytes32("SET_BUILDER_IMAGE_ID"); - console2.log("SET_BUILDER_IMAGE_ID:", Strings.toHexString(uint256(SET_BUILDER_IMAGE_ID))); - string memory SET_BUILDER_GUEST_URL = vm.envString("SET_BUILDER_GUEST_URL"); - console2.log("SET_BUILDER_GUEST_URL:", SET_BUILDER_GUEST_URL); - - // Deploy new contracts - vm.broadcast(deployerAddress()); - RiscZeroSetVerifier setVerifier = - new RiscZeroSetVerifier{salt: CREATE2_SALT}(risc0Router(), SET_BUILDER_IMAGE_ID, SET_BUILDER_GUEST_URL); - - vm.broadcast(deployerAddress()); - RiscZeroVerifierEmergencyStop estop = - new RiscZeroVerifierEmergencyStop{salt: CREATE2_SALT}(setVerifier, verifierEstopOwner); - - // Print in TOML format - console2.log(""); - console2.log("[[chains.%s.risc0-verifiers]]", chainKey); - console2.log("name = \"RiscZeroSetVerifier\""); - console2.log("version = \"%s\"", setVerifier.VERSION()); - console2.log("selector = \"%s\"", Strings.toHexString(uint256(uint32(setVerifier.SELECTOR())), 4)); - console2.log("verifier = \"%s\"", address(setVerifier)); - console2.log("estop = \"%s\"", address(estop)); - console2.log("unroutable = true # remove when added to the router"); - } -} - -/// @notice Schedule addition of a verifier to the RISC Zero router. -/// @dev Use the following environment variables: -/// * VERIFIER_SELECTOR the selector of the verifier to add -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * GNOSIS_EXECUTE (optional) if true, print Gnosis Safe calldata instead of broadcasting -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleAddVerifierToRisc0Router is RiscZeroManagementScript { - function run() external withConfig { - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - - RiscZeroVerifierEmergencyStop estop = risc0VerifierEstop(); - IRiscZeroSelectable selectableVerifier = IRiscZeroSelectable(address(estop.verifier())); - bytes4 selector = selectableVerifier.SELECTOR(); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", risc0TimelockController().getMinDelay()); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory data = abi.encodeCall(risc0Router().addVerifier, (selector, estop)); - address dest = address(risc0Router()); - risc0Simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), dest, selector, data, scheduleDelay); - return; - } - vm.broadcast(adminAddress()); - risc0TimelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - function _printGnosisSafeInfo( - address timelockAddress, - address dest, - bytes4 selector, - bytes memory data, - uint256 scheduleDelay - ) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE ADD VERIFIER TO RISC0 ROUTER INFO ==="); - console2.log("Target RISC Zero Timelock Controller Address (To): ", timelockAddress); - console2.log("RISC Zero Verifier Router Address (dest): ", dest); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", dest, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish addition of a verifier to the RISC Zero router. -/// @dev Use the following environment variables: -/// * VERIFIER_SELECTOR the selector of the verifier to add -/// * GNOSIS_EXECUTE (optional) if true, print Gnosis Safe calldata instead of broadcasting -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishAddVerifierToRisc0Router is RiscZeroManagementScript { - function run() external withConfig { - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - - RiscZeroVerifierEmergencyStop estop = risc0VerifierEstop(); - IRiscZeroSelectable selectableVerifier = IRiscZeroSelectable(address(estop.verifier())); - bytes4 selector = selectableVerifier.SELECTOR(); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - - bytes memory data = abi.encodeCall(risc0Router().addVerifier, (selector, estop)); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), address(risc0Router()), selector, data); - return; - } - - vm.broadcast(adminAddress()); - risc0TimelockController().execute(address(risc0Router()), 0, data, 0, 0); - } - - function _printGnosisSafeInfo(address timelockAddress, address dest, bytes4 selector, bytes memory data) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE ADD VERIFIER TO RISC0 ROUTER INFO ==="); - console2.log("Target RISC Zero Timelock Controller Address (To): ", timelockAddress); - console2.log("RISC Zero Verifier Router Address (dest): ", dest); - console2.log("Selector: ", Strings.toHexString(uint256(uint32(selector)))); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", dest, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Schedule updating the RISC Zero timelock delay. -/// @dev Use the following environment variables: -/// * RISC0_MIN_DELAY new minimum delay in seconds for the RISC Zero timelock -/// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action -/// * GNOSIS_EXECUTE (optional) if true, print Gnosis Safe calldata instead of broadcasting -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract ScheduleUpdateRisc0TimelockDelay is RiscZeroManagementScript { - function run() external withConfig { - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - uint256 minDelay = vm.envUint("RISC0_MIN_DELAY"); - console2.log("minDelay:", minDelay); - - uint256 scheduleDelay = vm.envOr("SCHEDULE_DELAY", risc0TimelockController().getMinDelay()); - console2.log("scheduleDelay:", scheduleDelay); - - bytes memory data = abi.encodeCall(risc0TimelockController().updateDelay, minDelay); - address dest = address(risc0TimelockController()); - risc0Simulate(dest, data); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), minDelay, data, scheduleDelay); - return; - } - - vm.broadcast(adminAddress()); - risc0TimelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); - } - - function _printGnosisSafeInfo(address timelockAddress, uint256 minDelay, bytes memory data, uint256 scheduleDelay) - internal - pure - { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE SCHEDULE RISC0 TIMELOCK DELAY INFO ==="); - console2.log("Target RISC Zero Timelock Controller Address (To): ", timelockAddress); - console2.log("New min delay: ", minDelay); - console2.log("scheduleDelay: ", scheduleDelay); - - bytes memory callData = abi.encodeWithSignature( - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", timelockAddress, 0, data, 0, 0, scheduleDelay - ); - console2.log("Function: schedule(address,uint256,bytes,bytes32,bytes32,uint256)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} - -/// @notice Finish updating the RISC Zero timelock delay. -/// @dev Use the following environment variables: -/// * RISC0_MIN_DELAY new minimum delay in seconds for the RISC Zero timelock -/// * GNOSIS_EXECUTE (optional) if true, print Gnosis Safe calldata instead of broadcasting -/// -/// See the Foundry documentation for more information about Solidity scripts. -/// https://book.getfoundry.sh/guides/scripting-with-solidity -contract FinishUpdateRisc0TimelockDelay is RiscZeroManagementScript { - function run() external withConfig { - bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); - uint256 minDelay = vm.envUint("RISC0_MIN_DELAY"); - console2.log("minDelay:", minDelay); - - bytes memory data = abi.encodeCall(risc0TimelockController().updateDelay, minDelay); - - if (gnosisExecute) { - _printGnosisSafeInfo(address(risc0TimelockController()), minDelay, data); - return; - } - - vm.broadcast(adminAddress()); - risc0TimelockController().execute(address(risc0TimelockController()), 0, data, 0, 0); - } - - function _printGnosisSafeInfo(address timelockAddress, uint256 minDelay, bytes memory data) internal pure { - console2.log("================================"); - console2.log("================================"); - console2.log("=== GNOSIS SAFE EXECUTE RISC0 TIMELOCK DELAY INFO ==="); - console2.log("Target RISC Zero Timelock Controller Address (To): ", timelockAddress); - console2.log("New min delay: ", minDelay); - - bytes memory callData = - abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", timelockAddress, 0, data, 0, 0); - console2.log("Function: execute(address,uint256,bytes,bytes32,bytes32)"); - console2.log("Calldata:"); - console2.logBytes(callData); - console2.log(""); - console2.log("================================"); - } -} diff --git a/contracts/shanghai/scripts/NEW_CHAIN_DEPLOYMENT.md b/contracts/shanghai/scripts/NEW_CHAIN_DEPLOYMENT.md deleted file mode 100644 index 75e773289a..0000000000 --- a/contracts/shanghai/scripts/NEW_CHAIN_DEPLOYMENT.md +++ /dev/null @@ -1,383 +0,0 @@ -# Deploying Boundless to a New Chain - -End-to-end guide for deploying the full Boundless stack (verifiers + market) to a new EVM chain. This guide assumes a Shanghai-compatible L2 (no PUSH0); for Cancun chains, use the default profile and scripts under `contracts/scripts/` instead of `contracts/shanghai/scripts/`. - -> [!NOTE] -> All commands assume your working directory is the repo root. - -## Prerequisites - -- [Foundry](https://book.getfoundry.sh/getting-started/installation) -- [yq v4+](https://github.com/mikefarah/yq) -- `python3` with `tomlkit` (`pip install tomlkit`) -- A funded deployer wallet on the target chain -- A funded wallet on Ethereum mainnet (for bridging ZKC, if applicable) - -## Overview - -| Step | What | Script / Tool | -| ---- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| 1 | Add chain config | `deployment_verifier.toml`, `deployment_secrets.toml`, `deployment.toml` | -| 2 | Deploy RISC Zero timelocked router | `manage-verifier DeployRisc0TimelockRouter` | -| 3 | Deploy RISC Zero verifiers (Groth16, SetVerifier) | `manage-verifier DeployEstop*Verifier` | -| 4 | Route RISC Zero verifiers | `manage-verifier ScheduleAddVerifierToRisc0Router` / `FinishAddVerifierToRisc0Router` | -| 5 | Deploy Boundless layered router | `manage-verifier DeployTimelockRouter` (with `parent-router` = RISC Zero router) | -| 6 | Deploy Blake3Groth16 verifier and route it | `manage-verifier DeployEstopBlake3Groth16Verifier` + `ScheduleAddVerifier` / `FinishAddVerifier` | -| 7 | Bridge ZKC collateral token | Chain's native bridge (e.g. `scripts/bridge-zkc-to-taiko.sh`) | -| 8 | Deploy BoundlessMarket | `manage DeployBoundlessMarket` | -| 9 | Verify contracts on block explorer | `verify-*.sh` scripts | -| 10 | Update Rust deployment constants | `crates/boundless-market/src/deployments.rs`, `crates/boundless-cli/src/config.rs` | - ---- - -## Step 1: Add Chain Configuration - -### `contracts/deployment_verifier.toml` - -Add a new `[chains.]` section: - -```toml -[chains.mychain-mainnet] -name = "MyChain Mainnet" -id = 12345 -etherscan-url = "https://explorer.mychain.xyz/" -foundry-profile = "shanghai" # if Shanghai EVM; omit for Cancun - -# Accounts -admin = "0x..." # Safe or EOA that will admin the timelock + market -``` - -### `contracts/deployment_secrets.toml` - -```toml -[chains.mychain-mainnet] -rpc-url = "https://rpc.mychain.xyz" -etherscan-api-key = "..." -``` - -### `contracts/deployment.toml` - -Add a `[deployment.mychain-mainnet]` section. Start with placeholder addresses (zeroes); the deploy scripts will fill them in: - -```toml -[deployment.mychain-mainnet] -name = "MyChain Mainnet" -id = 12345 -etherscan-url = "https://explorer.mychain.xyz/" -version = "v1.0.0" - -# Boundless Market admins -admin = "0x..." - -# Contracts (filled by deploy scripts) -verifier = "0x0000000000000000000000000000000000000000" -application-verifier = "0x0000000000000000000000000000000000000000" -set-verifier = "0x0000000000000000000000000000000000000000" -boundless-market = "0x0000000000000000000000000000000000000000" -boundless-market-impl = "0x0000000000000000000000000000000000000000" -boundless-market-old-impl = "0x0000000000000000000000000000000000000000" -collateral-token = "0x0000000000000000000000000000000000000000" - -# Guests info -deployment-commit = "bda3b118" -assessor-image-id = "0x6c5a03c0785e91bc0ad0db486004116010680a03af4e712bcca3188e56694100" -assessor-guest-url = "https://gateway.beboundless.cloud/ipfs/bafybeiauvbhinz2yqm2vbgpl2njgoyaxhuwa2vbv6gts2ajcjfkw5m4ejq" -deprecated-assessor-duration = 0 -``` - -> [!TIP] -> Copy `assessor-image-id` and `assessor-guest-url` from the latest production deployment (e.g. `base-mainnet`). - ---- - -## Step 2: Deploy RISC Zero Timelocked Router - -The verifier infrastructure has two tiers: a **RISC Zero router** (manages core RISC Zero verifiers) and a **Boundless layered router** (wraps the RISC Zero router and adds Boundless-specific verifiers like Blake3Groth16). The RISC Zero router must be deployed first. - -```bash -export CHAIN_KEY=mychain-mainnet -export DEPLOYER_PRIVATE_KEY=0x... - -# Dry run first -contracts/shanghai/scripts/manage-verifier DeployRisc0TimelockRouter - -# If looks good, broadcast -contracts/shanghai/scripts/manage-verifier DeployRisc0TimelockRouter --broadcast -``` - -This deploys a `TimelockController` and `RiscZeroVerifierRouter`. Update `deployment_verifier.toml` with the deployed addresses: - -```toml -risc0-router = "0x..." -risc0-timelock-controller = "0x..." -risc0-timelock-delay = 0 -``` - -> [!IMPORTANT] -> Set `risc0-timelock-delay` appropriately: `0` for testnet, `259200` (3 days) for mainnet. - ---- - -## Step 3: Deploy RISC Zero Verifiers - -Deploy the core RISC Zero verifiers: `RiscZeroGroth16Verifier` and `RiscZeroSetVerifier`. - -```bash -# RiscZero Groth16 verifier -contracts/shanghai/scripts/manage-verifier DeployEstopGroth16Verifier --broadcast - -# RiscZero Set verifier -contracts/shanghai/scripts/manage-verifier DeployEstopSetVerifier --broadcast -``` - -After each deployment, add the verifier entry to `deployment_verifier.toml` under `risc0-verifiers`: - -```toml -[[chains.mychain-mainnet.risc0-verifiers]] -name = "RiscZeroGroth16Verifier" -version = "3.0.0" -selector = "0x..." -verifier = "0x..." -estop = "0x..." - -[[chains.mychain-mainnet.risc0-verifiers]] -name = "RiscZeroSetVerifier" -version = "0.9.0" -selector = "0x..." -verifier = "0x..." -estop = "0x..." -``` - ---- - -## Step 4: Route RISC Zero Verifiers - -Route each RISC Zero verifier through the RISC Zero router: - -```bash -# For each verifier, schedule and finish the add operation -VERIFIER_SELECTOR="0x..." contracts/shanghai/scripts/manage-verifier ScheduleAddVerifierToRisc0Router --broadcast - -# After timelock delay passes, finish -VERIFIER_SELECTOR="0x..." contracts/shanghai/scripts/manage-verifier FinishAddVerifierToRisc0Router --broadcast -``` - -Repeat for both `RiscZeroGroth16Verifier` and `RiscZeroSetVerifier`. - -> [!TIP] -> If timelock delay is 0 (testnet), you can schedule and finish immediately. - ---- - -## Step 5: Deploy Boundless Layered Router - -Now deploy the Boundless layered router, which wraps the RISC Zero router as its parent: - -```bash -contracts/shanghai/scripts/manage-verifier DeployTimelockRouter --broadcast -``` - -Ensure `parent-router` in `deployment_verifier.toml` points to the RISC Zero router address before deploying. Update the TOML with the deployed addresses: - -```toml -timelock-controller = "0x..." -timelock-delay = 0 -router = "0x..." -parent-router = "" -``` - ---- - -## Step 6: Deploy and Route Blake3Groth16 Verifier - -Deploy the Blake3Groth16 verifier and route it through the Boundless layered router: - -```bash -# Deploy -contracts/shanghai/scripts/manage-verifier DeployEstopBlake3Groth16Verifier --broadcast -``` - -Add to `deployment_verifier.toml` under `verifiers` (not `risc0-verifiers`): - -```toml -[[chains.mychain-mainnet.verifiers]] -name = "Blake3Groth16Verifier" -version = "0.0.1" -selector = "0x..." -verifier = "0x..." -estop = "0x..." -``` - -Route it through the layered router: - -```bash -VERIFIER_SELECTOR="0x..." contracts/shanghai/scripts/manage-verifier ScheduleAddVerifier --broadcast -VERIFIER_SELECTOR="0x..." contracts/shanghai/scripts/manage-verifier FinishAddVerifier --broadcast -``` - -Verify the full verifier stack: - -```bash -FOUNDRY_PROFILE=deployment-test forge test --match-contract=VerifierDeploymentTest -vv --fork-url=$RPC_URL -``` - -Update `deployment.toml` with the final router and set-verifier addresses: - -```toml -verifier = "" -application-verifier = "" # often the same as verifier -set-verifier = "" -``` - ---- - -## Step 7: Bridge ZKC Collateral Token - -If the target chain is an L2, you need to bridge ZKC from Ethereum mainnet. The bridged token becomes the market's collateral token. - -### Using the bridge script (Taiko example) - -```bash -PRIVATE_KEY=0x... ./scripts/bridge-zkc-to-taiko.sh -``` - -### General approach for other chains - -1. **Approve** the chain's L1 bridge/vault contract to spend ZKC (`0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555`) -2. **Bridge** a small amount (1 ZKC) via the chain's native bridge — this triggers auto-deployment of a bridged ERC20 on L2 -3. **Claim** the bridge message on L2 (via bridge UI or contract call) -4. **Record** the bridged token address and update `collateral-token` in `deployment.toml` - -> [!NOTE] -> Most L2 bridged tokens do NOT support ERC20Permit. The `collateral_token_supports_permit()` function in `deployments.rs` already returns `false` for all chains except Ethereum mainnet, Sepolia, and Anvil — so new L2s are handled correctly by default. - ---- - -## Step 8: Deploy BoundlessMarket - -Ensure `deployment.toml` has the correct `verifier`, `application-verifier`, `set-verifier`, `collateral-token`, `assessor-image-id`, and `assessor-guest-url` for your chain before deploying. - -```bash -export CHAIN_KEY=mychain-mainnet -export DEPLOYER_PRIVATE_KEY=0x... - -# Dry run -contracts/shanghai/scripts/manage DeployBoundlessMarket - -# Broadcast -contracts/shanghai/scripts/manage DeployBoundlessMarket --broadcast -``` - -The script auto-updates `deployment.toml` with `boundless-market`, `boundless-market-impl`, and `boundless-market-deployment-commit`. - ---- - -## Step 9: Verify Contracts - -### Implementation - -```bash -export CHAIN_KEY=mychain-mainnet -export RPC_URL=https://rpc.mychain.xyz -contracts/shanghai/scripts/verify-boundless-market.sh -``` - -### Proxy (ERC1967Proxy) - -```bash -export FOUNDRY_PROFILE=shanghai - -IMPL="" -ADMIN="" -GUEST_URL="" - -INIT_CALLDATA=$(cast calldata "initialize(address,string)" "$ADMIN" "$GUEST_URL") -CONSTRUCTOR_ARGS=$(cast abi-encode "constructor(address,bytes)" "$IMPL" "$INIT_CALLDATA") - -forge verify-contract --watch \ - --chain-id= \ - --constructor-args="$CONSTRUCTOR_ARGS" \ - --verifier-url "https://api.etherscan.io/v2/api?chainid=" \ - --etherscan-api-key="$ETHERSCAN_API_KEY" \ - \ - lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy -``` - -> [!TIP] -> For blockscout-based explorers, use `--verifier blockscout --verifier-url https://api.explorer.xyz/api/`. For Etherscan V2-compatible explorers, use `--verifier-url "https://api.etherscan.io/v2/api?chainid="`. - -### Verifier contracts - -```bash -contracts/shanghai/scripts/verify-router.sh -contracts/shanghai/scripts/verify-blake3-groth16-verifier.sh -contracts/shanghai/scripts/verify-risc0-groth16-verifier.sh -contracts/shanghai/scripts/verify-risc0-set-verifier.sh -``` - ---- - -## Step 10: Update Rust Code - -### `crates/boundless-market/src/deployments.rs` - -Add a deployment constant and wire it into `from_chain()`: - -```rust -pub const MY_CHAIN: Deployment = Deployment { - market_chain_id: Some(NamedChain::MyChain as u64), - boundless_market_address: address!("0x..."), - verifier_router_address: Some(address!("0x...")), - set_verifier_address: address!("0x..."), - collateral_token_address: Some(address!("0x...")), - order_stream_url: None, - indexer_url: None, - deployment_block: Some(12345), -}; -``` - -Add to `from_chain()`: - -```rust -NamedChain::MyChain => Some(MY_CHAIN), -``` - -> [!NOTE] -> Check that `NamedChain::MyChain` exists in `alloy-chains`. If not, users must pass deployment info explicitly via CLI flags or config. - -### `crates/boundless-cli/src/config.rs` - -Add `"mychain-mainnet"` to both match blocks (requestor and prover): - -```rust -"mychain-mainnet" => Some(boundless_market::deployments::MY_CHAIN), -``` - -### `crates/boundless-cli/src/display.rs` - -Add to `network_name_from_chain_id()`: - -```rust -Some(12345) => "MyChain Mainnet", -``` - -### Rebuild and verify - -```bash -cargo check -p boundless-market -p boundless-cli -RUSTFLAGS=-Dwarnings RISC0_SKIP_BUILD=1 RISC0_SKIP_BUILD_KERNELS=1 cargo clippy -p boundless-market -p boundless-cli --all-targets -``` - ---- - -## Post-Deployment Checklist - -- [ ] All verifiers deployed and routed -- [ ] Deployment tests pass on fork (`FOUNDRY_PROFILE=deployment-test forge test ...`) -- [ ] ZKC bridged and collateral token address recorded -- [ ] BoundlessMarket deployed, initialized, and admin set -- [ ] All contracts verified on block explorer -- [ ] `deployment.toml` and `deployment_verifier.toml` updated with all addresses -- [ ] Rust deployment constants added and compile clean -- [ ] CLI config supports the new chain name -- [ ] `~/.boundless/config.toml` can be pointed at the new chain for testing diff --git a/contracts/shanghai/scripts/VERIFIER_DEPLOYMENT.md b/contracts/shanghai/scripts/VERIFIER_DEPLOYMENT.md deleted file mode 100644 index 8da9e36672..0000000000 --- a/contracts/shanghai/scripts/VERIFIER_DEPLOYMENT.md +++ /dev/null @@ -1,433 +0,0 @@ -# Contract Operations Guide - -An operations guide for the Boundless verifier contracts. - -> [!NOTE] -> All the commands in this guide assume your current working directory is the root of the repo. - -## Dependencies - -Requires [Foundry](https://book.getfoundry.sh/getting-started/installation). - -> [!NOTE] -> Running the `manage-verifier` commands will run in simulation mode (i.e. will not send transactions) unless the `--broadcast` flag is passed. -> When setting `GNOSIS_EXECUTE=true` all the transactions calldata will be printed so that they can be copied over to the Safe web app. - -Commands in this guide use `yq` to parse the TOML config files. - -You can install `yq` by following the [directions on GitHub][yq-install], or using `go install`. - -```sh -go install github.com/mikefarah/yq/v4@latest -``` - -## Configuration - -Configurations and deployment state information is stored in `deployment_verifier.toml`. -It contains information about each chain (e.g. name, ID, Etherscan URL), and addresses for the timelock, router, and verifier contracts on each chain. - -Accompanying the `deployment_verifier.toml` file is a `deployment_secrets.toml` file with the following schema. -It is used to store somewhat sensitive API keys for RPC services and Etherscan. -Note that it does not contain private keys or API keys for Fireblocks. -It should never be committed to `git`, and the API keys should be rotated if this occurs. - -```toml -[chains.$CHAIN_KEY] -rpc-url = "..." -etherscan-api-key = "..." -``` - -## Environment - -### Public Networks (Testnet or Mainnet) - -Set the chain you are operating on by the key from the `deployment_verifier.toml` file. -An example chain key is "ethereum-sepolia", and you can look at `deployment_verifier.toml` for the full list. - -```sh -export CHAIN_KEY="xxx-testnet" -``` - -**Based on the chain key, the `manage-verifier` script will automatically load environment variables from deployment_verifier.toml and deployment_secrets.toml** - -If the chain you are deploying to is not in `deployment_secrets.toml`, set your RPC URL, public and private key, and Etherscan API key: - -```sh -export RPC_URL=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].rpc-url" contracts/deployment_secrets.toml | tee /dev/stderr) -export ETHERSCAN_URL=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-url" contracts/deployment_verifier.toml | tee /dev/stderr) -export ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" contracts/deployment_secrets.toml | tee /dev/stderr) -``` - -> [!TIP] -> Foundry has a [config full of information about each chain][alloy-chains], mapped from chain ID. -> It includes the Etherscan compatible API URL, which is how only specifying the API key works. -> You can find this list in the following source file: - -Example RPC URLs: - -- `https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY` -- `https://sepolia.infura.io/v3/YOUR_API_KEY` - -## Deploy the timelocked router - -1. Dry run the contract deployment: - - > [!IMPORTANT] - > Adjust the `MIN_DELAY` (or `timelock-delay` in the toml) to a value appropriate for the environment (e.g. 0 second for testnet and 259200 seconds (3 days) for mainnet). - - ```sh - contracts/scripts/manage-verifier DeployTimelockRouter - - ... - - == Logs == - minDelay: 1 - proposers: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - executors: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - admin: 0x0000000000000000000000000000000000000000 - Deployed TimelockController to 0x5FbDB2315678afecb367f032d93F642f64180aa3 - Deployed VerifierLayeredRouter to 0x918063A3fa14C59b390B18db8b1A565780E8b933 - ``` - -2. Run the command again with `--broadcast`. - - This will result in two transactions sent from the deployer address. - -3. Test the deployment. - - ```console - FOUNDRY_PROFILE=deployment-test forge test --match-contract=VerifierDeploymentTest -vv --fork-url=${RPC_URL:?} - ``` - -## Deploy a Blae3Groth16 verifier with emergency stop mechanism - -This is a two-step process, guarded by the `TimelockController`. - -### Deploy the verifier - -1. Dry run deployment of BlakeGroth16 verifier and estop: - - ```sh - contracts/scripts/manage-verifier DeployEstopBlake3Groth16Verifier - ``` - - > [!IMPORTANT] - > Check the logs from this dry run to verify the estop owner is the expected address. - > It should be equal to the admin address on the given chain. - > Note that it should not be the `TimelockController`. - > Also check the chain ID to ensure you are deploying to the chain you expect. - > And check the selector to make sure it matches what you expect. - -2. Send deployment transactions for verifier and estop by running the command again with `--broadcast`. - - This will result in two transactions sent from the deployer address. - -3. Add the addresses for the newly deployed contract to the `deployment_verifier.toml` file. - -4. Test the deployment. - - ```sh - FOUNDRY_PROFILE=deployment-test forge test --match-contract=VerifierDeploymentTest -vv --fork-url=${RPC_URL:?} - ``` - -5. Print the operation to schedule the operation to add the verifier to the router. - - ```sh - GNOSIS_EXECUTE=true VERIFIER_SELECTOR="0x..." bash contracts/scripts/manage-verifier ScheduleAddVerifier - ``` - -6. Send the transaction for the scheduled update via Safe. - -### Finish the update - -After the delay on the timelock controller has passed, the operation to add the new verifier to the router can be executed. - -1. Print the transaction calldata to execute the add verifier operation: - - ```sh - GNOSIS_EXECUTE=true VERIFIER_SELECTOR="0x..." bash contracts/scripts/manage-verifier FinishAddVerifier - ``` - -2. Send the transaction for execution via Safe - -3. Remove the `unroutable` field from the selected verifier. - -4. Test the deployment. - - ```console - FOUNDRY_PROFILE=deployment-test forge test --match-contract=VerifierDeploymentTest -vv --fork-url=${RPC_URL:?} - ``` - -## Remove a verifier - -This is a two-step process, guarded by the `TimelockController`. - -### Schedule the update - -1. Print the transaction to schedule the remove verifier operation: - - ```sh - GNOSIS_EXECUTE=true VERIFIER_SELECTOR="0x..." contracts/scripts/manage-verifier ScheduleRemoveVerifier - ``` - -2. Send the transaction for execution via Safe - -### Finish the update - -1. Print the transaction to execute the remove verifier operation: - - ```sh - GNOSIS_EXECUTE=true VERIFIER_SELECTOR="0x..." contracts/scripts/manage-verifier FinishRemoveVerifier - ``` - -2. Send the transaction for execution via Safe - -3. Update `deployment_verifier.toml` and set `unroutable = true` on the removed verifier. - -4. Test the deployment. - - ```console - FOUNDRY_PROFILE=deployment-test forge test --match-contract=VerifierDeploymentTest -vv --fork-url=${RPC_URL:?} - ``` - -## Update the TimelockController minimum delay - -This is a two-step process, guarded by the `TimelockController`. - -The minimum delay (`MIN_DELAY`) on the timelock controller is denominated in seconds. - -### Schedule the update - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true MIN_DELAY=10 contracts/scripts/manage-verifier ScheduleUpdateDelay - ``` - -2. Send the transaction for execution via Safe - -### Finish the update - -Execute the action: - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true MIN_DELAY=10 contracts/scripts/manage-verifier FinishUpdateDelay - ``` - -2. Send the transaction for execution via Safe - -3. Test the deployment. - - ```console - FOUNDRY_PROFILE=deployment-test forge test --match-contract=VerifierDeploymentTest -vv --fork-url=${RPC_URL:?} - ``` - -## Cancel a scheduled timelock operation - -Use the following steps to cancel an operation that is pending on the `TimelockController`. - -1. Identify the operation ID and set the environment variable. - - > TIP: One way to get the operation ID is to open the contract in Etherscan and look at the events. - > On the `CallScheduled` event, the ID is labeled as `[topic1]`. - > - > ```sh - > open ${ETHERSCAN_URL:?}/address/${TIMELOCK_CONTROLLER:?}#events - > ``` - - ```sh - export OPERATION_ID="0x..." \ - ``` - -2. Print the transaction calldata to cancel the operation. - - ```sh - GNOSIS_EXECUTE=true contracts/scripts/manage-verifier CancelOperation - ``` - -3. Send the transaction for execution via Safe - -## Grant access to the TimelockController - -This is a two-step process, guarded by the `TimelockController`. - -Three roles are supported: - -- `proposer` -- `executor` -- `canceller` - -### Schedule the update - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true \ - ROLE="executor" \ - ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ - bash contracts/scripts/manage-verifier ScheduleGrantRole - ``` - -2. Send the transaction for execution via Safe - -### Finish the update - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true \ - ROLE="executor" \ - ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ - bash contracts/scripts/manage-verifier FinishGrantRole - ``` - -2. Send the transaction for execution via Safe. - -3. Confirm the update: - - ```sh - # Query the role code. - cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'EXECUTOR_ROLE()(bytes32)' - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 - - # Check that the account now has that role. - cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'hasRole(bytes32, address)(bool)' \ - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ - 0x00000000000000aabbccddeeff00000000000000 - true - ``` - -## Revoke access to the TimelockController - -This is a two-step process, guarded by the `TimelockController`. - -Three roles are supported: - -- `proposer` -- `executor` -- `canceller` - -### Schedule the update - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true \ - ROLE="executor" \ - ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ - bash contracts/scripts/manage-verifier ScheduleRevokeRole - ``` - -2. Send the transaction for execution via Safe - -Confirm the role code: - -```sh -cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'EXECUTOR_ROLE()(bytes32)' -0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 -``` - -### Finish the update - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true \ - ROLE="executor" \ - ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ - bash contracts/scripts/manage-verifier FinishRevokeRole - ``` - -2. Send the transaction for execution via Safe - -3. Confirm the update: - - ```sh - # Query the role code. - cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'EXECUTOR_ROLE()(bytes32)' - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 - - # Check that the account no longer has that role. - cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'hasRole(bytes32, address)(bool)' \ - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ - 0x00000000000000aabbccddeeff00000000000000 - false - ``` - -## Renounce access to the TimelockController - -If your private key is compromised, you can renounce your role(s) without waiting for the time delay. Repeat this action for any of the roles you might have, such as: - -- proposer -- executor -- canceller - -> ![WARNING] -> Renouncing authorization on the timelock controller may make it permanently inoperable. - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true \ - RENOUNCE_ROLE="executor" \ - RENOUNCE_ADDRESS="0x00000000000000aabbccddeeff00000000000000" \ - bash contracts/scripts/manage-verifier RenounceRole - ``` - -2. Send the transaction for execution via Safe - -3. Confirm: - - ```sh - cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'hasRole(bytes32, address)(bool)' \ - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ - ${RENOUNCE_ADDRESS:?} - false - ``` - -## Activate the emergency stop - -Activate the emergency stop: - -> ![WARNING] -> Activating the emergency stop will make that verifier permanently inoperable. - -> ![NOTE] -> In order to send a transaction to the estop contract in Fireblocks, the addresses need to be added to the allow-list. -> If this has not already been done, do this as a pre-step. - -1. Print the transaction calldata: - - ```sh - GNOSIS_EXECUTE=true \ - VERIFIER_SELCTOR="0x..." \ - bash contracts/scripts/manage-verifier ActivateEstop - ``` - -2. Send the transaction for execution via Safe - -3. Test the activation: - - ```sh - cast call --rpc-url ${RPC_URL:?} \ - ${VERIFIER_ESTOP:?} \ - 'paused()(bool)' - true - ``` - -[yq-install]: https://github.com/mikefarah/yq?tab=readme-ov-file#install -[alloy-chains]: https://github.com/alloy-rs/chains/blob/main/src/named.rs diff --git a/contracts/shanghai/scripts/VERIFY.md b/contracts/shanghai/scripts/VERIFY.md deleted file mode 100644 index 8841eb14d7..0000000000 --- a/contracts/shanghai/scripts/VERIFY.md +++ /dev/null @@ -1,41 +0,0 @@ -# Contracts verification examples - -## Sample Forge verification for POVW - -Constructor args must be provided manually. - -``` -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address,address,bytes32)' \ - "0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319" \ - "0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555" \ - "0x004b225edce73d0fd399993c14bea083a08bc346eacd68a644946179ebc4818f" \ -)" -forge verify-contract 0x553ff40b2A36E728CdD79768acb825fb58551bce contracts/src/povw/PovwAccounting.sol:PovwAccounting --constructor-args=${CONSTRUCTOR_ARGS:?} --etherscan-api-key --watch -``` - -``` -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address,address,bytes32)' \ - "0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319" \ - "0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555" \ - "0x004b225edce73d0fd399993c14bea083a08bc346eacd68a644946179ebc4818f" \ -)" -forge verify-contract 0x553ff40b2A36E728CdD79768acb825fb58551bce contracts/src/povw/PovwAccounting.sol:PovwAccounting --constructor-args=${CONSTRUCTOR_ARGS:?} --etherscan-api-key --watch -``` - -## Sample Forge verification for Boundless Market - -Constructor args must be provided manually. - -``` -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address,bytes32,bytes32,uint32,address)' \ - "0x0b144e07a0826182b6b59788c34b32bfa86fb711" \ - "0x03831182a226b5f5a4d358704a9f9d0bcd4dc48e6e577dc7db84d94892024938" \ - "0x0000000000000000000000000000000000000000000000000000000000000000" \ - "0x0000000000000000000000000000000000000000000000000000000000000000" \ - "0xAA61bB7777bD01B684347961918f1E07fBbCe7CF" \ -)" -forge verify-contract 0x8d3D36400d0a8Cf7cF217D28D366a0189F0850B6 contracts/src/BoundlessMarket.sol:BoundlessMarket --constructor-args=${CONSTRUCTOR_ARGS:?} --rpc-url $RPC_URL --etherscan-api-key --watch -``` diff --git a/contracts/shanghai/scripts/find_rollback_address b/contracts/shanghai/scripts/find_rollback_address deleted file mode 100755 index 61c4327c26..0000000000 --- a/contracts/shanghai/scripts/find_rollback_address +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." - -# Run from the repo root for consistency. -cd ${REPO_ROOT_DIR:?} - -if [ -n "$STACK_TAG" ]; then - DEPLOY_KEY=${CHAIN_KEY:?}-${STACK_TAG:?} -else - DEPLOY_KEY=${CHAIN_KEY:?} -fi - -load_env_var() { - local var_name="$1" - local config_key="$2" - local config_file="$3" - - # Get current value of the variable - local current_value=$(eval echo \$$var_name) - - if [ -z "$current_value" ]; then - echo "$var_name from $config_file: " > /dev/stderr - local new_value=$(yq eval -e "$config_key" "$REPO_ROOT_DIR/contracts/$config_file") - [ -n "$new_value" ] && [[ "$new_value" != "null" ]] || exit 1 - export $var_name="$new_value" - else - echo "$var_name from env $current_value" - fi -} - -echo "Loading environment variables from deployment TOML files" -load_env_var "RPC_URL" ".chains[\"${CHAIN_KEY:?}\"].rpc-url" "deployment_secrets.toml" -load_env_var "PROXY_ADDRESS" ".deployment[\"${DEPLOY_KEY:?}\"].boundless-market" "deployment.toml" -load_env_var "CHAIN_ID" ".deployment[\"${DEPLOY_KEY:?}\"].id" "deployment.toml" - -# Check if we're on the correct network -CONNECTED_CHAIN_ID=$(cast chain-id --rpc-url ${RPC_URL:?}) -if [[ "${CONNECTED_CHAIN_ID:?}" != "${CHAIN_ID:?}" ]]; then - echo -e "${RED}Error: connected chain id and configured chain id do not match: ${CONNECTED_CHAIN_ID:?} != ${CHAIN_ID:?} ${NC}" - exit 1 -fi - -# Config -SLOT="0x360894A13BA1A3210667C828492DB98DCA3E2076CC3735A920A3CA505D382BBC" # EIP-1967 slot -STEP=86400 # 24 hours in seconds -# STEP=3600 # 1 hour in seconds -STEPS_BACK=7 # Number of steps to go back in time -AVG_BLOCK_TIME=2 - -# Determine OS for date formatting -platform=$(uname) -if [[ "$platform" == "Darwin" ]]; then - # macOS - format_date() { date -r "$1" "+%Y-%m-%d"; } -else - # Linux - format_date() { date -d "@$1" "+%Y-%m-%d"; } -fi - -# Get current implementation -CURRENT_RAW=$(cast storage "$PROXY_ADDRESS" "$SLOT" --rpc-url "$RPC_URL") -CURRENT_IMPL="0x${CURRENT_RAW:26}" -echo "Current implementation: $CURRENT_IMPL" - -LATEST_BLOCK=$(cast block-number --rpc-url "$RPC_URL") -LATEST_TS=$(cast block "$LATEST_BLOCK" --json --rpc-url "$RPC_URL" | jq -r .timestamp) - -for ((i=1; i<=STEPS_BACK; i++)); do - TARGET_TS=$((LATEST_TS - i * STEP)) - DELTA_SECS=$((LATEST_TS - TARGET_TS)) - BLOCK_DELTA=$((DELTA_SECS / AVG_BLOCK_TIME)) - APPROX_BLOCK=$((LATEST_BLOCK - BLOCK_DELTA)) - DATE_STR=$(format_date "$TARGET_TS") - - VALUE=$(cast storage "$PROXY_ADDRESS" "$SLOT" --block "$APPROX_BLOCK" --rpc-url "$RPC_URL" 2>/dev/null || true) - [[ -z "$VALUE" ]] && { echo "[$DATE_STR] Could not fetch storage at block $APPROX_BLOCK"; continue; } - - IMPL="0x${VALUE:26}" - - if [[ "$IMPL" != "$CURRENT_IMPL" ]]; then - echo "[$DATE_STR] Block $APPROX_BLOCK: Implementation was $IMPL" - fi -done \ No newline at end of file diff --git a/contracts/shanghai/scripts/hp b/contracts/shanghai/scripts/hp deleted file mode 100755 index d7297d2b70..0000000000 --- a/contracts/shanghai/scripts/hp +++ /dev/null @@ -1,141 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -# Default mint amount 100 tokens (can be overridden by environment variable) -DEFAULT_MINT_AMOUNT="${DEFAULT_MINT_AMOUNT:-100000000000000000000}" - -# Check if required environment variables are set -check_env_vars() { - local vars=("PRIVATE_KEY" "RPC_URL" "HIT_POINTS_ADDRESS") - for var in "${vars[@]}"; do - if [ -z "${!var}" ]; then - echo "Error: $var environment variable is not set" - exit 1 - fi - done -} - -# Mint tokens -mint() { - local target_address=$1 - local amount=${2:-$DEFAULT_MINT_AMOUNT} - - if [ -z "$target_address" ]; then - echo "Usage: $0 mint [amount]" - echo "Default amount: $DEFAULT_MINT_AMOUNT" - exit 1 - fi - - echo "Minting HP for $target_address with amount $amount" - cast send --private-key "$PRIVATE_KEY" \ - --rpc-url "$RPC_URL" \ - "$HIT_POINTS_ADDRESS" "mint(address, uint256)" "$target_address" "$amount" -} - -# Grant `MINTER` role to an address -grant_minter_role() { - local target_address=$1 - - if [ -z "$target_address" ]; then - echo "Usage: $0 grant-minter-role " - exit 1 - fi - - echo "Granting MINTER role to $target_address" - cast send --private-key "$PRIVATE_KEY" \ - --rpc-url "$RPC_URL" \ - "$HIT_POINTS_ADDRESS" "grantMinterRole(address)" "$target_address" -} - -# Grant `AUTHORIZED_TRANSFER` role to an address -grant_auth_transfer_role() { - local target_address=$1 - - if [ -z "$target_address" ]; then - echo "Usage: $0 grant-auth-transfer-role " - exit 1 - fi - - echo "Granting AUTHORIZED_TRANSFER role to $target_address" - cast send --private-key "$PRIVATE_KEY" \ - --rpc-url "$RPC_URL" \ - "$HIT_POINTS_ADDRESS" "grantAuthorizedTransferRole(address)" "$target_address" -} - -# Revoke `MINTER` role from an address -revoke_minter_role() { - local target_address=$1 - - if [ -z "$target_address" ]; then - echo "Usage: $0 revoke-minter-role " - exit 1 - fi - - echo "Revoking `MINTER` role from $target_address" - cast send --private-key "$PRIVATE_KEY" \ - --rpc-url "$RPC_URL" \ - "$HIT_POINTS_ADDRESS" "revokeMinterRole(address)" "$target_address" -} - -# Revoke `AUTHORIZED_TRANSFER` role from an address -revoke_auth_transfer_role() { - local target_address=$1 - - if [ -z "$target_address" ]; then - echo "Usage: $0 revoke-auth-transfer-role " - exit 1 - fi - - echo "Revoking `AUTHORIZED_TRANSFER` role from $target_address" - cast send --private-key "$PRIVATE_KEY" \ - --rpc-url "$RPC_URL" \ - "$HIT_POINTS_ADDRESS" "revokeAuthorizedTransferRole(address)" "$target_address" -} - -# Get token balance -balance() { - local target_address=$1 - if [ -z "$target_address" ]; then - echo "Usage: $0 balance " - exit 1 - fi - echo "Checking HP balance for $target_address" - cast call --rpc-url "$RPC_URL" \ - "$HIT_POINTS_ADDRESS" "balanceOf(address)(uint256)" "$target_address" -} - -# Main script logic -check_env_vars - -case "$1" in - mint) - mint "$2" "$3" - ;; - grant-minter-role) - grant_minter_role "$2" - ;; - revoke-minter-role) - revoke_minter_role "$2" - ;; - grant-auth-transfer-role) - grant_auth_transfer_role "$2" - ;; - revoke-auth-transfer-role) - revoke_auth_transfer_role "$2" - ;; - balance) - balance "$2" - ;; - *) - echo "Usage: $0 {mint|grant-minter-role|revoke-minter-role|grant-auth-transfer-role|revoke-auth-transfer-role|balance} [arguments]" - echo "Examples:" - echo " $0 mint 0x1234... 100000000000000000000" - echo " $0 grant-minter-role 0x5678..." - echo " $0 revoke-minter-role 0x9abc..." - echo " $0 grant-auth-transfer-role 0x5678..." - echo " $0 revoke-auth-transfer-role 0x9abc..." - echo " $0 balance 0x1234..." - exit 1 - ;; -esac \ No newline at end of file diff --git a/contracts/shanghai/scripts/manage b/contracts/shanghai/scripts/manage deleted file mode 100755 index cf2cc02241..0000000000 --- a/contracts/shanghai/scripts/manage +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -SCRIPT_FILE="${SCRIPT_DIR}/Manage.s.sol" -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." -FIREBLOCKS=0 -export FOUNDRY_PROFILE=shanghai - -# Check for python3, required for updating the deployment toml -if ! command -v python3 >/dev/null 2>&1; then - echo "❌ python3 is not installed" - exit 1 -fi - -# Check for tomlkit (Python package), required for updating the deployment toml -if ! python3 -c "import tomlkit" >/dev/null 2>&1; then - echo "❌ tomlkit is not installed for python3" - echo "To install: python3 -m pip install tomlkit" - exit 1 -fi - -# Check for yq -if ! command -v yq >/dev/null 2>&1; then - echo "❌ yq is not installed" - echo "Install yq v4+ from: https://github.com/mikefarah/yq" - exit 1 -fi - -POSITIONAL_ARGS=() -FORGE_SCRIPT_FLAGS=() - -while [[ $# -gt 0 ]]; do - case $1 in - -f|--fireblocks) - FIREBLOCKS=1 - shift # past argument - ;; - --broadcast|--verify) - FORGE_SCRIPT_FLAGS+=("$1") - shift - ;; - -*|--*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") # save positional arg - shift # past argument - ;; - esac -done - -set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters - -# HINT: deployment_secrets.toml contains API keys. You can write it yourself, or ask a friend. -load_env_var() { - local var_name="$1" - local config_key="$2" - local config_file="$3" - - # Get current value of the variable - local current_value=$(eval echo \$$var_name) - - if [ -z "$current_value" ]; then - echo "$var_name from $config_file: " > /dev/stderr - local new_value=$(yq eval -e "$config_key" "$REPO_ROOT_DIR/contracts/$config_file") - [ -n "$new_value" ] && [[ "$new_value" != "null" ]] || exit 1 - export $var_name="$new_value" - else - echo "$var_name from env $current_value" - fi -} - -# Run a Forge script with support for Fireblocks with options set automatically -forge_script() { - # Set our function. If the function is "help", or if the function is - # unspecified, then print some help. - local script_function="${1:-help}" - shift - - if [ "${script_function:?}" == "help" ]; then - cat << EOF -🔧 BoundlessMarket Management Script -================================== - -Usage: $0 [options] - -Commands: - DeployBoundlessMarket Deploy the BoundlessMarket contract - UpgradeBoundlessMarket Upgrade the BoundlessMarket contract - RollbackBoundlessMarket Rollback the BoundlessMarket contract to previous version - AddBoundlessMarketAdmin Add admin to BoundlessMarket contract - RemoveBoundlessMarketAdmin Remove admin from BoundlessMarket contract - -Options: - -f, --fireblocks Use Fireblocks for transaction signing - --broadcast Broadcast transactions to network - --verify Verify contracts on Etherscan - -h, --help Show this help message - -Environment Variables: - CHAIN_KEY Required. Deployment environment key (anvil, ethereum-mainnet, ethereum-sepolia, ethereum-sepolia-staging) - STACK_TAG Optional. Stack tag for multi-deployment environments - DEPLOYER_PRIVATE_KEY Required. Private key for transaction signing (0x...) - ADMIN_TO_ADD Required for AddBoundlessMarketAdmin. Admin address to add (0x...) - ADMIN_TO_REMOVE Required for RemoveBoundlessMarketAdmin. Admin address to remove (0x...) - -Examples: - # Deploy BoundlessMarket - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... $0 DeployBoundlessMarket --broadcast - - # Upgrade BoundlessMarket - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... $0 UpgradeBoundlessMarket --broadcast - - # Add admin to BoundlessMarket - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... ADMIN_TO_ADD=0x... $0 AddBoundlessMarketAdmin --broadcast - - # Remove admin from BoundlessMarket - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... ADMIN_TO_REMOVE=0x... $0 RemoveBoundlessMarketAdmin --broadcast - -Notes: - - Network configuration is loaded from deployment.toml and deployment_secrets.toml - - Private keys must be provided via DEPLOYER_PRIVATE_KEY environment variable - - Admin operations support GNOSIS_EXECUTE=true for Gnosis Safe calldata generation - - Admin removal ensures at least one admin remains on the contract - - All operations automatically update deployment.toml -EOF - exit 0 - fi - - # Load environment variables only when running actual commands - if [ -n "$STACK_TAG" ]; then - DEPLOY_KEY=${CHAIN_KEY:?}-${STACK_TAG:?} - else - DEPLOY_KEY=${CHAIN_KEY:?} - fi - - echo "Loading environment variables from deployment TOML files" - load_env_var "RPC_URL" ".chains[\"${CHAIN_KEY:?}\"].rpc-url" "deployment_secrets.toml" - load_env_var "ETHERSCAN_API_KEY" ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" "deployment_secrets.toml" - load_env_var "CHAIN_ID" ".deployment[\"${DEPLOY_KEY:?}\"].id" "deployment.toml" - - # Check if we're on the correct network - CONNECTED_CHAIN_ID=$(cast chain-id --rpc-url ${RPC_URL:?}) - if [[ "${CONNECTED_CHAIN_ID:?}" != "${CHAIN_ID:?}" ]]; then - echo -e "${RED}Error: connected chain id and configured chain id do not match: ${CONNECTED_CHAIN_ID:?} != ${CHAIN_ID:?} ${NC}" - exit 1 - fi - - local target="${SCRIPT_FILE:?}:${script_function:?}" - echo "Running forge script $target" - - if [ $FIREBLOCKS -gt 0 ]; then - # Check for fireblocks - if ! command -v fireblocks-json-rpc &> /dev/null - then - echo "fireblocks-json-rpc not found" - echo "can be installed with npm install -g @fireblocks/fireblocks-json-rpc" - exit 1 - fi - - # Run forge via fireblocks - fireblocks-json-rpc --verbose --rpcUrl ${RPC_URL:?} --http --apiKey ${FIREBLOCKS_API_KEY:?} -- \ - forge script ${FORGE_SCRIPT_FLAGS} \ - --slow --unlocked \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - --rpc-url {} \ - "$target" "$@" - else - # Run forge - forge script ${FORGE_SCRIPT_FLAGS} \ - --private-key=${DEPLOYER_PRIVATE_KEY:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - --rpc-url ${RPC_URL:?} \ - "$target" "$@" - fi -} - -# Run from the repo root for consistency. -cd ${REPO_ROOT_DIR:?} - -# Get current git commit hash for deployment tracking -CURRENT_COMMIT=$(git rev-parse --short HEAD) -export CURRENT_COMMIT - -forge_script "$@" \ No newline at end of file diff --git a/contracts/shanghai/scripts/manage-povw b/contracts/shanghai/scripts/manage-povw deleted file mode 100755 index 89d39d6c12..0000000000 --- a/contracts/shanghai/scripts/manage-povw +++ /dev/null @@ -1,423 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -SCRIPT_FILE="${SCRIPT_DIR}/Manage.PoVW.s.sol" -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." -FIREBLOCKS=0 -export FOUNDRY_PROFILE=shanghai - -# Check for python3, required for updating the deployment toml -if ! command -v python3 >/dev/null 2>&1; then - echo "❌ python3 is not installed" - exit 1 -fi - -# Check for tomlkit (Python package), required for updating the deployment toml -if ! python3 -c "import tomlkit" >/dev/null 2>&1; then - echo "❌ tomlkit is not installed for python3" - echo "To install: python3 -m pip install tomlkit" - exit 1 -fi - -# Check for yq -if ! command -v yq >/dev/null 2>&1; then - echo "❌ yq is not installed" - echo "Install yq v4+ from: https://github.com/mikefarah/yq" - exit 1 -fi - -POSITIONAL_ARGS=() -FORGE_SCRIPT_FLAGS=() - -while [[ $# -gt 0 ]]; do - case $1 in - -f|--fireblocks) - FIREBLOCKS=1 - shift # past argument - ;; - --broadcast|--verify) - FORGE_SCRIPT_FLAGS+=("$1") - shift - ;; - -*|--*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") # save positional arg - shift # past argument - ;; - esac -done - -set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters - -# HINT: deployment_secrets.toml contains API keys. You can write it yourself, or ask a friend. -load_env_var() { - local var_name="$1" - local config_key="$2" - local config_file="$3" - - # Get current value of the variable - local current_value=$(eval echo \$$var_name) - - if [ -z "$current_value" ]; then - local new_value=$(yq eval -e "$config_key" "$REPO_ROOT_DIR/contracts/$config_file") - [ -n "$new_value" ] && [[ "$new_value" != "null" ]] || exit 1 - export $var_name="$new_value" - echo "$var_name from $config_file: $new_value" > /dev/stderr - else - echo "$var_name from env: $current_value" > /dev/stderr - fi -} - -# Create reference build for upgrades -create_reference_build() { - local contract_type="$1" - - # Check if we should skip reference build creation - if [ -n "${SKIP_REFERENCE_BUILD}" ]; then - echo "🔄 SKIP_REFERENCE_BUILD is set - checking for existing reference build..." - if [ -d "contracts/build-info-reference" ] && [ -n "$(ls -A contracts/build-info-reference 2>/dev/null)" ]; then - echo "✅ Reusing existing reference build in contracts/build-info-reference/" - echo "📁 Build info files: $(ls -1 contracts/build-info-reference | wc -l) files" - # Set flag to indicate reference build was reused - export REFERENCE_BUILD_REUSED="true" - return 0 - else - echo "⚠️ No existing reference build found, creating new one..." - fi - fi - - echo "Creating reference build for $contract_type upgrade safety checks..." - - # Get the deployment commit for the specific contract being upgraded - local deployed_commit="" - case "$contract_type" in - "PoVWAccounting") - deployed_commit=$(yq eval -e ".deployment.${DEPLOY_KEY}.povw-accounting-deployment-commit" "${REPO_ROOT_DIR}/contracts/deployment.toml" 2>/dev/null || echo "") - ;; - "PoVWMint") - deployed_commit=$(yq eval -e ".deployment.${DEPLOY_KEY}.povw-mint-deployment-commit" "${REPO_ROOT_DIR}/contracts/deployment.toml" 2>/dev/null || echo "") - ;; - *) - echo "❌ Unknown contract type: $contract_type" - exit 1 - ;; - esac - - if [ -z "$deployed_commit" ]; then - echo "❌ No deployment commit found for $contract_type in ${DEPLOY_KEY}" - echo "Cannot create reference build for upgrade" - exit 1 - fi - - echo "📦 Creating reference build from commit: $deployed_commit" - - # Create worktree for reference build - local worktree_path="../povw-reference-${deployed_commit}" - - # Clean up existing worktree if it exists - if [ -d "$worktree_path" ]; then - echo "🧹 Cleaning up existing reference worktree..." - git worktree remove "$worktree_path" --force 2>/dev/null || true - fi - - # Clean up existing reference build info - if [ -d "contracts/build-info-reference" ]; then - echo "🧹 Cleaning up existing reference build info..." - rm -rf "contracts/build-info-reference" - fi - - # Create new worktree - git worktree add "$worktree_path" "$deployed_commit" - - # Build reference and copy build info - echo "🔨 Building reference contracts..." - ( - cd "$worktree_path" - # First run cargo build to generate PovwImageId.sol and other generated files - echo "Running cargo build to generate required files..." - if cargo build; then - echo "✅ Cargo build completed successfully" - else - echo "❌ Cargo build failed in reference worktree" - echo "This may prevent forge build from succeeding" - fi - - # Check if generated files exist - if [ -f "contracts/src/libraries/PovwImageId.sol" ]; then - echo "✅ PovwImageId.sol generated successfully" - else - echo "❌ PovwImageId.sol not found after cargo build" - fi - - # Then clean and do a full forge build to ensure complete build info - echo "Running forge clean..." - if forge clean; then - echo "✅ Forge clean completed" - else - echo "❌ Forge clean failed" - fi - - echo "Running forge build..." - echo "Current directory: $(pwd)" - echo "Foundry config check:" - forge config --basic || echo "Forge config failed" - - if FOUNDRY_PROFILE=shanghai-povw-deploy forge build 2>&1; then - echo "✅ Forge build completed successfully" - # Check if build info directory was created (using FOUNDRY_OUT path) - if [ -d "contracts/out/build-info" ] && [ "$(ls -A contracts/out/build-info)" ]; then - echo "✅ Build info directory created with $(ls contracts/out/build-info | wc -l) files" - else - echo "❌ Build info directory is empty or missing" - echo "Checking contracts/out directory structure:" - ls -la contracts/out/ 2>/dev/null || echo "contracts/out directory does not exist" - echo "Checking if build-info subdirectory exists:" - ls -la contracts/out/build-info/ 2>/dev/null || echo "contracts/out/build-info directory does not exist" - fi - else - echo "❌ Forge build failed in reference worktree" - echo "Build output should be shown above" - fi - - # Copy build info from contracts/out/build-info (matches FOUNDRY_OUT setting) - if [ -d "contracts/out/build-info" ] && [ "$(ls -A contracts/out/build-info)" ]; then - mkdir -p "$OLDPWD/contracts/build-info-reference" - cp -R contracts/out/build-info/* "$OLDPWD/contracts/build-info-reference/" - echo "✅ Copied build info files from contracts/out/build-info to build-info-reference" - else - echo "❌ No build info created in reference build" - echo "Expected location: contracts/out/build-info" - echo "Possible causes:" - echo " - Cargo build failed to generate required files" - echo " - Forge compilation errors (check build output above)" - echo " - Missing dependencies in worktree" - echo " - FOUNDRY_OUT path mismatch" - if [ -d "out/build-info" ]; then - echo " - Found build-info in out/ instead of contracts/out/ (path mismatch)" - fi - fi - ) - - # Clean up worktree - git worktree remove "$worktree_path" --force - - # Verify build info was copied - if [ -d "contracts/build-info-reference" ] && [ -n "$(ls -A contracts/build-info-reference 2>/dev/null)" ]; then - echo "✅ Reference build created in contracts/build-info-reference/" - echo "📁 Build info files: $(ls -1 contracts/build-info-reference | wc -l) files" - else - echo "❌ Failed to create reference build info" - echo "This may cause upgrade validation to fail" - fi -} - -# Run a Forge script with support for Fireblocks with options set automatically -forge_script() { - # Set our function. If the function is "help", or if the function is - # unspecified, then print some help. - local script_function="${1:-help}" - shift - - if [ "${script_function:?}" == "help" ]; then - cat << EOF -🔧 PoVW Contract Management Script -================================ - -Usage: $0 [options] - -Commands: - DeployPoVW Deploy both PovwAccounting and PovwMint contracts - UpgradePoVWAccounting Upgrade the PovwAccounting contract - UpgradePoVWMint Upgrade the PovwMint contract - RollbackPoVWAccounting Rollback the PovwAccounting contract to previous version - RollbackPoVWMint Rollback the PovwMint contract to previous version - TransferPoVWOwnership Transfer ownership of both PovwAccounting and PovwMint contracts - -Options: - -f, --fireblocks Use Fireblocks for transaction signing - --broadcast Broadcast transactions to network - --verify Verify contracts on Etherscan - -h, --help Show this help message - -Environment Variables: - CHAIN_KEY Required. Deployment environment key (anvil, ethereum-mainnet, ethereum-sepolia, ethereum-sepolia-staging) - DEPLOYER_PRIVATE_KEY Required. Private key for transaction signing (0x...) - NEW_ADMIN Required for ownership transfers. New admin address (0x...) - SKIP_REFERENCE_BUILD Optional. Skip creating reference build and current build if reference already exists - SKIP_SAFETY_CHECKS Optional. Skip all upgrade safety checks and reference build creation (DANGEROUS - use with extreme caution) - -Production Deployment Requirements: - - deployment.toml must have povw-accounting-admin and povw-mint-admin addresses set (not 0x0) - - deployment.toml must have zkc and vezkc addresses set - - Image IDs are loaded from PovwImageId.sol library (can override with env vars) - - POVW_LOG_UPDATER_ID and POVW_MINT_CALCULATOR_ID env vars (optional overrides) - -Development Mode: - RISC0_DEV_MODE=1 Enables mock contract deployment - - Deploys mock verifier and ZKC contracts automatically - - Uses mock image IDs for testing - - Skips deployment.toml updates - -Examples: - # Deploy to anvil in dev mode - RISC0_DEV_MODE=1 CHAIN_KEY=anvil DEPLOYER_PRIVATE_KEY=0xac... $0 DeployPoVW --broadcast - - # Upgrade PovwAccounting - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... $0 UpgradePoVWAccounting --broadcast - - # Upgrade PovwAccounting skipping safety checks (DANGEROUS) - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... SKIP_SAFETY_CHECKS=true $0 UpgradePoVWAccounting --broadcast - - # Transfer ownership of both PoVW contracts - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... NEW_ADMIN=0x... $0 TransferPoVWOwnership --broadcast - -Notes: - - Network configuration is loaded from deployment.toml and deployment_secrets.toml - - Private keys must be provided via DEPLOYER_PRIVATE_KEY environment variable - - Ownership transfers require NEW_ADMIN environment variable with new admin address - - Upgrades and rollbacks use the current owner from the deployed contracts - - Upgrades require reference builds for safety checks (use SKIP_REFERENCE_BUILD=1 to skip builds and reuse existing) - - Fireblocks requires fireblocks-json-rpc to be installed - - All deployments automatically update deployment.toml - - Image IDs are loaded from PovwImageId.sol unless overridden by environment variables - - Ownership transfers are immediate with regular Ownable (no acceptance required) -EOF - exit 0 - fi - - # Load environment variables only when running actual commands - DEPLOY_KEY=${CHAIN_KEY:?} - echo "Loading environment variables from deployment TOML files" - load_env_var "RPC_URL" ".chains[\"${CHAIN_KEY:?}\"].rpc-url" "deployment_secrets.toml" - load_env_var "ETHERSCAN_API_KEY" ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" "deployment_secrets.toml" - load_env_var "CHAIN_ID" ".deployment[\"${DEPLOY_KEY:?}\"].id" "deployment.toml" - - # Check if we're on the correct network - CONNECTED_CHAIN_ID=$(cast chain-id --rpc-url ${RPC_URL:?}) - if [[ "${CONNECTED_CHAIN_ID:?}" != "${CHAIN_ID:?}" ]]; then - echo -e "${RED}Error: connected chain id and configured chain id do not match: ${CONNECTED_CHAIN_ID:?} != ${CHAIN_ID:?} ${NC}" - exit 1 - fi - - # Use standalone Deploy.PoVW.s.sol for deployments, Manage.PoVW.s.sol for everything else - local target - if [ "${script_function:?}" == "DeployPoVW" ]; then - target="${SCRIPT_DIR}/Deploy.PoVW.s.sol:DeployPoVW" - echo "Running standalone deployment script $target" - else - target="${SCRIPT_FILE:?}:${script_function:?}" - echo "Running management script $target" - fi - - if [ $FIREBLOCKS -gt 0 ]; then - # Check for fireblocks - if ! command -v fireblocks-json-rpc &> /dev/null - then - echo "fireblocks-json-rpc not found" - echo "can be installed with npm install -g @fireblocks/fireblocks-json-rpc" - exit 1 - fi - - # Run forge via fireblocks - fireblocks-json-rpc --verbose --rpcUrl ${RPC_URL:?} --http --apiKey ${FIREBLOCKS_API_KEY:?} -- \ - FOUNDRY_PROFILE=shanghai-povw-deploy forge script ${FORGE_SCRIPT_FLAGS} \ - --slow --unlocked \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - --rpc-url {} \ - "$target" "$@" - else - # Run forge - FOUNDRY_PROFILE=shanghai-povw-deploy forge script ${FORGE_SCRIPT_FLAGS} \ - --private-key=${DEPLOYER_PRIVATE_KEY:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - --rpc-url ${RPC_URL:?} \ - "$target" "$@" - fi -} - -# Run from the repo root for consistency. -cd ${REPO_ROOT_DIR:?} - -# Check for clean working directory for deployments and upgrades -check_clean_working_directory() { - local command="$1" - - # Only check for deployment and upgrade commands - case "$command" in - DeployPoVW|UpgradePoVWAccounting|UpgradePoVWMint) - if ! git diff --quiet --exit-code; then - export HAS_UNSTAGED_CHANGES="true" - export UNSTAGED_FILES="$(git diff --name-only | tr '\n' ' ')" - fi - - if ! git diff --quiet --cached --exit-code; then - export HAS_STAGED_CHANGES="true" - export STAGED_FILES="$(git diff --cached --name-only | tr '\n' ' ')" - fi - ;; - esac -} - -# Get current git commit hash for deployment tracking -CURRENT_COMMIT=$(git rev-parse --short HEAD) -export CURRENT_COMMIT - -# Create reference build for upgrade commands -if [ $# -gt 0 ]; then - case "$1" in - UpgradePoVWAccounting) - # Skip reference build if safety checks are disabled - if [ "${SKIP_SAFETY_CHECKS}" = "true" ]; then - echo "⏩ Skipping reference build creation (SKIP_SAFETY_CHECKS=true)" - else - create_reference_build "PoVWAccounting" - - # Only rebuild current contracts if reference build wasn't reused - if [ "${REFERENCE_BUILD_REUSED}" != "true" ]; then - echo "🔨 Ensuring current build is clean and complete..." - echo "Running cargo build to generate required files..." - cargo build - forge clean - FOUNDRY_PROFILE=shanghai-povw-deploy forge build - echo "✅ Current build completed" - else - echo "⏩ Skipping current build (reference build was reused)" - fi - fi - ;; - UpgradePoVWMint) - # Skip reference build if safety checks are disabled - if [ "${SKIP_SAFETY_CHECKS}" = "true" ]; then - echo "⏩ Skipping reference build creation (SKIP_SAFETY_CHECKS=true)" - else - create_reference_build "PoVWMint" - - # Only rebuild current contracts if reference build wasn't reused - if [ "${REFERENCE_BUILD_REUSED}" != "true" ]; then - echo "🔨 Ensuring current build is clean and complete..." - echo "Running cargo build to generate required files..." - cargo build - forge clean - FOUNDRY_PROFILE=shanghai-povw-deploy forge build - echo "✅ Current build completed" - else - echo "⏩ Skipping current build (reference build was reused)" - fi - fi - ;; - esac -fi - -# Default to help if no arguments provided -if [ $# -eq 0 ]; then - forge_script "help" -else - # Check for clean working directory before deployment/upgrade commands - check_clean_working_directory "$1" - forge_script "$@" -fi \ No newline at end of file diff --git a/contracts/shanghai/scripts/manage-verifier b/contracts/shanghai/scripts/manage-verifier deleted file mode 100755 index a261eb63ac..0000000000 --- a/contracts/shanghai/scripts/manage-verifier +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -SCRIPT_FILE="${SCRIPT_DIR}/ManageVerifier.s.sol" -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." -FIREBLOCKS=0 -export FOUNDRY_PROFILE=shanghai - -# # Check for python3, required for updating the deployment toml -# if ! command -v python3 >/dev/null 2>&1; then -# echo "❌ python3 is not installed" -# exit 1 -# fi - -# # Check for tomlkit (Python package), required for updating the deployment toml -# if ! python3 -c "import tomlkit" >/dev/null 2>&1; then -# echo "❌ tomlkit is not installed for python3" -# echo "To install: python3 -m pip install tomlkit" -# exit 1 -# fi - -# Check for yq -if ! command -v yq >/dev/null 2>&1; then - echo "❌ yq is not installed" - echo "Install yq v4+ from: https://github.com/mikefarah/yq" - exit 1 -fi - -POSITIONAL_ARGS=() -FORGE_SCRIPT_FLAGS=() - -while [[ $# -gt 0 ]]; do - case $1 in - -f|--fireblocks) - FIREBLOCKS=1 - shift # past argument - ;; - --broadcast|--verify) - FORGE_SCRIPT_FLAGS+=("$1") - shift - ;; - -g) - shift - FORGE_SCRIPT_FLAGS+=("--gas-estimate-multiplier=$1") - shift - ;; - -*|--*) - FORGE_SCRIPT_FLAGS+=("$1") - shift - # If the next arg exists and doesn't start with -, treat it as the flag's value - if [[ $# -gt 0 && ! "$1" =~ ^- ]]; then - FORGE_SCRIPT_FLAGS+=("$1") - shift - fi - ;; - *) - POSITIONAL_ARGS+=("$1") # save positional arg - shift # past argument - ;; - esac -done - -set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters - -# HINT: deployment_secrets.toml contains API keys. You can write it yourself, or ask a friend. -load_env_var() { - local var_name="$1" - local config_key="$2" - local config_file="$3" - - # Get current value of the variable - local current_value=$(eval echo \$$var_name) - - if [ -z "$current_value" ]; then - echo "$var_name from $config_file: " > /dev/stderr - local new_value=$(yq eval -e "$config_key" "$REPO_ROOT_DIR/contracts/$config_file") - [ -n "$new_value" ] && [[ "$new_value" != "null" ]] || exit 1 - export $var_name="$new_value" - else - echo "$var_name from env $current_value" - fi -} - -# Run a Forge script with support for Fireblocks with options set automatically -forge_script() { - # Set our function. If the function is "help", or if the function is - # unspecified, then print some help. - local script_function="${1:-help}" - shift - - if [ "${script_function:?}" == "help" ]; then - cat << EOF -🔧 Verifiers Management Script -================================== - -Usage: $0 [options] - -Commands: - DeployTimelockRouter Deploy the TimelockController and Verifier Router contracts - DeployEstopBlake3Groth16Verifier Deploy the Estop and Blake3 Groth16 Verifier contracts - ScheduleAddVerifier Schedule adding a verifier to the Verifier Router contract - FinishAddVerifier Finish adding a verifier to the Verifier Router contract - ScheduleRemoveVerifier Schedule removing a verifier from the Verifier Router contract - FinishRemoveVerifier Finish removing a verifier from the Verifier Router contract - ScheduleUpdateDelay Schedule updating the timelock delay on the TimelockController contract - FinishUpdateDelay Finish updating the timelock delay on the TimelockController contract - CancelOperation Cancel a scheduled operation on the TimelockController contract - ScheduleGrantRole Schedule granting a role to an account on the TimelockController contract - FinishGrantRole Finish granting a role to an account on the TimelockController contract - ScheduleRevokeRole Schedule revoking a role from an account on the TimelockController contract - FinishRevokeRole Finish revoking a role from an account on the TimelockController contract - RenounceRole Renounce a role on the TimelockController contract - ActivateEstop Activate the emergency stop on the Verifier Router contract - - RISC Zero Stack (upstream verifier infrastructure): - DeployRisc0TimelockRouter Deploy RISC Zero TimelockController + RiscZeroVerifierRouter - DeployEstopGroth16Verifier Deploy RiscZeroGroth16Verifier + emergency stop - DeployEstopSetVerifier Deploy RiscZeroSetVerifier + emergency stop - ScheduleAddVerifierToRisc0Router Schedule adding a verifier to the RISC Zero router - FinishAddVerifierToRisc0Router Finish adding a verifier to the RISC Zero router - ScheduleUpdateRisc0TimelockDelay Schedule updating the RISC Zero timelock delay - FinishUpdateRisc0TimelockDelay Finish updating the RISC Zero timelock delay - -Options: - -f, --fireblocks Use Fireblocks for transaction signing - --broadcast Broadcast transactions to network - --verify Verify contracts on Etherscan - -g Gas estimate multiplier (e.g. -g 200 for 200%) - -h, --help Show this help message - - Any additional flags (e.g. --gas-limit, --with-gas-price) are passed - through to \`forge script\`. - -Environment Variables: - CHAIN_KEY Required. Deployment environment key (anvil, ethereum-mainnet, ethereum-sepolia, ethereum-sepolia-staging) - STACK_TAG Optional. Stack tag for multi-deployment environments - DEPLOYER_PRIVATE_KEY Required. Private key for transaction signing (0x...) - DEPLOYER_ADDRESS Optional. Address for transaction signing - ADMIN_ADDRESS Optional. Address to use as admin for deployed contracts - VERIFIER_ESTOP_OWNER Optional. Address to set as estop owner for deployed verifiers (defaults to ADMIN_ADDRESS) - GNOSIS_EXECUTE Optional. If true, generate Gnosis Safe calldata for admin operations - VERIFIER_SELECTOR Required for verifier management commands. Verifier selector to add/remove (string) - SCHEDULE_DELAY Optional. Delay (in seconds) for scheduling operations (defaults to timelock delay) - MIN_DELAY Minimum delay (in seconds) for updating the timelock controller - OPERATION_ID Required for CancelOperation command. Operation ID to cancel (bytes32 string) - RENOUNCE_ADDRESS Optional. Address to renounce role from (defaults to DEPLOYER_PRIVATE_KEY address) - RENOUNCE_ROLE Required for RenounceRole command. Role to renounce (bytes32 string) - ACCOUNT Required for role management commands. Account to grant/revoke role to/from - ROLE Required for role management commands. Role to grant/revoke (bytes32 string) - RISC0_TIMELOCK_CONTROLLER Optional. Override RISC Zero timelock address from config - RISC0_ROUTER Optional. Override RISC Zero router address from config - RISC0_MIN_DELAY Required for ScheduleUpdateRisc0TimelockDelay/FinishUpdateRisc0TimelockDelay - SET_BUILDER_IMAGE_ID Required for DeployEstopSetVerifier. SetBuilder guest image ID - SET_BUILDER_GUEST_URL Required for DeployEstopSetVerifier. SetBuilder guest URL - -Examples: - # Deploy TimelockController and Verifier Router - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... $0 DeployTimelockRouter --broadcast - - # Deploy Estop and Blake3 Groth16 Verifier - CHAIN_KEY=ethereum-sepolia DEPLOYER_PRIVATE_KEY=0x... $0 DeployEstopBlake3Groth16Verifier --broadcast - -Notes: - - Network configuration is loaded from deployment_verifier.toml and deployment_secrets.toml - - Private keys must be provided via DEPLOYER_PRIVATE_KEY environment variable - - Admin operations support GNOSIS_EXECUTE=true for Gnosis Safe calldata generation -EOF - exit 0 - fi - - # Load environment variables only when running actual commands - if [ -n "$STACK_TAG" ]; then - DEPLOY_KEY=${CHAIN_KEY:?}-${STACK_TAG:?} - else - DEPLOY_KEY=${CHAIN_KEY:?} - fi - - echo "Loading environment variables from deployment_verifier TOML files" - load_env_var "RPC_URL" ".chains[\"${CHAIN_KEY:?}\"].rpc-url" "deployment_secrets.toml" - load_env_var "ETHERSCAN_API_KEY" ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" "deployment_secrets.toml" - load_env_var "CHAIN_ID" ".chains[\"${CHAIN_KEY:?}\"].id" "deployment_verifier.toml" - - # Load optional gas-estimate-multiplier from config - GAS_MULTIPLIER=$(yq eval ".chains[\"${CHAIN_KEY:?}\"].gas-estimate-multiplier // \"\"" "$REPO_ROOT_DIR/contracts/deployment_verifier.toml") - if [[ -n "$GAS_MULTIPLIER" ]] && ! printf '%s\n' "${FORGE_SCRIPT_FLAGS[@]}" | grep -q -- '--gas-estimate-multiplier='; then - echo "Using gas-estimate-multiplier from config: ${GAS_MULTIPLIER}" - FORGE_SCRIPT_FLAGS+=("--gas-estimate-multiplier=$GAS_MULTIPLIER") - fi - - # Load optional gas-limit from config - GAS_LIMIT=$(yq eval ".chains[\"${CHAIN_KEY:?}\"].gas-limit // \"\"" "$REPO_ROOT_DIR/contracts/deployment_verifier.toml") - if [[ -n "$GAS_LIMIT" ]] && ! printf '%s\n' "${FORGE_SCRIPT_FLAGS[@]}" | grep -q -- '--gas-limit'; then - echo "Using gas-limit from config: ${GAS_LIMIT}" - FORGE_SCRIPT_FLAGS+=("--gas-limit=$GAS_LIMIT") - fi - - # Load optional evm-version from config (e.g. "shanghai" for chains without MCOPY support) - EVM_VERSION=$(yq eval ".chains[\"${CHAIN_KEY:?}\"].evm-version // \"\"" "$REPO_ROOT_DIR/contracts/deployment_verifier.toml") - if [[ -n "$EVM_VERSION" ]] && ! printf '%s\n' "${FORGE_SCRIPT_FLAGS[@]}" | grep -q -- '--evm-version'; then - echo "Using evm-version from config: ${EVM_VERSION}" - FORGE_SCRIPT_FLAGS+=("--evm-version=$EVM_VERSION") - fi - - # Check if we're on the correct network - CONNECTED_CHAIN_ID=$(cast chain-id --rpc-url ${RPC_URL:?}) - if [[ "${CONNECTED_CHAIN_ID:?}" != "${CHAIN_ID:?}" ]]; then - echo -e "${RED}Error: connected chain id and configured chain id do not match: ${CONNECTED_CHAIN_ID:?} != ${CHAIN_ID:?} ${NC}" - echo ${RPC_URL:?} - - exit 1 - fi - - local target="${SCRIPT_FILE:?}:${script_function:?}" - echo "Running forge script $target" - - if [ $FIREBLOCKS -gt 0 ]; then - # Check for fireblocks - if ! command -v fireblocks-json-rpc &> /dev/null - then - echo "fireblocks-json-rpc not found" - echo "can be installed with npm install -g @fireblocks/fireblocks-json-rpc" - exit 1 - fi - - # Run forge via fireblocks - fireblocks-json-rpc --verbose --rpcUrl ${RPC_URL:?} --http --apiKey ${FIREBLOCKS_API_KEY:?} -- \ - forge script "${FORGE_SCRIPT_FLAGS[@]}" \ - --slow --unlocked \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - --rpc-url {} \ - "$target" "$@" - else - # Run forge - forge script "${FORGE_SCRIPT_FLAGS[@]}" \ - --private-key=${DEPLOYER_PRIVATE_KEY:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - --rpc-url ${RPC_URL:?} \ - "$target" "$@" - fi -} - -# Run from the repo root for consistency. -cd ${REPO_ROOT_DIR:?} - -# Get current git commit hash for deployment tracking -CURRENT_COMMIT=$(git rev-parse --short HEAD) -export CURRENT_COMMIT - -forge_script "$@" \ No newline at end of file diff --git a/contracts/shanghai/scripts/test b/contracts/shanghai/scripts/test deleted file mode 100755 index 3f94170b1f..0000000000 --- a/contracts/shanghai/scripts/test +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -# Usage: -# CHAIN_KEY=anvil scripts/test -# CHAIN_KEY=anvil scripts/test --match-contract "PoVWDeploymentTest" - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." -FOUNDRY_PROFILE=${FOUNDRY_PROFILE:-"shanghai"} -# 0x73c457ba is the selector for ZKVM_V3.0, update when necessary -SELECTOR=${SELECTOR:-"0x73c457ba"} -RISC0_DEV_MODE=${RISC0_DEV_MODE:-"false"} -if [[ "$RISC0_DEV_MODE" == "true" || "$RISC0_DEV_MODE" == "1" ]]; then - echo "Running in RISC0 dev mode" - SELECTOR="0xFFFFFFFF" # Use a mock selector in dev mode -else - echo "Running in production mode" -fi - -if [ -n "$STACK_TAG" ]; then - DEPLOY_KEY=${CHAIN_KEY:?}-${STACK_TAG:?} -else - DEPLOY_KEY=${CHAIN_KEY:?} -fi - -load_env_var() { - local var_name="$1" - local config_key="$2" - local config_file="$3" - - # Get current value of the variable - local current_value=$(eval echo \$$var_name) - - if [ -z "$current_value" ]; then - echo "$var_name from $config_file: " > /dev/stderr - local new_value=$(yq eval -e "$config_key" "$REPO_ROOT_DIR/contracts/$config_file") - [ -n "$new_value" ] && [[ "$new_value" != "null" ]] || exit 1 - export $var_name="$new_value" - else - echo "$var_name from env $current_value" - fi -} - -echo "Loading environment variables from deployment TOML files" -load_env_var "RPC_URL" ".chains[\"${CHAIN_KEY:?}\"].rpc-url" "deployment_secrets.toml" -load_env_var "CHAIN_ID" ".deployment[\"${DEPLOY_KEY:?}\"].id" "deployment.toml" - -# Check if we're on the correct network -CONNECTED_CHAIN_ID=$(cast chain-id --rpc-url ${RPC_URL:?}) -if [[ "${CONNECTED_CHAIN_ID:?}" != "${CHAIN_ID:?}" ]]; then - echo -e "${RED}Error: connected chain id and configured chain id do not match: ${CONNECTED_CHAIN_ID:?} != ${CHAIN_ID:?} ${NC}" - exit 1 -fi - -# Run Forge test -forge_test() { - echo "Running forge test" - FOUNDRY_PROFILE=${FOUNDRY_PROFILE:?} \ - RISC0_DEV_MODE=${RISC0_DEV_MODE:?} \ - SELECTOR=${SELECTOR:?} \ - forge test \ - --fork-url ${RPC_URL:?} \ - -vvvv \ - "$@" -} - -# Run from the repo root for consistency. -cd ${REPO_ROOT_DIR:?} - -forge_test "$@" \ No newline at end of file diff --git a/contracts/shanghai/scripts/verify-blake3-groth16-verifier.sh b/contracts/shanghai/scripts/verify-blake3-groth16-verifier.sh deleted file mode 100755 index 2025e194e9..0000000000 --- a/contracts/shanghai/scripts/verify-blake3-groth16-verifier.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_DIR="${SCRIPT_DIR:?}/../.." -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." - -export FOUNDRY_PROFILE=shanghai - -if [ -z "$ETHERSCAN_API_KEY" ]; then - echo -n 'ETHERSCAN_API_KEY from deployment_secrets.toml: ' > /dev/stderr - export ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" $CONTRACTS_DIR/deployment_secrets.toml) -else - echo -n "ETHERSCAN_API_KEY from env $ETHERSCAN_API_KEY" -fi - -export CHAIN_ID=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].id" $CONTRACTS_DIR/deployment_verifier.toml) -export VERIFIER_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\").verifier" $CONTRACTS_DIR/deployment_verifier.toml) -export ESTOP_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\").estop" $CONTRACTS_DIR/deployment_verifier.toml) -export ADMIN_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].admin" $CONTRACTS_DIR/deployment_verifier.toml) - -export CONTROL_ID_FILE="${SCRIPT_DIR:?}/../src/blake3-groth16/ControlID.sol" -export CONTROL_ROOT=$(grep "CONTROL_ROOT" "$CONTROL_ID_FILE" | sed -E 's/.*hex"([^"]+)".*/0x\1/') -export BN254_CONTROL_ID=$(grep "BN254_CONTROL_ID" "$CONTROL_ID_FILE" | sed -E 's/.*hex"([^"]+)".*/0x\1/') - -# NOTE: forge verify-contract seems to fail if an absolute path is used for the contract address. -cd $REPO_ROOT_DIR - -# Run forge build to ensure artifacts are available and built with the right options. -forge build - -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(bytes32,bytes32)' \ - ${CONTROL_ROOT:?} \ - ${BN254_CONTROL_ID:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${VERIFIER_ADDRESS:?} \ - contracts/shanghai/src/blake3-groth16/Blake3Groth16Verifier.sol:Blake3Groth16Verifier - -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address,address)' \ - ${VERIFIER_ADDRESS:?} \ - ${ADMIN_ADDRESS:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${ESTOP_ADDRESS:?} \ - lib/risc0-ethereum/contracts/src/RiscZeroVerifierEmergencyStop.sol:RiscZeroVerifierEmergencyStop diff --git a/contracts/shanghai/scripts/verify-boundless-market.sh b/contracts/shanghai/scripts/verify-boundless-market.sh deleted file mode 100755 index fc876947c3..0000000000 --- a/contracts/shanghai/scripts/verify-boundless-market.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_DIR="${SCRIPT_DIR:?}/../.." -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." - -export FOUNDRY_PROFILE=shanghai - -if [ -z "$ETHERSCAN_API_KEY" ]; then - echo -n 'ETHERSCAN_API_KEY from deployment_secrets.toml: ' > /dev/stderr - export ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" $CONTRACTS_DIR/deployment_secrets.toml) -else - echo -n "ETHERSCAN_API_KEY from env $ETHERSCAN_API_KEY" -fi - -export CHAIN_ID=$(yq eval -e ".deployment.[\"${CHAIN_KEY:?}\"].id" $CONTRACTS_DIR/deployment.toml) -export ADMIN_ADDRESS=$(yq eval -e ".deployment[\"${CHAIN_KEY:?}\"].admin-2" $CONTRACTS_DIR/deployment.toml) -export VERIFIER_ROUTER=$(yq eval -e ".deployment[\"${CHAIN_KEY:?}\"].verifier" $CONTRACTS_DIR/deployment.toml) -export APPLICATION_VERIFIER_ROUTER=$(yq eval -e ".deployment[\"${CHAIN_KEY:?}\"].application-verifier" $CONTRACTS_DIR/deployment.toml) -export BOUNDLESS_MARKET_IMPL=$(yq eval -e ".deployment[\"${CHAIN_KEY:?}\"].boundless-market-impl" $CONTRACTS_DIR/deployment.toml) -export COLLATERAL_TOKEN=$(yq eval -e ".deployment[\"${CHAIN_KEY:?}\"].collateral-token" $CONTRACTS_DIR/deployment.toml) -export ASSESSOR_IMAGE_ID=$(yq eval -e ".deployment[\"${CHAIN_KEY:?}\"].assessor-image-id" $CONTRACTS_DIR/deployment.toml) -export DEPRECATED_ASSESSOR_DURATION=$(yq eval -e ".deployment[\"${CHAIN_KEY:?}\"].deprecated-assessor-duration" $CONTRACTS_DIR/deployment.toml) -export DEPRECATED_ASSESSOR_ID=$(cast call --rpc-url ${RPC_URL:?} ${BOUNDLESS_MARKET_IMPL:?} 'DEPRECATED_ASSESSOR_ID()(bytes32)') - -# NOTE: forge verify-contract seems to fail if an absolute path is used for the contract address. -cd $REPO_ROOT_DIR - -# Run forge build to ensure artifacts are available and built with the right options. -forge build - -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address, address, bytes32, bytes32, uint32, address)' \ - ${VERIFIER_ROUTER:?} \ - ${APPLICATION_VERIFIER_ROUTER:?} \ - ${ASSESSOR_IMAGE_ID:?} \ - ${DEPRECATED_ASSESSOR_ID:?} \ - ${DEPRECATED_ASSESSOR_DURATION:?} \ - ${COLLATERAL_TOKEN:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${BOUNDLESS_MARKET_IMPL:?} \ - contracts/shanghai/src/BoundlessMarket.sol:BoundlessMarket diff --git a/contracts/shanghai/scripts/verify-risc0-groth16-verifier.sh b/contracts/shanghai/scripts/verify-risc0-groth16-verifier.sh deleted file mode 100755 index 3ac36c6881..0000000000 --- a/contracts/shanghai/scripts/verify-risc0-groth16-verifier.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_DIR="${SCRIPT_DIR:?}/../.." -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." - -export FOUNDRY_PROFILE=shanghai - -if [ -z "$ETHERSCAN_API_KEY" ]; then - echo -n 'ETHERSCAN_API_KEY from deployment_secrets.toml: ' > /dev/stderr - ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" $CONTRACTS_DIR/deployment_secrets.toml) - export ETHERSCAN_API_KEY -else - echo -n "ETHERSCAN_API_KEY from env $ETHERSCAN_API_KEY" -fi - -CHAIN_ID=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].id" $CONTRACTS_DIR/deployment_verifier.toml) -ADMIN_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].admin" $CONTRACTS_DIR/deployment_verifier.toml) - -# Read the groth16 verifier and estop addresses from risc0-verifiers config. -# VERIFIER_SELECTOR must be set (e.g. 0x73c457ba for ZKVM_V3.0). -VERIFIER_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\").verifier" $CONTRACTS_DIR/deployment_verifier.toml) -ESTOP_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\").estop" $CONTRACTS_DIR/deployment_verifier.toml) - -# Read control IDs from the upstream Groth16 verifier library. -CONTROL_ID_FILE="lib/risc0-ethereum/contracts/src/groth16/ControlID.sol" -CONTROL_ROOT=$(grep "CONTROL_ROOT" "$REPO_ROOT_DIR/$CONTROL_ID_FILE" | sed -E 's/.*hex"([^"]+)".*/0x\1/') -BN254_CONTROL_ID=$(grep "BN254_CONTROL_ID" "$REPO_ROOT_DIR/$CONTROL_ID_FILE" | sed -E 's/.*hex"([^"]+)".*/0x\1/') - -export CHAIN_ID ADMIN_ADDRESS VERIFIER_ADDRESS ESTOP_ADDRESS CONTROL_ID_FILE CONTROL_ROOT BN254_CONTROL_ID - -# NOTE: forge verify-contract seems to fail if an absolute path is used for the contract address. -cd $REPO_ROOT_DIR - -# Run forge build to ensure artifacts are available and built with the right options. -forge build - -# Verify the RiscZeroGroth16Verifier -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(bytes32,bytes32)' \ - ${CONTROL_ROOT:?} \ - ${BN254_CONTROL_ID:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${VERIFIER_ADDRESS:?} \ - lib/risc0-ethereum/contracts/src/groth16/RiscZeroGroth16Verifier.sol:RiscZeroGroth16Verifier - -# Verify the RiscZeroVerifierEmergencyStop wrapping the Groth16 verifier -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address,address)' \ - ${VERIFIER_ADDRESS:?} \ - ${ADMIN_ADDRESS:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${ESTOP_ADDRESS:?} \ - lib/risc0-ethereum/contracts/src/RiscZeroVerifierEmergencyStop.sol:RiscZeroVerifierEmergencyStop diff --git a/contracts/shanghai/scripts/verify-risc0-router.sh b/contracts/shanghai/scripts/verify-risc0-router.sh deleted file mode 100755 index 588499787f..0000000000 --- a/contracts/shanghai/scripts/verify-risc0-router.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_DIR="${SCRIPT_DIR:?}/../.." -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." - -export FOUNDRY_PROFILE=shanghai - -if [ -z "$ETHERSCAN_API_KEY" ]; then - echo -n 'ETHERSCAN_API_KEY from deployment_secrets.toml: ' > /dev/stderr - ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" $CONTRACTS_DIR/deployment_secrets.toml) - export ETHERSCAN_API_KEY -else - echo -n "ETHERSCAN_API_KEY from env $ETHERSCAN_API_KEY" -fi - -CHAIN_ID=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].id" $CONTRACTS_DIR/deployment_verifier.toml) -ADMIN_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].admin" $CONTRACTS_DIR/deployment_verifier.toml) -RISC0_ROUTER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-router" $CONTRACTS_DIR/deployment_verifier.toml) -RISC0_TIMELOCK_CONTROLLER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-timelock-controller" $CONTRACTS_DIR/deployment_verifier.toml) -RISC0_TIMELOCK_DELAY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-timelock-delay" $CONTRACTS_DIR/deployment_verifier.toml) - -export CHAIN_ID ADMIN_ADDRESS RISC0_ROUTER RISC0_TIMELOCK_CONTROLLER RISC0_TIMELOCK_DELAY - -# NOTE: forge verify-contract seems to fail if an absolute path is used for the contract address. -cd $REPO_ROOT_DIR - -# Run forge build to ensure artifacts are available and built with the right options. -forge build - -# Verify the RiscZeroVerifierRouter -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address)' \ - ${RISC0_TIMELOCK_CONTROLLER:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${RISC0_ROUTER:?} \ - contracts/shanghai/src/verifier/RiscZeroVerifierRouter.sol:RiscZeroVerifierRouter - -# Verify the TimelockController -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(uint256,address[],address[],address)' \ - ${RISC0_TIMELOCK_DELAY:?} \ - "[${ADMIN_ADDRESS:?}]" \ - "[${ADMIN_ADDRESS:?}]" \ - ${ADMIN_ADDRESS:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${RISC0_TIMELOCK_CONTROLLER:?} \ - lib/openzeppelin-contracts/contracts/governance/TimelockController.sol:TimelockController diff --git a/contracts/shanghai/scripts/verify-risc0-set-verifier.sh b/contracts/shanghai/scripts/verify-risc0-set-verifier.sh deleted file mode 100755 index 4fcb790127..0000000000 --- a/contracts/shanghai/scripts/verify-risc0-set-verifier.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_DIR="${SCRIPT_DIR:?}/../.." -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." - -export FOUNDRY_PROFILE=shanghai - -if [ -z "$ETHERSCAN_API_KEY" ]; then - echo -n 'ETHERSCAN_API_KEY from deployment_secrets.toml: ' > /dev/stderr - ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" $CONTRACTS_DIR/deployment_secrets.toml) - export ETHERSCAN_API_KEY -else - echo -n "ETHERSCAN_API_KEY from env $ETHERSCAN_API_KEY" -fi - -CHAIN_ID=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].id" $CONTRACTS_DIR/deployment_verifier.toml) -ADMIN_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].admin" $CONTRACTS_DIR/deployment_verifier.toml) -RISC0_ROUTER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-router" $CONTRACTS_DIR/deployment_verifier.toml) - -# Read the set verifier and estop addresses from risc0-verifiers config. -# VERIFIER_SELECTOR must be set (e.g. 0x242f9d5b for RiscZeroSetVerifier). -VERIFIER_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\").verifier" $CONTRACTS_DIR/deployment_verifier.toml) -ESTOP_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].risc0-verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\").estop" $CONTRACTS_DIR/deployment_verifier.toml) - -export CHAIN_ID ADMIN_ADDRESS RISC0_ROUTER VERIFIER_ADDRESS ESTOP_ADDRESS - -# SET_BUILDER_IMAGE_ID and SET_BUILDER_GUEST_URL must be provided as env vars, -# matching the values used at deployment time. -: "${SET_BUILDER_IMAGE_ID:?SET_BUILDER_IMAGE_ID must be set}" -: "${SET_BUILDER_GUEST_URL:?SET_BUILDER_GUEST_URL must be set}" - -# NOTE: forge verify-contract seems to fail if an absolute path is used for the contract address. -cd $REPO_ROOT_DIR - -# Run forge build to ensure artifacts are available and built with the right options. -forge build - -# Verify the RiscZeroSetVerifier -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address,bytes32,string)' \ - ${RISC0_ROUTER:?} \ - ${SET_BUILDER_IMAGE_ID:?} \ - "${SET_BUILDER_GUEST_URL:?}" \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${VERIFIER_ADDRESS:?} \ - lib/risc0-ethereum/contracts/src/RiscZeroSetVerifier.sol:RiscZeroSetVerifier - -# Verify the RiscZeroVerifierEmergencyStop wrapping the set verifier -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address,address)' \ - ${VERIFIER_ADDRESS:?} \ - ${ADMIN_ADDRESS:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${ESTOP_ADDRESS:?} \ - lib/risc0-ethereum/contracts/src/RiscZeroVerifierEmergencyStop.sol:RiscZeroVerifierEmergencyStop diff --git a/contracts/shanghai/scripts/verify-router.sh b/contracts/shanghai/scripts/verify-router.sh deleted file mode 100755 index 8637d78c58..0000000000 --- a/contracts/shanghai/scripts/verify-router.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_DIR="${SCRIPT_DIR:?}/../.." -REPO_ROOT_DIR="${SCRIPT_DIR:?}/../../.." - -export FOUNDRY_PROFILE=shanghai - -if [ -z "$ETHERSCAN_API_KEY" ]; then - echo -n 'ETHERSCAN_API_KEY from deployment_secrets.toml: ' > /dev/stderr - export ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" $CONTRACTS_DIR/deployment_secrets.toml) -else - echo -n "ETHERSCAN_API_KEY from env $ETHERSCAN_API_KEY" -fi - -export CHAIN_ID=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].id" $CONTRACTS_DIR/deployment_verifier.toml) -export ADMIN_ADDRESS=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].admin" $CONTRACTS_DIR/deployment_verifier.toml) -export TIMELOCK_CONTROLLER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].timelock-controller" $CONTRACTS_DIR/deployment_verifier.toml) -export VERIFIER_ROUTER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].router" $CONTRACTS_DIR/deployment_verifier.toml) -export PARENT_ROUTER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].parent-router" $CONTRACTS_DIR/deployment_verifier.toml) -export MIN_DELAY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].timelock-delay" $CONTRACTS_DIR/deployment_verifier.toml) - - -# NOTE: forge verify-contract seems to fail if an absolute path is used for the contract address. -cd $REPO_ROOT_DIR - -# Run forge build to ensure artifacts are available and built with the right options. -forge build - -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(address, address)' \ - ${TIMELOCK_CONTROLLER:?} \ - ${PARENT_ROUTER:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${VERIFIER_ROUTER:?} \ - contracts/shanghai/src/verifier/VerifierLayeredRouter.sol:VerifierLayeredRouter - -CONSTRUCTOR_ARGS="$(\ - cast abi-encode 'constructor(uint256,address[],address[],address)' \ - ${MIN_DELAY:?} \ - [${ADMIN_ADDRESS:?}] \ - [${ADMIN_ADDRESS:?}] \ - ${ADMIN_ADDRESS:?} \ -)" -forge verify-contract --watch \ - --chain-id=${CHAIN_ID:?} \ - --constructor-args=${CONSTRUCTOR_ARGS:?} \ - --etherscan-api-key=${ETHERSCAN_API_KEY:?} \ - ${TIMELOCK_CONTROLLER:?} \ - lib/openzeppelin-contracts/contracts/governance/TimelockController.sol:TimelockController diff --git a/contracts/shanghai/src/BoundlessMarketCallback.sol b/contracts/shanghai/src/BoundlessMarketCallback.sol deleted file mode 100644 index 5caa7ae2c8..0000000000 --- a/contracts/shanghai/src/BoundlessMarketCallback.sol +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.26; - -import {IRiscZeroVerifier, Receipt, ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; -import {IBoundlessMarketCallback} from "./IBoundlessMarketCallback.sol"; - -/// @notice Contract for handling proofs delivered by the Boundless Market's callback mechanism. -/// @dev This contract provides a framework for applications to safely handle proofs delivered by -/// the Boundless Market for a specific image ID. The intention is for developers to inherit the contract -/// and implement the internal `_handleProof` function. -/// @dev We recommend a best practice of "trust but verify" whenever receiving proofs, so we verify the proof -/// here even though the Boundless Market already verifies the proof as part of its fulfillment process. -/// Proof verification in Boundless is cheap as it is just a merkle proof, so this adds minimal overhead. -abstract contract BoundlessMarketCallback is IBoundlessMarketCallback { - using ReceiptClaimLib for ReceiptClaim; - - IRiscZeroVerifier public immutable VERIFIER; - address public immutable BOUNDLESS_MARKET; - bytes32 public immutable IMAGE_ID; - - /// @notice Initializes the callback contract with verifier and market addresses - /// @param verifier The RISC Zero verifier contract address - /// @param boundlessMarket The BoundlessMarket contract address - /// @param imageId The image ID to accept proofs of. - constructor(IRiscZeroVerifier verifier, address boundlessMarket, bytes32 imageId) { - VERIFIER = verifier; - BOUNDLESS_MARKET = boundlessMarket; - IMAGE_ID = imageId; - } - - /// @inheritdoc IBoundlessMarketCallback - function handleProof(bytes32 imageId, bytes calldata journal, bytes calldata seal) public { - require(msg.sender == BOUNDLESS_MARKET, "Invalid sender"); - require(imageId == IMAGE_ID, "Invalid Image ID"); - // Verify the proof before calling callback - bytes32 claimDigest = ReceiptClaimLib.ok(imageId, sha256(journal)).digest(); - VERIFIER.verifyIntegrity(Receipt(seal, claimDigest)); - _handleProof(imageId, journal, seal); - } - - /// @notice Internal function to be implemented by inheriting contracts - /// @dev Override this function to implement custom proof handling logic - /// @param imageId The ID of the RISC Zero guest image that produced the proof - /// @param journal The output journal from the RISC Zero guest execution - /// @param seal The cryptographic seal proving correct execution - function _handleProof(bytes32 imageId, bytes calldata journal, bytes calldata seal) internal virtual; -} diff --git a/contracts/shanghai/src/HitPoints.sol b/contracts/shanghai/src/HitPoints.sol deleted file mode 100644 index 8b19ea3736..0000000000 --- a/contracts/shanghai/src/HitPoints.sol +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pragma solidity ^0.8.26; - -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -import {IHitPoints} from "./IHitPoints.sol"; - -/// @title HitPoints ERC20 -/// @notice Implementation of a restricted transfer token using ERC20 -contract HitPoints is ERC20, ERC20Permit, IHitPoints, AccessControl, Ownable { - // Maximum allowed balance (uint96 max value) - uint256 constant MAX_BALANCE = type(uint96).max; - // Role identifier for minting operation - bytes32 public constant MINTER = keccak256("MINTER"); - // Role identifier for authorized transfer - bytes32 public constant AUTHORIZED_TRANSFER = keccak256("AUTHORIZED_TRANSFER"); - - constructor(address initialOwner) ERC20("HitPoints", "HP") ERC20Permit("HitPoints") Ownable(initialOwner) { - _grantRole(DEFAULT_ADMIN_ROLE, initialOwner); - // Authorize address(0) as a sender and receiver to simplify mints and burns. - _grantRole(AUTHORIZED_TRANSFER, address(0)); - } - - /// @inheritdoc Ownable - function transferOwnership(address newOwner) public override onlyOwner { - _revokeRole(DEFAULT_ADMIN_ROLE, owner()); - _grantRole(DEFAULT_ADMIN_ROLE, newOwner); - super.transferOwnership(newOwner); - } - - /// @inheritdoc IHitPoints - function grantMinterRole(address account) external onlyOwner { - _grantRole(MINTER, account); - } - - /// @inheritdoc IHitPoints - function revokeMinterRole(address account) external onlyOwner { - _revokeRole(MINTER, account); - } - - /// @inheritdoc IHitPoints - function grantAuthorizedTransferRole(address account) external onlyOwner { - _grantRole(AUTHORIZED_TRANSFER, account); - } - - /// @inheritdoc IHitPoints - function revokeAuthorizedTransferRole(address account) external onlyOwner { - _revokeRole(AUTHORIZED_TRANSFER, account); - } - - /// @inheritdoc IHitPoints - function mint(address account, uint256 value) external onlyRole(MINTER) { - _mint(account, value); - } - - function _update(address from, address to, uint256 value) internal virtual override { - // Either the sender or the receiver must be authorized. - if (!hasRole(AUTHORIZED_TRANSFER, from) && !hasRole(AUTHORIZED_TRANSFER, to)) { - revert UnauthorizedTransfer(); - } - - super._update(from, to, value); - - // Ensure the recipient's balance didn't exceed MAX_BALANCE. - if (to != address(0) && balanceOf(to) > MAX_BALANCE) { - revert BalanceExceedsLimit(to, balanceOf(to) - value, value); - } - } -} diff --git a/contracts/shanghai/src/IHitPoints.sol b/contracts/shanghai/src/IHitPoints.sol deleted file mode 100644 index 1c0af1434d..0000000000 --- a/contracts/shanghai/src/IHitPoints.sol +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pragma solidity ^0.8.26; - -/// @title IHitPoints ERC20 -/// @notice Interface of a restricted transfer token using ERC20 -interface IHitPoints { - /// @dev Thrown when trying to transfer tokens from/to an unauthorized address - error UnauthorizedTransfer(); - /// @dev Thrown when balance exceeds uint96 max - error BalanceExceedsLimit(address account, uint256 currentBalance, uint256 addedAmount); - - /// @notice Grants the MINTER role to an account - /// @dev This role is used to allow minting new tokens - /// @param account The address that will receive the minter role - function grantMinterRole(address account) external; - - /// @notice Revokes the MINTER role from an account - /// @param account The address that will lose the minter role - function revokeMinterRole(address account) external; - - /// @notice Grants the AUTHORIZED_TRANSFER role to an account - /// @dev This role is used to allow transfers from/to an address - /// @param account The address that will receive the authorized transfer role - function grantAuthorizedTransferRole(address account) external; - - /// @notice Revokes the AUTHORIZED_TRANSFER role from an account - /// @param account The address that will lose the authorized transfer role - function revokeAuthorizedTransferRole(address account) external; - - /// @notice Creates new tokens and assigns them to an account - /// @param account The address that will receive the minted tokens - /// @param value The `value` amount of tokens to mint - function mint(address account, uint256 value) external; -} diff --git a/contracts/shanghai/src/SetBuilderImageID.sol b/contracts/shanghai/src/SetBuilderImageID.sol deleted file mode 100644 index 850c1221a3..0000000000 --- a/contracts/shanghai/src/SetBuilderImageID.sol +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -// This file is automatically generated - -pragma solidity ^0.8.26; - -library ImageID { - bytes32 public constant SET_BUILDER_GUEST_ID = - bytes32(0xadf6561b339c5965ed862f4f25d5ab573abffa2e71573f5e278bed06d9cc0afe); -} diff --git a/contracts/shanghai/src/blake3-groth16/Blake3Groth16Verifier.sol b/contracts/shanghai/src/blake3-groth16/Blake3Groth16Verifier.sol deleted file mode 100644 index 8ad73c79a2..0000000000 --- a/contracts/shanghai/src/blake3-groth16/Blake3Groth16Verifier.sol +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.9; - -import {SafeCast} from "openzeppelin/contracts/utils/math/SafeCast.sol"; - -import {Groth16Verifier} from "./Groth16Verifier.sol"; -import { - IRiscZeroVerifier, - Output, - OutputLib, - Receipt, - ReceiptClaim, - ReceiptClaimLib, - VerificationFailed -} from "risc0/IRiscZeroVerifier.sol"; -import {StructHash} from "risc0/StructHash.sol"; -import {reverseByteOrderUint256} from "risc0/Util.sol"; -import {IRiscZeroSelectable} from "risc0/IRiscZeroSelectable.sol"; - -/// @notice A Groth16 seal over the claimed receipt claim. -struct Seal { - uint256[2] a; - uint256[2][2] b; - uint256[2] c; -} - -/// @notice Error raised when this verifier receives a receipt with a selector that does not match -/// its own. The selector value is calculated from the verifier parameters, and so this -/// usually indicates a mismatch between the version of the prover and this verifier. -error SelectorMismatch(bytes4 received, bytes4 expected); - -/// @notice Blake3Groth16 verifier contract for RISC Zero receipts of execution. -contract Blake3Groth16Verifier is IRiscZeroVerifier, IRiscZeroSelectable, Groth16Verifier { - using ReceiptClaimLib for ReceiptClaim; - using OutputLib for Output; - using SafeCast for uint256; - - /// @notice Semantic version of the RISC Zero system of which this contract is part. - /// @dev This is set to be equal to the version of the risc0-zkvm crate. - string public constant VERSION = "0.0.1"; - - /// @notice Control root hash binding the set of circuits in the RISC Zero system. - /// @dev This value controls what set of recursion programs (e.g. lift, join, resolve), and - /// therefore what version of the zkVM circuit, will be accepted by this contract. Each - /// instance of this verifier contract will accept a single release of the RISC Zero circuits. - /// - /// New releases of RISC Zero's zkVM require updating these values. These values can be - /// calculated from the [risc0 monorepo][1] using: `cargo xtask bootstrap`. - /// - /// [1]: https://github.com/risc0/risc0 - bytes16 public immutable CONTROL_ROOT_0; - bytes16 public immutable CONTROL_ROOT_1; - bytes32 public immutable BN254_CONTROL_ID; - - /// @notice A short key attached to the seal to select the correct verifier implementation. - /// @dev The selector is taken from the hash of the verifier parameters including the Groth16 - /// verification key and the control IDs that commit to the RISC Zero circuits. If two - /// receipts have different selectors (i.e. different verifier parameters), then it can - /// generally be assumed that they need distinct verifier implementations. This is used as - /// part of the RISC Zero versioning mechanism. - /// - /// A selector is not intended to be collision resistant, in that it is possible to find - /// two preimages that result in the same selector. This is acceptable since it's purpose - /// to a route a request among a set of trusted verifiers, and to make errors of sending a - /// receipt to a mismatching verifiers easier to debug. It is analogous to the ABI - /// function selectors. - bytes4 public immutable SELECTOR; - - /// @notice Identifier for the Groth16 verification key encoded into the base contract. - /// @dev This value is computed at compile time. - function verifierKeyDigest() internal pure returns (bytes32) { - bytes32[] memory icDigests = new bytes32[](2); - icDigests[0] = sha256(abi.encodePacked(IC0x, IC0y)); - icDigests[1] = sha256(abi.encodePacked(IC1x, IC1y)); - - return sha256( - abi.encodePacked( - // tag - sha256("risc0_groth16.VerifyingKey"), - // down - sha256(abi.encodePacked(alphax, alphay)), - sha256(abi.encodePacked(betax1, betax2, betay1, betay2)), - sha256(abi.encodePacked(gammax1, gammax2, gammay1, gammay2)), - sha256(abi.encodePacked(deltax1, deltax2, deltay1, deltay2)), - StructHash.taggedList(sha256("risc0_groth16.VerifyingKey.IC"), icDigests), - // down length - uint16(5) << 8 - ) - ); - } - - constructor(bytes32 controlRoot, bytes32 bn254ControlId) { - (CONTROL_ROOT_0, CONTROL_ROOT_1) = splitDigest(controlRoot); - BN254_CONTROL_ID = bn254ControlId; - - SELECTOR = bytes4( - sha256( - abi.encodePacked( - // tag - sha256("risc0.Groth16ReceiptVerifierParameters"), - // down - controlRoot, - reverseByteOrderUint256(uint256(bn254ControlId)), - verifierKeyDigest(), - // down length - uint16(3) << 8 - ) - ) - ); - } - - /// @notice splits a digest into two 128-bit halves to use as public signal inputs. - /// @dev RISC Zero's Circom verifier circuit takes each of two hash digests in two 128-bit - /// chunks. These values can be derived from the digest by splitting the digest in half and - /// then reversing the bytes of each. - function splitDigest(bytes32 digest) internal pure returns (bytes16, bytes16) { - uint256 reversed = reverseByteOrderUint256(uint256(digest)); - return (bytes16(uint128(reversed)), bytes16(uint128(reversed >> 128))); - } - - /// @inheritdoc IRiscZeroVerifier - function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external pure { - seal; - imageId; - journalDigest; - revert("Use verifyIntegrity"); - } - - /// @inheritdoc IRiscZeroVerifier - function verifyIntegrity(Receipt calldata receipt) external view { - return _verifyIntegrity(receipt.seal, receipt.claimDigest); - } - - /// @notice internal implementation of verifyIntegrity, factored to avoid copying calldata bytes to memory. - function _verifyIntegrity(bytes calldata seal, bytes32 claimDigest) internal view { - // Check that the seal has a matching selector. Mismatch generally indicates that the - // prover and this verifier are using different parameters, and so the verification - // will not succeed. - if (SELECTOR != bytes4(seal[:4])) { - revert SelectorMismatch({received: bytes4(seal[:4]), expected: SELECTOR}); - } - - // Run the Groth16 verify procedure. - Seal memory decodedSeal = abi.decode(seal[4:], (Seal)); - bool verified = this.verifyProof( - decodedSeal.a, - decodedSeal.b, - decodedSeal.c, - [ - /// Blake3(Sha256(control_root, pre_state_digest, post_state_digest, id_bn254_fr), journal)[:31] - uint256(claimDigest) - ] - ); - - // Revert is verification failed. - if (!verified) { - revert VerificationFailed(); - } - } -} diff --git a/contracts/shanghai/src/blake3-groth16/ControlID.sol b/contracts/shanghai/src/blake3-groth16/ControlID.sol deleted file mode 100644 index faf38a629b..0000000000 --- a/contracts/shanghai/src/blake3-groth16/ControlID.sol +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -// This file is automatically generated by: -// cargo xtask bootstrap-groth16 - -pragma solidity ^0.8.9; - -library ControlID { - bytes32 public constant CONTROL_ROOT = hex"a54dc85ac99f851c92d7c96d7318af41dbe7c0194edfcc37eb4d422a998c1f56"; - // NOTE: This has the opposite byte order to the value in the risc0 repository. - bytes32 public constant BN254_CONTROL_ID = hex"04446e66d300eb7fb45c9726bb53c793dda407a62e9601618bb43c5c14657ac0"; -} diff --git a/contracts/shanghai/src/blake3-groth16/Groth16Verifier.sol b/contracts/shanghai/src/blake3-groth16/Groth16Verifier.sol deleted file mode 100644 index 093ad633de..0000000000 --- a/contracts/shanghai/src/blake3-groth16/Groth16Verifier.sol +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -/* - Copyright 2021 0KIMS association. - - This file is generated with [snarkJS](https://github.com/iden3/snarkjs). - - snarkJS is a free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - snarkJS is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public - License for more details. - - You should have received a copy of the GNU General Public License - along with snarkJS. If not, see . -*/ - -pragma solidity >=0.7.0 <0.9.0; - -contract Groth16Verifier { - // Scalar field size - uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - // Base field size - uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; - - // Verification Key data - uint256 constant alphax = 16428432848801857252194528405604668803277877773566238944394625302971855135431; - uint256 constant alphay = 16846502678714586896801519656441059708016666274385668027902869494772365009666; - uint256 constant betax1 = 3182164110458002340215786955198810119980427837186618912744689678939861918171; - uint256 constant betax2 = 16348171800823588416173124589066524623406261996681292662100840445103873053252; - uint256 constant betay1 = 4920802715848186258981584729175884379674325733638798907835771393452862684714; - uint256 constant betay2 = 19687132236965066906216944365591810874384658708175106803089633851114028275753; - uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; - uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; - uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; - uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; - uint256 constant deltax1 = 18786665442134809547367793008388252094276956707083189371748822844215202271178; - uint256 constant deltax2 = 17296777349791701671871010047490559682924748762983962242018229225890177681165; - uint256 constant deltay1 = 21546884238630900902634517213362010321565339505810557359182294051078510536811; - uint256 constant deltay2 = 7214627676570978956115414107903354102221009447018809863680303520130992055423; - - - uint256 constant IC0x = 1396989810128049774239906514097458055670219613079348950494410066757721605523; - uint256 constant IC0y = 20069629286434534534516684991063672335613842540347999544849171590987775766961; - - uint256 constant IC1x = 19282603452922066135228857769519044667044696173320493211119861249451600114594; - uint256 constant IC1y = 11966256187809052800087108088094647243345273965264062329687482664981607072161; - - - // Memory data - uint16 constant pVk = 0; - uint16 constant pPairing = 128; - - uint16 constant pLastMem = 896; - - function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) public view returns (bool) { - assembly { - function checkField(v) { - if iszero(lt(v, r)) { - mstore(0, 0) - return(0, 0x20) - } - } - - // G1 function to multiply a G1 value(x,y) to value in an address - function g1_mulAccC(pR, x, y, s) { - let success - let mIn := mload(0x40) - mstore(mIn, x) - mstore(add(mIn, 32), y) - mstore(add(mIn, 64), s) - - success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) - - if iszero(success) { - mstore(0, 0) - return(0, 0x20) - } - - mstore(add(mIn, 64), mload(pR)) - mstore(add(mIn, 96), mload(add(pR, 32))) - - success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) - - if iszero(success) { - mstore(0, 0) - return(0, 0x20) - } - } - - function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { - let _pPairing := add(pMem, pPairing) - let _pVk := add(pMem, pVk) - - mstore(_pVk, IC0x) - mstore(add(_pVk, 32), IC0y) - - // Compute the linear combination vk_x - - g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) - - - // -A - mstore(_pPairing, calldataload(pA)) - mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) - - // B - mstore(add(_pPairing, 64), calldataload(pB)) - mstore(add(_pPairing, 96), calldataload(add(pB, 32))) - mstore(add(_pPairing, 128), calldataload(add(pB, 64))) - mstore(add(_pPairing, 160), calldataload(add(pB, 96))) - - // alpha1 - mstore(add(_pPairing, 192), alphax) - mstore(add(_pPairing, 224), alphay) - - // beta2 - mstore(add(_pPairing, 256), betax1) - mstore(add(_pPairing, 288), betax2) - mstore(add(_pPairing, 320), betay1) - mstore(add(_pPairing, 352), betay2) - - // vk_x - mstore(add(_pPairing, 384), mload(add(pMem, pVk))) - mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) - - - // gamma2 - mstore(add(_pPairing, 448), gammax1) - mstore(add(_pPairing, 480), gammax2) - mstore(add(_pPairing, 512), gammay1) - mstore(add(_pPairing, 544), gammay2) - - // C - mstore(add(_pPairing, 576), calldataload(pC)) - mstore(add(_pPairing, 608), calldataload(add(pC, 32))) - - // delta2 - mstore(add(_pPairing, 640), deltax1) - mstore(add(_pPairing, 672), deltax2) - mstore(add(_pPairing, 704), deltay1) - mstore(add(_pPairing, 736), deltay2) - - - let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) - - isOk := and(success, mload(_pPairing)) - } - - let pMem := mload(0x40) - mstore(0x40, add(pMem, pLastMem)) - - // Validate that all evaluations ∈ F - - checkField(calldataload(add(_pubSignals, 0))) - - - // Validate all evaluations - let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) - - mstore(0, isValid) - return(0, 0x20) - } - } -} diff --git a/contracts/shanghai/src/config/VerifierConfig.sol b/contracts/shanghai/src/config/VerifierConfig.sol deleted file mode 100644 index f619219ae7..0000000000 --- a/contracts/shanghai/src/config/VerifierConfig.sol +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.20; - -import {Vm} from "forge-std/Vm.sol"; -import {console2} from "forge-std/console2.sol"; -import {stdToml} from "forge-std/StdToml.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -/// Deployment of a single verifier. -/// -/// Many verifiers may be part of a deployment, with the router serving the purpose of making them -/// all accessible at a single address. -struct VerifierDeployment { - string name; - string version; - bytes4 selector; - address verifier; - address estop; - /// Specifies that this verifier is not deployed to the verifier router. - /// Default is false since most of the verifiers in the config are intended to be routable. - bool unroutable; - /// Flag set when the verifier has had its estop activated. Once activated, - /// the estop verifier is permanently inoperable. - bool stopped; -} - -/// Deployment of the verifier contracts on a particular chain. -/// -/// The deployment.toml file contains a number of deployments. Each is indexed by a "chain key", -/// such as "ethereum-mainnet". This struct represents the values in one of those deployments. -struct Deployment { - /// A friendly name for the network, such as "Ethereum Mainnet". - string name; - /// Chain ID for the network. - uint256 chainId; - /// Admin address for emergency stop contracts on this network, as well as the proposer for the - /// timelock controller that acts as the admin for the router. - address admin; - /// Address of the verifier router in this deployment. - address router; - /// Address of the parent verifier router in this deployment. - address parentRouter; - /// Address of the timelock control in this deployment, which is set as the admin of the router. - address timelockController; - /// Min delay configured on the timelock controller. - uint256 timelockDelay; - /// Deployed verifier implementations. - VerifierDeployment[] verifiers; - /// Address of the RISC Zero stack verifier router (upstream verifier infrastructure). - address risc0Router; - /// Address of the RISC Zero stack timelock controller (upstream verifier infrastructure). - address risc0TimelockController; - /// Min delay configured on the RISC Zero stack timelock controller. - uint256 risc0TimelockDelay; - /// Deployed RISC Zero stack verifier implementations. - VerifierDeployment[] risc0Verifiers; -} - -library DeploymentLib { - /// Copy the deployment from memory to storage. - /// Solidity does not allow this to be done via the assignment operator. - function copyTo(Deployment memory mem, Deployment storage stor) internal { - stor.name = mem.name; - stor.chainId = mem.chainId; - stor.admin = mem.admin; - stor.router = mem.router; - stor.parentRouter = mem.parentRouter; - stor.timelockController = mem.timelockController; - stor.timelockDelay = mem.timelockDelay; - delete stor.verifiers; - for (uint256 i = 0; i < mem.verifiers.length; i++) { - stor.verifiers.push(mem.verifiers[i]); - } - stor.risc0Router = mem.risc0Router; - stor.risc0TimelockController = mem.risc0TimelockController; - stor.risc0TimelockDelay = mem.risc0TimelockDelay; - delete stor.risc0Verifiers; - for (uint256 i = 0; i < mem.risc0Verifiers.length; i++) { - stor.risc0Verifiers.push(mem.risc0Verifiers[i]); - } - } -} - -library ConfigLoader { - /// Reference the vm address without needing to inherit from Script. - Vm private constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - /// Given the contents of the deployment.toml file, determine the active chain key. - /// This function first checks the "CHAIN_KEY" environment variable and uses the value if set. - /// If not set, this function looks for a deployment in the given TOML with a matching chainId - /// field and returns the first matching result. - function determineChainKey(string memory configToml) internal view returns (string memory) { - // Get the config profile from the environment variable, or leave it empty - string memory chainKey = VM.envOr("CHAIN_KEY", string("")); - - if (bytes(chainKey).length != 0) { - console2.log("Using chain key %s set via environment variable", chainKey); - } else { - // Since no chain key is set, select the default one based on the chainId - console2.log("Determining chain key from chain ID %d", block.chainid); - string[] memory chainKeys = VM.parseTomlKeys(configToml, ".chains"); - for (uint256 i = 0; i < chainKeys.length; i++) { - if (stdToml.readUint(configToml, string.concat(".chains.", chainKeys[i], ".id")) == block.chainid) { - chainKey = chainKeys[i]; - console2.log("Using chain key %s from the config for chain ID %d", chainKey, block.chainid); - break; - } - } - } - require(bytes(chainKey).length != 0, "failed to determine the chain key in config TOML"); - - // Double check that there chain-key and connected chain ID match. TODO: Is this too restrictive? - uint256 chainId = stdToml.readUint(configToml, string.concat(".chains.", chainKey, ".id")); - require( - chainId == block.chainid, "chosen chain key is associated with chain ID that does not match connected chain" - ); - - return chainKey; - } - - function loadDeploymentConfig(string memory configFilePath) internal view returns (Deployment memory) { - string memory configToml = VM.readFile(configFilePath); - string memory chainKey = determineChainKey(configToml); - return ConfigParser.parseConfig(configToml, chainKey); - } -} - -library ConfigParser { - using SafeCast for uint256; - - /// Reference the vm address without needing to inherit from Script. - Vm private constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - function parseConfig(string memory config, string memory chainKey) internal view returns (Deployment memory) { - string memory chain = string.concat(".chains.", chainKey); - - Deployment memory deploymentConfig; - deploymentConfig.name = stdToml.readString(config, string.concat(chain, ".name")); - deploymentConfig.chainId = stdToml.readUint(config, string.concat(chain, ".id")); - deploymentConfig.admin = stdToml.readAddressOr(config, string.concat(chain, ".admin"), address(0)); - deploymentConfig.router = stdToml.readAddressOr(config, string.concat(chain, ".router"), address(0)); - deploymentConfig.parentRouter = stdToml.readAddress(config, string.concat(chain, ".parent-router")); - deploymentConfig.timelockController = - stdToml.readAddressOr(config, string.concat(chain, ".timelock-controller"), address(0)); - deploymentConfig.timelockDelay = stdToml.readUint(config, string.concat(chain, ".timelock-delay")); - - // Iterate over the verifier struct entries to get the length; - // NOTE: We do this because Solidity doesn't support dynamic arrays in memory :| - uint256 verifiersLength = 0; - string memory verifierKey = string.concat(chain, ".verifiers[", VM.toString(verifiersLength), "]"); - while (stdToml.keyExists(config, verifierKey)) { - verifiersLength++; - verifierKey = string.concat(chain, ".verifiers[", VM.toString(verifiersLength), "]"); - } - deploymentConfig.verifiers = new VerifierDeployment[](verifiersLength); - - // Iterate over the verifier struct entries and parse them. - uint256 verifierIndex = 0; - verifierKey = string.concat(chain, ".verifiers[", VM.toString(verifierIndex), "]"); - while (stdToml.keyExists(config, verifierKey)) { - VerifierDeployment memory verifier; - verifier.name = stdToml.readStringOr(config, string.concat(verifierKey, ".name"), ""); - verifier.version = stdToml.readStringOr(config, string.concat(verifierKey, ".version"), ""); - verifier.selector = bytes4(stdToml.readUint(config, string.concat(verifierKey, ".selector")).toUint32()); - verifier.verifier = stdToml.readAddress(config, string.concat(verifierKey, ".verifier")); - verifier.estop = stdToml.readAddress(config, string.concat(verifierKey, ".estop")); - verifier.unroutable = stdToml.readBoolOr(config, string.concat(verifierKey, ".unroutable"), false); - verifier.stopped = stdToml.readBoolOr(config, string.concat(verifierKey, ".stopped"), false); - - deploymentConfig.verifiers[verifierIndex] = verifier; - - verifierIndex++; - verifierKey = string.concat(chain, ".verifiers[", VM.toString(verifierIndex), "]"); - } - - // Parse RISC Zero stack fields (upstream verifier infrastructure). - deploymentConfig.risc0Router = stdToml.readAddressOr(config, string.concat(chain, ".risc0-router"), address(0)); - deploymentConfig.risc0TimelockController = - stdToml.readAddressOr(config, string.concat(chain, ".risc0-timelock-controller"), address(0)); - deploymentConfig.risc0TimelockDelay = - stdToml.readUintOr(config, string.concat(chain, ".risc0-timelock-delay"), uint256(0)); - - // Iterate over the risc0-verifiers struct entries to get the length. - uint256 risc0VerifiersLength = 0; - string memory risc0VerifierKey = - string.concat(chain, ".risc0-verifiers[", VM.toString(risc0VerifiersLength), "]"); - while (stdToml.keyExists(config, risc0VerifierKey)) { - risc0VerifiersLength++; - risc0VerifierKey = string.concat(chain, ".risc0-verifiers[", VM.toString(risc0VerifiersLength), "]"); - } - deploymentConfig.risc0Verifiers = new VerifierDeployment[](risc0VerifiersLength); - - // Iterate over the risc0-verifiers struct entries and parse them. - uint256 risc0VerifierIndex = 0; - risc0VerifierKey = string.concat(chain, ".risc0-verifiers[", VM.toString(risc0VerifierIndex), "]"); - while (stdToml.keyExists(config, risc0VerifierKey)) { - VerifierDeployment memory risc0Verifier; - risc0Verifier.name = stdToml.readStringOr(config, string.concat(risc0VerifierKey, ".name"), ""); - risc0Verifier.version = stdToml.readStringOr(config, string.concat(risc0VerifierKey, ".version"), ""); - risc0Verifier.selector = - bytes4(stdToml.readUint(config, string.concat(risc0VerifierKey, ".selector")).toUint32()); - risc0Verifier.verifier = stdToml.readAddress(config, string.concat(risc0VerifierKey, ".verifier")); - risc0Verifier.estop = stdToml.readAddress(config, string.concat(risc0VerifierKey, ".estop")); - risc0Verifier.unroutable = stdToml.readBoolOr(config, string.concat(risc0VerifierKey, ".unroutable"), false); - risc0Verifier.stopped = stdToml.readBoolOr(config, string.concat(risc0VerifierKey, ".stopped"), false); - - deploymentConfig.risc0Verifiers[risc0VerifierIndex] = risc0Verifier; - - risc0VerifierIndex++; - risc0VerifierKey = string.concat(chain, ".risc0-verifiers[", VM.toString(risc0VerifierIndex), "]"); - } - - return deploymentConfig; - } -} diff --git a/contracts/shanghai/src/libraries/AssessorImageID.sol b/contracts/shanghai/src/libraries/AssessorImageID.sol deleted file mode 100644 index 5b075ebfdd..0000000000 --- a/contracts/shanghai/src/libraries/AssessorImageID.sol +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -// This file is automatically generated - -pragma solidity ^0.8.20; - -library ImageID { - bytes32 public constant ASSESSOR_GUEST_ID = - bytes32(0x2dd7efde6c97ca2bc4982fd37c5f63470755976396f0c1332d668ea7d3342aec); -} diff --git a/contracts/shanghai/src/libraries/UtilImageID.sol b/contracts/shanghai/src/libraries/UtilImageID.sol deleted file mode 100644 index 7751785129..0000000000 --- a/contracts/shanghai/src/libraries/UtilImageID.sol +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -// This file is automatically generated - -pragma solidity ^0.8.20; - -library ImageID { - bytes32 public constant ECHO_ID = bytes32(0x37e05f394b58198b98b7e5584ddde8ddf50b18b63192bb4d2c8617c5681a6aaf); - bytes32 public constant IDENTITY_ID = bytes32(0xb4805ec2db7eb6d287c31c3ecec5ba7e994f73ea9e5c22b7b86085ccd0a2c15d); - bytes32 public constant LOOP_ID = bytes32(0x3c2acd1fa87aa85a91533a80c20f8e664f61cf84a4e48457754c3152061b30c6); -} diff --git a/contracts/shanghai/src/povw/IPovwAccounting.sol b/contracts/shanghai/src/povw/IPovwAccounting.sol deleted file mode 100644 index 6362d6e52a..0000000000 --- a/contracts/shanghai/src/povw/IPovwAccounting.sol +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pragma solidity ^0.8.26; - -/// An update to a work log. -struct WorkLogUpdate { - /// The log ID associated with this update. This log ID is interpreted as an address for the - /// purpose of verifying a signature to authorize the update. - address workLogId; - /// Initial log commitment from which this update is calculated. - /// @dev This commits to all the PoVW nonces consumed prior to this update. - bytes32 initialCommit; - /// Updated log commitment after the update is applied. - /// @dev This commits to all the PoVW nonces consumed after this update. - bytes32 updatedCommit; - /// Work value verified in this update. - /// @dev This value will be used by the mint calculator to allocate rewards. - uint64 updateValue; - /// Recipient of any rewards associated with this update, authorized by the hold of the private - /// key associated with the work log ID. - address valueRecipient; -} - -/// Journal committed to by the log updater guest. -struct Journal { - WorkLogUpdate update; - /// EIP712 domain digest. The verifying contract must validate this to be equal to it own - /// expected EIP712 domain digest. - bytes32 eip712Domain; -} - -/// The currently pending epoch, which is still active. -struct PendingEpoch { - /// @notice Verifiable work value that has been submitted in this epoch so far. - uint96 totalWork; - /// @notice The pending epoch number. - /// @dev This may not be the current epoch number in the case that the epoch deadline has passed, - /// but the finalizeEpoch method has not been called. - uint256 number; -} - -interface IPovwAccounting { - /// @notice Event emitted during the finalization of an epoch. - /// @dev This event is emitted in some block after the end of the epoch, when the finalizeEpoch - /// function is called. Note that this is no later than the first time that updateWorkLog - /// is called after the pending epoch has ended. - /// @param epoch The number of the epoch that is being finalized. - /// @param totalWork The total value of the work submitted by provers during this epoch. - event EpochFinalized(uint256 indexed epoch, uint256 totalWork); - - /// @notice Event emitted when when a work log update is processed. - /// @param workLogId The work log identifier, which also serves as an authentication public key. - /// @param epochNumber The number of the epoch in which the update is processed. - /// The value of the update will be weighted against the total work completed in this epoch. - /// @param initialCommit The initial work log commitment for the update. - /// @param updatedCommit The updated work log commitment after the update has been processed. - /// @param updateValue Value of the work in this update. - /// @param valueRecipient The recipient of any rewards associated with this update. - event WorkLogUpdated( - address indexed workLogId, - uint256 epochNumber, - bytes32 initialCommit, - bytes32 updatedCommit, - uint256 updateValue, - address valueRecipient - ); - - /// Return the number and total work (so far) of the pending epoch. - function pendingEpoch() external view returns (PendingEpoch memory); - - /// Finalize the pending epoch, logging the finalized epoch number and total work. - function finalizeEpoch() external; - - /// @notice Update a work log and log an event with the associated update value. - /// @dev The stored work log root is updated, preventing the same nonce from being counted twice. - /// Work reported in this update will be assigned to the current epoch. A receipt from the work - /// log updater is used to ensure the integrity of the update. - /// - /// If an epoch is pending finalization, finalization occurs atomically with this call. - function updateWorkLog( - address workLogId, - bytes32 updatedCommit, - uint64 updateValue, - address valueRecipient, - bytes calldata seal - ) external; - - /// @notice Get the current work log commitment for the given work log. - /// @dev This commits to the consumed nonces that have been included in a log update. - function workLogCommit(address workLogId) external view returns (bytes32); -} diff --git a/contracts/shanghai/src/povw/IPovwMint.sol b/contracts/shanghai/src/povw/IPovwMint.sol deleted file mode 100644 index 3dfab830d4..0000000000 --- a/contracts/shanghai/src/povw/IPovwMint.sol +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pragma solidity ^0.8.26; - -import {Steel} from "steel/Steel.sol"; - -/// An update to the commitment for the processing of a work log. -struct MintCalculatorUpdate { - /// Work log ID associated that is updated. - address workLogId; - /// The initial value of the log commitment to which this update is based on. - bytes32 initialCommit; - /// The value of the log commitment after this update is applied. - bytes32 updatedCommit; -} - -/// A mint action authorized by the mint calculator. -struct MintCalculatorMint { - /// Address of the recipient for the mint. - address recipient; - /// Value of the rewards to credit towards the recipient. - uint256 value; -} - -/// Journal committed by the mint calculator guest, which contains update and mint actions. -struct MintCalculatorJournal { - /// Updates the work log commitments. - MintCalculatorMint[] mints; - /// Mints to issue. - MintCalculatorUpdate[] updates; - /// Address of the queried PovwAccounting contract. Must be checked to be equal to the expected address. - address povwAccountingAddress; - /// Address of the queried IZKCRewards contract. Must be checked to be equal to the expected address. - address zkcRewardsAddress; - /// Address of the queried IZKC contract. Must be checked to be equal to the expected address. - address zkcAddress; - /// A Steel commitment. Must be a valid commitment in the current chain. - Steel.Commitment steelCommit; -} - -/// PovwMint controls the minting of token rewards associated with Proof of Verifiable Work (PoVW). -/// -/// This contract consumes updates produced by the mint calculator guest, mints token rewards, and -/// maintains state to ensure that any given token reward is minted at most once. -interface IPovwMint { - /// @dev selector 0x36ce79a0 - error InvalidSteelCommitment(); - /// @dev selector 0x98d6328f - error IncorrectSteelContractAddress(address expected, address received); - /// @dev selector 0xf4a2b615 - error IncorrectInitialUpdateCommit(bytes32 expected, bytes32 received); - - /// @notice Mint tokens as a reward for verifiable work. - function mint(bytes calldata journalBytes, bytes calldata seal) external; - - /// @notice Get the current work log commitment for the given work log. - /// @dev This commits to the consumed nonces for all updates that have been included in a mint operation. - function workLogCommit(address workLogId) external view returns (bytes32); -} diff --git a/contracts/shanghai/src/povw/PovwAccounting.sol b/contracts/shanghai/src/povw/PovwAccounting.sol deleted file mode 100644 index cd3462d2d8..0000000000 --- a/contracts/shanghai/src/povw/PovwAccounting.sol +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.26; - -import {IRiscZeroVerifier} from "risc0/IRiscZeroSetVerifier.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {IZKC} from "zkc/interfaces/IZKC.sol"; -import {IPovwAccounting, WorkLogUpdate, Journal, PendingEpoch} from "./IPovwAccounting.sol"; - -bytes32 constant EMPTY_LOG_ROOT = hex"b26927f749929e8484785e36e7ec93d5eeae4b58182f76f1e760263ab67f540c"; - -// Storage version of PendingEpoch, which fits in one slot. -// NOTE: Assumes that the epoch number will never exceed 64 bits -struct PendingEpochStorage { - uint96 totalWork; - uint64 number; -} - -contract PovwAccounting is IPovwAccounting, Initializable, EIP712Upgradeable, OwnableUpgradeable, UUPSUpgradeable { - using SafeCast for uint256; - - /// @dev The version of the contract, with respect to upgrades. - uint64 public constant VERSION = 1; - - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IRiscZeroVerifier public immutable VERIFIER; - - /// Image ID of the work log updater guest. The log updater ensures: - /// @dev The log updater ensures: - /// - /// * Update is signed by the ECDSA key associated with the log ID. - /// * State transition from initial to updated root is append-only. - /// * The update value is equal to the sum of work associated with new proofs. - /// - /// The log updater achieves some of these properties by verifying a proof from the log builder. - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - bytes32 public immutable LOG_UPDATER_ID; - - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IZKC public immutable TOKEN; - - mapping(address => bytes32) internal workLogCommits; - - PendingEpochStorage internal _pendingEpoch; - - // NOTE: When updating this constructor, crates/povw/build.rs must be updated as well. - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(IRiscZeroVerifier verifier, IZKC token, bytes32 logUpdaterId) { - require(address(verifier) != address(0), "verifier cannot be zero"); - require(address(token) != address(0), "token cannot be zero"); - require(logUpdaterId != bytes32(0), "logUpdaterId cannot be zero"); - VERIFIER = verifier; - TOKEN = token; - LOG_UPDATER_ID = logUpdaterId; - - _disableInitializers(); - } - - function initialize(address initialOwner) external initializer { - __Ownable_init(initialOwner); - __UUPSUpgradeable_init(); - __EIP712_init("PovwAccounting", "1"); - - _pendingEpoch = PendingEpochStorage({number: TOKEN.getCurrentEpoch().toUint64(), totalWork: 0}); - } - - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - - /// @inheritdoc IPovwAccounting - function pendingEpoch() external view returns (PendingEpoch memory) { - return PendingEpoch({totalWork: _pendingEpoch.totalWork, number: _pendingEpoch.number}); - } - - /// @inheritdoc IPovwAccounting - function finalizeEpoch() public { - uint64 newEpoch = TOKEN.getCurrentEpoch().toUint64(); - require(_pendingEpoch.number < newEpoch, "pending epoch has not ended"); - - _finalizePendingEpoch(newEpoch); - } - - /// End the pending epoch and start the new epoch. This function should - /// only be called after checking that the pending epoch has ended. - function _finalizePendingEpoch(uint64 newEpoch) internal { - // Emit the epoch finalized event, accessed with Steel to construct the mint authorization. - emit EpochFinalized(_pendingEpoch.number, _pendingEpoch.totalWork); - - // NOTE: This may cause the epoch number to increase by more than 1, if no updates occurred in - // an interim epoch. Any interim epoch that was skipped will have no work associated with it. - _pendingEpoch = PendingEpochStorage({number: newEpoch, totalWork: 0}); - } - - /// @inheritdoc IPovwAccounting - function updateWorkLog( - address workLogId, - bytes32 updatedCommit, - uint64 updateValue, - address valueRecipient, - bytes calldata seal - ) public { - uint64 currentEpoch = TOKEN.getCurrentEpoch().toUint64(); - if (_pendingEpoch.number < currentEpoch) { - _finalizePendingEpoch(currentEpoch); - } - - // Fetch the initial commit value, substituting with the precomputed empty root if new. - bytes32 initialCommit = workLogCommit(workLogId); - - // Verify the receipt from the work log builder, binding the initial root as the currently - // stored value. - WorkLogUpdate memory update = WorkLogUpdate({ - workLogId: workLogId, - initialCommit: initialCommit, - updatedCommit: updatedCommit, - updateValue: updateValue, - valueRecipient: valueRecipient - }); - Journal memory journal = Journal({update: update, eip712Domain: _domainSeparatorV4()}); - VERIFIER.verify(seal, LOG_UPDATER_ID, sha256(abi.encode(journal))); - - workLogCommits[workLogId] = updatedCommit; - _pendingEpoch.totalWork += uint96(updateValue); - - // Emit the update event, accessed with Steel to construct the mint authorization. - // Note that there is no restriction on multiple updates in the same epoch. Posting more than - // one update in an epoch. - emit WorkLogUpdated( - workLogId, - currentEpoch, - update.initialCommit, - update.updatedCommit, - uint256(updateValue), - update.valueRecipient - ); - } - - /// @inheritdoc IPovwAccounting - function workLogCommit(address workLogId) public view returns (bytes32) { - bytes32 commit = workLogCommits[workLogId]; - if (commit == bytes32(0)) { - return EMPTY_LOG_ROOT; - } - return commit; - } -} diff --git a/contracts/shanghai/src/povw/PovwMint.sol b/contracts/shanghai/src/povw/PovwMint.sol deleted file mode 100644 index 7e0969195b..0000000000 --- a/contracts/shanghai/src/povw/PovwMint.sol +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.26; - -import {IRiscZeroVerifier} from "risc0/IRiscZeroSetVerifier.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {PovwAccounting, EMPTY_LOG_ROOT} from "./PovwAccounting.sol"; -import {IZKC} from "zkc/interfaces/IZKC.sol"; -import {IRewards as IZKCRewards} from "zkc/interfaces/IRewards.sol"; -import {IPovwMint, MintCalculatorUpdate, MintCalculatorMint, MintCalculatorJournal} from "./IPovwMint.sol"; -import {Steel} from "steel/Steel.sol"; - -/// PovwMint controls the minting of token rewards associated with Proof of Verifiable Work (PoVW). -/// -/// This contract consumes updates produced by the mint calculator guest, mints token rewards, and -/// maintains state to ensure that any given token reward is minted at most once. -contract PovwMint is IPovwMint, Initializable, OwnableUpgradeable, UUPSUpgradeable { - /// @dev The version of the contract, with respect to upgrades. - uint64 public constant VERSION = 1; - - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IRiscZeroVerifier public immutable VERIFIER; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IZKC public immutable TOKEN; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IZKCRewards public immutable TOKEN_REWARDS; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - PovwAccounting public immutable ACCOUNTING; - - /// @notice Image ID of the mint calculator guest. - /// @dev The mint calculator ensures: - /// * An event was logged by the PoVW accounting contract for each log update and epoch finalization. - /// * Each event is counted at most once. - /// * Events form an unbroken chain from initialCommit to updatedCommit. This constitutes an - /// exhaustiveness check such that the prover cannot exclude updates, and thereby deny a reward. - /// * Mint value is calculated correctly from the PoVW totals in each included epoch. - /// * An event was logged by the PoVW accounting contract for epoch finalization. - /// * The total work from the epoch finalization event is used in the mint calculation. - /// * The mint recipient is set correctly. - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - bytes32 public immutable MINT_CALCULATOR_ID; - - /// @notice Mapping from work log ID to the most recent work log commit for which a mint has occurred. - /// @notice Each time a mint occurs associated with a work log, this value ratchets forward. - /// It ensure that any given work log update can be used in at most one mint. - mapping(address => bytes32) public workLogCommits; - - // NOTE: When updating this constructor, crates/povw/build.rs must be updated as well. - /// @custom:oz-upgrades-unsafe-allow constructor - constructor( - IRiscZeroVerifier verifier, - PovwAccounting accounting, - bytes32 mintCalculatorId, - IZKC token, - IZKCRewards tokenRewards - ) { - require(address(verifier) != address(0), "verifier cannot be zero"); - require(address(accounting) != address(0), "accounting cannot be zero"); - require(address(tokenRewards) != address(0), "tokenRewards cannot be zero"); - require(address(token) != address(0), "token cannot be zero"); - require(mintCalculatorId != bytes32(0), "mintCalculatorId cannot be zero"); - - VERIFIER = verifier; - ACCOUNTING = accounting; - TOKEN = token; - TOKEN_REWARDS = tokenRewards; - MINT_CALCULATOR_ID = mintCalculatorId; - - _disableInitializers(); - } - - function initialize(address initialOwner) external initializer { - __Ownable_init(initialOwner); - __UUPSUpgradeable_init(); - } - - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - - /// @inheritdoc IPovwMint - function mint(bytes calldata journalBytes, bytes calldata seal) external { - // Verify the mint is authorized by the mint calculator guest. - VERIFIER.verify(seal, MINT_CALCULATOR_ID, sha256(journalBytes)); - MintCalculatorJournal memory journal = abi.decode(journalBytes, (MintCalculatorJournal)); - if (!Steel.validateCommitment(journal.steelCommit)) { - revert InvalidSteelCommitment(); - } - if (journal.povwAccountingAddress != address(ACCOUNTING)) { - revert IncorrectSteelContractAddress({ - expected: address(ACCOUNTING), received: journal.povwAccountingAddress - }); - } - if (journal.zkcAddress != address(TOKEN)) { - revert IncorrectSteelContractAddress({expected: address(TOKEN), received: journal.zkcAddress}); - } - if (journal.zkcRewardsAddress != address(TOKEN_REWARDS)) { - revert IncorrectSteelContractAddress({ - expected: address(TOKEN_REWARDS), received: journal.zkcRewardsAddress - }); - } - - // Ensure the initial commit for each update is correct and update the final commit. - for (uint256 i = 0; i < journal.updates.length; i++) { - MintCalculatorUpdate memory update = journal.updates[i]; - - // On the first mint for a journal, the initialCommit should be equal to the empty root. - bytes32 expectedCommit = workLogCommits[update.workLogId]; - if (expectedCommit == bytes32(0)) { - expectedCommit = EMPTY_LOG_ROOT; - } - - if (update.initialCommit != expectedCommit) { - revert IncorrectInitialUpdateCommit({expected: expectedCommit, received: update.initialCommit}); - } - workLogCommits[update.workLogId] = update.updatedCommit; - } - - // Issue all of the mint calls indicated in the journal. - for (uint256 i = 0; i < journal.mints.length; i++) { - MintCalculatorMint memory mintData = journal.mints[i]; - TOKEN.mintPoVWRewardsForRecipient(mintData.recipient, mintData.value); - } - } - - /// @inheritdoc IPovwMint - function workLogCommit(address workLogId) public view returns (bytes32) { - bytes32 commit = workLogCommits[workLogId]; - if (commit == bytes32(0)) { - return EMPTY_LOG_ROOT; - } - return commit; - } -} diff --git a/contracts/shanghai/src/verifier/RiscZeroVerifierRouter.sol b/contracts/shanghai/src/verifier/RiscZeroVerifierRouter.sol deleted file mode 100644 index 0e9f9c84bd..0000000000 --- a/contracts/shanghai/src/verifier/RiscZeroVerifierRouter.sol +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2025 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.9; - -import {Ownable, Ownable2Step} from "openzeppelin/contracts/access/Ownable2Step.sol"; - -import {IRiscZeroVerifier, Receipt} from "risc0/IRiscZeroVerifier.sol"; - -/// @notice Router for IRiscZeroVerifier, allowing multiple implementations to be accessible behind a single address. -contract RiscZeroVerifierRouter is IRiscZeroVerifier, Ownable2Step { - /// @notice Mapping from 4-byte verifier selector to verifier contracts. - /// Used to route receipts to verifiers that are able to check the receipt. - mapping(bytes4 => IRiscZeroVerifier) public verifiers; - - /// @notice Value of an entry that has never been set. - IRiscZeroVerifier internal constant UNSET = IRiscZeroVerifier(address(0)); - /// @notice A "tombstone" value used to mark verifier entries that have been removed from the mapping. - IRiscZeroVerifier internal constant TOMBSTONE = IRiscZeroVerifier(address(1)); - - /// @notice Error raised when attempting to verify a receipt with a selector that is not - /// registered on this router. Generally, this indicates a version mismatch where the - /// prover generated a receipt with version of the zkVM that does not match any - /// registered version on this router contract. - error SelectorUnknown(bytes4 selector); - /// @notice Error raised when attempting to add a verifier for a selector that is already registered. - error SelectorInUse(bytes4 selector); - /// @notice Error raised when attempting to verify a receipt with a selector that has been - /// removed, or attempting to add a new verifier with a selector that was previously - /// registered and then removed. - error SelectorRemoved(bytes4 selector); - /// @notice Error raised when attempting to add a verifier with a zero address. - error VerifierAddressZero(); - - constructor(address admin) Ownable(admin) {} - - /// @notice Adds a verifier to the router, such that it can receive receipt verification calls. - function addVerifier(bytes4 selector, IRiscZeroVerifier verifier) external virtual onlyOwner { - if (verifiers[selector] == TOMBSTONE) { - revert SelectorRemoved({selector: selector}); - } - if (verifiers[selector] != UNSET) { - revert SelectorInUse({selector: selector}); - } - if (address(verifier) == address(0)) { - revert VerifierAddressZero(); - } - verifiers[selector] = verifier; - } - - /// @notice Removes verifier from the router, such that it can not receive verification calls. - /// Removing a selector sets it to the tombstone value. It can never be set to any - /// other value, and can never be reused for a new verifier, in order to enforce the - /// property that each selector maps to at most one implementation across time. - function removeVerifier(bytes4 selector) external virtual onlyOwner { - // Simple check to reduce the chance of accidents. - // NOTE: If there ever _is_ a reason to remove a selector that has never been set, the owner - // can call addVerifier with the tombstone address. - if (verifiers[selector] == UNSET) { - revert SelectorUnknown({selector: selector}); - } - verifiers[selector] = TOMBSTONE; - } - - /// @notice Get the associated verifier, reverting if the selector is unknown or removed. - function getVerifier(bytes4 selector) public view virtual returns (IRiscZeroVerifier) { - IRiscZeroVerifier verifier = verifiers[selector]; - if (verifier == UNSET) { - revert SelectorUnknown({selector: selector}); - } - if (verifier == TOMBSTONE) { - revert SelectorRemoved({selector: selector}); - } - return verifier; - } - - /// @notice Get the associated verifier, reverting if the selector is unknown or removed. - function getVerifier(bytes calldata seal) public view returns (IRiscZeroVerifier) { - // Use the first 4 bytes of the seal at the selector to look up in the mapping. - return getVerifier(bytes4(seal[0:4])); - } - - /// @inheritdoc IRiscZeroVerifier - function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external view virtual { - getVerifier(seal).verify(seal, imageId, journalDigest); - } - - /// @inheritdoc IRiscZeroVerifier - function verifyIntegrity(Receipt calldata receipt) external view virtual { - getVerifier(receipt.seal).verifyIntegrity(receipt); - } -} diff --git a/contracts/shanghai/src/verifier/VerifierLayeredRouter.sol b/contracts/shanghai/src/verifier/VerifierLayeredRouter.sol deleted file mode 100644 index f9abb9b4d5..0000000000 --- a/contracts/shanghai/src/verifier/VerifierLayeredRouter.sol +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.9; - -import {IRiscZeroVerifier, Receipt} from "risc0/IRiscZeroVerifier.sol"; -import {RiscZeroVerifierRouter} from "./RiscZeroVerifierRouter.sol"; - -/// @notice A layered router enabling additional verifier implementations to be registered on top of a -/// parent router, while delegating unknown selectors to the parent. -/// @dev Resolution checks this router first and falls back to the parent router when unset. -contract VerifierLayeredRouter is RiscZeroVerifierRouter { - /// @notice The parent RISC Zero verifier router used as fallback. - RiscZeroVerifierRouter public immutable parentRouter; - - constructor(address owner, RiscZeroVerifierRouter _parentRouter) RiscZeroVerifierRouter(owner) { - require(address(_parentRouter) != address(0), "Parent router address cannot be zero"); - parentRouter = _parentRouter; - } - - /// @notice Gets the parent RISC Zero verifier router. - function getParentRouter() external view returns (RiscZeroVerifierRouter) { - return parentRouter; - } - - /// @notice Adds a verifier to the router, such that it can receive receipt verification calls. - /// @dev Ensures that the selector is not already registered or removed in either this router or the parent router. - function addVerifier(bytes4 selector, IRiscZeroVerifier verifier) external override onlyOwner { - // Ensure the selector is not removed from the parent router. - if (parentRouter.verifiers(selector) == TOMBSTONE) { - revert SelectorRemoved({selector: selector}); - } - // Ensure the selector is not already in use in the parent router. - if (parentRouter.verifiers(selector) != UNSET) { - revert SelectorInUse({selector: selector}); - } - // Ensure the selector is not removed from this router. - if (verifiers[selector] == TOMBSTONE) { - revert SelectorRemoved({selector: selector}); - } - // Ensure the selector is not already in use in this router. - if (verifiers[selector] != UNSET) { - revert SelectorInUse({selector: selector}); - } - // Ensure the verifier address is not zero. - if (address(verifier) == address(0)) { - revert VerifierAddressZero(); - } - verifiers[selector] = verifier; - } - - /// @inheritdoc RiscZeroVerifierRouter - function removeVerifier(bytes4 selector) external override onlyOwner { - verifiers[selector] = TOMBSTONE; - } - - /// @notice Get the associated verifier, falling back to the parent router if unset. - function getVerifier(bytes4 selector) public view override returns (IRiscZeroVerifier) { - IRiscZeroVerifier verifier = verifiers[selector]; - // If the verifier is unset, fall back to the parent router. - if (verifier == UNSET) { - return parentRouter.getVerifier(selector); - } - if (verifier == TOMBSTONE) { - revert SelectorRemoved({selector: selector}); - } - return verifier; - } - - /// @inheritdoc IRiscZeroVerifier - function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external view override { - bytes4 selector = bytes4(seal[0:4]); - IRiscZeroVerifier v = verifiers[selector]; - - if (v == UNSET) { - // Single external call to parent (it resolves + forwards) - parentRouter.verify(seal, imageId, journalDigest); - return; - } - if (v == TOMBSTONE) { - revert SelectorRemoved({selector: selector}); - } - v.verify(seal, imageId, journalDigest); - } - - /// @inheritdoc IRiscZeroVerifier - function verifyIntegrity(Receipt calldata receipt) external view override { - bytes4 selector = bytes4(receipt.seal[0:4]); - IRiscZeroVerifier v = verifiers[selector]; - - if (v == UNSET) { - parentRouter.verifyIntegrity(receipt); - return; - } - if (v == TOMBSTONE) { - revert SelectorRemoved({selector: selector}); - } - v.verifyIntegrity(receipt); - } -} diff --git a/contracts/shanghai/src/zkc/IStakingRewards.sol b/contracts/shanghai/src/zkc/IStakingRewards.sol deleted file mode 100644 index 7a7f5aa4fa..0000000000 --- a/contracts/shanghai/src/zkc/IStakingRewards.sol +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pragma solidity ^0.8.26; - -/// @title StakingRewards -/// @notice Contract for distributing staking rewards based on veZKC staking positions -/// @dev Users can claim rewards for specific epochs based on their staking value -interface IStakingRewards { - /// @notice Claim rewards for the given epochs - /// @param epochs The epochs to claim rewards for - /// @return amount The amount of rewards claimed - function claimRewards(uint256[] calldata epochs) external returns (uint256 amount); - - /// @notice Claim rewards for the given epochs and send to a recipient - /// @param epochs The epochs to claim rewards for - /// @param recipient The address to receive the minted rewards - /// @return amount The amount of rewards claimed - function claimRewardsToRecipient(uint256[] calldata epochs, address recipient) external returns (uint256); - - /// @notice Calculate the rewards a user is owed for the given epochs. If the epoch has not ended yet, it will return zero rewards. - /// @param user The user address - /// @param epochs The epochs to calculate rewards for - /// @return rewards The rewards owed - function calculateRewards(address user, uint256[] calldata epochs) external returns (uint256[] memory); - - /// @notice Calculate unclaimed rewards for a user - returns 0 for already claimed epochs - /// @param user The user address - /// @param epochs The epochs to calculate unclaimed rewards for - /// @return rewards The unclaimed rewards (0 if already claimed) - function calculateUnclaimedRewards(address user, uint256[] calldata epochs) external returns (uint256[] memory); - - /// @notice Check if a user has claimed rewards for a specific epoch - /// @param user The user address - /// @param epoch The epoch to check - /// @return claimed Whether rewards have been claimed - function hasUserClaimedRewards(address user, uint256 epoch) external view returns (bool claimed); - - /// @notice Get the current epoch from the ZKC contract - /// @return currentEpoch The current epoch number - function getCurrentEpoch() external view returns (uint256 currentEpoch); -} diff --git a/contracts/shanghai/test/Blake3Groth16Verifier.t.sol b/contracts/shanghai/test/Blake3Groth16Verifier.t.sol deleted file mode 100644 index 6bbc622606..0000000000 --- a/contracts/shanghai/test/Blake3Groth16Verifier.t.sol +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.13; - -import {Test} from "forge-std/Test.sol"; - -import { - Output, - OutputLib, - - // Receipt needs to be renamed due to collision with type on the Test contract. - Receipt as RiscZeroReceipt, - ReceiptClaim, - ReceiptClaimLib, - SystemState, - SystemStateLib, - VerificationFailed -} from "risc0/IRiscZeroVerifier.sol"; -import {ControlID} from "../src/blake3-groth16/ControlID.sol"; -import {Blake3Groth16Verifier} from "../src/blake3-groth16/Blake3Groth16Verifier.sol"; -import {TestReceipt} from "./receipts/Blake3Groth16TestReceipt.sol"; - -contract Blake3Groth16VerifierTest is Test { - using OutputLib for Output; - using ReceiptClaimLib for ReceiptClaim; - using SystemStateLib for SystemState; - - RiscZeroReceipt internal receipt = RiscZeroReceipt(TestReceipt.SEAL, TestReceipt.CLAIM_DIGEST); - - Blake3Groth16Verifier internal verifier; - - function setUp() external { - verifier = new Blake3Groth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); - } - - function testConsistentSystemStateZeroDigest() external pure { - require( - ReceiptClaimLib.SYSTEM_STATE_ZERO_DIGEST - == sha256( - abi.encodePacked( - SystemStateLib.TAG_DIGEST, - // down - bytes32(0), - // data - uint32(0), - // down.length - uint16(1) << 8 - ) - ) - ); - } - - function testVerifyKnownGoodReceipt() external view { - verifier.verifyIntegrity(receipt); - } - - function expectVerificationFailure(bytes memory seal, ReceiptClaim memory claim) internal { - bytes32 claimDigest = claim.digest(); - vm.expectRevert(VerificationFailed.selector); - verifier.verifyIntegrity(RiscZeroReceipt(seal, claimDigest)); - } - - function testSelectorIsStable() external view { - require(verifier.SELECTOR() == hex"62f049f6"); - } -} diff --git a/contracts/shanghai/test/BoundlessMarket.t.sol b/contracts/shanghai/test/BoundlessMarket.t.sol deleted file mode 100644 index eed5bec245..0000000000 --- a/contracts/shanghai/test/BoundlessMarket.t.sol +++ /dev/null @@ -1,4371 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {console} from "forge-std/console.sol"; -import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import { - IRiscZeroVerifier, - ReceiptClaim, - Receipt as RiscZeroReceipt, - ReceiptClaimLib, - VerificationFailed -} from "risc0/IRiscZeroVerifier.sol"; -import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; -import {TestUtils} from "./TestUtils.sol"; -import {Client} from "./clients/Client.sol"; -import {IERC1967} from "@openzeppelin/contracts/interfaces/IERC1967.sol"; -import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {HitPoints} from "../src/HitPoints.sol"; - -import {BoundlessMarket} from "../src/BoundlessMarket.sol"; -import {Callback} from "../src/types/Callback.sol"; -import { - FulfillmentDataImageIdAndJournal, - FulfillmentDataLibrary, - FulfillmentDataType -} from "../src/types/FulfillmentData.sol"; -import {RequestId} from "../src/types/RequestId.sol"; -import {AssessorCallback} from "../src/types/AssessorCallback.sol"; -import {BoundlessMarketLib} from "../src/libraries/BoundlessMarketLib.sol"; -import {MerkleProofish} from "../src/libraries/MerkleProofish.sol"; -import {ProofRequest} from "../src/types/ProofRequest.sol"; -import {LockRequest} from "../src/types/LockRequest.sol"; -import {Fulfillment} from "../src/types/Fulfillment.sol"; -import {AssessorReceipt} from "../src/types/AssessorReceipt.sol"; -import {Offer} from "../src/types/Offer.sol"; -import {Requirements} from "../src/types/Requirements.sol"; -import {Predicate, PredicateLibrary, PredicateType} from "../src/types/Predicate.sol"; -import {IBoundlessMarket} from "../src/IBoundlessMarket.sol"; - -import {RiscZeroSetVerifier} from "risc0/RiscZeroSetVerifier.sol"; -import {Fulfillment} from "../src/types/Fulfillment.sol"; -import {MockCallback} from "./MockCallback.sol"; -import {Selector} from "../src/types/Selector.sol"; - -import {SmartContractClient} from "./clients/SmartContractClient.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; - -Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - -bytes32 constant APP_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000001; -bytes32 constant APP_IMAGE_ID_2 = 0x0000000000000000000000000000000000000000000000000000000000000002; -bytes32 constant SET_BUILDER_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000002; -bytes32 constant ASSESSOR_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000003; -bytes32 constant DEPRECATED_ASSESSOR_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000004; -uint32 constant DEPRECATED_ASSESSOR_DURATION = 1 minutes; - -bytes constant APP_JOURNAL = bytes("GUEST JOURNAL"); -bytes constant APP_JOURNAL_2 = bytes("GUEST JOURNAL 2"); - -contract BoundlessMarketTest is Test { - using ReceiptClaimLib for ReceiptClaim; - using BoundlessMarketLib for Requirements; - using BoundlessMarketLib for ProofRequest; - using BoundlessMarketLib for Offer; - using TestUtils for RiscZeroSetVerifier; - using TestUtils for Selector[]; - using TestUtils for AssessorCallback[]; - using SafeCast for uint256; - using SafeCast for int256; - - RiscZeroMockVerifier internal verifier; - BoundlessMarket internal boundlessMarket; - - address internal boundlessMarketSource; - address internal proxy; - RiscZeroSetVerifier internal setVerifier; - HitPoints internal collateralToken; - mapping(uint256 => Client) internal clients; - mapping(uint256 => Client) internal provers; - mapping(uint256 => SmartContractClient) internal smartContractClients; - Client internal testProver; - address internal testProverAddress; - uint256 initialBalance; - int256 internal stakeBalanceSnapshot; - int256 internal collateralTreasuryBalanceSnapshot; - - uint256 constant DEFAULT_BALANCE = 1000 ether; - uint256 constant EXPECTED_DEFAULT_MAX_GAS_FOR_VERIFY = 50000; - uint256 constant EXPECTED_SLASH_BURN_BPS = 5000; - - ReceiptClaim internal appClaim = ReceiptClaimLib.ok(APP_IMAGE_ID, sha256(APP_JOURNAL)); - - Vm.Wallet internal ownerWallet = vm.createWallet("OWNER"); - - MockCallback internal mockCallback; - MockCallback internal mockHighGasCallback; - - function setUp() public { - vm.deal(ownerWallet.addr, DEFAULT_BALANCE); - - vm.startPrank(ownerWallet.addr); - - // Deploy the implementation contracts - verifier = new RiscZeroMockVerifier(bytes4(0)); - setVerifier = new RiscZeroSetVerifier(verifier, SET_BUILDER_IMAGE_ID, "https://set-builder.dev.null"); - collateralToken = new HitPoints(ownerWallet.addr); - - // Deploy the UUPS proxy with the implementation - boundlessMarketSource = address( - new BoundlessMarket( - setVerifier, - setVerifier, - ASSESSOR_IMAGE_ID, - DEPRECATED_ASSESSOR_IMAGE_ID, - DEPRECATED_ASSESSOR_DURATION, - address(collateralToken) - ) - ); - proxy = UnsafeUpgrades.deployUUPSProxy( - boundlessMarketSource, - abi.encodeCall(BoundlessMarket.initialize, (ownerWallet.addr, "https://assessor.dev.null")) - ); - boundlessMarket = BoundlessMarket(proxy); - - // Initialize MockCallbacks - mockCallback = new MockCallback(setVerifier, address(boundlessMarket), APP_IMAGE_ID, 10_000); - mockHighGasCallback = new MockCallback(setVerifier, address(boundlessMarket), APP_IMAGE_ID, 250_000); - - collateralToken.grantMinterRole(ownerWallet.addr); - collateralToken.grantAuthorizedTransferRole(proxy); - vm.stopPrank(); - - testProver = getProver(1); - testProverAddress = testProver.addr(); - for (uint256 i = 0; i < 5; i++) { - getClient(i); - getProver(i); - getSmartContractClient(i); - } - - initialBalance = address(boundlessMarket).balance; - - stakeBalanceSnapshot = type(int256).max; - collateralTreasuryBalanceSnapshot = type(int256).max; - - // Verify that OWNER has the admin role - assertTrue( - boundlessMarket.hasRole(boundlessMarket.ADMIN_ROLE(), ownerWallet.addr), - "OWNER address does not have admin role after deployment" - ); - } - - function expectedSlashBurnAmount(uint256 amount) internal pure returns (uint96) { - return uint96((uint256(amount) * EXPECTED_SLASH_BURN_BPS) / 10000); - } - - function expectedSlashTransferAmount(uint256 amount) internal pure returns (uint96) { - return uint96((uint256(amount) * (10000 - EXPECTED_SLASH_BURN_BPS)) / 10000); - } - - function expectMarketBalanceUnchanged() internal view { - uint256 finalBalance = address(boundlessMarket).balance; - console.log("Initial balance:", initialBalance); - console.log("Final balance:", finalBalance); - require(finalBalance == initialBalance, "Market balance changed during the test"); - } - - function snapshotMarketCollateralBalance() public { - stakeBalanceSnapshot = collateralToken.balanceOf(address(boundlessMarket)).toInt256(); - } - - function expectMarketCollateralBalanceChange(int256 change) public view { - require(stakeBalanceSnapshot != type(int256).max, "market stake balance snapshot is not set"); - int256 newBalance = collateralToken.balanceOf(address(boundlessMarket)).toInt256(); - console.log("Market stake balance at block %d: %d", block.number, newBalance.toUint256()); - int256 expectedBalance = stakeBalanceSnapshot + change; - require(expectedBalance >= 0, "expected market stake balance cannot be less than 0"); - console.log("Market expected stake balance at block %d: %d", block.number, expectedBalance.toUint256()); - require(expectedBalance == newBalance, "market stake balance is not equal to expected value"); - } - - function snapshotMarketStakeTreasuryBalance() public { - collateralTreasuryBalanceSnapshot = boundlessMarket.balanceOfCollateral(address(boundlessMarket)).toInt256(); - } - - function expectMarketCollateralTreasuryBalanceChange(int256 change) public view { - require( - collateralTreasuryBalanceSnapshot != type(int256).max, - "market collateral treasury balance snapshot is not set" - ); - int256 newBalance = boundlessMarket.balanceOfCollateral(address(boundlessMarket)).toInt256(); - console.log("Market stake treasury balance at block %d: %d", block.number, newBalance.toUint256()); - int256 expectedBalance = collateralTreasuryBalanceSnapshot + change; - require(expectedBalance >= 0, "expected market treasury stake balance cannot be less than 0"); - console.log("Market expected stake treasury balance at block %d: %d", block.number, expectedBalance.toUint256()); - require(expectedBalance == newBalance, "market stake treasury balance is not equal to expected value"); - } - - function expectRequestFulfilled(RequestId requestId) internal view { - require(boundlessMarket.requestIsFulfilled(requestId), "Request should be fulfilled"); - require(!boundlessMarket.requestIsSlashed(requestId), "Request should not be slashed"); - } - - function expectRequestFulfilledAndSlashed(RequestId requestId) internal view { - require(boundlessMarket.requestIsFulfilled(requestId), "Request should be fulfilled"); - require(boundlessMarket.requestIsSlashed(requestId), "Request should be slashed"); - } - - function expectRequestNotFulfilled(RequestId requestId) internal view { - require(!boundlessMarket.requestIsFulfilled(requestId), "Request should not be fulfilled"); - } - - function expectRequestSlashed(RequestId requestId) internal view { - require(boundlessMarket.requestIsSlashed(requestId), "Request should be slashed"); - } - - function expectRequestNotSlashed(RequestId requestId) internal view { - require(!boundlessMarket.requestIsSlashed(requestId), "Request should be slashed"); - } - - // Creates a client account with the given index, gives it some Ether, - // gives it some Stake Token, and deposits both into the market. - function getClient(uint256 index) internal returns (Client) { - if (address(clients[index]) != address(0)) { - return clients[index]; - } - Client client = createClientContract(string.concat("CLIENT_", vm.toString(index))); - fundClient(client); - clients[index] = client; - return client; - } - - // Creates a client account with the given index, gives it some Ether, - // gives it some Stake Token, and deposits both into the market. - function getSmartContractClient(uint256 index) internal returns (SmartContractClient) { - if (address(smartContractClients[index]) != address(0)) { - return smartContractClients[index]; - } - SmartContractClient client = createSmartContractClientContract(string.concat("SC_CLIENT_", vm.toString(index))); - fundSmartContractClient(client); - smartContractClients[index] = client; - return client; - } - - // Creates a prover account with the given index, gives it some Ether, - // gives it some Stake Token, and deposits both into the market. - function getProver(uint256 index) internal returns (Client) { - if (address(provers[index]) != address(0)) { - return provers[index]; - } - Client prover = createClientContract(string.concat("PROVER_", vm.toString(index))); - fundClient(prover); - provers[index] = prover; - return prover; - } - - function fundClient(Client client) internal { - address clientAddress = client.addr(); - // Deal the client from Ether and deposit it in the market. - vm.deal(clientAddress, DEFAULT_BALANCE); - vm.prank(clientAddress); - boundlessMarket.deposit{value: DEFAULT_BALANCE}(); - - // Snapshot their initial ETH balance. - client.snapshotBalance(); - - // Mint some stake tokens. - vm.prank(ownerWallet.addr); - collateralToken.mint(clientAddress, DEFAULT_BALANCE); - - uint256 deadline = block.timestamp + 1 hours; - (uint8 v, bytes32 r, bytes32 s) = client.signPermit(proxy, DEFAULT_BALANCE, deadline); - vm.prank(clientAddress); - boundlessMarket.depositCollateralWithPermit(DEFAULT_BALANCE, deadline, v, r, s); - - // Snapshot their initial stake balance. - client.snapshotCollateralBalance(); - } - - function fundSmartContractClient(SmartContractClient client) internal { - address walletAddress = client.addr(); - address signerAddress = client.signerAddr(); - - // Deal the SCW some Ether and deposit it in the market. - vm.deal(walletAddress, DEFAULT_BALANCE); - vm.prank(signerAddress); - client.execute( - address(boundlessMarket), - abi.encodeWithSelector(IBoundlessMarket.deposit.selector, DEFAULT_BALANCE), - DEFAULT_BALANCE - ); - - // Snapshot their initial ETH balance. - client.snapshotBalance(); - - // Mint some stake tokens. - vm.prank(ownerWallet.addr); - collateralToken.mint(walletAddress, DEFAULT_BALANCE); - - vm.prank(signerAddress); - client.execute( - address(collateralToken), abi.encodeWithSelector(IERC20.approve.selector, boundlessMarket, DEFAULT_BALANCE) - ); - - vm.prank(signerAddress); - client.execute( - address(boundlessMarket), - abi.encodeWithSelector(IBoundlessMarket.depositCollateral.selector, DEFAULT_BALANCE) - ); - - // check balances - assertEq(boundlessMarket.balanceOf(walletAddress), DEFAULT_BALANCE); - assertEq(boundlessMarket.balanceOfCollateral(walletAddress), DEFAULT_BALANCE); - - // Snapshot their initial stake balance. - client.snapshotCollateralBalance(); - } - - // Create a client, using a trick to set the address equal to the wallet address. - function createClientContract(string memory identifier) internal returns (Client) { - Vm.Wallet memory wallet = vm.createWallet(identifier); - Client client = new Client(wallet); - client.initialize(identifier, boundlessMarket, collateralToken); - return client; - } - - function createSmartContractClientContract(string memory identifier) internal returns (SmartContractClient) { - Vm.Wallet memory signer = vm.createWallet(string.concat(identifier, "_SIGNER")); - SmartContractClient client = new SmartContractClient(signer); - client.initialize(identifier, boundlessMarket, collateralToken); - return client; - } - - function submitRoot(bytes32 root) internal { - boundlessMarket.submitRoot( - address(setVerifier), - root, - verifier.mockProve( - SET_BUILDER_IMAGE_ID, sha256(abi.encodePacked(SET_BUILDER_IMAGE_ID, uint256(1 << 255), root)) - ) - .seal - ); - } - - function createFillAndSubmitRoot(ProofRequest memory request, bytes memory journal, address prover) - internal - returns (Fulfillment memory, AssessorReceipt memory) - { - return createFillAndSubmitRoot(request, journal, prover, FulfillmentDataType.ImageIdAndJournal); - } - - function createFillAndSubmitRoot( - ProofRequest memory request, - bytes memory journal, - address prover, - FulfillmentDataType fillType - ) internal returns (Fulfillment memory, AssessorReceipt memory) { - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory journals = new bytes[](1); - journals[0] = journal; - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, prover, fillType); - return (fills[0], assessorReceipt); - } - - function createDeprecatedFillAndSubmitRoot(ProofRequest memory request, bytes memory journal, address prover) - internal - returns (Fulfillment memory, AssessorReceipt memory) - { - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory journals = new bytes[](1); - journals[0] = journal; - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createDeprecatedFillsAndSubmitRoot(requests, journals, prover); - return (fills[0], assessorReceipt); - } - - function createFillsAndSubmitRoot(ProofRequest[] memory requests, bytes[] memory journals, address prover) - internal - returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) - { - return createFillsAndSubmitRoot(requests, journals, prover, FulfillmentDataType.ImageIdAndJournal); - } - - function createFillsAndSubmitRoot( - ProofRequest[] memory requests, - bytes[] memory journals, - address prover, - FulfillmentDataType fillType - ) internal returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) { - bytes32 root; - (fills, assessorReceipt, root) = createFills(requests, journals, prover, fillType, ASSESSOR_IMAGE_ID); - // submit the root to the set verifier - submitRoot(root); - return (fills, assessorReceipt); - } - - function createDeprecatedFillsAndSubmitRoot(ProofRequest[] memory requests, bytes[] memory journals, address prover) - internal - returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) - { - bytes32 root; - (fills, assessorReceipt, root) = createDeprecatedFills(requests, journals, prover); - // submit the root to the set verifier - submitRoot(root); - return (fills, assessorReceipt); - } - - function createFills( - ProofRequest[] memory requests, - bytes[] memory journals, - address prover, - FulfillmentDataType fillType, - bytes32 assessorImageId - ) internal view returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) { - // initialize the fullfillments; one for each request; - // the seal is filled in later, by calling fillInclusionProof - fills = new Fulfillment[](requests.length); - Selector[] memory selectors = new Selector[](0); - AssessorCallback[] memory callbacks = new AssessorCallback[](0); - - for (uint8 i = 0; i < requests.length; i++) { - bytes32 claimDigest; - bytes memory fulfillmentData; - bytes memory journal = journals[i]; - PredicateType predicateType = requests[i].requirements.predicate.predicateType; - bytes32 imageId; - if (predicateType != PredicateType.ClaimDigestMatch) { - imageId = bytesToBytes32(requests[i].requirements.predicate.data); - claimDigest = ReceiptClaimLib.ok(imageId, sha256(journal)).digest(); - } else { - // this is hacky, but for ClaimDigestMatch, the imageId is not known, - // so we just use the APP_IMAGE_ID as the default - imageId = APP_IMAGE_ID; - claimDigest = bytesToBytes32(requests[i].requirements.predicate.data); - } - if (fillType == FulfillmentDataType.ImageIdAndJournal) { - fulfillmentData = abi.encode(FulfillmentDataImageIdAndJournal({imageId: imageId, journal: journal})); - } - Fulfillment memory fill = Fulfillment({ - id: requests[i].id, - requestDigest: MessageHashUtils.toTypedDataHash( - boundlessMarket.eip712DomainSeparator(), requests[i].eip712Digest() - ), - claimDigest: claimDigest, - fulfillmentData: fulfillmentData, - fulfillmentDataType: fillType, - seal: bytes("") - }); - fills[i] = fill; - if (requests[i].requirements.selector != bytes4(0)) { - selectors = selectors.addSelector(i, requests[i].requirements.selector); - } - if (requests[i].requirements.callback.addr != address(0)) { - callbacks = callbacks.addCallback( - AssessorCallback({ - index: i, - gasLimit: requests[i].requirements.callback.gasLimit, - addr: requests[i].requirements.callback.addr - }) - ); - } - } - - // compute the assessor claim - ReceiptClaim memory assessorClaim = TestUtils.mockAssessor(fills, assessorImageId, selectors, callbacks, prover); - // compute the batchRoot of the batch Merkle Tree (without the assessor) - (bytes32 batchRoot, bytes32[][] memory tree) = TestUtils.mockSetBuilder(fills); - - bytes32 assessorLeaf = TestUtils.hashLeaf(assessorClaim.digest()); - root = MerkleProofish._hashPair(batchRoot, assessorLeaf); - - // compute all the inclusion proofs for the fullfillments - TestUtils.fillInclusionProofs(setVerifier, fills, assessorLeaf, tree); - // compute the assessor fill - assessorReceipt = AssessorReceipt({ - seal: TestUtils.mockAssessorSeal(setVerifier, batchRoot), - selectors: selectors, - callbacks: callbacks, - prover: prover - }); - - return (fills, assessorReceipt, root); - } - - function createFills(ProofRequest[] memory requests, bytes[] memory journals, address prover) - internal - view - returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) - { - (fills, assessorReceipt, root) = - createFills(requests, journals, prover, FulfillmentDataType.ImageIdAndJournal, ASSESSOR_IMAGE_ID); - } - - function createDeprecatedFills(ProofRequest[] memory requests, bytes[] memory journals, address prover) - internal - view - returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) - { - (fills, assessorReceipt, root) = createFills( - requests, journals, prover, FulfillmentDataType.ImageIdAndJournal, DEPRECATED_ASSESSOR_IMAGE_ID - ); - } - - function newBatch(uint256 batchSize) internal returns (ProofRequest[] memory requests, bytes[] memory journals) { - requests = new ProofRequest[](batchSize); - journals = new bytes[](batchSize); - for (uint256 j = 0; j < 5; j++) { - getClient(j); - } - for (uint256 i = 0; i < batchSize; i++) { - Client client = clients[i % 5]; - ProofRequest memory request = client.request(uint32(i / 5)); - bytes memory clientSignature = client.sign(request); - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - requests[i] = request; - journals[i] = APP_JOURNAL; - } - } - - function newBatchWithSelector(uint256 batchSize, bytes4 selector) - internal - returns (ProofRequest[] memory requests, bytes[] memory journals) - { - requests = new ProofRequest[](batchSize); - journals = new bytes[](batchSize); - for (uint256 j = 0; j < 5; j++) { - getClient(j); - } - for (uint256 i = 0; i < batchSize; i++) { - Client client = clients[i % 5]; - ProofRequest memory request = client.request(uint32(i / 5)); - request.requirements.selector = selector; - bytes memory clientSignature = client.sign(request); - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - requests[i] = request; - journals[i] = APP_JOURNAL; - } - } - - function newBatchWithCallback(uint256 batchSize) - internal - returns (ProofRequest[] memory requests, bytes[] memory journals) - { - requests = new ProofRequest[](batchSize); - journals = new bytes[](batchSize); - for (uint256 j = 0; j < 5; j++) { - getClient(j); - } - for (uint256 i = 0; i < batchSize; i++) { - Client client = clients[i % 5]; - ProofRequest memory request = client.request(uint32(i / 5)); - request.requirements.callback.addr = address(mockCallback); - request.requirements.callback.gasLimit = 500_000; - bytes memory clientSignature = client.sign(request); - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - requests[i] = request; - journals[i] = APP_JOURNAL; - } - } - - function bytesToBytes32(bytes memory b) internal pure returns (bytes32) { - bytes32 out; - for (uint256 i = 0; i < 32; i++) { - out |= bytes32(b[i] & 0xFF) >> (i * 8); - } - return out; - } -} - -contract BoundlessMarketBasicTest is BoundlessMarketTest { - using ReceiptClaimLib for ReceiptClaim; - using BoundlessMarketLib for Offer; - using BoundlessMarketLib for ProofRequest; - using SafeCast for uint256; - - function _stringEquals(string memory a, string memory b) private pure returns (bool) { - return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); - } - - function testBytecodeSize() public { - vm.snapshotValue("bytecode size proxy", address(proxy).code.length); - vm.snapshotValue("bytecode size implementation", boundlessMarketSource.code.length); - } - - function testDeposit() public { - vm.deal(testProverAddress, 1 ether); - // Deposit funds into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Deposit(testProverAddress, 1 ether); - vm.prank(testProverAddress); - boundlessMarket.deposit{value: 1 ether}(); - testProver.expectBalanceChange(1 ether); - } - - function testDeposits() public { - address newUser = address(uint160(3)); - vm.deal(newUser, 2 ether); - - // Deposit funds into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Deposit(newUser, 1 ether); - vm.prank(newUser); - boundlessMarket.deposit{value: 1 ether}(); - vm.snapshotGasLastCall("deposit: first ever deposit"); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Deposit(newUser, 1 ether); - vm.prank(newUser); - boundlessMarket.deposit{value: 1 ether}(); - vm.snapshotGasLastCall("deposit: second deposit"); - } - - function testDepositTo() public { - vm.deal(testProverAddress, 1 ether); - // Deposit funds into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Deposit(testProverAddress, 1 ether); - vm.prank(testProverAddress); - boundlessMarket.depositTo{value: 1 ether}(testProverAddress); - testProver.expectBalanceChange(1 ether); - } - - function testDepositsTo() public { - address newUser = address(uint160(3)); - vm.deal(newUser, 2 ether); - - // Deposit funds into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Deposit(newUser, 1 ether); - vm.prank(newUser); - boundlessMarket.depositTo{value: 1 ether}(newUser); - vm.snapshotGasLastCall("depositTo: first ever deposit"); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Deposit(newUser, 1 ether); - vm.prank(newUser); - boundlessMarket.depositTo{value: 1 ether}(newUser); - vm.snapshotGasLastCall("depositTo: second deposit"); - } - - function testAdminRoleSetup() public view { - assertTrue( - boundlessMarket.hasRole(boundlessMarket.ADMIN_ROLE(), ownerWallet.addr), "Owner should have admin role" - ); - } - - function testWithdraw() public { - // Deposit funds into the market - vm.deal(testProverAddress, 1 ether); - vm.prank(testProverAddress); - boundlessMarket.deposit{value: 1 ether}(); - - // Withdraw funds from the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Withdrawal(testProverAddress, 1 ether); - vm.prank(testProverAddress); - boundlessMarket.withdraw(1 ether); - expectMarketBalanceUnchanged(); - - // Attempt to withdraw extra funds from the market. - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InsufficientBalance.selector, testProverAddress)); - vm.prank(testProverAddress); - boundlessMarket.withdraw(DEFAULT_BALANCE + 1); - expectMarketBalanceUnchanged(); - } - - function testWithdrawals() public { - // Deposit funds into the market - vm.deal(testProverAddress, 3 ether); - vm.prank(testProverAddress); - boundlessMarket.deposit{value: 3 ether}(); - - // Withdraw funds from the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Withdrawal(testProverAddress, 1 ether); - vm.prank(testProverAddress); - boundlessMarket.withdraw(1 ether); - vm.snapshotGasLastCall("withdraw: 1 ether"); - - uint256 balance = boundlessMarket.balanceOf(testProverAddress); - vm.prank(testProverAddress); - boundlessMarket.withdraw(balance); - vm.snapshotGasLastCall("withdraw: full balance"); - assertEq(boundlessMarket.balanceOf(testProverAddress), 0); - - // Attempt to withdraw extra funds from the market. - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InsufficientBalance.selector, testProverAddress)); - vm.prank(testProverAddress); - boundlessMarket.withdraw(DEFAULT_BALANCE + 1); - } - - function testCollateralDeposit() public { - // Mint some tokens - vm.prank(ownerWallet.addr); - collateralToken.mint(testProverAddress, 2); - - // Approve the market to spend the testProver's collateralToken - vm.prank(testProverAddress); - ERC20(address(collateralToken)).approve(address(boundlessMarket), 2); - vm.snapshotGasLastCall("ERC20 approve: required for depositCollateral"); - - // Deposit stake into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralDeposit(testProverAddress, 1); - vm.prank(testProverAddress); - boundlessMarket.depositCollateral(1); - vm.snapshotGasLastCall("depositCollateral: 1 HP (tops up market account)"); - testProver.expectCollateralBalanceChange(1); - - // Deposit stake into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralDeposit(testProverAddress, 1); - vm.prank(testProverAddress); - boundlessMarket.depositCollateral(1); - vm.snapshotGasLastCall("depositCollateral: full (drains testProver account)"); - testProver.expectCollateralBalanceChange(2); - } - - function testCollateralDepositWithPermit() public { - // Mint some tokens - vm.prank(ownerWallet.addr); - collateralToken.mint(testProverAddress, 2); - - // Approve the market to spend the testProver's collateralToken - uint256 deadline = block.timestamp + 1 hours; - (uint8 v, bytes32 r, bytes32 s) = testProver.signPermit(address(boundlessMarket), 1, deadline); - - // Deposit stake into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralDeposit(testProverAddress, 1); - vm.prank(testProverAddress); - boundlessMarket.depositCollateralWithPermit(1, deadline, v, r, s); - vm.snapshotGasLastCall("depositCollateralWithPermit: 1 HP (tops up market account)"); - testProver.expectCollateralBalanceChange(1); - - // Approve the market to spend the testProver's collateralToken - (v, r, s) = testProver.signPermit(address(boundlessMarket), 1, deadline); - - // Deposit stake into the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralDeposit(testProverAddress, 1); - vm.prank(testProverAddress); - boundlessMarket.depositCollateralWithPermit(1, deadline, v, r, s); - vm.snapshotGasLastCall("depositCollateralWithPermit: full (drains testProver account)"); - testProver.expectCollateralBalanceChange(2); - } - - function testCollateralDepositTo() public { - Client sender = getClient(2); - Client receiver = getClient(3); - address senderAddr = sender.addr(); - address receiverAddr = receiver.addr(); - - vm.prank(ownerWallet.addr); - collateralToken.mint(senderAddr, 2); - - vm.prank(senderAddr); - ERC20(address(collateralToken)).approve(address(boundlessMarket), 2); - - uint256 senderBalanceBefore = boundlessMarket.balanceOfCollateral(senderAddr); - uint256 receiverBalanceBefore = boundlessMarket.balanceOfCollateral(receiverAddr); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralDeposit(receiverAddr, 1); - vm.prank(senderAddr); - boundlessMarket.depositCollateralTo(receiverAddr, 1); - - assertEq(boundlessMarket.balanceOfCollateral(senderAddr), senderBalanceBefore); - assertEq(boundlessMarket.balanceOfCollateral(receiverAddr), receiverBalanceBefore + 1); - } - - function testCollateralDepositWithPermitTo() public { - Client sender = getClient(2); - Client receiver = getClient(3); - address senderAddr = sender.addr(); - address receiverAddr = receiver.addr(); - - vm.prank(ownerWallet.addr); - collateralToken.mint(senderAddr, 2); - - uint256 deadline = block.timestamp + 1 hours; - (uint8 v, bytes32 r, bytes32 s) = sender.signPermit(address(boundlessMarket), 1, deadline); - - uint256 senderBalanceBefore = boundlessMarket.balanceOfCollateral(senderAddr); - uint256 receiverBalanceBefore = boundlessMarket.balanceOfCollateral(receiverAddr); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralDeposit(receiverAddr, 1); - vm.prank(senderAddr); - boundlessMarket.depositCollateralWithPermitTo(receiverAddr, 1, deadline, v, r, s); - - assertEq(boundlessMarket.balanceOfCollateral(senderAddr), senderBalanceBefore); - assertEq(boundlessMarket.balanceOfCollateral(receiverAddr), receiverBalanceBefore + 1); - } - - function testStakeWithdraw() public { - // Withdraw stake from the market - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralWithdrawal(testProverAddress, 1); - vm.prank(testProverAddress); - boundlessMarket.withdrawCollateral(1); - vm.snapshotGasLastCall("withdrawCollateral: 1 HP balance"); - testProver.expectCollateralBalanceChange(-1); - assertEq(collateralToken.balanceOf(testProverAddress), 1, "TestProver should have 1 hitPoint after withdrawing"); - - // Withdraw full stake from the market - uint256 remainingBalance = boundlessMarket.balanceOfCollateral(testProverAddress); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CollateralWithdrawal(testProverAddress, remainingBalance); - vm.prank(testProverAddress); - boundlessMarket.withdrawCollateral(remainingBalance); - vm.snapshotGasLastCall("withdrawCollateral: full balance"); - testProver.expectCollateralBalanceChange(-int256(DEFAULT_BALANCE)); - assertEq( - collateralToken.balanceOf(testProverAddress), - DEFAULT_BALANCE, - "TestProver should have DEFAULT_BALANCE hitPoint after withdrawing" - ); - - // Attempt to withdraw extra funds from the market. - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InsufficientBalance.selector, testProverAddress)); - vm.prank(testProverAddress); - boundlessMarket.withdrawCollateral(1); - } - - function testSubmitRequest() public { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSignature = client.sign(request); - - // Submit the request with no funds - // Expect the event to be emitted - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestSubmitted(request.id, request, clientSignature); - boundlessMarket.submitRequest(request, clientSignature); - vm.snapshotGasLastCall("submitRequest: without ether"); - - // Submit the request with funds - // Expect the event to be emitted - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.Deposit(client.addr(), uint256(request.offer.maxPrice)); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestSubmitted(request.id, request, clientSignature); - vm.deal(client.addr(), request.offer.maxPrice); - address clientAddress = client.addr(); - vm.prank(clientAddress); - boundlessMarket.submitRequest{value: request.offer.maxPrice}(request, clientSignature); - vm.snapshotGasLastCall("submitRequest: with maxPrice ether"); - } - - function _testLockRequest(bool withSig) private returns (Client, ProofRequest memory) { - return _testLockRequest(withSig, ""); - } - - function _testLockRequest(bool withSig, string memory snapshot) private returns (Client, ProofRequest memory) { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSignature = client.sign(request); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request})); - - // Expect the event to be emitted - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestLocked(request.id, testProverAddress, request, clientSignature); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - - // Ensure the balances are correct - client.expectBalanceChange(-1 ether); - testProver.expectCollateralBalanceChange(-1 ether); - - // Verify the lock request - assertTrue(boundlessMarket.requestIsLocked(request.id), "Request should be locked-in"); - - expectMarketBalanceUnchanged(); - - return (client, request); - } - - function testLockRequest() public returns (Client, ProofRequest memory) { - return _testLockRequest(false, "lockinRequest: base case"); - } - - function testLockRequestWithSignature() public returns (Client, ProofRequest memory) { - return _testLockRequest(true, "lockinRequest: with prover signature"); - } - - function _testLockRequestAlreadyLocked(bool withSig) private { - (Client client, ProofRequest memory request) = _testLockRequest(withSig); - bytes memory clientSignature = client.sign(request); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request})); - - // Attempt to lock the request again - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsLocked.selector, request.id)); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - - expectMarketBalanceUnchanged(); - } - - function testLockRequestAlreadyLocked() public { - return _testLockRequestAlreadyLocked(true); - } - - function testLockRequestWithSignatureAlreadyLocked() public { - return _testLockRequestAlreadyLocked(false); - } - - function _testLockRequestBadClientSignature(bool withSig) private { - Client clientA = getClient(1); - Client clientB = getClient(2); - ProofRequest memory request1 = clientA.request(1); - ProofRequest memory request2 = clientA.request(2); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request1})); - - // case: request signed by a different client - bytes memory badClientSignature = clientB.sign(request1); - vm.expectRevert(IBoundlessMarket.InvalidSignature.selector); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request1, badClientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request1, badClientSignature); - } - - // case: client signed a different request - badClientSignature = clientA.sign(request2); - vm.expectRevert(IBoundlessMarket.InvalidSignature.selector); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request1, badClientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request1, badClientSignature); - } - - clientA.expectBalanceChange(0 ether); - clientB.expectBalanceChange(0 ether); - testProver.expectBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - function testLockRequestBadClientSignature() public { - return _testLockRequestBadClientSignature(true); - } - - function testLockRequestWithSignatureBadClientSignature() public { - return _testLockRequestBadClientSignature(false); - } - - function testLockRequestWithSignatureProverSignatureIncorrectRequest() public { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSignature = client.sign(request); - // Prover signs the incorrect request. - bytes memory badProverSignature = testProver.signLockRequest(LockRequest({request: client.request(2)})); - - // NOTE: Error is "InsufficientBalance" because we will recover _some_ address. - // It should be random and never correspond to a real account. - // TODO: This address will need to change anytime we change the ProofRequest struct or - // the way it is hashed for signatures. Find a good way to avoid this. - vm.expectRevert( - abi.encodeWithSelector( - IBoundlessMarket.InsufficientBalance.selector, address(0x013a129A6254FDb452a94b92385645b7959A7c5A) - ) - ); - boundlessMarket.lockRequestWithSignature(request, clientSignature, badProverSignature); - - client.expectBalanceChange(0 ether); - testProver.expectBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - function testLockRequestWithSignatureProverSignatureIncorrectDomain() public { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSignature = client.sign(request); - // Prover signs ProofRequest struct rather than LockRequest struct. - // NOTE: This was how the contract worked in a previous version. This is included as a regression test. - bytes memory badProverSignature = testProver.sign(request); - - // NOTE: Error is "InsufficientBalance" because we will recover _some_ address. - // It should be random and never correspond to a real account. - // TODO: This address will need to change anytime we change the ProofRequest struct or - // the way it is hashed for signatures. Find a good way to avoid this. - vm.expectRevert( - abi.encodeWithSelector( - IBoundlessMarket.InsufficientBalance.selector, address(0x2949a308c21BD8bC839EFeCD4465cBebdE3F7388) - ) - ); - boundlessMarket.lockRequestWithSignature(request, clientSignature, badProverSignature); - - client.expectBalanceChange(0 ether); - testProver.expectBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - function _testLockRequestNotEnoughFunds(bool withSig) private { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSignature = client.sign(request); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request})); - - address clientAddress = client.addr(); - vm.prank(clientAddress); - boundlessMarket.withdraw(DEFAULT_BALANCE); - - // case: client does not have enough funds to cover for the lock request - // should revert with "InsufficientBalance(address requester)" - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InsufficientBalance.selector, client.addr())); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - - vm.prank(clientAddress); - boundlessMarket.deposit{value: DEFAULT_BALANCE}(); - - vm.prank(testProverAddress); - boundlessMarket.withdrawCollateral(DEFAULT_BALANCE); - // case: prover does not have enough funds to cover for the lock request stake - // should revert with "InsufficientBalance(address requester)" - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InsufficientBalance.selector, testProverAddress)); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - } - - function testLockRequestNotEnoughFunds() public { - return _testLockRequestNotEnoughFunds(true); - } - - function testLockRequestWithSignatureNotEnoughFunds() public { - return _testLockRequestNotEnoughFunds(false); - } - - function _testLockRequestExpired(bool withSig) private { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSignature = client.sign(request); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request})); - - vm.warp(request.offer.deadline() + 1); - - // Attempt to lock the request after it has expired - // should revert with "RequestIsExpired({requestId: request.id, deadline: deadline})" - vm.expectRevert( - abi.encodeWithSelector( - IBoundlessMarket.RequestLockIsExpired.selector, request.id, request.offer.lockDeadline() - ) - ); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - - expectMarketBalanceUnchanged(); - } - - function testLockRequestExpired() public { - return _testLockRequestExpired(true); - } - - function testLockRequestWithSignatureExpired() public { - return _testLockRequestExpired(false); - } - - function _testLockRequestLockExpired(bool withSig) private { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSignature = client.sign(request); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request})); - - vm.warp(request.offer.lockDeadline() + 1); - - vm.expectRevert( - abi.encodeWithSelector( - IBoundlessMarket.RequestLockIsExpired.selector, request.id, request.offer.lockDeadline() - ) - ); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - - expectMarketBalanceUnchanged(); - } - - function testLockRequestLockExpired() public { - return _testLockRequestLockExpired(true); - } - - function testLockRequestWithSignatureLockExpired() public { - return _testLockRequestLockExpired(false); - } - - function _testLockRequestInvalidRequest1(bool withSig) private { - Offer memory offer = Offer({ - minPrice: 2 ether, - maxPrice: 1 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(0), - lockTimeout: uint32(1), - timeout: uint32(1), - lockCollateral: 10 ether - }); - - Client client = getClient(1); - ProofRequest memory request = client.request(1, offer); - bytes memory clientSignature = client.sign(request); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request})); - - // Attempt to lock a request with maxPrice smaller than minPrice - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InvalidRequest.selector)); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - - expectMarketBalanceUnchanged(); - } - - function testLockRequestInvalidRequest1() public { - return _testLockRequestInvalidRequest1(true); - } - - function testLockRequestWithSignatureInvalidRequest1() public { - return _testLockRequestInvalidRequest1(false); - } - - function _testLockRequestInvalidRequest2(bool withSig) private { - Offer memory offer = Offer({ - minPrice: 1 ether, - maxPrice: 1 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(2), - lockTimeout: uint32(1), - timeout: uint32(1), - lockCollateral: 10 ether - }); - - Client client = getClient(1); - ProofRequest memory request = client.request(1, offer); - bytes memory clientSignature = client.sign(request); - bytes memory proverSignature = testProver.signLockRequest(LockRequest({request: request})); - - // Attempt to lock a request with rampUpPeriod greater than timeout - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InvalidRequest.selector)); - if (withSig) { - boundlessMarket.lockRequestWithSignature(request, clientSignature, proverSignature); - } else { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } - - expectMarketBalanceUnchanged(); - } - - function testLockRequestInvalidRequest2() public { - return _testLockRequestInvalidRequest2(true); - } - - function testLockRequestWithSignatureInvalidRequest2() public { - return _testLockRequestInvalidRequest2(false); - } - - enum LockRequestMethod { - LockRequest, - LockRequestWithSig, - None - } - - function _testFulfillSameBlock(uint32 requestIdx, LockRequestMethod lockinMethod) - private - returns (Client, ProofRequest memory) - { - return _testFulfillSameBlock(requestIdx, lockinMethod, ""); - } - - // Base for fulfillment tests with different methods for lock, including none. All paths should yield the same result. - function _testFulfillSameBlock(uint32 requestIdx, LockRequestMethod lockinMethod, string memory snapshot) - private - returns (Client, ProofRequest memory) - { - Client client = getClient(1); - ProofRequest memory request = client.request(requestIdx); - bytes memory clientSignature = client.sign(request); - - client.snapshotBalance(); - testProver.snapshotBalance(); - - if (lockinMethod == LockRequestMethod.LockRequest) { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } else if (lockinMethod == LockRequestMethod.LockRequestWithSig) { - boundlessMarket.lockRequestWithSignature( - request, clientSignature, testProver.signLockRequest(LockRequest({request: request})) - ); - } - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - if (lockinMethod == LockRequestMethod.None) { - // Annoying boilerplate for creating singleton lists. - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(request); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } else { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.fulfill(fills, assessorReceipt); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } - - // Check that the proof was submitted - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - - return (client, request); - } - - // Base for fulfillment tests with deprecated assessor. - function _testFulfillDeprecatedAssessor(uint32 requestIdx) private { - Client client = getClient(1); - ProofRequest memory request = client.request(requestIdx); - bytes memory clientSignature = client.sign(request); - - client.snapshotBalance(); - testProver.snapshotBalance(); - - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createDeprecatedFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - if (block.timestamp <= boundlessMarket.DEPRECATED_ASSESSOR_EXPIRES_AT()) { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.fulfill(fills, assessorReceipt); - - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - } else { - vm.expectRevert(VerificationFailed.selector); - boundlessMarket.fulfill(fills, assessorReceipt); - } - - expectMarketBalanceUnchanged(); - } - - // Base for fulfillmentAndWithdraw tests with different methods for lock, including none. All paths should yield the same result. - function _testFulfillAndWithdrawSameBlock(uint32 requestIdx, LockRequestMethod lockinMethod, string memory snapshot) - private - returns (Client, ProofRequest memory) - { - Client client = getClient(1); - ProofRequest memory request = client.request(requestIdx); - bytes memory clientSignature = client.sign(request); - - client.snapshotBalance(); - testProver.snapshotBalance(); - - if (lockinMethod == LockRequestMethod.LockRequest) { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } else if (lockinMethod == LockRequestMethod.LockRequestWithSig) { - boundlessMarket.lockRequestWithSignature( - request, clientSignature, testProver.signLockRequest(LockRequest({request: request})) - ); - } - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - uint256 initialBalance = boundlessMarket.balanceOf(testProverAddress) + testProverAddress.balance; - - if (lockinMethod == LockRequestMethod.None) { - // Annoying boilerplate for creating singleton lists. - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(request); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.priceAndFulfillAndWithdraw(requests, clientSignatures, fills, assessorReceipt); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } else { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.fulfillAndWithdraw(fills, assessorReceipt); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } - - // Check that the proof was submitted - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - assert(boundlessMarket.balanceOf(testProverAddress) == 0); - assert(testProverAddress.balance == initialBalance + 1 ether); - - return (client, request); - } - - // Base for submitRoot and fulfillment tests with different methods for lock, including none. All paths should yield the same result. - function _testSubmitRootAndFulfillSameBlock( - uint32 requestIdx, - LockRequestMethod lockinMethod, - string memory snapshot - ) private returns (Client, ProofRequest memory) { - Client client = getClient(1); - ProofRequest memory request = client.request(requestIdx); - bytes memory clientSignature = client.sign(request); - - client.snapshotBalance(); - testProver.snapshotBalance(); - - if (lockinMethod == LockRequestMethod.LockRequest) { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } else if (lockinMethod == LockRequestMethod.LockRequestWithSig) { - boundlessMarket.lockRequestWithSignature( - request, clientSignature, testProver.signLockRequest(LockRequest({request: request})) - ); - } - - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory journals = new bytes[](1); - journals[0] = APP_JOURNAL; - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) = - createFills(requests, journals, testProverAddress); - - bytes memory seal = - verifier.mockProve( - SET_BUILDER_IMAGE_ID, sha256(abi.encodePacked(SET_BUILDER_IMAGE_ID, uint256(1 << 255), root)) - ) - .seal; - - if (lockinMethod == LockRequestMethod.None) { - // Annoying boilerplate for creating singleton lists. - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(request); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fills[0]); - boundlessMarket.submitRootAndPriceAndFulfill( - address(setVerifier), root, seal, requests, clientSignatures, fills, assessorReceipt - ); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } else { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fills[0]); - boundlessMarket.submitRootAndPriceAndFulfill( - address(setVerifier), root, seal, new ProofRequest[](0), new bytes[](0), fills, assessorReceipt - ); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } - - // Check that the proof was submitted - expectRequestFulfilled(fills[0].id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - - return (client, request); - } - - // Base for submitRootAndFulfillAndWithdraw tests with different methods for lock, including none. All paths should yield the same result. - function _testSubmitRootAndFulfillAndWithdrawSameBlock( - uint32 requestIdx, - LockRequestMethod lockinMethod, - string memory snapshot - ) private returns (Client, ProofRequest memory) { - Client client = getClient(1); - ProofRequest memory request = client.request(requestIdx); - bytes memory clientSignature = client.sign(request); - - client.snapshotBalance(); - testProver.snapshotBalance(); - - if (lockinMethod == LockRequestMethod.LockRequest) { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - } else if (lockinMethod == LockRequestMethod.LockRequestWithSig) { - boundlessMarket.lockRequestWithSignature( - request, clientSignature, testProver.signLockRequest(LockRequest({request: request})) - ); - } - - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory journals = new bytes[](1); - journals[0] = APP_JOURNAL; - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) = - createFills(requests, journals, testProverAddress); - - bytes memory seal = - verifier.mockProve( - SET_BUILDER_IMAGE_ID, sha256(abi.encodePacked(SET_BUILDER_IMAGE_ID, uint256(1 << 255), root)) - ) - .seal; - - uint256 initialBalance = boundlessMarket.balanceOf(testProverAddress) + testProverAddress.balance; - - if (lockinMethod == LockRequestMethod.None) { - // Annoying boilerplate for creating singleton lists. - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(request); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fills[0]); - boundlessMarket.submitRootAndPriceAndFulfillAndWithdraw( - address(setVerifier), root, seal, requests, clientSignatures, fills, assessorReceipt - ); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } else { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fills[0]); - boundlessMarket.submitRootAndPriceAndFulfillAndWithdraw( - address(setVerifier), root, seal, new ProofRequest[](0), new bytes[](0), fills, assessorReceipt - ); - if (!_stringEquals(snapshot, "")) { - vm.snapshotGasLastCall(snapshot); - } - } - - // Check that the proof was submitted - expectRequestFulfilled(fills[0].id); - - client.expectBalanceChange(-1 ether); - assert(boundlessMarket.balanceOf(testProverAddress) == 0); - assert(testProverAddress.balance == initialBalance + 1 ether); - - return (client, request); - } - - function testFulfillLockedRequest() public { - _testFulfillSameBlock(1, LockRequestMethod.LockRequest, "fulfill: a locked request"); - } - - function testFulfillAndWithdrawLockedRequest() public { - _testFulfillAndWithdrawSameBlock(1, LockRequestMethod.LockRequest, "fulfillAndWithdraw: a locked request"); - } - - function testFulfillLockedRequestWithSig() public { - _testFulfillSameBlock( - 1, LockRequestMethod.LockRequestWithSig, "fulfill: a locked request (locked via prover signature)" - ); - } - - function testFulfillDeprecatedAssessor() public { - _testFulfillDeprecatedAssessor(1); - // Warp past the deprecated assessor expiration time - vm.warp(block.timestamp + DEPRECATED_ASSESSOR_DURATION + 1 minutes); - _testFulfillDeprecatedAssessor(2); - } - - function testSubmitRootAndFulfillLockedRequest() public { - _testSubmitRootAndFulfillSameBlock(1, LockRequestMethod.LockRequest, "submitRootAndFulfill: a locked request"); - } - - function testSubmitRootAndFulfillAndWithdrawLockedRequest() public { - _testSubmitRootAndFulfillAndWithdrawSameBlock( - 1, LockRequestMethod.LockRequest, "submitRootAndFulfillAndWithdraw: a locked request" - ); - } - - function testSubmitRootAndFulfillLockedRequestWithSig() public { - _testSubmitRootAndFulfillSameBlock( - 1, - LockRequestMethod.LockRequestWithSig, - "submitRootAndFulfill: a locked request (locked via prover signature)" - ); - } - - // Check that a single client can create many requests, with the full range of indices, and - // complete the flow each time. - function testFulfillLockedRequestRangeOfRequestIdx() public { - for (uint32 idx = 0; idx < 512; idx++) { - _testFulfillSameBlock(idx, LockRequestMethod.LockRequest); - } - _testFulfillSameBlock(0xdeadbeef, LockRequestMethod.LockRequest); - _testFulfillSameBlock(0xffffffff, LockRequestMethod.LockRequest); - } - - function testFulfillLargeJournal() external { - // Generate a 10kB buffer full of non-zero bytes. - // 10kB = 320 bytes32 values (10240/32) - bytes32[] memory buffer32 = new bytes32[](320); - for (uint256 i = 0; i < buffer32.length; i++) { - buffer32[i] = bytes32(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)); - } - bytes memory bigJournal = abi.encodePacked(buffer32); - - Client client = getClient(1); - ProofRequest memory request = client.request(1); - request.requirements.predicate = - Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(sha256(bigJournal))}); - bytes memory clientSignature = client.sign(request); - - client.snapshotBalance(); - testProver.snapshotBalance(); - - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, bigJournal, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall("fulfill: a locked request with 10kB journal"); - - // Check that the proof was submitted - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - // While a request is locked, another prover can fulfill it but will not receive a payment. - function testFulfillLockedRequestByOtherProverNotRequirePayment() - public - returns (Client, Client, ProofRequest memory) - { - Client client = getClient(1); - ProofRequest memory request = client.request(3); - - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - - Client otherProver = getProver(2); - address otherProverAddress = otherProver.addr(); - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, otherProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.RequestIsLocked.selector, request.id - )); - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall("fulfill: another prover fulfills without payment"); - - expectRequestFulfilled(fill.id); - - // Provers stake is still on the line. - testProver.expectCollateralBalanceChange(-int256(uint256(request.offer.lockCollateral))); - - // No payment should have been made, as the other prover filled while the request is still locked. - otherProver.expectBalanceChange(0); - otherProver.expectCollateralBalanceChange(0); - - expectMarketBalanceUnchanged(); - - return (client, otherProver, request); - } - - // If a request was fulfilled and payment was already sent, we don't allow it to be fulfilled again. - function testFulfillLockedRequestAlreadyFulfilledAndPaid() public { - _testFulfillAlreadyFulfilled(1, LockRequestMethod.LockRequest); - _testFulfillAlreadyFulfilled(2, LockRequestMethod.LockRequestWithSig); - } - - // This is the only case where fulfill can be called twice successfully. - // In some cases, a request can be fulfilled without payment being sent. This test starts with - // one of those cases and checks that the prover can submit fulfillment again to get payment. - function testFulfillLockedRequestAlreadyFulfilledByOtherProver() public { - (, Client otherProver, ProofRequest memory request) = testFulfillLockedRequestByOtherProverNotRequirePayment(); - testProver.snapshotBalance(); - testProver.snapshotCollateralBalance(); - otherProver.snapshotBalance(); - otherProver.snapshotCollateralBalance(); - - expectRequestFulfilled(request.id); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall( - "fulfill: fulfilled by the locked prover for payment (request already fulfilled by another prover)" - ); - - expectRequestFulfilled(request.id); - - // Prover should now have received back their stake plus payment for the request. - testProver.expectBalanceChange(1 ether); - testProver.expectCollateralBalanceChange(1 ether); - - // No payment should have been made to the other prover that filled while the request was locked. - otherProver.expectBalanceChange(0); - otherProver.expectCollateralBalanceChange(0); - - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequestProverAddressNotMatchAssessorReceipt() public { - Client client = getClient(1); - - ProofRequest memory request = client.request(3); - - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - // address(3) is just a standin for some other address. - address mockOtherProverAddr = address(uint160(3)); - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - assessorReceipt.prover = mockOtherProverAddr; - vm.expectRevert(VerificationFailed.selector); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Prover should have their original balance less the stake amount. - testProver.expectCollateralBalanceChange(-int256(uint256(request.offer.lockCollateral))); - expectMarketBalanceUnchanged(); - } - - // Tests trying to fulfill a request that was locked and has now expired. - function testFulfillLockedRequestFullyExpired() public returns (Client, ProofRequest memory) { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - client.snapshotBalance(); - testProver.snapshotBalance(); - - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - // At this point the client should have only been charged the 1 ETH at lock time. - client.expectBalanceChange(-1 ether); - - // Advance the chain ahead to simulate the request timeout. - vm.warp(request.offer.deadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // Try the priceAndFulfill path. - bytes[] memory paymentErrors = - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - assert( - keccak256(paymentErrors[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsExpired.selector, request.id)) - ); - expectRequestNotFulfilled(fill.id); - - // Client is out 1 eth until slash is called. - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(0 ether); - testProver.expectCollateralBalanceChange(-1 ether); - expectMarketBalanceUnchanged(); - - // Try the fulfill path as well. Should be the same results. - paymentErrors = boundlessMarket.fulfill(fills, assessorReceipt); - assert( - keccak256(paymentErrors[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsExpired.selector, request.id)) - ); - expectRequestNotFulfilled(fill.id); - - // Client is out 1 eth until slash is called. - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(0 ether); - testProver.expectCollateralBalanceChange(-1 ether); - expectMarketBalanceUnchanged(); - - return (client, request); - } - - function testFulfillLockedRequestMultipleRequestsSameIndex() public { - _testFulfillRepeatIndex(LockRequestMethod.LockRequest); - } - - function testFulfillLockedRequestMultipleRequestsSameIndexWithSig() public { - _testFulfillRepeatIndex(LockRequestMethod.LockRequestWithSig); - } - - // Scenario when a prover locks a request, fails to deliver it within the lock expiry, - // then another prover fulfills a request after the lock has expired, - // but before the request as a whole has expired. - function testFulfillWasLockedRequestByOtherProver() public returns (ProofRequest memory, Client, Client, Client) { - // Create a request with a lock timeout of 50 blocks, and overall timeout of 100. - Client client = getClient(1); - ProofRequest memory request = client.request( - 1, - Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(50), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }) - ); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - Client locker = getProver(1); - Client otherProver = getProver(2); - - client.snapshotBalance(); - locker.snapshotBalance(); - otherProver.snapshotBalance(); - - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(request, clientSignature); - // At this point the client should have only been charged the 1 ETH at lock time. - client.expectBalanceChange(-1 ether); - - // Advance the chain ahead to simulate the lock timeout. - vm.warp(request.offer.lockDeadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, otherProver.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, otherProver.addr(), fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, otherProver.addr(), fill); - - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - // Check that the proof was submitted - expectRequestFulfilled(fill.id); - - // Client's fee should be returned on fulfill. - client.expectBalanceChange(0 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - otherProver.expectBalanceChange(0 ether); - otherProver.expectCollateralBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - - return (request, client, locker, otherProver); - } - - function testFulfillWasLockedClientWithdrawsBalance() public { - Client client = getClient(1); - ProofRequest memory request = client.request( - 1, - Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(50), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }) - ); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - address clientAddress = client.addr(); - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - uint256 balance = boundlessMarket.balanceOf(clientAddress); - vm.prank(clientAddress); - boundlessMarket.withdraw(balance); - - client.snapshotBalance(); - - // Advance the chain ahead to simulate the lock timeout. - vm.warp(request.offer.lockDeadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // Fulfill should complete successfully. - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - expectRequestFulfilled(fill.id); - - // Client should get back 1 eth upon fulfill. - client.expectBalanceChange(1 ether); - testProver.expectBalanceChange(0 ether); - testProver.expectCollateralBalanceChange(-1 ether); - } - - // Scenario when a prover locks a request, fails to deliver it within the lock expiry, - // but does deliver it before the request expires. Here they should lose their stake, - // but receive payment for the request. - function testFulfillWasLockedRequestByOriginalLocker() public returns (ProofRequest memory, Client) { - // Create a request with a lock timeout of 50 blocks, and overall timeout of 100. - Client client = getClient(1); - ProofRequest memory request = client.request( - 1, - Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(50), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }) - ); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - Client locker = getProver(1); - - client.snapshotBalance(); - locker.snapshotBalance(); - - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(request, clientSignature); - - // Advance the chain ahead to simulate the lock timeout. - vm.warp(request.offer.lockDeadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, locker.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, lockerAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, lockerAddress, fill); - - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - // Check that the proof was submitted - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(0 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - expectMarketBalanceUnchanged(); - return (request, locker); - } - - // One request is locked, fully expires. - // A second request with the same id is then fulfilled. - // Slash should award stake to the fulfiller of the second request. - function testFulfillWasLockedRequestRepeatIndexStakeRollover() public { - Client client = getClient(1); - - Offer memory offerA = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: uint32(100), - lockCollateral: 1 ether - }); - Offer memory offerB = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp) + uint64(offerA.timeout) + 1, - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: 100, - lockCollateral: 1 ether - }); - - ProofRequest memory requestA = client.request(1, offerA); - ProofRequest memory requestB = client.request(1, offerB); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = requestB; - bytes memory clientSignatureA = client.sign(requestA); - bytes memory clientSignatureB = client.sign(requestB); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignatureB; - Client locker = getProver(1); - Client fulfiller = getProver(2); - - client.snapshotBalance(); - locker.snapshotBalance(); - fulfiller.snapshotBalance(); - - // Lock-in request A. - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(requestA, clientSignatureA); - - vm.warp(uint64(block.timestamp) + uint64(offerA.timeout) + 1); - // Attempt to fill request B. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(requestB, APP_JOURNAL, fulfiller.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - // Check that the request ID is marked as fulfilled. - expectRequestFulfilled(fill.id); - - boundlessMarket.slash(fill.id); - - client.expectBalanceChange(-1 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - fulfiller.expectBalanceChange(1 ether); - fulfiller.expectCollateralBalanceChange(uint256(expectedSlashTransferAmount(offerA.lockCollateral)).toInt256()); - expectMarketBalanceUnchanged(); - } - - // One request is locked, the lock expires, but the request is not yet expired. - // A second request with the same id is then fulfilled. - // Slash should award stake to the fulfiller of the second request. - function testFulfillWasLockedRequestRepeatIndexStakeRolloverFirstRequestNotExpired() public { - Client client = getClient(1); - - Offer memory offerA = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }); - Offer memory offerB = Offer({ - minPrice: 2 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(0), - lockTimeout: offerA.timeout + 101, - timeout: offerA.timeout + 101, - lockCollateral: 1 ether - }); - - ProofRequest memory requestA = client.request(1, offerA); - ProofRequest memory requestB = client.request(1, offerB); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = requestB; - bytes memory clientSignatureA = client.sign(requestA); - bytes memory clientSignatureB = client.sign(requestB); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignatureB; - Client locker = getProver(1); - Client fulfiller = getProver(2); - - client.snapshotBalance(); - locker.snapshotBalance(); - fulfiller.snapshotBalance(); - - // Lock-in request A. - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(requestA, clientSignatureA); - - vm.warp(offerA.lockDeadline() + 1); - // Attempt to fill request B. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(requestB, APP_JOURNAL, fulfiller.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - // Check that the request ID is marked as fulfilled. - expectRequestFulfilled(fill.id); - - // Slash should revert as the original locked request has not yet fully expired. - vm.expectRevert( - abi.encodeWithSelector( - IBoundlessMarket.RequestIsNotExpired.selector, fill.id, uint64(block.timestamp) + uint64(offerA.timeout) - ) - ); - boundlessMarket.slash(fill.id); - - // Advance to where the original locked request has fully expired. - vm.warp(uint64(block.timestamp) + uint64(offerA.timeout) + 1); - - vm.prank(lockerAddress); - boundlessMarket.slash(fill.id); - - client.expectBalanceChange(-2 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - fulfiller.expectBalanceChange(2 ether); - fulfiller.expectCollateralBalanceChange(uint256(expectedSlashTransferAmount(offerA.lockCollateral)).toInt256()); - expectMarketBalanceUnchanged(); - } - - // One request is locked and the client is charged 2 ether. The request expires unfulfilled. - // A second request with the same id is then fulfilled for a cost of just 1 ether. - // The client should be refunded the difference. - function testFulfillWasLockedRequestRepeatIndexSecondRequestCheaper() public { - Client client = getClient(1); - - // Create two distinct requests with the same ID. It should be the case that only one can be - // filled, and if one is locked, the other cannot be filled. - Offer memory offerA = Offer({ - minPrice: 2 ether, - maxPrice: 3 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }); - Offer memory offerB = Offer({ - minPrice: 1 ether, - maxPrice: 1 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(0), - lockTimeout: uint32(100), - timeout: uint32(block.timestamp) + offerA.timeout + 101, - lockCollateral: 1 ether - }); - - ProofRequest memory requestA = client.request(1, offerA); - ProofRequest memory requestB = client.request(1, offerB); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = requestB; - bytes memory clientSignatureA = client.sign(requestA); - bytes memory clientSignatureB = client.sign(requestB); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignatureB; - Client locker = getProver(1); - Client fulfiller = getProver(2); - - client.snapshotBalance(); - locker.snapshotBalance(); - fulfiller.snapshotBalance(); - - // Lock-in request A. - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(requestA, clientSignatureA); - - client.expectBalanceChange(-2 ether); - - vm.warp(offerA.lockDeadline() + 1); - - // Attempt to fill request B, which costs just 1 ether at the time of fulfillment. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(requestB, APP_JOURNAL, fulfiller.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - // Client should be refunded 1 ether, meaning their net balance change is -1 - client.expectBalanceChange(-1 ether); - - // Check that the request ID is marked as fulfilled. - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - fulfiller.expectBalanceChange(1 ether); - fulfiller.expectCollateralBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - // One request is locked, expires, and is slashed. - // A second request with the same id is then fulfilled. - function testFulfillWasLockedRequestRepeatIndexStakeRolloverSlashedBeforeFulfill() public { - Client client = getClient(1); - - // Create two distinct requests with the same ID. It should be the case that only one can be - // filled, and if one is locked, the other cannot be filled. - Offer memory offerA = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: uint32(100), - lockCollateral: 1 ether - }); - Offer memory offerB = Offer({ - minPrice: 3 ether, - maxPrice: 3 ether, - rampUpStart: uint64(block.timestamp) + uint64(offerA.timeout) + 1, - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: 100, - lockCollateral: 1 ether - }); - - ProofRequest memory requestA = client.request(1, offerA); - ProofRequest memory requestB = client.request(1, offerB); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = requestB; - bytes memory clientSignatureA = client.sign(requestA); - bytes memory clientSignatureB = client.sign(requestB); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignatureB; - Client locker = getProver(1); - Client fulfiller = getProver(2); - - client.snapshotBalance(); - locker.snapshotBalance(); - fulfiller.snapshotBalance(); - - // Lock-in request A. - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(requestA, clientSignatureA); - - vm.warp(uint64(block.timestamp) + uint64(offerA.timeout) + 1); - - // Slash the request first. - vm.prank(lockerAddress); - boundlessMarket.slash(requestA.id); - - // Attempt to fill request B. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(requestB, APP_JOURNAL, fulfiller.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - address fulfillerAddress = fulfiller.addr(); - vm.prank(fulfillerAddress); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - // Check that the request ID is marked as fulfilled. - expectRequestFulfilledAndSlashed(fill.id); - - client.expectBalanceChange(-3 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - fulfiller.expectBalanceChange(3 ether); - fulfiller.expectCollateralBalanceChange(0 ether); - } - - // Scenario when a prover locks a request, fails to deliver it within the lock expiry, - // but does deliver it before the request expires. Here they should lose most of their stake - // (not all), and receive no payment from the client. - function testFulfillWasLockedRequestDoubleFulfill() public { - // Create a request with a lock timeout of 50 blocks, and overall timeout of 100. - Client client = getClient(1); - ProofRequest memory request = client.request( - 1, - Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(50), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }) - ); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - Client locker = getProver(1); - address lockerAddress = locker.addr(); - - client.snapshotBalance(); - locker.snapshotBalance(); - - vm.prank(lockerAddress); - boundlessMarket.lockRequest(request, clientSignature); - - // Advance the chain ahead to simulate the lock timeout. - vm.warp(request.offer.lockDeadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, lockerAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.RequestIsFulfilled.selector, request.id - )); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - vm.snapshotGasLastCall("priceAndFulfill: fulfill already fulfilled was locked request"); - - // Check that the proof was submitted - expectRequestFulfilled(fill.id); - - // Check balances after the fulfillment but before slash. - client.expectBalanceChange(0 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - - vm.warp(request.offer.deadline() + 1); - boundlessMarket.slash(request.id); - - // Check balances after the slash. - client.expectBalanceChange(0 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-int256(uint256(expectedSlashBurnAmount(request.offer.lockCollateral)))); - } - - // Scenario when a prover locks a request, fails to deliver it within the lock expiry, - // another prover fulfills the request, and then the locker tries to fulfill the request - // before the request as a whole has expired. A proof should still be delivered and no revert - // should occur, since we support multiple proofs being delivered for a single request. No - // balance changes should occur. - function testFulfillWasLockedRequestLockerFulfillAfterAnotherProverFulfill() public { - (ProofRequest memory request, Client client, Client locker,) = testFulfillWasLockedRequestByOtherProver(); - - locker.snapshotBalance(); - locker.snapshotCollateralBalance(); - - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - // The locker should have no balance change. - // Now the locker tries to fulfill the request. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, locker.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // But its already been fulfilled by the other prover. - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.RequestIsFulfilled.selector, request.id - )); - - // The proof should still be delivered. - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, locker.addr(), fill); - - // The fulfillment should not revert, as we support multiple proofs being delivered for a single request. - bytes[] memory paymentErrors = - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - assert( - keccak256(paymentErrors[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsFulfilled.selector, request.id)) - ); - - // The locker should have no balance change. - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - // Scenario when a prover locks a request, fails to deliver it within the lock expiry, - // another prover fulfills the request, and then the locker tries to fulfill the request - // _after_ the request has fully expired. - // - // In this case the request has fully expired, so the proof should NOT be delivered, - // however we should not revert (as this allows partial fulfillment of other requests in the batch). - function testFulfillWasLockedRequestLockerFulfillAfterAnotherProverFulfillAndRequestExpired() public { - (ProofRequest memory request, Client client, Client locker,) = testFulfillWasLockedRequestByOtherProver(); - - locker.snapshotBalance(); - locker.snapshotCollateralBalance(); - - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - // Advance the chain ahead to simulate the request expiration. - vm.warp(request.offer.deadline() + 1); - - // The locker should have no balance change. - // Now the locker tries to fulfill the request. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, locker.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // In this case the request has fully expired, so the proof should NOT be delivered, - // however we should not revert (as this allows partial fulfillment of other requests in the batch) - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.RequestIsExpired.selector, request.id - )); - - // The fulfillment should not revert, as we support multiple proofs being delivered for a single request. - bytes[] memory paymentErrors = - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - assert( - keccak256(paymentErrors[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsExpired.selector, request.id)) - ); - - // The locker should have no balance change. - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - // A request is locked with a valid smart contract signature (signature is checked onchain at lock time) - // and then a prover tries to fulfill it specifying an invalid smart contract signature. The signature could - // be invalid for a number of reasons, including the smart contract wallet rotating their signers so the old signature - // is no longer valid. - // Since there is possibility of funds being pulled in the multiple request same id case, we ensure we check - // the SC signature again. - function testFulfillWasLockedRequestByInvalidSmartContractSignature() public { - SmartContractClient client = getSmartContractClient(1); - // Request ID indicates smart contract signature, but the signature is invalid. - ProofRequest memory request = client.request( - 1, - Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(50), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }) - ); - bytes memory validClientSignature = client.sign(request); - bytes memory invalidClientSignature = bytes("invalid"); - - boundlessMarket.lockRequestWithSignature( - request, validClientSignature, testProver.signLockRequest(LockRequest({request: request})) - ); - vm.warp(request.offer.lockDeadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // Fulfill should succeed even though the lock has expired when the request matches what was locked. - boundlessMarket.fulfill(fills, assessorReceipt); - - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = invalidClientSignature; - // Fulfill should revert during the signature check during pricing, since the signature is invalid. - // NOTE: This should revert, even though we know the request was signed previously because - // of signature validation during the lock operation, because the signature in this call is - // invalid. As a principle, all data in a message must be validated, even if the data given - // is superfluous. - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InvalidSignature.selector)); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - clientSignatures[0] = validClientSignature; - // Fulfill should succeed if the signature is valid. - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(0 ether); - testProver.expectBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillNeverLocked() public { - _testFulfillSameBlock(1, LockRequestMethod.None, "priceAndFulfill: a single request that was not locked"); - } - - /// Fulfill without locking should still work even if the prover does not have stake. - function testFulfillNeverLockedProverNoStake() public { - vm.prank(testProverAddress); - boundlessMarket.withdrawCollateral(DEFAULT_BALANCE); - - _testFulfillSameBlock( - 1, - LockRequestMethod.None, - "priceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list" - ); - } - - function testSubmitRootAndFulfillNeverLocked() public { - _testSubmitRootAndFulfillSameBlock( - 1, LockRequestMethod.None, "submitRootAndPriceAndFulfill: a single request that was not locked" - ); - } - - /// SubmitRootAndFulfill without locking should still work even if the prover does not have stake. - function testSubmitRootAndFulfillNeverLockedProverNoStake() public { - vm.prank(testProverAddress); - boundlessMarket.withdrawCollateral(DEFAULT_BALANCE); - - _testSubmitRootAndFulfillSameBlock( - 1, - LockRequestMethod.None, - "submitRootAndPriceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list" - ); - } - - function testFulfillNeverLockedNotPriced() public { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // Attempt to fulfill a request without locking or pricing it. - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsNotLockedOrPriced.selector, request.id)); - boundlessMarket.fulfill(fills, assessorReceipt); - - expectMarketBalanceUnchanged(); - } - - // Should revert as you can not fulfill a request twice, except for in the case covered by: - // `testFulfillLockedRequestAlreadyFulfilledByOtherProver` - function testFulfillNeverLockedAlreadyFulfilledAndPaid() public { - _testFulfillAlreadyFulfilled(3, LockRequestMethod.None); - } - - function testFulfillNeverLockedFullyExpired() public returns (Client, ProofRequest memory) { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.warp(request.offer.deadline() + 1); - - bytes[] memory paymentErrors = - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - assert( - keccak256(paymentErrors[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsExpired.selector, request.id)) - ); - expectRequestNotFulfilled(fill.id); - - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsNotLockedOrPriced.selector, request.id)); - boundlessMarket.fulfill(fills, assessorReceipt); - - expectRequestNotFulfilled(fill.id); - client.expectBalanceChange(0 ether); - testProver.expectBalanceChange(0 ether); - testProver.expectCollateralBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - - return (client, request); - } - - function testFulfillNeverLockedClientWithdrawsBalance() public { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - address clientAddress = client.addr(); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - uint256 balance = boundlessMarket.balanceOf(clientAddress); - vm.prank(clientAddress); - boundlessMarket.withdraw(balance); - - // expect emit of payment requirement failed - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.InsufficientBalance.selector, clientAddress - )); - vm.prank(clientAddress); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - expectRequestFulfilled(fill.id); - } - - function testFulfillNeverLockedRequestMultipleRequestsSameIndex() public { - _testFulfillRepeatIndex(LockRequestMethod.None); - } - - // Fulfill a batch of locked requests - function testFulfillLockedRequests() public { - // Provide a batch definition as an array of clients and how many requests each submits. - uint256[5] memory batch = [uint256(1), 2, 1, 3, 1]; - uint256 batchSize = 0; - for (uint256 i = 0; i < batch.length; i++) { - batchSize += batch[i]; - } - ProofRequest[] memory requests = new ProofRequest[](batchSize); - bytes[] memory journals = new bytes[](batchSize); - uint256 expectedRevenue = 0; - uint256 idx = 0; - for (uint256 i = 0; i < batch.length; i++) { - Client client = getClient(i); - - for (uint256 j = 0; j < batch[i]; j++) { - ProofRequest memory request = client.request(uint32(j)); - - // TODO: This is a fragile part of this test. It should be improved. - uint256 desiredPrice = uint256(1.5 ether); - vm.warp(request.offer.timeAtPrice(desiredPrice)); - expectedRevenue += desiredPrice; - - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - - requests[idx] = request; - journals[idx] = APP_JOURNAL; - idx++; - } - } - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress); - - for (uint256 i = 0; i < fills.length; i++) { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(fills[i].id, testProverAddress, fills[i].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(fills[i].id, testProverAddress, fills[i]); - } - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall(string.concat("fulfill: a batch of ", vm.toString(batchSize))); - - for (uint256 i = 0; i < fills.length; i++) { - // Check that the proof was submitted - expectRequestFulfilled(fills[i].id); - } - - testProver.expectBalanceChange(int256(uint256(expectedRevenue))); - expectMarketBalanceUnchanged(); - } - - // Fulfill a batch of locked ClaimDigestMatch requests with no journal - function testFulfillLockedRequestsNoJournal() public { - // Provide a batch definition as an array of clients and how many requests each submits. - uint256[5] memory batch = [uint256(1), 2, 1, 3, 1]; - uint256 batchSize = 0; - for (uint256 i = 0; i < batch.length; i++) { - batchSize += batch[i]; - } - ProofRequest[] memory requests = new ProofRequest[](batchSize); - bytes[] memory journals = new bytes[](batchSize); - uint256 expectedRevenue = 0; - uint256 idx = 0; - - for (uint256 i = 0; i < batch.length; i++) { - Client client = getClient(i); - - for (uint256 j = 0; j < batch[i]; j++) { - ProofRequest memory request = client.request(uint32(j)); - bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); - - request.requirements.predicate = Predicate({ - predicateType: PredicateType.ClaimDigestMatch, - data: abi.encode(ReceiptClaimLib.ok(imageId, sha256(APP_JOURNAL)).digest()) - }); - - // TODO: This is a fragile part of this test. It should be improved. - uint256 desiredPrice = uint256(1.5 ether); - vm.warp(request.offer.timeAtPrice(desiredPrice)); - expectedRevenue += desiredPrice; - - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - - requests[idx] = request; - journals[idx] = APP_JOURNAL; - idx++; - } - } - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress, FulfillmentDataType.None); - - for (uint256 i = 0; i < fills.length; i++) { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(fills[i].id, testProverAddress, fills[i].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(fills[i].id, testProverAddress, fills[i]); - } - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall(string.concat("fulfill (no journal): a batch of ", vm.toString(batchSize))); - for (uint256 i = 0; i < fills.length; i++) { - // Check that the proof was submitted - expectRequestFulfilled(fills[i].id); - } - - testProver.expectBalanceChange(int256(uint256(expectedRevenue))); - expectMarketBalanceUnchanged(); - } - - // Testing that reordering request IDs in a batch will cause the fulfill to revert. - function testFulfillShuffleIds() public { - uint256[5] memory batch = [uint256(1), 2, 1, 3, 1]; - uint256 batchSize = 0; - for (uint256 i = 0; i < batch.length; i++) { - batchSize += batch[i]; - } - ProofRequest[] memory requests = new ProofRequest[](batchSize); - bytes[] memory journals = new bytes[](batchSize); - bytes[] memory signatures = new bytes[](batchSize); - uint256 idx = 0; - for (uint256 i = 0; i < batch.length; i++) { - Client client = getClient(i); - - for (uint256 j = 0; j < batch[i]; j++) { - ProofRequest memory request = client.request(uint32(j)); - - requests[idx] = request; - journals[idx] = APP_JOURNAL; - signatures[idx] = client.sign(request); - idx++; - } - } - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress); - - // Swap first two IDs - RequestId id0 = fills[0].id; - fills[0].id = fills[1].id; - fills[1].id = id0; - - vm.warp(requests[0].offer.timeAtPrice(uint256(1.5 ether))); - vm.expectRevert(VerificationFailed.selector); - boundlessMarket.priceAndFulfill(requests, signatures, fills, assessorReceipt); - - expectMarketBalanceUnchanged(); - } - - // Testing that reordering fulfillments in a batch will cause the fulfill to revert. - function testFulfillShuffleFills() public { - uint256 batchSize = 2; - ProofRequest[] memory requests = new ProofRequest[](batchSize); - bytes[] memory journals = new bytes[](batchSize); - - // First request - Client client = getClient(0); - ProofRequest memory request = client.request(uint32(0)); - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - requests[0] = request; - journals[0] = APP_JOURNAL; - - // Second request - client = getClient(1); - request = client.request(uint32(1)); - - request.requirements = Requirements({ - predicate: PredicateLibrary.createDigestMatchPredicate(bytes32(APP_IMAGE_ID_2), sha256(APP_JOURNAL_2)), - selector: bytes4(0), - callback: Callback({addr: address(0), gasLimit: 0}) - }); - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - requests[1] = request; - journals[1] = APP_JOURNAL_2; - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress); - - bytes memory fulfillmentData0 = fills[0].fulfillmentData; - bytes32 claimDigest0 = fills[0].claimDigest; - - fills[0].fulfillmentData = fills[1].fulfillmentData; - fills[1].fulfillmentData = fulfillmentData0; - - fills[0].claimDigest = fills[1].claimDigest; - fills[1].claimDigest = claimDigest0; - - vm.expectRevert(VerificationFailed.selector); - boundlessMarket.fulfill(fills, assessorReceipt); - - expectMarketBalanceUnchanged(); - } - - // Test that a smart contract signature can be used to price a request. - // The smart contract signature must be validated when a request is priced. This - // ensures that the smart contract signature is checked in the never locked path, - // since the signature is not checked at lock time (nor in the assessor). - function testPriceRequestSmartContractSignature() external { - SmartContractClient client = getSmartContractClient(1); - ProofRequest memory request = client.request(3); - bytes memory clientSignature = client.sign(request); - - // Expect isValidSignature to be called on the smart contract wallet - bytes32 requestHash = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), request.eip712Digest()); - vm.expectCall( - client.addr(), abi.encodeWithSelector(IERC1271.isValidSignature.selector, requestHash, clientSignature) - ); - boundlessMarket.priceRequest(request, clientSignature); - } - - function testPriceRequestSmartContractSignatureExceedsGasLimit() external { - SmartContractClient client = getSmartContractClient(1); - client.smartWallet().setGasCost(boundlessMarket.ERC1271_MAX_GAS_FOR_CHECK() + 1); - ProofRequest memory request = client.request(3); - bytes memory clientSignature = client.sign(request); - - // Expect isValidSignature to be called on the smart contract wallet - bytes32 requestHash = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), request.eip712Digest()); - vm.expectCall( - client.addr(), abi.encodeWithSelector(IERC1271.isValidSignature.selector, requestHash, clientSignature) - ); - vm.expectRevert(bytes("")); // revert due to out of gas results in empty error - boundlessMarket.priceRequest(request, clientSignature); - } - - // Test that a smart contract signature can be used to price and fulfill a request. - // The smart contract signature must be validated when a request is priced. This - // ensures that the smart contract signature is validated during the never locked path, - // since the signature is not checked at lock time (nor in the assessor). - function testPriceAndFulfillSmartContractSignature() external { - SmartContractClient client = getSmartContractClient(1); - ProofRequest memory request = client.request(3); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - // Expect isValidSignature to be called on the smart contract wallet - bytes32 requestHash = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), request.eip712Digest()); - vm.expectCall( - client.addr(), abi.encodeWithSelector(IERC1271.isValidSignature.selector, requestHash, clientSignature) - ); - - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - vm.snapshotGasLastCall("priceAndFulfill: a single request (smart contract signature)"); - - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - // Fulfill a batch of locked requests and withdraw - function testFulfillAndWithdrawLockedRequests() public { - // Provide a batch definition as an array of clients and how many requests each submits. - uint256[5] memory batch = [uint256(1), 2, 1, 3, 1]; - uint256 batchSize = 0; - for (uint256 i = 0; i < batch.length; i++) { - batchSize += batch[i]; - } - - ProofRequest[] memory requests = new ProofRequest[](batchSize); - bytes[] memory journals = new bytes[](batchSize); - uint256 expectedRevenue = 0; - uint256 idx = 0; - for (uint256 i = 0; i < batch.length; i++) { - Client client = getClient(i); - - for (uint256 j = 0; j < batch[i]; j++) { - ProofRequest memory request = client.request(uint32(j)); - - // TODO: This is a fragile part of this test. It should be improved. - uint256 desiredPrice = uint256(1.5 ether); - vm.warp(request.offer.timeAtPrice(desiredPrice)); - expectedRevenue += desiredPrice; - - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - - requests[idx] = request; - journals[idx] = APP_JOURNAL; - idx++; - } - } - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress); - - uint256 initialBalance = testProverAddress.balance + boundlessMarket.balanceOf(testProverAddress); - - for (uint256 i = 0; i < fills.length; i++) { - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(fills[i].id, testProverAddress, fills[i].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(fills[i].id, testProverAddress, fills[i]); - } - boundlessMarket.fulfillAndWithdraw(fills, assessorReceipt); - vm.snapshotGasLastCall(string.concat("fulfillAndWithdraw: a batch of ", vm.toString(batchSize))); - - for (uint256 i = 0; i < fills.length; i++) { - // Check that the proof was submitted - expectRequestFulfilled(fills[i].id); - } - - assert(boundlessMarket.balanceOf(testProverAddress) == 0); - assert(testProverAddress.balance == initialBalance + uint256(expectedRevenue)); - } - - function testPriceAndFulfillLockedRequest() external { - Client client = getClient(1); - ProofRequest memory request = client.request(3); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(request); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - vm.snapshotGasLastCall("priceAndFulfill: a single request"); - - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function testSubmitRootAndPriceAndFulfillLockedRequest() external { - Client client = getClient(1); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = client.request(3); - bytes[] memory journals = new bytes[](1); - journals[0] = APP_JOURNAL; - - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) = - createFills(requests, journals, testProverAddress); - - bytes memory seal = - verifier.mockProve( - SET_BUILDER_IMAGE_ID, sha256(abi.encodePacked(SET_BUILDER_IMAGE_ID, uint256(1 << 255), root)) - ) - .seal; - - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(requests[0]); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(requests[0].id, testProverAddress, fills[0].requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(requests[0].id, testProverAddress, fills[0]); - boundlessMarket.submitRootAndPriceAndFulfill( - address(setVerifier), root, seal, requests, clientSignatures, fills, assessorReceipt - ); - vm.snapshotGasLastCall("submitRootAndPriceAndFulfill: a single request"); - - expectRequestFulfilled(fills[0].id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function _testFulfillAlreadyFulfilled(uint32 idx, LockRequestMethod lockinMethod) private { - (, ProofRequest memory request) = _testFulfillSameBlock(idx, lockinMethod); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = getClient(1).sign(request); - - // TODO(#704): Workaround in test for edge case described in #704 - vm.warp(request.offer.lockDeadline() + 1); - - // Attempt to fulfill a request already fulfilled - // should return "RequestIsFulfilled({requestId: request.id})" - bytes[] memory paymentError = - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - assert( - keccak256(paymentError[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsFulfilled.selector, request.id)) - ); - - expectMarketBalanceUnchanged(); - } - - function testPriceAndFulfillWithSelector() external { - Client client = getClient(1); - ProofRequest memory request = client.request(3); - request.requirements.selector = setVerifier.SELECTOR(); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(request); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - vm.snapshotGasLastCall("priceAndFulfill: a single request (with selector)"); - - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillRequestWrongSelector() public { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - request.requirements.selector = setVerifier.SELECTOR(); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // Attempt to fulfill a request with wrong selector. - assessorReceipt.selectors[0] = Selector({index: 0, value: bytes4(0xdeadbeef)}); - vm.expectRevert( - abi.encodeWithSelector( - IBoundlessMarket.SelectorMismatch.selector, bytes4(0xdeadbeef), setVerifier.SELECTOR() - ) - ); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - expectMarketBalanceUnchanged(); - } - - function testFulfillApplicationVerificationGasLimit() public { - Client client = getClient(1); - ProofRequest memory request = client.request(3); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - FulfillmentDataImageIdAndJournal memory fulfillmentData = - FulfillmentDataLibrary.decodeFulfillmentDataImageIdAndJournal(fill.fulfillmentData); - bytes32 claimDigest = ReceiptClaimLib.ok(fulfillmentData.imageId, sha256(fulfillmentData.journal)).digest(); - - // If no selector is specified, we expect the call to verifyIntegrity to use the default - // gas limit when verifying the application. - vm.expectCall( - address(setVerifier), - 0, - uint64(EXPECTED_DEFAULT_MAX_GAS_FOR_VERIFY), - abi.encodeWithSelector(IRiscZeroVerifier.verifyIntegrity.selector, RiscZeroReceipt(fill.seal, claimDigest)) - ); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillVerificationGasLimitForSelector() public { - Client client = getClient(1); - ProofRequest memory request = client.request(3); - request.requirements.selector = setVerifier.SELECTOR(); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - FulfillmentDataImageIdAndJournal memory fulfillmentData = - FulfillmentDataLibrary.decodeFulfillmentDataImageIdAndJournal(fill.fulfillmentData); - bytes32 claimDigest = ReceiptClaimLib.ok(fulfillmentData.imageId, sha256(fulfillmentData.journal)).digest(); - - // If a selector is specified, we expect the call to verifyIntegrity to not use the default - // gas limit, so the minimum gas it should have should exceed it. - vm.expectCallMinGas( - address(setVerifier), - 0, - uint64(EXPECTED_DEFAULT_MAX_GAS_FOR_VERIFY + 1), - abi.encodeWithSelector(IRiscZeroVerifier.verifyIntegrity.selector, RiscZeroReceipt(fill.seal, claimDigest)) - ); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - expectRequestFulfilled(fill.id); - - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function _testFulfillRepeatIndex(LockRequestMethod lockinMethod) private { - Client client = getClient(1); - - // Create two distinct requests with the same ID. It should be the case that only one can be - // filled, and if one is locked, the other cannot be filled. - Offer memory offerA = client.defaultOffer(); - Offer memory offerB = client.defaultOffer(); - offerB.maxPrice = 3 ether; - ProofRequest memory requestA = client.request(1, offerA); - ProofRequest memory requestB = client.request(1, offerB); - bytes memory clientSignatureA = client.sign(requestA); - - // Lock-in request A. - if (lockinMethod == LockRequestMethod.LockRequest) { - vm.prank(testProverAddress); - boundlessMarket.lockRequest(requestA, clientSignatureA); - } else if (lockinMethod == LockRequestMethod.LockRequestWithSig) { - boundlessMarket.lockRequestWithSignature( - requestA, clientSignatureA, testProver.signLockRequest(LockRequest({request: requestA})) - ); - } - - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Attempt to fill request B. - (Fulfillment memory fillB, AssessorReceipt memory assessorReceiptB) = - createFillAndSubmitRoot(requestB, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fillsB = new Fulfillment[](1); - fillsB[0] = fillB; - - if (lockinMethod == LockRequestMethod.None) { - // Annoying boilerplate for creating singleton lists. - // Here we price/lock with request A and try to fill with request B. - ProofRequest[] memory requestsA = new ProofRequest[](1); - requestsA[0] = requestA; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignatureA; - - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsNotLockedOrPriced.selector, requestA.id)); - boundlessMarket.priceAndFulfill(requestsA, clientSignatures, fillsB, assessorReceiptB); - - expectRequestNotFulfilled(fillB.id); - } else { - // Attempting to fulfill request B should revert, since it has never been seen onchain. - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsNotLockedOrPriced.selector, requestA.id)); - boundlessMarket.fulfill(fillsB, assessorReceiptB); - expectRequestNotFulfilled(fillB.id); - - // Attempting to price and fulfill with request B should return a - // payment error since request A is still locked. - ProofRequest[] memory requestsB = new ProofRequest[](1); - requestsB[0] = requestB; - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = client.sign(requestB); - - bytes[] memory paymentErrors = - boundlessMarket.priceAndFulfill(requestsB, clientSignatures, fillsB, assessorReceiptB); - assert( - keccak256(paymentErrors[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsLocked.selector, requestB.id)) - ); - expectRequestFulfilled(fillB.id); - } - - // No balance changes should have occurred after lockin. - client.expectBalanceChange(0 ether); - testProver.expectBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - function testSubmitRootAndFulfill() public { - (ProofRequest[] memory requests, bytes[] memory journals) = newBatch(2); - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) = - createFills(requests, journals, testProverAddress); - - bytes memory seal = - verifier.mockProve( - SET_BUILDER_IMAGE_ID, sha256(abi.encodePacked(SET_BUILDER_IMAGE_ID, uint256(1 << 255), root)) - ) - .seal; - boundlessMarket.submitRootAndFulfill(address(setVerifier), root, seal, fills, assessorReceipt); - vm.snapshotGasLastCall("submitRootAndFulfill: a batch of 2 requests"); - - for (uint256 j = 0; j < fills.length; j++) { - expectRequestFulfilled(fills[j].id); - } - } - - function testSlashLockedRequestFullyExpired() public returns (Client, ProofRequest memory) { - (Client client, ProofRequest memory request) = testFulfillLockedRequestFullyExpired(); - // Provers stake balance is subtracted at lock time, not when slash is called - testProver.expectCollateralBalanceChange(-uint256(request.offer.lockCollateral).toInt256()); - - snapshotMarketCollateralBalance(); - snapshotMarketStakeTreasuryBalance(); - - // Slash the request - // Burning = sending tokens to address 0xdEaD, expect a transfer event to be emitted to address 0xdEaD - vm.expectEmit(true, true, true, false); - emit IERC20.Transfer(address(proxy), address(0xdEaD), request.offer.lockCollateral); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.ProverSlashed( - request.id, - expectedSlashBurnAmount(request.offer.lockCollateral), - expectedSlashTransferAmount(request.offer.lockCollateral), - address(boundlessMarket) - ); - - boundlessMarket.slash(request.id); - vm.snapshotGasLastCall("slash: base case"); - - expectMarketCollateralBalanceChange(-int256(int96(expectedSlashBurnAmount(request.offer.lockCollateral)))); - expectMarketCollateralTreasuryBalanceChange( - int256(int96(expectedSlashTransferAmount(request.offer.lockCollateral))) - ); - - client.expectBalanceChange(0 ether); - testProver.expectCollateralBalanceChange(-uint256(request.offer.lockCollateral).toInt256()); - - // Check that the request is slashed and is not fulfilled - expectRequestSlashed(request.id); - - return (client, request); - } - - // Prover locks a request, the request expires, then they fulfill a request with the same ID. - // Prover should be slashable, but still able to fulfill the other request and receive payment for it. - function testSlashLockedRequestMultipleRequestsSameIndex() public { - Client client = getClient(1); - - // Create two distinct requests with the same ID. - Offer memory offerA = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: uint32(100), - lockCollateral: 1 ether - }); - Offer memory offerB = Offer({ - minPrice: 3 ether, - maxPrice: 3 ether, - rampUpStart: uint64(block.timestamp) + uint64(offerA.timeout) + 1, - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: 100, - lockCollateral: 1 ether - }); - ProofRequest memory requestA = client.request(1, offerA); - ProofRequest memory requestB = client.request(1, offerB); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = requestB; - bytes memory clientSignatureA = client.sign(requestA); - bytes memory clientSignatureB = client.sign(requestB); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignatureB; - - client.snapshotBalance(); - testProver.snapshotBalance(); - - vm.prank(testProverAddress); - boundlessMarket.lockRequest(requestA, clientSignatureA); - - vm.warp(requestA.offer.deadline() + 1); - - // Attempt to fill request B. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(requestB, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - boundlessMarket.slash(requestA.id); - - expectRequestFulfilledAndSlashed(fill.id); - - client.expectBalanceChange(-3 ether); - testProver.expectBalanceChange(3 ether); - // They lose their original stake, but gain a portion of the slashed stake. - testProver.expectCollateralBalanceChange( - -1 ether + int256(uint256(expectedSlashTransferAmount(requestA.offer.lockCollateral))) - ); - expectMarketBalanceUnchanged(); - } - - // Handles case where a third-party that was not locked fulfills the request, and the locked prover does not. - // Once the locked prover is slashed, we expect the request to be both "fulfilled" and "slashed". - // We expect a portion of slashed funds to go to the market treasury. - function testSlashLockedRequestFulfilledByOtherProverDuringLock() public { - Client client = getClient(1); - ProofRequest memory request = client.request(1); - - // Lock to "testProver" but "prover2" fulfills the request - boundlessMarket.lockRequestWithSignature( - request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) - ); - - Client testProver2 = getClient(2); - (address testProver2Address,,,) = testProver2.wallet(); - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProver2Address); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - boundlessMarket.fulfill(fills, assessorReceipt); - expectRequestFulfilled(fill.id); - - vm.warp(request.offer.deadline() + 1); - - // Slash the original prover that locked and didnt deliver - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.ProverSlashed( - request.id, - expectedSlashBurnAmount(request.offer.lockCollateral), - expectedSlashTransferAmount(request.offer.lockCollateral), - address(boundlessMarket) - ); - boundlessMarket.slash(request.id); - - client.expectBalanceChange(0 ether); - testProver.expectCollateralBalanceChange(-uint256(request.offer.lockCollateral).toInt256()); - testProver2.expectCollateralBalanceChange(0 ether); - - // We expect the request is both slashed and fulfilled - require(boundlessMarket.requestIsSlashed(request.id), "Request should be slashed"); - require(boundlessMarket.requestIsFulfilled(request.id), "Request should be fulfilled"); - } - - function testSlashInvalidRequestID() public { - // Attempt to slash an invalid request ID - // should revert with "RequestIsNotLocked({requestId: request.id})" - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsNotLocked.selector, 0xa)); - boundlessMarket.slash(RequestId.wrap(0xa)); - - expectMarketBalanceUnchanged(); - } - - function testSlashLockedRequestNotExpired() public { - (, ProofRequest memory request) = testLockRequest(); - - // Attempt to slash a request not expired - // should revert with "RequestIsNotExpired({requestId: request.id, deadline: deadline})" - vm.expectRevert( - abi.encodeWithSelector(IBoundlessMarket.RequestIsNotExpired.selector, request.id, request.offer.deadline()) - ); - boundlessMarket.slash(request.id); - - expectMarketBalanceUnchanged(); - } - - // Even if the lock has expired, you can not slash until the request is fully expired, as we need to know if the - // request was eventually fulfilled or not to decide who to send stake to. - function testSlashWasLockedRequestNotFullyExpired() public { - Client client = getClient(1); - ProofRequest memory request = client.request( - 1, - Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(50), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }) - ); - bytes memory clientSignature = client.sign(request); - - Client locker = getProver(1); - client.snapshotBalance(); - locker.snapshotBalance(); - - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(request, clientSignature); - // At this point the client should have only been charged the 1 ETH at lock time. - client.expectBalanceChange(-1 ether); - - // Advance the chain ahead to simulate the lock timeout. - vm.warp(request.offer.lockDeadline() + 1); - - // Attempt to slash a request not expired - // should revert with "RequestIsNotExpired({requestId: request.id, deadline: deadline})" - vm.expectRevert( - abi.encodeWithSelector(IBoundlessMarket.RequestIsNotExpired.selector, request.id, request.offer.deadline()) - ); - boundlessMarket.slash(request.id); - - expectMarketBalanceUnchanged(); - } - - function _testSlashFulfilledSameBlock(uint32 idx, LockRequestMethod lockinMethod) private { - (, ProofRequest memory request) = _testFulfillSameBlock(idx, lockinMethod); - - if (lockinMethod == LockRequestMethod.None) { - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsNotLocked.selector, request.id)); - } else { - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsFulfilled.selector, request.id)); - } - - boundlessMarket.slash(request.id); - - expectMarketBalanceUnchanged(); - } - - function testSlashLockedRequestFulfilledByLocker() public { - _testSlashFulfilledSameBlock(1, LockRequestMethod.LockRequest); - _testSlashFulfilledSameBlock(2, LockRequestMethod.LockRequestWithSig); - } - - function testSlashNeverLockedRequestFulfilled() public { - _testSlashFulfilledSameBlock(3, LockRequestMethod.None); - } - - // Test slashing in the scenario where a request is fulfilled by another prover after the lock expires. - // but before the request as a whole has expired. - function testSlashWasLockedRequestFulfilledByOtherProver() - public - returns (ProofRequest memory, Client, Client, Client) - { - snapshotMarketStakeTreasuryBalance(); - (ProofRequest memory request, Client client, Client locker, Client otherProver) = - testFulfillWasLockedRequestByOtherProver(); - vm.warp(request.offer.deadline() + 1); - otherProver.snapshotCollateralBalance(); - - // We expect the prover that ultimately fulfilled the request to receive stake. - // Burning = sending tokens to address 0xdEaD, expect a transfer event to be emitted to address 0xdEaD - vm.expectEmit(true, true, true, false); - emit IERC20.Transfer(address(proxy), address(0xdEaD), request.offer.lockCollateral); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.ProverSlashed( - request.id, - expectedSlashBurnAmount(request.offer.lockCollateral), - expectedSlashTransferAmount(request.offer.lockCollateral), - otherProver.addr() - ); - - boundlessMarket.slash(request.id); - vm.snapshotGasLastCall("slash: fulfilled request after lock deadline"); - - // Prover should have their original balance less the stake amount. - testProver.expectCollateralBalanceChange(-uint256(request.offer.lockCollateral).toInt256()); - // Other prover should receive a portion of the stake - otherProver.expectCollateralBalanceChange( - uint256(expectedSlashTransferAmount(request.offer.lockCollateral)).toInt256() - ); - - expectMarketCollateralTreasuryBalanceChange(0); - expectMarketBalanceUnchanged(); - - return (request, client, locker, otherProver); - } - - // In this case the lock expires, the request is fulfilled by another prover, the request is slashed, - // and then finally the locker tries to fulfill the request. - // - // In this case the request has fully expired, so the proof should NOT be delivered, - // however we should not revert (as this allows partial fulfillment of other requests in the batch). - function testSlashWasLockedRequestFulfilledByOtherProverFulfillAfterRequestExpired() public { - (ProofRequest memory request, Client client, Client locker,) = testSlashWasLockedRequestFulfilledByOtherProver(); - vm.warp(request.offer.deadline() + 1); - - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - // Advance the chain ahead to simulate the request expiration. - vm.warp(request.offer.deadline() + 1); - - // The locker should have no balance change. - // Now the locker tries to fulfill the request. - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, locker.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // In this case the request has fully expired, so the proof should NOT be delivered, - // however we should not revert (as this allows partial fulfillment of other requests in the batch) - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.RequestIsExpired.selector, request.id - )); - - // The fulfillment should not revert, as we support multiple proofs being delivered for a single request. - bytes[] memory paymentErrors = - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - assert( - keccak256(paymentErrors[0]) - == keccak256(abi.encodeWithSelector(IBoundlessMarket.RequestIsExpired.selector, request.id)) - ); - } - - // Test slashing in the scenario where a request is fulfilled by the locker after the lock expires. - // but before the request as a whole has expired. - function testSlashWasLockedRequestFulfilledByLocker() public { - snapshotMarketStakeTreasuryBalance(); - (ProofRequest memory request, Client prover) = testFulfillWasLockedRequestByOriginalLocker(); - vm.warp(request.offer.deadline() + 1); - - // We expect the prover that ultimately fulfilled the request to receive stake. - // Burning = sending tokens to address 0xdEaD, expect a transfer event to be emitted to address 0xdEaD - vm.expectEmit(true, true, true, false); - emit IERC20.Transfer(address(proxy), address(0xdEaD), request.offer.lockCollateral); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.ProverSlashed( - request.id, - expectedSlashBurnAmount(request.offer.lockCollateral), - expectedSlashTransferAmount(request.offer.lockCollateral), - prover.addr() - ); - - boundlessMarket.slash(request.id); - - // Prover should have their original balance less the stake amount plus the stake for eventually filling. - prover.expectCollateralBalanceChange( - -uint256(request.offer.lockCollateral).toInt256() - + uint256(expectedSlashTransferAmount(request.offer.lockCollateral)).toInt256() - ); - - expectMarketCollateralTreasuryBalanceChange(0); - expectMarketBalanceUnchanged(); - } - - function testSlashSlash() public { - (, ProofRequest memory request) = testSlashLockedRequestFullyExpired(); - expectRequestSlashed(request.id); - - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsSlashed.selector, request.id)); - boundlessMarket.slash(request.id); - } - - function testLockRequestSmartContractSignature() public { - SmartContractClient client = getSmartContractClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSig = client.sign(request); - - // Expect isValidSignature to be called on the smart contract wallet - bytes32 requestHash = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), request.eip712Digest()); - vm.expectCall(client.addr(), abi.encodeWithSelector(IERC1271.isValidSignature.selector, requestHash, clientSig)); - - // Call lockRequest with the smart contract signature - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSig); - - // Verify the lock request - assertTrue(boundlessMarket.requestIsLocked(request.id), "Request should be locked"); - } - - // Test that the smart contract client receives the proof request when isValidSignature is called, - // if the client signature provided is empty. This enables custom smart contract clients that want to authorize - // payments based on how a proof request is structured. - function testLockRequestSmartContractClientValidatesPassthroughEmptySignature() public { - SmartContractClient client = getSmartContractClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSig = bytes(""); - client.setExpectedSignature(clientSig); - - // Expect isValidSignature to be called on the smart contract wallet with the proof request as the signature. - bytes32 requestHash = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), request.eip712Digest()); - vm.expectCall(client.addr(), abi.encodeWithSelector(IERC1271.isValidSignature.selector, requestHash, clientSig)); - - // Call lockRequest with the smart contract signature - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSig); - - // Verify the lock request - assertTrue(boundlessMarket.requestIsLocked(request.id), "Request should be locked"); - } - - function testLockRequestSmartContractSignatureInvalid() public { - SmartContractClient client = getSmartContractClient(1); - ProofRequest memory request = client.request(1); - bytes memory clientSig = bytes("invalid_signature"); - - // Expect isValidSignature to be called on the smart contract wallet - bytes32 requestHash = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), request.eip712Digest()); - vm.expectCall(client.addr(), abi.encodeWithSelector(IERC1271.isValidSignature.selector, requestHash, clientSig)); - - // Call lockRequest with the smart contract signature - vm.prank(testProverAddress); - vm.expectRevert(IBoundlessMarket.InvalidSignature.selector); - boundlessMarket.lockRequest(request, clientSig); - } - - function testLockRequestSmartContractSignatureExceedsGasLimit() public { - SmartContractClient client = getSmartContractClient(1); - client.smartWallet().setGasCost(boundlessMarket.ERC1271_MAX_GAS_FOR_CHECK() + 1); - ProofRequest memory request = client.request(1); - bytes memory clientSig = client.sign(request); - - // Expect isValidSignature to be called on the smart contract wallet - bytes32 requestHash = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), request.eip712Digest()); - vm.expectCall(client.addr(), abi.encodeWithSelector(IERC1271.isValidSignature.selector, requestHash, clientSig)); - - // Call lockRequest with the smart contract signature - vm.prank(testProverAddress); - vm.expectRevert(bytes("")); // revert due to out of gas results in empty error - boundlessMarket.lockRequest(request, clientSig); - } - - function testLockRequestWithSignatureClientSmartContractSignatureInvalid() public { - SmartContractClient client = getSmartContractClient(1); - Client prover = getClient(2); - - ProofRequest memory request = client.request(1); - bytes memory clientSig = bytes("invalid_signature"); - bytes memory proverSig = prover.signLockRequest(LockRequest({request: request})); - - address proverAddress = prover.addr(); - vm.prank(proverAddress); - vm.expectRevert(IBoundlessMarket.InvalidSignature.selector); - boundlessMarket.lockRequestWithSignature(request, clientSig, proverSig); - } - - function testFulfillLockedRequestWithCallback() public { - Client client = getClient(1); - - // Create request with low gas callback - ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 500_000}); - - bytes memory clientSignature = client.sign(request); - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Lock and fulfill the request - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - vm.expectEmit(true, true, true, false); - bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); - emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify callback was called exactly once - assertEq(mockCallback.getCallCount(), 1, "Callback should be called exactly once"); - - // Verify request state and balances - expectRequestFulfilled(fill.id); - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequestWithCallbackNotEnoughGas() public { - Client client = getClient(1); - - // Create request with low gas callback - ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 500_000}); - - bytes memory clientSignature = client.sign(request); - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Lock and fulfill the request - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectRevert(IBoundlessMarket.InsufficientGas.selector); - boundlessMarket.fulfill{gas: 499_000}(fills, assessorReceipt); - - // Verify callback was not called - assertEq(mockCallback.getCallCount(), 0, "Callback should not be called"); - - expectRequestNotFulfilled(request.id); - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequestWithCallbackExceedGasLimit() public { - Client client = getClient(1); - - // Create request with high gas callback that will exceed limit - ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockHighGasCallback), gasLimit: 10_000}); - - bytes memory clientSignature = client.sign(request); - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Lock and fulfill the request - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.CallbackFailed(request.id, address(mockHighGasCallback), ""); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify callback was attempted - assertEq(mockHighGasCallback.getCallCount(), 0, "Callback not succeed"); - - // Verify request state and balances - expectRequestFulfilled(fill.id); - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequestWithCallbackByOtherProver() public { - Client client = getClient(1); - - // Create request with low gas callback - ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 100_000}); - - bytes memory clientSignature = client.sign(request); - - // Lock request with testProver - boundlessMarket.lockRequestWithSignature( - request, clientSignature, testProver.signLockRequest(LockRequest({request: request})) - ); - - // Have otherProver fulfill without requiring payment - Client otherProver = getProver(2); - address otherProverAddress = otherProver.addr(); - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, otherProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, otherProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.RequestIsLocked.selector, request.id - )); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, otherProverAddress, fill); - vm.expectEmit(true, true, true, true); - bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); - emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); - - vm.prank(otherProverAddress); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify callback was called exactly once - assertEq(mockCallback.getCallCount(), 1, "Callback should be called exactly once"); - - // Verify request state and balances - expectRequestFulfilled(fill.id); - testProver.expectCollateralBalanceChange(-int256(uint256(request.offer.lockCollateral))); - otherProver.expectBalanceChange(0); - otherProver.expectCollateralBalanceChange(0); - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequestWithCallbackAlreadyFulfilledByOtherProver() public { - Client client = getClient(1); - - ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 100_000}); - - bytes memory clientSignature = client.sign(request); - - // Lock request with testProver - boundlessMarket.lockRequestWithSignature( - request, clientSignature, testProver.signLockRequest(LockRequest({request: request})) - ); - - // Have otherProver fulfill without requiring payment - Client otherProver = getProver(2); - address otherProverAddress = address(otherProver); - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, otherProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, otherProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.PaymentRequirementsFailed(abi.encodeWithSelector( - IBoundlessMarket.RequestIsLocked.selector, request.id - )); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, otherProverAddress, fill); - vm.expectEmit(true, true, true, true); - bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); - emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify callback was called exactly once - assertEq(mockCallback.getCallCount(), 1, "Callback should be called exactly once"); - - // Now have original locker fulfill to get payment - (fill, assessorReceipt) = createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress); - fills[0] = fill; - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify callback is called again - assertEq(mockCallback.getCallCount(), 2, "Callback should be called twice"); - - expectRequestFulfilled(fill.id); - testProver.expectBalanceChange(1 ether); - testProver.expectCollateralBalanceChange(0 ether); - otherProver.expectBalanceChange(0); - otherProver.expectCollateralBalanceChange(0); - expectMarketBalanceUnchanged(); - } - - function testFulfillWasLockedRequestWithCallbackByOtherProver() public { - Client client = getClient(1); - - // Create request with lock timeout of 50 blocks, overall timeout of 100 - ProofRequest memory request = client.request( - 1, - Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(50), - lockTimeout: uint32(50), - timeout: uint32(100), - lockCollateral: 1 ether - }) - ); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 100_000}); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = request; - - bytes memory clientSignature = client.sign(request); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignature; - - Client locker = getProver(1); - Client otherProver = getProver(2); - - client.snapshotBalance(); - locker.snapshotBalance(); - otherProver.snapshotBalance(); - - address lockerAddress = locker.addr(); - vm.prank(lockerAddress); - boundlessMarket.lockRequest(request, clientSignature); - client.expectBalanceChange(-1 ether); - - // Advance chain ahead to simulate lock timeout - vm.warp(request.offer.lockDeadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, otherProver.addr()); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, otherProver.addr(), fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, otherProver.addr(), fill); - vm.expectEmit(true, true, true, true); - bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); - emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); - boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - - // Verify callback was called exactly once - assertEq(mockCallback.getCallCount(), 1, "Callback should be called exactly once"); - - // Check request state and balances - expectRequestFulfilled(fill.id); - client.expectBalanceChange(0 ether); - locker.expectBalanceChange(0 ether); - locker.expectCollateralBalanceChange(-1 ether); - otherProver.expectBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillWasLockedRequestWithCallbackMultipleRequestsSameIndex() public { - Client client = getClient(1); - - // Create first request with callback A - Offer memory offerA = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: uint32(100), - lockCollateral: 1 ether - }); - ProofRequest memory requestA = client.request(1, offerA); - requestA.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 10_000}); - bytes memory clientSignatureA = client.sign(requestA); - - // Create second request with same ID but different callback - Offer memory offerB = Offer({ - minPrice: 1 ether, - maxPrice: 3 ether, - rampUpStart: offerA.rampUpStart, - rampUpPeriod: offerA.rampUpPeriod, - lockTimeout: offerA.lockTimeout + 100, - timeout: offerA.timeout + 100, - lockCollateral: offerA.lockCollateral - }); - ProofRequest memory requestB = client.request(1, offerB); - requestB.requirements.callback = Callback({addr: address(mockHighGasCallback), gasLimit: 300_000}); - ProofRequest[] memory requests = new ProofRequest[](1); - requests[0] = requestB; - bytes memory clientSignatureB = client.sign(requestB); - bytes[] memory clientSignatures = new bytes[](1); - clientSignatures[0] = clientSignatureB; - - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Withdraw some funds so we only have funds to cover for the first offer - // and we have a deficit for the second offer to test the partial payment path - vm.prank(client.addr()); - boundlessMarket.withdraw(DEFAULT_BALANCE - 2 ether); - - // Lock request A - vm.prank(testProverAddress); - boundlessMarket.lockRequest(requestA, clientSignatureA); - - // Advance chain ahead to simulate request A lock timeout - vm.warp(requestA.offer.lockDeadline() + 1); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(requestB, APP_JOURNAL, testProverAddress); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - // Since the request being fulfilled is distinct from the one that was locked, the - // transaction should revert if the request is not priced before fulfillment. - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.RequestIsNotLockedOrPriced.selector, requestB.id)); - boundlessMarket.fulfill(fills, assessorReceipt); - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(requestB.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(requestB.id, testProverAddress, fill); - vm.expectEmit(true, true, true, true); - bytes32 imageId = bytesToBytes32(requestB.requirements.predicate.data); - emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); - bytes[] memory errors = boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); - // Verify that the second request was partially payed - assertEq(errors.length, 1, "Expected one error"); - assertEq( - errors[0], - abi.encodeWithSelector(IBoundlessMarket.PartialPayment.selector, 3 ether, 2 ether), - "Unexpected error" - ); - - // Verify only the second request's callback was called - assertEq(mockCallback.getCallCount(), 0, "First request's callback should not be called"); - assertEq(mockHighGasCallback.getCallCount(), 1, "Second request's callback should be called once"); - - // Deposit back original funds so that the Market original balance is restored - vm.prank(client.addr()); - boundlessMarket.deposit{value: DEFAULT_BALANCE - 2 ether}(); - - // Verify request state and balances - expectRequestFulfilled(fill.id); - client.expectBalanceChange(-2 ether); - testProver.expectBalanceChange(2 ether); - testProver.expectCollateralBalanceChange(-1 ether); // Lost stake from lock - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequestClaimDigestWithFulfillmentDataImageIdAndJournal() public { - Client client = getClient(1); - bytes32 claimDigest = ReceiptClaimLib.ok(APP_IMAGE_ID, sha256(APP_JOURNAL)).digest(); - - // Create request - ProofRequest memory request = client.request(1); - request.requirements.predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); - - bytes memory clientSignature = client.sign(request); - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Lock and fulfill the request - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress, FulfillmentDataType.ImageIdAndJournal); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify request state and balances - expectRequestFulfilled(fill.id); - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequesClaimDigestWithFulfillmentDataNone() public { - Client client = getClient(1); - bytes32 claimDigest = ReceiptClaimLib.ok(APP_IMAGE_ID, sha256(APP_JOURNAL)).digest(); - - // Create request - ProofRequest memory request = client.request(1); - request.requirements.predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); - - bytes memory clientSignature = client.sign(request); - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Lock and fulfill the request - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress, FulfillmentDataType.None); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify request state and balances - expectRequestFulfilled(fill.id); - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } - - // Test that if a callback was requested, but the fulfillment data doesnt have the journal, - // the fulfillment reverts and the callback is not called. - function testFulfillLockedRequestWithCallbackAndFulfillmentDataNone() public { - Client client = getClient(1); - bytes32 claimDigest = ReceiptClaimLib.ok(APP_IMAGE_ID, sha256(APP_JOURNAL)).digest(); - - // Create request with low gas callback - ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 500_000}); - request.requirements.predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); - - bytes memory clientSignature = client.sign(request); - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Lock and fulfill the request - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress, FulfillmentDataType.None); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectRevert(IBoundlessMarket.UnfulfillableCallback.selector); - boundlessMarket.fulfill(fills, assessorReceipt); - - // Verify callback was not called - assertEq(mockCallback.getCallCount(), 0, "Callback should be called exactly 0 times"); - - // Verify request state and balances - expectRequestNotFulfilled(fill.id); - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(0 ether); - expectMarketBalanceUnchanged(); - } - - function testFulfillLockedRequestClaimDigestWithCallbackImageIdAndJournal() public { - Client client = getClient(1); - bytes32 claimDigest = ReceiptClaimLib.ok(APP_IMAGE_ID, sha256(APP_JOURNAL)).digest(); - // Create request - ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 500_000}); - request.requirements.predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); - - bytes memory clientSignature = client.sign(request); - client.snapshotBalance(); - testProver.snapshotBalance(); - - // Lock and fulfill the request - vm.prank(testProverAddress); - boundlessMarket.lockRequest(request, clientSignature); - - (Fulfillment memory fill, AssessorReceipt memory assessorReceipt) = - createFillAndSubmitRoot(request, APP_JOURNAL, testProverAddress, FulfillmentDataType.ImageIdAndJournal); - Fulfillment[] memory fills = new Fulfillment[](1); - fills[0] = fill; - - vm.expectEmit(true, true, true, true); - emit IBoundlessMarket.RequestFulfilled(request.id, testProverAddress, fill.requestDigest); - vm.expectEmit(true, true, true, false); - emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); - vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(APP_IMAGE_ID, APP_JOURNAL, fill.seal); - - boundlessMarket.fulfill(fills, assessorReceipt); - - assertEq(mockCallback.getCallCount(), 1, "Callback should be called exactly 1 time"); - - // Verify request state and balances - expectRequestFulfilled(fill.id); - client.expectBalanceChange(-1 ether); - testProver.expectBalanceChange(1 ether); - expectMarketBalanceUnchanged(); - } -} - -contract BoundlessMarketBench is BoundlessMarketTest { - using BoundlessMarketLib for Offer; - - function benchFulfill(uint256 batchSize, string memory snapshot) public { - (ProofRequest[] memory requests, bytes[] memory journals) = newBatch(batchSize); - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress); - - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall(string.concat("fulfill: batch of ", snapshot)); - - for (uint256 j = 0; j < fills.length; j++) { - expectRequestFulfilled(fills[j].id); - } - } - - function benchFulfillWithSelector(uint256 batchSize, string memory snapshot) public { - (ProofRequest[] memory requests, bytes[] memory journals) = - newBatchWithSelector(batchSize, setVerifier.SELECTOR()); - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress); - - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall(string.concat("fulfill (with selector): batch of ", snapshot)); - - for (uint256 j = 0; j < fills.length; j++) { - expectRequestFulfilled(fills[j].id); - } - } - - function benchFulfillWithCallback(uint256 batchSize, string memory snapshot) public { - (ProofRequest[] memory requests, bytes[] memory journals) = newBatchWithCallback(batchSize); - (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = - createFillsAndSubmitRoot(requests, journals, testProverAddress); - - boundlessMarket.fulfill(fills, assessorReceipt); - vm.snapshotGasLastCall(string.concat("fulfill (with callback): batch of ", snapshot)); - - for (uint256 j = 0; j < fills.length; j++) { - expectRequestFulfilled(fills[j].id); - } - } - - function testBenchFulfill001() public { - benchFulfill(1, "001"); - } - - function testBenchFulfill002() public { - benchFulfill(2, "002"); - } - - function testBenchFulfill004() public { - benchFulfill(4, "004"); - } - - function testBenchFulfill008() public { - benchFulfill(8, "008"); - } - - function testBenchFulfill016() public { - benchFulfill(16, "016"); - } - - function testBenchFulfill032() public { - benchFulfill(32, "032"); - } - - function testBenchFulfill064() public { - benchFulfill(64, "064"); - } - - function testBenchFulfill128() public { - benchFulfill(128, "128"); - } - - function testBenchFulfillWithSelector001() public { - benchFulfillWithSelector(1, "001"); - } - - function testBenchFulfillWithSelector002() public { - benchFulfillWithSelector(2, "002"); - } - - function testBenchFulfillWithSelector004() public { - benchFulfillWithSelector(4, "004"); - } - - function testBenchFulfillWithSelector008() public { - benchFulfillWithSelector(8, "008"); - } - - function testBenchFulfillWithSelector016() public { - benchFulfillWithSelector(16, "016"); - } - - function testBenchFulfillWithSelector032() public { - benchFulfillWithSelector(32, "032"); - } - - function testBenchFulfillWithCallback001() public { - benchFulfillWithCallback(1, "001"); - } - - function testBenchFulfillWithCallback002() public { - benchFulfillWithCallback(2, "002"); - } - - function testBenchFulfillWithCallback004() public { - benchFulfillWithCallback(4, "004"); - } - - function testBenchFulfillWithCallback008() public { - benchFulfillWithCallback(8, "008"); - } - - function testBenchFulfillWithCallback016() public { - benchFulfillWithCallback(16, "016"); - } - - function testBenchFulfillWithCallback032() public { - benchFulfillWithCallback(32, "032"); - } -} - -contract BoundlessMarketUpgradeTest is BoundlessMarketTest { - using BoundlessMarketLib for Offer; - - function testUnsafeUpgrade() public { - vm.startPrank(ownerWallet.addr); - proxy = UnsafeUpgrades.deployUUPSProxy( - address( - new BoundlessMarket( - setVerifier, - setVerifier, - ASSESSOR_IMAGE_ID, - DEPRECATED_ASSESSOR_IMAGE_ID, - DEPRECATED_ASSESSOR_DURATION, - address(0x01) - ) - ), - abi.encodeCall(BoundlessMarket.initialize, (ownerWallet.addr, "https://assessor.dev.null")) - ); - boundlessMarket = BoundlessMarket(proxy); - address implAddressV1 = UnsafeUpgrades.getImplementationAddress(proxy); - - // Should emit an `Upgraded` event - vm.expectEmit(false, true, true, true); - emit IERC1967.Upgraded(address(0)); - UnsafeUpgrades.upgradeProxy( - proxy, - address( - new BoundlessMarket( - setVerifier, - setVerifier, - ASSESSOR_IMAGE_ID, - DEPRECATED_ASSESSOR_IMAGE_ID, - DEPRECATED_ASSESSOR_DURATION, - address(0x01) - ) - ), - "", - ownerWallet.addr - ); - vm.stopPrank(); - address implAddressV2 = UnsafeUpgrades.getImplementationAddress(proxy); - - assertFalse(implAddressV2 == implAddressV1); - - (bytes32 imageId, string memory imageUrl) = boundlessMarket.imageInfo(); - assertEq(imageId, ASSESSOR_IMAGE_ID, "Image ID should be the same after upgrade"); - assertEq(imageUrl, "https://assessor.dev.null", "Image URL should be the same after upgrade"); - } - - function testGrantAdminRole() public { - address newAdmin = vm.createWallet("NEW_ADMIN").addr; - bytes32 adminRole = boundlessMarket.ADMIN_ROLE(); - - vm.prank(ownerWallet.addr); - boundlessMarket.grantRole(adminRole, newAdmin); - - assertTrue(boundlessMarket.hasRole(adminRole, newAdmin), "New admin should have admin role"); - assertTrue(boundlessMarket.hasRole(adminRole, ownerWallet.addr), "Original owner should still have admin role"); - } -} diff --git a/contracts/shanghai/test/BoundlessMarketCallback.t.sol b/contracts/shanghai/test/BoundlessMarketCallback.t.sol deleted file mode 100644 index 534bc304c8..0000000000 --- a/contracts/shanghai/test/BoundlessMarketCallback.t.sol +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import { - IRiscZeroVerifier, - Receipt as RiscZeroReceipt, - ReceiptClaim, - ReceiptClaimLib -} from "risc0/IRiscZeroVerifier.sol"; -import {BoundlessMarketCallback} from "../src/BoundlessMarketCallback.sol"; - -// Test implementation of BoundlessMarketCallback -contract TestCallback is BoundlessMarketCallback { - event ProofHandled(bytes32 imageId, bytes journal, bytes seal); - - constructor(IRiscZeroVerifier verifier, address boundlessMarket, bytes32 imageId) - BoundlessMarketCallback(verifier, boundlessMarket, imageId) - {} - - function _handleProof(bytes32 imageId, bytes calldata journal, bytes calldata seal) internal override { - emit ProofHandled(imageId, journal, seal); - } -} - -contract MockRiscZeroVerifier is IRiscZeroVerifier { - function verify(bytes calldata seal, bytes32 imageId, bytes32 claimDigest) public view {} - function verifyIntegrity(RiscZeroReceipt calldata receipt) public view {} -} - -contract BoundlessMarketCallbackTest is Test { - using ReceiptClaimLib for ReceiptClaim; - - MockRiscZeroVerifier public verifier; - TestCallback public callback; - address public boundlessMarket; - - bytes32 constant TEST_IMAGE_ID = bytes32(uint256(1)); - bytes constant TEST_JOURNAL = "test journal"; - bytes constant TEST_SEAL = "test seal"; - - function setUp() public { - verifier = new MockRiscZeroVerifier(); - boundlessMarket = makeAddr("boundlessMarket"); - callback = new TestCallback(verifier, boundlessMarket, TEST_IMAGE_ID); - } - - function testHandleProof() public { - vm.expectEmit(true, true, true, true); - emit TestCallback.ProofHandled(TEST_IMAGE_ID, TEST_JOURNAL, TEST_SEAL); - - // Expect a call to verify with the correct parameters - bytes32 expectedJournalDigest = ReceiptClaimLib.ok(TEST_IMAGE_ID, sha256(TEST_JOURNAL)).digest(); - - vm.prank(boundlessMarket); - vm.expectCall( - address(verifier), - abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, (RiscZeroReceipt(TEST_SEAL, expectedJournalDigest))) - ); - callback.handleProof(TEST_IMAGE_ID, TEST_JOURNAL, TEST_SEAL); - } - - function testHandleProofIncorrectCaller() public { - vm.prank(makeAddr("other")); - vm.expectRevert("Invalid sender"); - callback.handleProof(TEST_IMAGE_ID, TEST_JOURNAL, TEST_SEAL); - } - - function testHandleProofIncorrectImageId() public { - vm.prank(boundlessMarket); - vm.expectRevert("Invalid Image ID"); - callback.handleProof(bytes32(uint256(99)), TEST_JOURNAL, TEST_SEAL); - } -} diff --git a/contracts/shanghai/test/HitPoints.t.sol b/contracts/shanghai/test/HitPoints.t.sol deleted file mode 100644 index 01b2f7ac7f..0000000000 --- a/contracts/shanghai/test/HitPoints.t.sol +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; -import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -import {HitPoints} from "../src/HitPoints.sol"; -import {IHitPoints} from "../src/IHitPoints.sol"; - -contract HitPointsTest is Test { - HitPoints public token; - address public owner; - address public authorized; - address public user; - AccessControl public accessControl; - - function setUp() public { - owner = address(this); - authorized = makeAddr("authorized"); - user = makeAddr("user"); - - token = new HitPoints(owner); - accessControl = AccessControl(address(token)); - token.grantMinterRole(owner); - } - - function testInitialState() public view { - assertEq(token.name(), "HitPoints"); - assertEq(token.symbol(), "HP"); - assertEq(token.decimals(), 18); - assertEq(token.owner(), owner); - assertTrue(accessControl.hasRole(accessControl.DEFAULT_ADMIN_ROLE(), owner)); - assertTrue(accessControl.hasRole(token.MINTER(), owner)); - assertTrue(accessControl.hasRole(token.AUTHORIZED_TRANSFER(), address(0))); - assertFalse(accessControl.hasRole(token.AUTHORIZED_TRANSFER(), authorized)); - } - - function testTransferOwnership() public { - token.transferOwnership(user); - assertEq(token.owner(), user); - assertTrue(accessControl.hasRole(accessControl.DEFAULT_ADMIN_ROLE(), user)); - assertFalse(accessControl.hasRole(accessControl.DEFAULT_ADMIN_ROLE(), owner)); - } - - function testGrantRevokeRoles() public { - token.grantMinterRole(authorized); - assertTrue(accessControl.hasRole(token.MINTER(), authorized)); - token.revokeMinterRole(authorized); - assertFalse(accessControl.hasRole(token.MINTER(), authorized)); - - token.grantAuthorizedTransferRole(authorized); - assertTrue(accessControl.hasRole(token.AUTHORIZED_TRANSFER(), authorized)); - token.revokeAuthorizedTransferRole(authorized); - assertFalse(accessControl.hasRole(token.AUTHORIZED_TRANSFER(), authorized)); - } - - function testMint() public { - uint256 initialSupply = token.totalSupply(); - token.mint(user, 100); - assertEq(token.balanceOf(user), 100); - assertEq(token.totalSupply(), initialSupply + 100); - } - - function testMintRevertUnauthorized() public { - vm.prank(user); - vm.expectRevert( - abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, user, keccak256("MINTER")) - ); - token.mint(user, 100); - } - - function testTransferToAuthorizedRecipient() public { - token.mint(user, 100); - token.grantAuthorizedTransferRole(authorized); - - vm.prank(user); - bool success = token.transfer(authorized, 50); - assertTrue(success); - assertEq(token.balanceOf(user), 50); - } - - function testTransferFromAuthorizedRecipient() public { - token.mint(authorized, 100); - token.grantAuthorizedTransferRole(authorized); - - vm.prank(authorized); - bool success = token.transfer(user, 50); - assertTrue(success); - assertEq(token.balanceOf(authorized), 50); - assertEq(token.balanceOf(user), 50); - } - - function testTransferRevertUnauthorizedRecipient() public { - token.mint(user, 100); - - vm.prank(user); - vm.expectRevert(abi.encodeWithSelector(IHitPoints.UnauthorizedTransfer.selector)); - bool success = token.transfer(authorized, 50); - assertFalse(success); - } - - function testApproveAndTransferFrom() public { - token.mint(user, 100); - token.grantAuthorizedTransferRole(authorized); - - vm.prank(user); - token.approve(authorized, 50); - - vm.prank(authorized); - bool success = token.transferFrom(user, authorized, 50); - assertTrue(success); - - assertEq(token.balanceOf(user), 50); - assertEq(token.balanceOf(authorized), 50); - } - - function testTransferFromRevertUnauthorizedRecipient() public { - token.mint(user, 100); - - vm.prank(user); - token.approve(authorized, 50); - - vm.prank(authorized); - vm.expectRevert(abi.encodeWithSelector(IHitPoints.UnauthorizedTransfer.selector)); - bool success = token.transferFrom(user, authorized, 50); - assertFalse(success); - } - - function testFuzzMint(address _user, uint256 _amount) public { - vm.assume(_user != address(0)); - vm.assume(_amount <= type(uint96).max); - - uint256 initialSupply = token.totalSupply(); - - token.mint(_user, _amount); - - assertEq(token.balanceOf(_user), _amount); - assertEq(token.totalSupply(), initialSupply + _amount); - } - - function testFuzzMintExceedLimit(address _user, uint256 _existingAmount) public { - vm.assume(_user != address(0)); - vm.assume(_existingAmount <= type(uint96).max - 1); - - // Mint existing amount - token.mint(_user, _existingAmount); - - // Calculate mint amount that would exceed limit - uint256 _mintAmount = type(uint96).max - _existingAmount + 1; - - // Expect revert when minting would exceed uint96 max - vm.expectRevert( - abi.encodeWithSelector(IHitPoints.BalanceExceedsLimit.selector, _user, _existingAmount, _mintAmount) - ); - token.mint(_user, _mintAmount); - } - - function testFuzzTransfer(address _from, uint256 _amount) public { - vm.assume(_from != address(0)); - vm.assume(_amount > 0); // Ensure non-zero transfer - vm.assume(_amount <= type(uint96).max); - - // Create a recipient - address recipient = makeAddr("recipient"); - - // Authorize the sender - token.grantAuthorizedTransferRole(_from); - - // Mint tokens to the sender - token.mint(_from, _amount); - - // Perform the transfer - vm.prank(_from); - bool success = token.transfer(recipient, _amount); - assertTrue(success); - - // Check balances - assertEq(token.balanceOf(_from), 0, "Sender balance should be zero"); - assertEq(token.balanceOf(recipient), _amount, "Recipient balance should match transferred amount"); - } - - function testFuzzTransferExceedLimit(address _recipient) public { - vm.assume(_recipient != address(0)); - - address _sender = makeAddr("sender"); - - // Ensure authorized - token.grantAuthorizedTransferRole(_recipient); - token.grantAuthorizedTransferRole(_sender); - - // Amount that would almost max out uint96 - uint256 existingBalance = type(uint96).max - 1; - - // Mint to recipient - token.mint(_recipient, existingBalance); - - // Mint a transfer amount to sender - uint256 transferAmount = 2; - token.mint(_sender, transferAmount); - - // Expect revert when transfer would exceed uint96 max - vm.prank(_sender); - vm.expectRevert( - abi.encodeWithSelector(IHitPoints.BalanceExceedsLimit.selector, _recipient, existingBalance, transferAmount) - ); - bool success = token.transfer(_recipient, transferAmount); - assertFalse(success); - } -} diff --git a/contracts/shanghai/test/MockCallback.sol b/contracts/shanghai/test/MockCallback.sol deleted file mode 100644 index d1430e204a..0000000000 --- a/contracts/shanghai/test/MockCallback.sol +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.26; - -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {BoundlessMarketCallback} from "../src/BoundlessMarketCallback.sol"; - -/// @notice Mock callback contract for testing BoundlessMarket callbacks -/// @dev This contract allows configuring how much gas the callback should consume -contract MockCallback is BoundlessMarketCallback { - uint256 public callCount; - uint256 public targetGas; - - event MockCallbackCalled(bytes32 imageId, bytes journal, bytes seal); - - // Store info about each call - struct CallInfo { - bytes32 imageId; - bytes journal; - bytes seal; - } - - // Mapping used for mocking gas consumption - mapping(bytes32 => uint256) private gasConsumptionSlots; - - constructor(IRiscZeroVerifier verifier, address boundlessMarket, bytes32 imageId, uint256 _targetGas) - BoundlessMarketCallback(verifier, boundlessMarket, imageId) - { - targetGas = _targetGas; - } - - function _handleProof(bytes32 imageId, bytes calldata journal, bytes calldata seal) internal override { - uint256 startGas = gasleft(); - - emit MockCallbackCalled(imageId, journal, seal); - callCount++; - - // Consume gas by doing SSTORE operations to random slots - uint256 i = 0; - while (startGas - gasleft() < targetGas) { - bytes32 slot = keccak256(abi.encode(i)); - gasConsumptionSlots[slot] = i; - i++; - } - } - - function getCallCount() external view returns (uint256) { - return callCount; - } -} diff --git a/contracts/shanghai/test/MockZKC.sol b/contracts/shanghai/test/MockZKC.sol deleted file mode 100644 index b6bfc08479..0000000000 --- a/contracts/shanghai/test/MockZKC.sol +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; -import {IZKC} from "zkc/interfaces/IZKC.sol"; -import {IRewards as IZKCRewards} from "zkc/interfaces/IRewards.sol"; - -struct EpochEmissionsUpdate { - uint256 epoch; - uint256 emissions; -} - -contract MockZKC is IZKC, ERC20, ERC20Permit { - uint256 public constant EPOCH_DURATION = 2 days; - - EpochEmissionsUpdate[] internal epochEmissionsUpdates; - - constructor() ERC20("Mock ZKC", "MOCK_ZKC") ERC20Permit("Mock ZKC") { - // When the contract is created, the emissions rate is initially set to 100. - epochEmissionsUpdates.push(EpochEmissionsUpdate({epoch: 0, emissions: 100 * 10 ** decimals()})); - } - - /// Get the current epoch number for the ZKC system. - /// - /// The epoch number is guaranteed to be a monotonic increasing function, and is guaranteed to - /// be stable withing a block. - function getCurrentEpoch() public view returns (uint256) { - return block.timestamp / EPOCH_DURATION; - } - - // Returns the start time of the provided epoch. - function getEpochStartTime(uint256 epoch) public pure returns (uint256) { - return epoch * EPOCH_DURATION; - } - - // Returns the end time of the provided epoch. Meaning the final timestamp - // at which the epoch is "active". After this timestamp is finalized, the - // state at this timestamp represents the final state of the epoch. - function getEpochEndTime(uint256 epoch) public pure returns (uint256) { - return getEpochStartTime(epoch + 1) - 1; - } - - // This function only exists on the mock contract. - // forge-lint: disable-next-item(mixed-case-function) - function setPoVWEmissionsPerEpoch(uint256 emissions) external { - epochEmissionsUpdates.push(EpochEmissionsUpdate({epoch: getCurrentEpoch(), emissions: emissions})); - } - - // forge-lint: disable-next-item(mixed-case-function) - function getPoVWEmissionsForEpoch(uint256 epoch) external view returns (uint256) { - require(epoch < getCurrentEpoch(), "epoch must be past"); - - for (uint256 i = 0; i < epochEmissionsUpdates.length; i++) { - EpochEmissionsUpdate storage update = epochEmissionsUpdates[i]; - if (update.epoch < getCurrentEpoch()) { - return update.emissions; - } - } - revert("unreachable"); - } - - // forge-lint: disable-next-item(mixed-case-function) - function mintPoVWRewardsForRecipient(address recipient, uint256 amount) external { - _mint(recipient, amount); - } - - function mintStakingRewardsForRecipient(address recipient, uint256 amount) external { - _mint(recipient, amount); - } - - function claimedTotalSupply() external pure returns (uint256) { - revert("not implemented"); - } - - function getCurrentEpochEndTime() external pure returns (uint256) { - revert("not implemented"); - } - - function getEmissionsForEpoch(uint256 epoch) external pure returns (uint256) { - epoch; - revert("not implemented"); - } - - function getStakingEmissionsForEpoch(uint256 epoch) external pure returns (uint256) { - epoch; - revert("not implemented"); - } - - function getSupplyAtEpochStart(uint256 epoch) external pure returns (uint256) { - epoch; - revert("not implemented"); - } - - // forge-lint: disable-next-item(mixed-case-function) - function getTotalPoVWEmissionsAtEpochStart(uint256 epoch) external pure returns (uint256) { - epoch; - revert("not implemented"); - } - - function getTotalStakingEmissionsAtEpochStart(uint256 epoch) external pure returns (uint256) { - epoch; - revert("not implemented"); - } - - function initialMint(address[] calldata recipients, uint256[] calldata amounts) external pure { - recipients; - amounts; - revert("not implemented"); - } -} - -struct RewardsCapUpdate { - uint256 timepoint; - uint256 cap; -} - -contract MockZKCRewards is IZKCRewards { - mapping(address => RewardsCapUpdate[]) internal rewardsPovwPerEpochCapUpdates; - - // This function only exists on the mock contract. Setting to 0 resets the cap to uint256 max. - // forge-lint: disable-next-item(mixed-case-function) - function setPoVWRewardCap(address account, uint256 cap) external { - rewardsPovwPerEpochCapUpdates[account].push(RewardsCapUpdate({timepoint: block.timestamp, cap: cap})); - } - - // forge-lint: disable-next-item(mixed-case-function) - function getPoVWRewardCap(address account) external view returns (uint256) { - return getPastPoVWRewardCap(account, block.timestamp); - } - - // forge-lint: disable-next-item(mixed-case-function) - function getPastPoVWRewardCap(address account, uint256 timepoint) public view returns (uint256) { - require(timepoint <= block.timestamp, "timepoint must be less than current timestamp"); - - RewardsCapUpdate[] storage updates = rewardsPovwPerEpochCapUpdates[account]; - // No cap has been set for the given account. - if (updates.length == 0) { - return type(uint256).max; - } - for (uint256 i = 0; i < updates.length; i++) { - if (updates[i].timepoint <= block.timestamp) { - return updates[i].cap; - } - } - revert("unreachable"); - } - - function delegateRewards(address delegatee) external pure { - delegatee; - revert("not implemented"); - } - - function delegateRewardsBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) - external - pure - { - delegatee; - nonce; - expiry; - v; - r; - s; - revert("not implemented"); - } - - function getPastStakingRewards(address account, uint256 timepoint) external pure returns (uint256) { - account; - timepoint; - revert("not implemented"); - } - - function getPastTotalStakingRewards(uint256 timepoint) external pure returns (uint256) { - timepoint; - revert("not implemented"); - } - - function getStakingRewards(address account) external pure returns (uint256) { - account; - revert("not implemented"); - } - - function getTotalStakingRewards() external pure returns (uint256) { - revert("not implemented"); - } - - function rewardDelegates(address account) external pure returns (address) { - account; - revert("not implemented"); - } -} diff --git a/contracts/shanghai/test/TestUtils.sol b/contracts/shanghai/test/TestUtils.sol deleted file mode 100644 index 22ef7ca8f9..0000000000 --- a/contracts/shanghai/test/TestUtils.sol +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; -import {Seal, RiscZeroSetVerifier} from "risc0/RiscZeroSetVerifier.sol"; -import {Selector} from "../src/types/Selector.sol"; -import {AssessorCallback} from "../src/types/AssessorCallback.sol"; -import {AssessorCommitment} from "../src/types/AssessorCommitment.sol"; -import {AssessorJournal} from "../src/types/AssessorJournal.sol"; -import {Fulfillment} from "../src/types/Fulfillment.sol"; -import {MerkleProofish} from "../src/libraries/MerkleProofish.sol"; - -library TestUtils { - using ReceiptClaimLib for ReceiptClaim; - - bytes8 internal constant LEAF_TAG = bytes8("LEAF_TAG"); - - function mockAssessor( - Fulfillment[] memory fills, - bytes32 assessorImageId, - Selector[] memory selectors, - AssessorCallback[] memory callbacks, - address prover - ) internal pure returns (ReceiptClaim memory) { - bytes32[] memory leaves = new bytes32[](fills.length); - - for (uint256 i = 0; i < fills.length; i++) { - leaves[i] = AssessorCommitment( - i, fills[i].id, fills[i].requestDigest, fills[i].claimDigest, fills[i].fulfillmentDataDigest() - ).eip712Digest(); - } - - bytes32 root = MerkleProofish.processTree(leaves); - - bytes memory journal = - abi.encode(AssessorJournal({root: root, selectors: selectors, callbacks: callbacks, prover: prover})); - return ReceiptClaimLib.ok(assessorImageId, sha256(journal)); - } - - function mockAssessorSeal(RiscZeroSetVerifier setVerifier, bytes32 claimDigest) - internal - view - returns (bytes memory) - { - bytes32[] memory path = new bytes32[](1); - path[0] = claimDigest; - return encodeSeal(setVerifier, Proof({siblings: path})); - } - - function mockSetBuilder(Fulfillment[] memory fills) - internal - pure - returns (bytes32 batchRoot, bytes32[][] memory tree) - { - bytes32[] memory claimDigests = new bytes32[](fills.length); - for (uint256 i = 0; i < fills.length; i++) { - claimDigests[i] = fills[i].claimDigest; - } - // compute the merkle tree of the batch - (batchRoot, tree) = computeMerkleTree(claimDigests); - } - - function fillInclusionProofs( - RiscZeroSetVerifier setVerifier, - Fulfillment[] memory fills, - bytes32 assessorLeaf, - bytes32[][] memory tree - ) internal view { - // generate inclusion proofs for each claim - Proof[] memory proofs = computeProofs(tree); - - for (uint256 i = 0; i < fills.length; i++) { - fills[i].seal = encodeSeal(setVerifier, append(proofs[i], assessorLeaf)); - } - } - - struct Proof { - bytes32[] siblings; - } - - // Build the Merkle Tree and return the root and the entire tree structure - function computeMerkleTree(bytes32[] memory values) internal pure returns (bytes32 root, bytes32[][] memory tree) { - require(values.length > 0, "Values list is empty, cannot compute Merkle root"); - - // Calculate the height of the tree (number of levels) - uint256 numLevels = log2Ceil(values.length) + 1; - - // Initialize the tree structure - tree = new bytes32[][](numLevels); - - // Hash the values with the leaf tag to form the leaf nodes. - tree[0] = new bytes32[](values.length); - for (uint256 i = 0; i < values.length; i++) { - tree[0][i] = hashLeaf(values[i]); - } - - // Build the tree level by level - uint256 currentLevelSize = values.length; - for (uint256 level = 0; currentLevelSize > 1; level++) { - uint256 nextLevelSize = (currentLevelSize + 1) / 2; - tree[level + 1] = new bytes32[](nextLevelSize); - - for (uint256 i = 0; i < nextLevelSize; i++) { - uint256 leftIndex = i * 2; - uint256 rightIndex = leftIndex + 1; - - bytes32 leftHash = tree[level][leftIndex]; - if (rightIndex < currentLevelSize) { - bytes32 rightHash = tree[level][rightIndex]; - - tree[level + 1][i] = MerkleProofish._hashPair(leftHash, rightHash); - } else { - // If the node has no right sibling, copy it up to the next level. - tree[level + 1][i] = leftHash; - } - } - - currentLevelSize = nextLevelSize; - } - - root = tree[tree.length - 1][0]; - } - - function computeProofs(bytes32[][] memory tree) internal pure returns (Proof[] memory proofs) { - uint256 numLeaves = tree[0].length; - uint256 proofLength = tree.length - 1; // Maximum possible length of the proof - proofs = new Proof[](numLeaves); - - // Generate proof for each leaf - for (uint256 leafIndex = 0; leafIndex < numLeaves; leafIndex++) { - bytes32[] memory tempSiblings = new bytes32[](proofLength); - uint256 actualProofLength = 0; - uint256 index = leafIndex; - - // Collect the siblings for the proof - for (uint256 level = 0; level < tree.length - 1; level++) { - uint256 siblingIndex = (index % 2 == 0) ? index + 1 : index - 1; - - if (siblingIndex < tree[level].length) { - tempSiblings[actualProofLength] = tree[level][siblingIndex]; - actualProofLength++; - } - - index /= 2; - } - - // Adjust the length of the proof to exclude any unused slots - proofs[leafIndex].siblings = new bytes32[](actualProofLength); - for (uint256 i = 0; i < actualProofLength; i++) { - proofs[leafIndex].siblings[i] = tempSiblings[i]; - } - } - } - - function hashLeaf(bytes32 value) internal pure returns (bytes32 leaf) { - return keccak256(abi.encodePacked(LEAF_TAG, value)); - } - - function encodeSeal(RiscZeroSetVerifier setVerifier, TestUtils.Proof memory merkleProof, bytes memory rootSeal) - internal - view - returns (bytes memory) - { - return abi.encodeWithSelector(setVerifier.SELECTOR(), Seal({path: merkleProof.siblings, rootSeal: rootSeal})); - } - - function encodeSeal(RiscZeroSetVerifier setVerifier, TestUtils.Proof memory merkleProof) - internal - view - returns (bytes memory) - { - bytes memory rootSeal; - return encodeSeal(setVerifier, merkleProof, rootSeal); - } - - function append(Proof memory proof, bytes32 newNode) internal pure returns (Proof memory) { - bytes32[] memory newSiblings = new bytes32[](proof.siblings.length + 1); - for (uint256 i = 0; i < proof.siblings.length; i++) { - newSiblings[i] = proof.siblings[i]; - } - newSiblings[proof.siblings.length] = newNode; - proof.siblings = newSiblings; - return proof; - } - - function log2Ceil(uint256 x) private pure returns (uint256) { - uint256 res = 0; - uint256 value = x; - while (value > 1) { - value = (value + 1) / 2; - res += 1; - } - return res; - } - - // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; - - // computes the hash of a permit - function getPermitHash(address owner, address spender, uint256 value, uint256 nonce, uint256 deadline) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline)); - } - - /// @notice Adds a non-zero selector at the given index - /// @dev Overwrites any existing selector at that index - /// @param self The Selectors struct to modify - /// @param index The index where to add the selector - /// @param selector The selector to add - function addSelector(Selector[] memory self, uint8 index, bytes4 selector) - internal - pure - returns (Selector[] memory) - { - // Create a new array with one additional element. - Selector[] memory newSelectors = new Selector[](self.length + 1); - for (uint256 i = 0; i < self.length; i++) { - newSelectors[i] = self[i]; - } - newSelectors[self.length] = Selector(index, selector); - return newSelectors; - } - - /// @notice Adds a non-zero callback at the given index - /// @dev Overwrites any existing callback at that index - /// @param self The Callbacks struct to modify - /// @param callback The callback to add - function addCallback(AssessorCallback[] memory self, AssessorCallback memory callback) - internal - pure - returns (AssessorCallback[] memory) - { - // Create a new array with one additional element. - AssessorCallback[] memory newCallbacks = new AssessorCallback[](self.length + 1); - for (uint256 i = 0; i < self.length; i++) { - newCallbacks[i] = self[i]; - } - newCallbacks[self.length] = callback; - return newCallbacks; - } -} diff --git a/contracts/shanghai/test/VerifierLayeredRouter.t.sol b/contracts/shanghai/test/VerifierLayeredRouter.t.sol deleted file mode 100644 index c5460e1848..0000000000 --- a/contracts/shanghai/test/VerifierLayeredRouter.t.sol +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.13; - -import {Test} from "forge-std/Test.sol"; -import {console2} from "forge-std/console2.sol"; -import {Ownable} from "openzeppelin/contracts/access/Ownable.sol"; - -import { - IRiscZeroVerifier, - Output, - OutputLib, - - // Receipt needs to be renamed due to collision with type on the Test contract. - Receipt as RiscZeroReceipt, - ReceiptClaim, - ReceiptClaimLib, - ExitCode, - SystemExitCode, - VerificationFailed -} from "risc0/IRiscZeroVerifier.sol"; -import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; -import {RiscZeroVerifierRouter} from "risc0/RiscZeroVerifierRouter.sol"; -import {RiscZeroVerifierRouter as BoundlessVerifierRouter} from "../src/verifier/RiscZeroVerifierRouter.sol"; -import {VerifierLayeredRouter} from "../src/verifier/VerifierLayeredRouter.sol"; - -library TestReceipt { - bytes public constant SEAL = - hex"7f3d01021e2cc73fcbc78acba09144eef4ee7a3bdeeacff3f50d801d6e62423a5ce863072df236b96ac4a8c91af0947d56e34560a7ba3a6fae79ca79bd70c30e2934cb842b72ad3493f70fd5b51a2a8eeead852563d8e8aae05afeeec8aa3ab719d3e42d0f6982e2de87cb6b2ccebab138c09c8a12674ae17d6b0ac0ffeee240af7ca39c00aab7f9aefb5d936bcb2d92c99a44548518130e1bcccdbccf84793862846c2422539985417029729b42c2254be325d915509c74269e4ad5bcc1c243a957a8c504b2162c32c72a3eb4a61becc8512f35603c0758bbbbd6b712efc49e6a3e68c52b42ef79a6f348875913f38da1e1dca85fc43b31097a2938a39480eec05700ea"; - bytes public constant JOURNAL = hex"6a75737420612073696d706c652072656365697074"; - bytes32 public constant IMAGE_ID = hex"be5ee8a820e3f10d48576fcefce4570c08f876b2d8a12a7dcd586b5901c7ab3d"; - bytes32 public constant USER_ID = hex"be5ee8a820e3f10d48576fcefce4570c08f876b2d8a12a7dcd586b5901c7ab3d"; -} - -contract RiscZeroVerifierLayeredRouterTest is Test { - using OutputLib for Output; - using ReceiptClaimLib for ReceiptClaim; - - bytes32 internal TEST_JOURNAL_DIGEST = sha256(TestReceipt.JOURNAL); - ReceiptClaim internal TEST_RECEIPT_CLAIM = ReceiptClaimLib.ok(TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - RiscZeroReceipt internal TEST_RECEIPT_A; - RiscZeroReceipt internal TEST_RECEIPT_B; - RiscZeroReceipt internal TEST_MANGLED_RECEIPT_A; - RiscZeroReceipt internal TEST_MANGLED_RECEIPT_B; - bytes4 internal SELECTOR_A; - bytes4 internal SELECTOR_B; - - RiscZeroMockVerifier internal verifierMockA; - RiscZeroMockVerifier internal verifierMockB; - RiscZeroVerifierRouter internal parentRouter; - VerifierLayeredRouter internal layeredRouter; - - function setUp() external { - parentRouter = new RiscZeroVerifierRouter(address(this)); - layeredRouter = new VerifierLayeredRouter(address(this), BoundlessVerifierRouter(address(parentRouter))); - - verifierMockA = new RiscZeroMockVerifier(bytes4(0xFFFFFFFF)); - verifierMockB = new RiscZeroMockVerifier(bytes4(uint32(1))); - - TEST_RECEIPT_A = verifierMockA.mockProve(TEST_RECEIPT_CLAIM.digest()); - TEST_RECEIPT_B = verifierMockB.mockProve(TEST_RECEIPT_CLAIM.digest()); - - TEST_MANGLED_RECEIPT_A = TEST_RECEIPT_A; - TEST_MANGLED_RECEIPT_A.seal[4] ^= bytes1(uint8(1)); - TEST_MANGLED_RECEIPT_B = TEST_RECEIPT_B; - TEST_MANGLED_RECEIPT_B.seal[4] ^= bytes1(uint8(1)); - - SELECTOR_A = verifierMockA.SELECTOR(); - SELECTOR_B = verifierMockB.SELECTOR(); - } - - function test_LayeredRouterGet() external view { - assertEq(address(layeredRouter.getParentRouter()), address(parentRouter)); - } - - function test_AddSelectorExistsInParentRouter() external { - parentRouter.addVerifier(SELECTOR_A, verifierMockA); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorInUse.selector, SELECTOR_A)); - layeredRouter.addVerifier(SELECTOR_A, verifierMockB); - } - - function test_AddRemovedSelectorInParentRouter() external { - parentRouter.addVerifier(SELECTOR_A, verifierMockA); - parentRouter.removeVerifier(SELECTOR_A); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorRemoved.selector, SELECTOR_A)); - layeredRouter.addVerifier(SELECTOR_A, verifierMockB); - } - - function test_AddSelector() external { - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - IRiscZeroVerifier verifier = layeredRouter.getVerifier(SELECTOR_A); - assertEq(address(verifier), address(verifierMockA)); - } - - function test_LayeredRouterVerifyIntegrity() external { - parentRouter.addVerifier(SELECTOR_A, verifierMockA); - layeredRouter.addVerifier(SELECTOR_B, verifierMockB); - // Expect exactly 2 calls, to verifier A/B with TEST_RECEIPT_x and TEST_MANGLED_RECEIPT_x. - vm.expectCall(address(verifierMockA), new bytes(0), 2); - vm.expectCall(address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_RECEIPT_A), 1); - vm.expectCall( - address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_MANGLED_RECEIPT_A), 1 - ); - vm.expectCall(address(verifierMockB), new bytes(0), 2); - vm.expectCall(address(verifierMockB), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_RECEIPT_B), 1); - vm.expectCall( - address(verifierMockB), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_MANGLED_RECEIPT_B), 1 - ); - layeredRouter.verifyIntegrity(TEST_RECEIPT_A); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_A); - - layeredRouter.verifyIntegrity(TEST_RECEIPT_B); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_B); - } - - function test_EmptyRouterVerifyIntegrity() external { - // Expect no calls to be made to the verifier controlled. - vm.expectCall(address(verifierMockA), new bytes(0), 0); - vm.expectCall(address(verifierMockB), new bytes(0), 0); - - // Empty router should always revert with selector unknown. - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorUnknown.selector, SELECTOR_A)); - layeredRouter.verifyIntegrity(TEST_RECEIPT_A); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorUnknown.selector, SELECTOR_B)); - layeredRouter.verifyIntegrity(TEST_RECEIPT_B); - } - - function test_SingleVerifierVerifyIntegrity() external { - // Expect exactly 2 calls, to verifier A with TEST_RECEIPT_A and TEST_MANGLED_RECEIPT_A. - vm.expectCall(address(verifierMockA), new bytes(0), 2); - vm.expectCall(address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_RECEIPT_A), 1); - vm.expectCall( - address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_MANGLED_RECEIPT_A), 1 - ); - vm.expectCall(address(verifierMockB), new bytes(0), 0); - - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - - layeredRouter.verifyIntegrity(TEST_RECEIPT_A); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_A); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorUnknown.selector, SELECTOR_B)); - layeredRouter.verifyIntegrity(TEST_RECEIPT_B); - } - - function test_TwoVerifiersVerifyIntegrity() external { - // Expect exactly 2 calls, to verifier A/B with TEST_RECEIPT_x and TEST_MANGLED_RECEIPT_x. - vm.expectCall(address(verifierMockA), new bytes(0), 2); - vm.expectCall(address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_RECEIPT_A), 1); - vm.expectCall( - address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_MANGLED_RECEIPT_A), 1 - ); - vm.expectCall(address(verifierMockB), new bytes(0), 2); - vm.expectCall(address(verifierMockB), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_RECEIPT_B), 1); - vm.expectCall( - address(verifierMockB), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_MANGLED_RECEIPT_B), 1 - ); - - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - layeredRouter.addVerifier(SELECTOR_B, verifierMockB); - - layeredRouter.verifyIntegrity(TEST_RECEIPT_A); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_A); - - layeredRouter.verifyIntegrity(TEST_RECEIPT_B); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_B); - } - - function test_RemoveVerifierVerifyIntegrity() external { - // Expect exactly 4 calls to verifier A with TEST_RECEIPT_A and TEST_MANGLED_RECEIPT_A. - // Expect exactly 2 calls to verifier B with TEST_RECEIPT_B and TEST_MANGLED_RECEIPT_B. - vm.expectCall(address(verifierMockA), new bytes(0), 4); - vm.expectCall(address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_RECEIPT_A), 2); - vm.expectCall( - address(verifierMockA), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_MANGLED_RECEIPT_A), 2 - ); - vm.expectCall(address(verifierMockB), new bytes(0), 2); - vm.expectCall(address(verifierMockB), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_RECEIPT_B), 1); - vm.expectCall( - address(verifierMockB), abi.encodeCall(IRiscZeroVerifier.verifyIntegrity, TEST_MANGLED_RECEIPT_B), 1 - ); - - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - layeredRouter.addVerifier(SELECTOR_B, verifierMockB); - - layeredRouter.verifyIntegrity(TEST_RECEIPT_A); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_A); - - layeredRouter.verifyIntegrity(TEST_RECEIPT_B); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_B); - - layeredRouter.removeVerifier(SELECTOR_B); - - layeredRouter.verifyIntegrity(TEST_RECEIPT_A); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verifyIntegrity(TEST_MANGLED_RECEIPT_A); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorRemoved.selector, SELECTOR_B)); - layeredRouter.verifyIntegrity(TEST_RECEIPT_B); - } - - function test_EmptyRouterVerify() external { - // Expect no calls to be made to the verifier controlled. - vm.expectCall(address(verifierMockA), new bytes(0), 0); - vm.expectCall(address(verifierMockB), new bytes(0), 0); - - // Empty router should always revert with selector unknown. - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorUnknown.selector, SELECTOR_A)); - layeredRouter.verify(TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorUnknown.selector, SELECTOR_B)); - layeredRouter.verify(TEST_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - } - - function test_SingleVerifierVerify() external { - // Expect exactly 2 calls, to verifier A with TEST_RECEIPT_A and TEST_MANGLED_RECEIPT_A. - vm.expectCall(address(verifierMockA), new bytes(0), 2); - vm.expectCall( - address(verifierMockA), - abi.encodeCall(IRiscZeroVerifier.verify, (TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST)), - 1 - ); - vm.expectCall( - address(verifierMockA), - abi.encodeCall( - IRiscZeroVerifier.verify, (TEST_MANGLED_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST) - ), - 1 - ); - vm.expectCall(address(verifierMockB), new bytes(0), 0); - - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - - layeredRouter.verify(TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verify(TEST_MANGLED_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorUnknown.selector, SELECTOR_B)); - layeredRouter.verify(TEST_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - } - - function test_TwoVerifiersVerify() external { - // Expect exactly 2 calls, to verifier A/B with TEST_RECEIPT_x and TEST_MANGLED_RECEIPT_x. - vm.expectCall(address(verifierMockA), new bytes(0), 2); - vm.expectCall( - address(verifierMockA), - abi.encodeCall(IRiscZeroVerifier.verify, (TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST)), - 1 - ); - vm.expectCall( - address(verifierMockA), - abi.encodeCall( - IRiscZeroVerifier.verify, (TEST_MANGLED_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST) - ), - 1 - ); - vm.expectCall(address(verifierMockB), new bytes(0), 2); - vm.expectCall( - address(verifierMockB), - abi.encodeCall(IRiscZeroVerifier.verify, (TEST_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST)), - 1 - ); - vm.expectCall( - address(verifierMockB), - abi.encodeCall( - IRiscZeroVerifier.verify, (TEST_MANGLED_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST) - ), - 1 - ); - - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - layeredRouter.addVerifier(SELECTOR_B, verifierMockB); - - layeredRouter.verify(TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verify(TEST_MANGLED_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - - layeredRouter.verify(TEST_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verify(TEST_MANGLED_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - } - - function test_RemoveVerifierVerify() external { - // Expect exactly 4 calls to verifier A with TEST_RECEIPT_A and TEST_MANGLED_RECEIPT_A. - // Expect exactly 2 calls to verifier B with TEST_RECEIPT_B and TEST_MANGLED_RECEIPT_B. - vm.expectCall(address(verifierMockA), new bytes(0), 4); - vm.expectCall( - address(verifierMockA), - abi.encodeCall(IRiscZeroVerifier.verify, (TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST)), - 2 - ); - vm.expectCall( - address(verifierMockA), - abi.encodeCall( - IRiscZeroVerifier.verify, (TEST_MANGLED_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST) - ), - 2 - ); - vm.expectCall(address(verifierMockB), new bytes(0), 2); - vm.expectCall( - address(verifierMockB), - abi.encodeCall(IRiscZeroVerifier.verify, (TEST_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST)), - 1 - ); - vm.expectCall( - address(verifierMockB), - abi.encodeCall( - IRiscZeroVerifier.verify, (TEST_MANGLED_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST) - ), - 1 - ); - - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - layeredRouter.addVerifier(SELECTOR_B, verifierMockB); - - layeredRouter.verify(TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verify(TEST_MANGLED_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - - layeredRouter.verify(TEST_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verify(TEST_MANGLED_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - - layeredRouter.removeVerifier(SELECTOR_B); - - layeredRouter.verify(TEST_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - vm.expectRevert(VerificationFailed.selector); - layeredRouter.verify(TEST_MANGLED_RECEIPT_A.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorRemoved.selector, SELECTOR_B)); - layeredRouter.verify(TEST_RECEIPT_B.seal, TestReceipt.IMAGE_ID, TEST_JOURNAL_DIGEST); - } - - function test_OnlyOwnerCanAddVerifier() external { - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - - layeredRouter.renounceOwnership(); - - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this))); - layeredRouter.addVerifier(SELECTOR_B, verifierMockB); - } - - function test_OnlyOwnerCanRemoveVerifier() external { - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - - layeredRouter.renounceOwnership(); - - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this))); - layeredRouter.removeVerifier(SELECTOR_A); - } - - function test_VerifierCanOnlyBeAddedOnce() external { - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorInUse.selector, SELECTOR_A)); - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - } - - function test_VerifierCannotBeAddedAfterRemove() external { - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - layeredRouter.removeVerifier(SELECTOR_A); - - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.SelectorRemoved.selector, SELECTOR_A)); - layeredRouter.addVerifier(SELECTOR_A, verifierMockA); - } - - function test_UnsetVerifierCanBeRemoved() external { - layeredRouter.removeVerifier(SELECTOR_A); - } - - function test_TransferRouterOwnership() external { - address newOwner = address(0xc0ffee); - - layeredRouter.transferOwnership(newOwner); - assertEq(layeredRouter.pendingOwner(), newOwner); - assertEq(layeredRouter.owner(), address(this)); - - vm.startPrank(newOwner); - layeredRouter.acceptOwnership(); - vm.stopPrank(); - - assertEq(layeredRouter.owner(), newOwner); - } - - function test_CannotAddZeroAddressVerifier() external { - vm.expectRevert(abi.encodeWithSelector(RiscZeroVerifierRouter.VerifierAddressZero.selector)); - layeredRouter.addVerifier(SELECTOR_A, IRiscZeroVerifier(address(0))); - } -} diff --git a/contracts/shanghai/test/clients/BaseClient.sol b/contracts/shanghai/test/clients/BaseClient.sol deleted file mode 100644 index d1388fff61..0000000000 --- a/contracts/shanghai/test/clients/BaseClient.sol +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.26; - -import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; -import {HitPoints} from "../../src/HitPoints.sol"; -import {Vm} from "forge-std/Test.sol"; -import {console} from "forge-std/console.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {Callback} from "../../src/types/Callback.sol"; -import {ProofRequest} from "../../src/types/ProofRequest.sol"; -import {LockRequest} from "../../src/types/LockRequest.sol"; -import {Offer} from "../../src/types/Offer.sol"; -import {Requirements} from "../../src/types/Requirements.sol"; -import {PredicateLibrary} from "../../src/types/Predicate.sol"; - -import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; - -Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); -bytes32 constant APP_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000001; -bytes32 constant SET_BUILDER_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000002; -bytes32 constant ASSESSOR_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000003; -bytes constant APP_JOURNAL = bytes("GUEST JOURNAL"); - -abstract contract BaseClient { - using SafeCast for uint256; - using SafeCast for int256; - - int256 public balanceSnapshot = type(int256).max; - int256 public stakeBalanceSnapshot = type(int256).max; - - string public identifier; - - IBoundlessMarket public boundlessMarket; - HitPoints public collateralToken; - - constructor() {} - - function initialize(string memory _identifier, IBoundlessMarket _boundlessMarket, HitPoints _collateralToken) - public - virtual - { - identifier = _identifier; - boundlessMarket = _boundlessMarket; - collateralToken = _collateralToken; - balanceSnapshot = type(int256).max; - } - - function addr() public view virtual returns (address); - - function sign(ProofRequest calldata req) public virtual returns (bytes memory); - - function signLockRequest(LockRequest calldata req) public virtual returns (bytes memory); - - function defaultOffer() public view returns (Offer memory) { - return Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - lockTimeout: uint32(100), - timeout: uint32(200), - lockCollateral: 1 ether - }); - } - - function defaultRequirements() public pure returns (Requirements memory) { - return Requirements({ - predicate: PredicateLibrary.createDigestMatchPredicate(bytes32(APP_IMAGE_ID), sha256(APP_JOURNAL)), - selector: bytes4(0), - callback: Callback({addr: address(0), gasLimit: 0}) - }); - } - - function request(uint32 idx) public virtual returns (ProofRequest memory); - - function request(uint32 idx, Offer memory offer) public virtual returns (ProofRequest memory); - - function snapshotBalance() public { - balanceSnapshot = boundlessMarket.balanceOf(addr()).toInt256(); - } - - function snapshotCollateralBalance() public { - stakeBalanceSnapshot = boundlessMarket.balanceOfCollateral(addr()).toInt256(); - } - - function expectBalanceChange(int256 change) public view { - require(balanceSnapshot != type(int256).max, "balance snapshot is not set"); - int256 newBalance = boundlessMarket.balanceOf(addr()).toInt256(); - console.log("%s balance at block %d: %d", identifier, block.number, newBalance.toUint256()); - int256 expectedBalance = balanceSnapshot + change; - require(expectedBalance >= 0, "expected balance cannot be less than 0"); - console.log("%s expected balance at block %d: %d", identifier, block.number, expectedBalance.toUint256()); - require(expectedBalance == newBalance, "balance is not equal to expected value"); - } - - function expectCollateralBalanceChange(int256 change) public view { - require(stakeBalanceSnapshot != type(int256).max, "collateral balance snapshot is not set"); - int256 newBalance = boundlessMarket.balanceOfCollateral(addr()).toInt256(); - console.log("%s collateral balance at block %d: %d", identifier, block.number, newBalance.toUint256()); - int256 expectedBalance = stakeBalanceSnapshot + change; - require(expectedBalance >= 0, "expected collateral balance cannot be less than 0"); - console.log( - "%s expected collateral balance at block %d: %d", identifier, block.number, expectedBalance.toUint256() - ); - require(expectedBalance == newBalance, "collateral balance is not equal to expected value"); - } -} diff --git a/contracts/shanghai/test/clients/Client.sol b/contracts/shanghai/test/clients/Client.sol deleted file mode 100644 index e6a701f516..0000000000 --- a/contracts/shanghai/test/clients/Client.sol +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.26; - -import {BaseClient} from "./BaseClient.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; -import {TestUtils} from "../TestUtils.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {ProofRequest} from "../../src/types/ProofRequest.sol"; -import {RequestIdLibrary} from "../../src/types/RequestId.sol"; -import {Input, InputType} from "../../src/types/Input.sol"; -import {Offer} from "../../src/types/Offer.sol"; -import {LockRequest} from "../../src/types/LockRequest.sol"; - -Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - -/// @dev Client is a wrapper around an EOA with logic for signing proof requests and submitting them to the market. -/// It also inherits functions for tracking balances and stake from BaseClient. -contract Client is BaseClient { - Vm.Wallet public wallet; - - constructor(Vm.Wallet memory _wallet) { - wallet = _wallet; - } - - function addr() public view override returns (address) { - return wallet.addr; - } - - function sign(ProofRequest calldata req) public override returns (bytes memory) { - bytes32 structDigest = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), req.eip712Digest()); - (uint8 v, bytes32 r, bytes32 s) = VM.sign(wallet, structDigest); - return abi.encodePacked(r, s, v); - } - - function signLockRequest(LockRequest calldata req) public override returns (bytes memory) { - bytes32 structDigest = - MessageHashUtils.toTypedDataHash(boundlessMarket.eip712DomainSeparator(), req.eip712Digest()); - (uint8 v, bytes32 r, bytes32 s) = VM.sign(wallet, structDigest); - return abi.encodePacked(r, s, v); - } - - function request(uint32 idx) public view override returns (ProofRequest memory) { - return ProofRequest({ - id: RequestIdLibrary.from(addr(), idx), - requirements: defaultRequirements(), - imageUrl: "https://image.dev.null", - input: Input({inputType: InputType.Url, data: bytes("https://input.dev.null")}), - offer: defaultOffer() - }); - } - - function request(uint32 idx, Offer memory offer) public view override returns (ProofRequest memory) { - return ProofRequest({ - id: RequestIdLibrary.from(addr(), idx), - requirements: defaultRequirements(), - imageUrl: "https://image.dev.null", - input: Input({inputType: InputType.Url, data: bytes("https://input.dev.null")}), - offer: offer - }); - } - - function signPermit(address spender, uint256 value, uint256 deadline) - public - returns (uint8 v, bytes32 r, bytes32 s) - { - return VM.sign( - wallet, - MessageHashUtils.toTypedDataHash( - collateralToken.DOMAIN_SEPARATOR(), - TestUtils.getPermitHash( - wallet.addr, spender, value, ERC20Permit(address(collateralToken)).nonces(wallet.addr), deadline - ) - ) - ); - } -} diff --git a/contracts/shanghai/test/clients/MockSmartContractWallet.sol b/contracts/shanghai/test/clients/MockSmartContractWallet.sol deleted file mode 100644 index 8e0bcd6c35..0000000000 --- a/contracts/shanghai/test/clients/MockSmartContractWallet.sol +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.26; - -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; - -/// @dev Simple mock implementation of an ERC-1271 compliant SCW. -contract MockSmartContractWallet is IERC1271 { - bytes private expectedSignature; - uint256 private gasCost = 0; - address private owner; - IBoundlessMarket public immutable MARKET; - bytes4 internal constant MAGICVALUE = 0x1626ba7e; // bytes4(keccak256("isValidSignature(bytes32,bytes)") - - constructor(bytes memory _expectedSignature, IBoundlessMarket _market, address _owner) { - expectedSignature = _expectedSignature; - MARKET = _market; - owner = _owner; - } - - function setExpectedSignature(bytes memory _expectedSignature) external { - expectedSignature = _expectedSignature; - } - - function setGasCost(uint256 _gasCost) external { - gasCost = _gasCost; - } - - function isValidSignature(bytes32, bytes memory _signature) external view returns (bytes4) { - // Consume gas by doing SLOAD operations to random slots - uint256 startGas = gasleft(); - uint256 i = 0; - while (startGas - gasleft() < gasCost) { - bytes32 slot = keccak256(abi.encode(i)); - bytes32 x; - assembly { - x := sload(slot) - } - i++; - } - - if (keccak256(_signature) == keccak256(expectedSignature)) { - return MAGICVALUE; - } - return 0xffffffff; - } - - // Allow the wallet to receive ETH - receive() external payable {} - - function execute(address target, bytes memory data, uint256 value) external payable { - require(msg.sender == owner, "Not authorized"); - (bool success,) = target.call{value: value}(data); - require(success, "Call failed"); - } -} diff --git a/contracts/shanghai/test/clients/SmartContractClient.sol b/contracts/shanghai/test/clients/SmartContractClient.sol deleted file mode 100644 index 39837a7754..0000000000 --- a/contracts/shanghai/test/clients/SmartContractClient.sol +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.26; - -import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; -import {HitPoints} from "../../src/HitPoints.sol"; -import {BaseClient} from "./BaseClient.sol"; -import {Test} from "forge-std/Test.sol"; -import {MockSmartContractWallet} from "./MockSmartContractWallet.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {ProofRequest} from "../../src/types/ProofRequest.sol"; -import {LockRequest} from "../../src/types/LockRequest.sol"; -import {RequestIdLibrary} from "../../src/types/RequestId.sol"; -import {Input, InputType} from "../../src/types/Input.sol"; -import {Offer} from "../../src/types/Offer.sol"; - -Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - -/// @dev SmartContractClient is essentially a wrapper around a smart contract wallet with logic for signing proof requests and submitting them to the market. -/// It also inherits functions for tracking balances and stake from BaseClient. -contract SmartContractClient is BaseClient, Test { - MockSmartContractWallet public smartWallet; - Vm.Wallet public signer; - - bytes private expectedSignature; - - constructor(Vm.Wallet memory _signer) { - expectedSignature = abi.encodePacked(keccak256(abi.encodePacked(_signer.addr))); - smartWallet = new MockSmartContractWallet(expectedSignature, boundlessMarket, _signer.addr); - signer = _signer; - } - - function initialize(string memory _identifier, IBoundlessMarket _boundlessMarket, HitPoints _collateralToken) - public - override - { - vm.label(address(smartWallet), _identifier); - super.initialize(_identifier, _boundlessMarket, _collateralToken); - } - - function addr() public view override returns (address) { - return address(smartWallet); - } - - function signerAddr() public view returns (address) { - return signer.addr; - } - - function request(uint32 idx) public view override returns (ProofRequest memory) { - return ProofRequest({ - id: RequestIdLibrary.from(addr(), idx, true), - requirements: defaultRequirements(), - imageUrl: "https://image.dev.null", - input: Input({inputType: InputType.Url, data: bytes("https://input.dev.null")}), - offer: defaultOffer() - }); - } - - function request(uint32 idx, Offer memory offer) public view override returns (ProofRequest memory) { - return ProofRequest({ - id: RequestIdLibrary.from(addr(), idx, true), - requirements: defaultRequirements(), - imageUrl: "https://image.dev.null", - input: Input({inputType: InputType.Url, data: bytes("https://input.dev.null")}), - offer: offer - }); - } - - function sign(ProofRequest calldata) public view override returns (bytes memory) { - return expectedSignature; - } - - function signLockRequest(LockRequest calldata) public view override returns (bytes memory) { - return expectedSignature; - } - - function execute(address target, bytes memory data) public { - vm.prank(signer.addr); - smartWallet.execute(target, data, 0); - } - - function execute(address target, bytes memory data, uint256 value) public { - vm.prank(signer.addr); - smartWallet.execute(target, data, value); - } - - function setExpectedSignature(bytes memory _expectedSignature) public { - smartWallet.setExpectedSignature(_expectedSignature); - } -} diff --git a/contracts/shanghai/test/receipts/Blake3Groth16TestReceipt.sol b/contracts/shanghai/test/receipts/Blake3Groth16TestReceipt.sol deleted file mode 100644 index 8d40029c49..0000000000 --- a/contracts/shanghai/test/receipts/Blake3Groth16TestReceipt.sol +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -// This file is automatically generated by: -// cargo xtask bootstrap-groth16 - -pragma solidity ^0.8.13; - -library TestReceipt { - bytes public constant SEAL = - hex"62f049f609e5e571a5daab1c3a3c4e02e4e3f3104218cb0bc39a1067859858d09eefd9102809fbb55dd140c09cf131dae9c271d7d386b021389e929f791e7aa4ffdb90741b8f4159cc9d57fce7c4bb5a7da795cb15cd35da3f1067218e1359fe2430af2b24b5ac63cdaf40cdb240039372942207e1432496eb7efa4dec0cee4bef90cece1ae0027bc89807ac1293ce8d9c8ce3dd999f6926ea0b5904acd0182c85a203b325b504efe9bcef7d9dca9cbf7f2d1b312bd189352e7a50a969b2175dc3a3079615a8fa448aba2fc439af0a5f01949a47507210c5f3088a485ecbe6c4343e7227145675020b043e2b1211be05c0dbab20d5e5423b5b53d193b7a8cb91455dca86"; - bytes32 public constant CLAIM_DIGEST = hex"00518e3981d8f63a944afd3d1d2b5c23ba7968488981875b7659e1beb2a95a63"; -} diff --git a/contracts/shanghai/test/receipts/Groth16TestReceiptV3_0.sol b/contracts/shanghai/test/receipts/Groth16TestReceiptV3_0.sol deleted file mode 100644 index aac94acc2c..0000000000 --- a/contracts/shanghai/test/receipts/Groth16TestReceiptV3_0.sol +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.13; - -library TestReceipt { - bytes public constant SEAL = - hex"73c457ba2ccb718fd9092cc11546eeded62a44d3ed274076dd3ec154fae8739f3432050b2005be2c5dbe6c08bfd04b30601a462540962bc26a2f38c5cfc0a4d76d8f1b8015e690a1b230081234867edeedb2f98bcdf33d0471c2aa5e8db63b72333f871527eb5d1fcf0a7af50fb8f42e8699e2c4eda3cd93f4e2a930096ae78e38bea4020c5c3d963dc453b4b302170e47c0cf53382255143c8fcef474d8b6eaaa8daaaf092c2f650809a3afbd122ef128cb882c2de7a6ccddd2e544b645fa3fedf6bcc92e09be04876a07778231fd5b93305d35fd8af23f040a11682a8c64130370804f28f07a76fa538755276e42c04b5f7eb97b04b68b65fa50e3181a0452069a3667"; - bytes public constant JOURNAL = hex"6a75737420612073696d706c652072656365697074"; - bytes32 public constant IMAGE_ID = hex"11d264ed8dfdee222b820f0278e4d7f55d4b69a5472253a471c102265a91ea1a"; - bytes32 public constant USER_ID = hex"11d264ed8dfdee222b820f0278e4d7f55d4b69a5472253a471c102265a91ea1a"; -} diff --git a/contracts/shanghai/test/receipts/SetInclusionTestReceiptV0_9.sol b/contracts/shanghai/test/receipts/SetInclusionTestReceiptV0_9.sol deleted file mode 100644 index 3caeaa324f..0000000000 --- a/contracts/shanghai/test/receipts/SetInclusionTestReceiptV0_9.sol +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -// SPDX-License-Identifier: BUSL-1.1 - -// This file is automatically generated by: -// cargo run --bin set-inclusion-test-receipt -- --path [set-builder-elf-path] - -pragma solidity ^0.8.13; - -library TestSetInclusionReceipt { - bytes public constant SEAL = - hex"242f9d5b0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010473c457ba15c3c7fdb99bd1c573b7d69dd1a89c853b41f1d70367b2e912e0c3bf37c402fa2c54688484cc19db6aa6be1ad74c32b099a174bdd0a104048b3306b7bb69f28106e6fab490b2203cb8fe31cf65d540d9c2f8acbd926e542dcda59e39762b3d3f221f1a3223d8f6ccc540adc0c5780c597a814948fc6df6a344ab3b9c591e1daf0cbddc4bf49c4a29502895ec34ee618fce7e3106182957754058f2e97d7a8aa12756726c5616593b99a9bfc22effc123472ffa41b4a386399d62c612a858861c1c8ec04df3786dcc678ec8c330e2c814e4e30bfd1bd1db73c32937ac87d076942688845682345c812472026c6eb2271d125e001928ac2eeb4582b3c5e5fedec700000000000000000000000000000000000000000000000000000000"; - bytes public constant JOURNAL = hex"6500000063000000680000006f0000005f00000074000000650000007300000074000000"; - bytes32 public constant IMAGE_ID = hex"93795eafc980e752ecb2ba6ecb8203b2ff82794c5dc1d419847fea4300d76c8f"; -} diff --git a/contracts/shanghai/test/types/Account.t.sol b/contracts/shanghai/test/types/Account.t.sol deleted file mode 100644 index c783f9f49c..0000000000 --- a/contracts/shanghai/test/types/Account.t.sol +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {Account} from "../../src/types/Account.sol"; - -/// @dev Wrapper contract to test Account. Declaring Account as a storage variable -/// directly in the test contract makes it type `StdCheatsSafe.Account` which causes -/// the library functions on type `Account` to not be available. -contract AccountTestContract { - Account account; - - function requestFlags(uint32 idx) public view returns (bool, bool) { - return account.requestFlags(idx); - } - - function setRequestLocked(uint32 idx) public { - account.setRequestLocked(idx); - } - - function setRequestFulfilled(uint32 idx) public { - account.setRequestFulfilled(idx); - } -} - -contract AccountTest is Test { - AccountTestContract account = new AccountTestContract(); - - function testRequestFlags() public { - uint32 idx = 5; - - // Initially, the request should not be locked or fulfilled - (bool locked, bool fulfilled) = account.requestFlags(idx); - assertFalse(locked, "Request should not be locked initially"); - assertFalse(fulfilled, "Request should not be fulfilled initially"); - - // Set the request as locked - account.setRequestLocked(idx); - (locked, fulfilled) = account.requestFlags(idx); - assertTrue(locked, "Request should be locked"); - assertFalse(fulfilled, "Request should not be fulfilled"); - - // Set the request as fulfilled - account.setRequestFulfilled(idx); - (locked, fulfilled) = account.requestFlags(idx); - assertTrue(locked, "Request should be locked"); - assertTrue(fulfilled, "Request should be fulfilled"); - } -} diff --git a/contracts/shanghai/test/types/FulfillmentContext.t.sol b/contracts/shanghai/test/types/FulfillmentContext.t.sol deleted file mode 100644 index 0dca3ab920..0000000000 --- a/contracts/shanghai/test/types/FulfillmentContext.t.sol +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {FulfillmentContext, FulfillmentContextLibrary} from "../../src/types/FulfillmentContext.sol"; - -contract FulfillmentContextLibraryTest is Test { - /// forge-config: default.fuzz.runs = 10000 - function testFuzz_PackUnpack(bool valid, bool expired, uint96 price) public pure { - FulfillmentContext memory original = FulfillmentContext({valid: valid, expired: expired, price: price}); - - uint256 packed = FulfillmentContextLibrary.pack(original); - FulfillmentContext memory unpacked = FulfillmentContextLibrary.unpack(packed); - - assertEq(unpacked.valid, original.valid, "Valid flag mismatch"); - assertEq(unpacked.expired, original.expired, "Expired flag mismatch"); - assertEq(unpacked.price, original.price, "Price mismatch"); - } - - /// forge-config: default.fuzz.runs = 10000 - function testFuzz_StoreAndLoadAndClear(bool valid, bool expired, uint96 price) public { - FulfillmentContext memory original = FulfillmentContext({valid: valid, expired: expired, price: price}); - bytes32 slot = keccak256("transient.fulfillment.slot"); - - // Store the FulfillmentContext in the specified slot - FulfillmentContextLibrary.store(original, slot); - - // Load and clear the FulfillmentContext from the specified slot - FulfillmentContext memory loaded = FulfillmentContextLibrary.load(slot); - - // Verify that the loaded FulfillmentContext matches the original - assertEq(loaded.valid, original.valid, "Valid flag mismatch"); - assertEq(loaded.expired, original.expired, "Expired flag mismatch"); - assertEq(loaded.price, original.price, "Price mismatch"); - - // Verify the slot was cleared - FulfillmentContext memory cleared = FulfillmentContextLibrary.load(slot); - assertEq(cleared.valid, false, "Should be cleared: valid"); - assertEq(cleared.expired, false, "Should be cleared: expired"); - assertEq(cleared.price, 0, "Should be cleared: price"); - } -} diff --git a/contracts/shanghai/test/types/Input.t.sol b/contracts/shanghai/test/types/Input.t.sol deleted file mode 100644 index b170d28a72..0000000000 --- a/contracts/shanghai/test/types/Input.t.sol +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {Input, InputLibrary, InputType} from "../../src/types/Input.sol"; - -contract InputTest is Test { - function testCreateInlineInput() public pure { - bytes memory data = "inline data"; - Input memory input = InputLibrary.createInlineInput(data); - - assertEq(uint8(input.inputType), uint8(InputType.Inline), "Input type should be Inline"); - assertEq(input.data, data, "Input data should match"); - } - - function testCreateUrlInput() public pure { - string memory url = "https://example.com"; - Input memory input = InputLibrary.createUrlInput(url); - - assertEq(uint8(input.inputType), uint8(InputType.Url), "Input type should be Url"); - assertEq(string(input.data), url, "Input data should match the URL"); - } -} diff --git a/contracts/shanghai/test/types/MerkleProofish.t.sol b/contracts/shanghai/test/types/MerkleProofish.t.sol deleted file mode 100644 index 157ccaafc0..0000000000 --- a/contracts/shanghai/test/types/MerkleProofish.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {MerkleProofish} from "../../src/libraries/MerkleProofish.sol"; - -contract MerkleProofishTest is Test { - function testProcessTree2() public pure { - bytes32[] memory leaves = new bytes32[](2); - leaves[0] = 0x6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9; - leaves[1] = 0x6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9; - - bytes32 root = MerkleProofish.processTree(leaves); - assertEq(root, 0x5032880539b5d039d4a4a8042745c9ad14934c96b76d7e61ea03550e29b234af); - } - - function testProcessTree3() public pure { - bytes32[] memory leaves = new bytes32[](3); - leaves[0] = 0x6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9; - leaves[1] = 0x6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9; - leaves[2] = 0x6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9; - - bytes32 root = MerkleProofish.processTree(leaves); - assertEq(root, 0xe004c72e4cb697fa97669508df099edbc053309343772a25e56412fc7db8ebef); - } -} diff --git a/contracts/shanghai/test/types/Offer.t.sol b/contracts/shanghai/test/types/Offer.t.sol deleted file mode 100644 index 27549cf04a..0000000000 --- a/contracts/shanghai/test/types/Offer.t.sol +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; -import {Offer} from "../../src/types/Offer.sol"; - -contract OfferTest is Test { - /// forge-config: default.allow_internal_expect_revert = true - function testBlockAtPrice() public { - Offer memory offer = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(100), - rampUpPeriod: 100, - lockTimeout: uint32(500), - timeout: uint32(500), - lockCollateral: 0.1 ether - }); - - assertEq(offer.timeAtPrice(1 ether), 0); - - assertEq(offer.timeAtPrice(1.01 ether), 101); - assertEq(offer.timeAtPrice(1.001 ether), 101); - - assertEq(offer.timeAtPrice(1.25 ether), 125); - assertEq(offer.timeAtPrice(1.5 ether), 150); - assertEq(offer.timeAtPrice(1.75 ether), 175); - assertEq(offer.timeAtPrice(1.99 ether), 199); - - assertEq(offer.timeAtPrice(2 ether), 200); - - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InvalidRequest.selector)); - offer.timeAtPrice(3 ether); - } - - function testPriceAt() public pure { - Offer memory offer = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(100), - rampUpPeriod: 100, - lockTimeout: uint32(500), - timeout: uint32(500), - lockCollateral: 0.1 ether - }); - - assertEq(offer.priceAt(0), 1 ether); - assertEq(offer.priceAt(100), 1 ether); - - assertEq(offer.priceAt(101), 1.01 ether); - assertEq(offer.priceAt(125), 1.25 ether); - assertEq(offer.priceAt(150), 1.5 ether); - assertEq(offer.priceAt(175), 1.75 ether); - assertEq(offer.priceAt(199), 1.99 ether); - - assertEq(offer.priceAt(200), 2 ether); - assertEq(offer.priceAt(500), 2 ether); - } - - function testDeadlines() public pure { - Offer memory offer = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(100), - rampUpPeriod: 100, - lockTimeout: uint32(150), - timeout: uint32(200), - lockCollateral: 0.1 ether - }); - - assertEq(offer.lockDeadline(), 250); - assertEq(offer.deadline(), 300); - } - - /// forge-config: default.allow_internal_expect_revert = true - function testInvalidLockTimeout() public { - Offer memory invalidOffer = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(100), - rampUpPeriod: 100, - lockTimeout: uint32(250), - timeout: uint32(200), - lockCollateral: 0.1 ether - }); - - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InvalidRequest.selector)); - invalidOffer.validate(); - } - - /// forge-config: default.allow_internal_expect_revert = true - function testDeadlineDeltaTooLarge() public { - Offer memory invalidOffer = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(100), - rampUpPeriod: 100, - lockTimeout: uint32(500), - timeout: uint32(uint32(500) + type(uint24).max + 1), // Makes deadline - lockDeadline > type(uint24).max - lockCollateral: 0.1 ether - }); - - vm.expectRevert(abi.encodeWithSelector(IBoundlessMarket.InvalidRequest.selector)); - invalidOffer.validate(); - } - - function testValidTimeouts() public pure { - Offer memory validOffer = Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(100), - rampUpPeriod: 100, - lockTimeout: uint32(500), - timeout: uint32(uint32(500) + type(uint24).max), // Maximum valid difference - lockCollateral: 0.1 ether - }); - - (uint64 lockDeadline, uint64 deadline) = validOffer.validate(); - - assertEq(lockDeadline, 600); // rampUpStart + lockTimeout - assertEq(deadline, uint32(600) + type(uint24).max); // rampUpStart + timeout - assertEq(deadline - lockDeadline, type(uint24).max); // Maximum allowed difference - } -} diff --git a/contracts/shanghai/test/types/Predicate.t.sol b/contracts/shanghai/test/types/Predicate.t.sol deleted file mode 100644 index a03aa57eb9..0000000000 --- a/contracts/shanghai/test/types/Predicate.t.sol +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; -import {Test} from "forge-std/Test.sol"; -import {Predicate, PredicateLibrary, PredicateType} from "../../src/types/Predicate.sol"; - -bytes32 constant IMAGE_ID = keccak256("ImageId for testing purposes"); - -contract PredicateTest is Test { - using ReceiptClaimLib for ReceiptClaim; - - function testEvalDigestMatch() public pure { - bytes32 hash = sha256(abi.encode("test")); - Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash); - assertEq( - uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch" - ); - - bytes memory journal = "test"; - - bool result = predicate.eval(IMAGE_ID, journal); - assertTrue(result, "Predicate evaluation should be true for matching digest"); - } - - function testEvalDigestMatchFail() public pure { - bytes32 hash = sha256(abi.encode("test")); - Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash); - assertEq( - uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch" - ); - - bytes memory journal = "different test"; - - bool result = predicate.eval(IMAGE_ID, journal); - assertFalse(result, "Predicate evaluation should be false for non-matching digest"); - } - - function testEvalPrefixMatch() public pure { - bytes memory prefix = "prefix"; - Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix); - bytes memory journal = "prefix and more"; - - bool result = predicate.eval(IMAGE_ID, journal); - assertTrue(result, "Predicate evaluation should be true for matching prefix"); - } - - function testEvalPrefixMatchFail() public pure { - bytes memory prefix = "prefix"; - Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix); - bytes memory journal = "different prefix"; - - bool result = predicate.eval(IMAGE_ID, journal); - assertFalse(result, "Predicate evaluation should be false for non-matching prefix"); - } - - function testEvalClaimDigestMatch() public pure { - bytes memory journal = "test"; - bytes32 journalDigest = sha256(abi.encode(journal)); - bytes32 claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest(); - Predicate memory predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); - assertEq( - uint8(predicate.predicateType), - uint8(PredicateType.ClaimDigestMatch), - "Predicate type should be ClaimDigestMatch" - ); - - bool result = predicate.eval(claimDigest); - assertTrue(result, "Predicate evaluation should be true for matching digest"); - } - - function testEvalClaimDigestMatchFail() public pure { - bytes memory journal = "test"; - bytes32 journalDigest = sha256(abi.encode(journal)); - bytes32 claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest(); - Predicate memory predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); - assertEq( - uint8(predicate.predicateType), - uint8(PredicateType.ClaimDigestMatch), - "Predicate type should be ClaimDigestMatch" - ); - - journal = "different test"; - journalDigest = sha256(abi.encode(journal)); - claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest(); - - bool result = predicate.eval(claimDigest); - assertFalse(result, "Predicate evaluation should be false for non-matching digest"); - } -} diff --git a/contracts/shanghai/test/types/ProofRequest.t.sol b/contracts/shanghai/test/types/ProofRequest.t.sol deleted file mode 100644 index 2fd6ee8da8..0000000000 --- a/contracts/shanghai/test/types/ProofRequest.t.sol +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {ProofRequest} from "../../src/types/ProofRequest.sol"; -import {Requirements} from "../../src/types/Requirements.sol"; -import {Input, InputType} from "../../src/types/Input.sol"; -import {PredicateLibrary} from "../../src/types/Predicate.sol"; -import {Callback} from "../../src/types/Callback.sol"; -import {Offer} from "../../src/types/Offer.sol"; -import {Account} from "../../src/types/Account.sol"; -import {RequestIdLibrary} from "../../src/types/RequestId.sol"; -import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; - -/// @dev Wrapper contract to test ProofRequest library functions. The library functions use -/// inputs of type calldata, so this contract enables our tests to make external calls that have calldata -/// to those functions. -contract ProofRequestTestContract { - mapping(address => Account) accounts; - - function validate(ProofRequest calldata request) external pure returns (uint64, uint64) { - return request.validate(); - } - - function setRequestFulfilled(address wallet1, uint32 idx1) external { - accounts[wallet1].setRequestFulfilled(idx1); - } - - function setRequestLocked(address wallet1, uint32 idx1) external { - accounts[wallet1].setRequestLocked(idx1); - } -} - -contract MockERC1271Wallet { - bytes4 internal constant MAGICVALUE = 0x1626ba7e; // bytes4(keccak256("isValidSignature(bytes32,bytes)") - - function isValidSignature(bytes32, bytes calldata) public pure returns (bytes4) { - return MAGICVALUE; - } -} - -contract MockInvalidERC1271Wallet { - function isValidSignature(bytes32, bytes calldata) public pure returns (bytes4) { - return 0xdeadbeef; - } -} - -contract ProofRequestTest is Test { - address wallet = address(0x123); - uint32 idx = 1; - bytes32 constant APP_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000001; - bytes32 constant SET_BUILDER_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000002; - bytes32 constant ASSESSOR_IMAGE_ID = 0x0000000000000000000000000000000000000000000000000000000000000003; - Vm.Wallet clientWallet; - Vm.Wallet proverWallet; - - ProofRequest defaultProofRequest; - - ProofRequestTestContract requestContract = new ProofRequestTestContract(); - - function setUp() public { - clientWallet = vm.createWallet("CLIENT"); - proverWallet = vm.createWallet("PROVER"); - - defaultProofRequest = ProofRequest({ - id: RequestIdLibrary.from(wallet, idx), - requirements: Requirements({ - predicate: PredicateLibrary.createDigestMatchPredicate(APP_IMAGE_ID, sha256(bytes("GUEST JOURNAL"))), - callback: Callback({gasLimit: 0, addr: address(0)}), - selector: bytes4(0) - }), - imageUrl: "https://image.dev.null", - input: Input({inputType: InputType.Url, data: bytes("https://input.dev.null")}), - offer: Offer({ - minPrice: 1 ether, - maxPrice: 2 ether, - rampUpStart: uint64(block.timestamp), - rampUpPeriod: uint32(10), - timeout: uint32(100), - lockTimeout: uint32(100), - lockCollateral: 1 ether - }) - }); - } - - function testValidateBasic() public view { - ProofRequest memory request = defaultProofRequest; - Offer memory offer = request.offer; - - (uint64 lockDeadline, uint64 deadline) = requestContract.validate(request); - assertEq(deadline, offer.deadline(), "Deadline should match the offer deadline"); - assertEq(lockDeadline, offer.lockDeadline(), "Lock deadline should match the offer lock deadline"); - } - - function testValidateInvalidPriceParameters() public { - ProofRequest memory request = defaultProofRequest; - request.offer.minPrice = 2 ether; - request.offer.maxPrice = 1 ether; - - vm.expectRevert(IBoundlessMarket.InvalidRequest.selector); - requestContract.validate(request); - } - - function testValidateInvalidTimeoutParameters() public { - ProofRequest memory request = defaultProofRequest; - request.offer.lockTimeout = 10; - request.offer.timeout = 5; - - vm.expectRevert(IBoundlessMarket.InvalidRequest.selector); - requestContract.validate(request); - - request.offer.lockTimeout = 5; - request.offer.timeout = 10; - request.offer.rampUpPeriod = 8; - vm.expectRevert(IBoundlessMarket.InvalidRequest.selector); - requestContract.validate(request); - - // sanity check - request.offer.timeout = 10; - request.offer.lockTimeout = 5; - request.offer.rampUpPeriod = 5; - requestContract.validate(request); - } - - function testValidateInvalidLockTimeoutLength() public { - ProofRequest memory request = defaultProofRequest; - // Difference exceeds what can be stored in the RequestLock type. - request.offer.lockTimeout = 5; - request.offer.timeout = type(uint32).max; - - vm.expectRevert(IBoundlessMarket.InvalidRequest.selector); - requestContract.validate(request); - } -} diff --git a/contracts/shanghai/test/types/RequestId.t.sol b/contracts/shanghai/test/types/RequestId.t.sol deleted file mode 100644 index ff70becc67..0000000000 --- a/contracts/shanghai/test/types/RequestId.t.sol +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {RequestId, RequestIdLibrary} from "../../src/types/RequestId.sol"; - -contract RequestIdTest is Test { - function testClientAndIndex() public view { - address testClient = address(this); - uint32 testIndex = 1; - - RequestId id = RequestIdLibrary.from(testClient, testIndex); - (address client, uint32 index) = id.clientAndIndex(); - assertEq(client, testClient, "Client address should match the original address"); - assertEq(index, testIndex, "Index should match the original index"); - } -} diff --git a/contracts/shanghai/test/types/RequestLock.t.sol b/contracts/shanghai/test/types/RequestLock.t.sol deleted file mode 100644 index d1b2dbba97..0000000000 --- a/contracts/shanghai/test/types/RequestLock.t.sol +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2026 Boundless Foundation, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. - -pragma solidity ^0.8.26; - -import {Test} from "forge-std/Test.sol"; -import {RequestLock, RequestLockLibrary} from "../../src/types/RequestLock.sol"; - -contract RequestLockTest is Test { - using RequestLockLibrary for RequestLock; - - RequestLock requestLock; - - function setUp() public { - requestLock = RequestLock({ - prover: address(0x123), - lockDeadline: uint64(block.timestamp + 100), - deadlineDelta: uint24(50), - requestLockFlags: 0, - price: 1 ether, - collateral: 1 ether, - requestDigest: bytes32(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef) - }); - } - - function assertSlotClear(uint256 slotNumber) private view { - uint256 slot; - assembly { - let num := add(requestLock.slot, slotNumber) - slot := sload(num) - } - assertEq(slot, 0, "Slot is not zero"); - } - - function assertSlot1Clear() private view { - assertSlotClear(1); - } - - function assertSlot2Clear() private view { - assertSlotClear(2); - } - - function testDeadline() public view { - uint64 expectedDeadline = requestLock.lockDeadline + requestLock.deadlineDelta; - assertEq(requestLock.deadline(), expectedDeadline, "Deadline calculation is incorrect"); - } - - function testSetProverPaidBeforeLockDeadline() public { - requestLock.setProverPaidBeforeLockDeadline(); - assertEq( - requestLock.requestLockFlags, - RequestLockLibrary.PROVER_PAID_DURING_LOCK_FLAG, - "Prover paid flag not set correctly" - ); - assertEq(requestLock.price, 0, "Price not zeroed out"); - assertEq(requestLock.collateral, 0, "Stake not zeroed out"); - // Request digest is needed for multiple proofs being delivered for a single request, - // and partial fulfillment use cases. - assertTrue(requestLock.requestDigest != bytes32(0), "Request digest should not be zero"); - assertSlot1Clear(); - } - - function testSetProverPaidAfterLockDeadline() public { - address prover = address(0x456); - requestLock.setProverPaidAfterLockDeadline(prover); - assertEq(requestLock.prover, prover); - assertTrue(requestLock.isProverPaidAfterLockDeadline()); - assertFalse(requestLock.isProverPaidBeforeLockDeadline()); - assertFalse(requestLock.isSlashed()); - } - - function testSetSlashed() public { - requestLock.setSlashed(); - assertEq(requestLock.requestLockFlags, RequestLockLibrary.SLASHED_FLAG, "Slashed flag not set correctly"); - assertEq(requestLock.price, 0, "Price not zeroed out"); - assertEq(requestLock.collateral, 0, "Stake not zeroed out"); - assertSlot1Clear(); - // Request digest is needed for multiple proofs being delivered for a single request, - // and partial fulfillment use cases. - assertTrue(requestLock.requestDigest != bytes32(0), "Request digest should not be zero"); - } - - function testIsProverPaidBeforeLockDeadline() public { - requestLock.setProverPaidBeforeLockDeadline(); - assertTrue(requestLock.isProverPaidBeforeLockDeadline()); - } - - function testIsProverPaidAfterLockDeadline() public { - requestLock.setProverPaidAfterLockDeadline(address(0x456)); - assertTrue(requestLock.isProverPaidAfterLockDeadline()); - assertNotEq(requestLock.price, 0, "Price not zeroed out"); - assertNotEq(requestLock.collateral, 0, "Stake not zeroed out"); - } - - function testIsProverPaid() public { - requestLock.setProverPaidBeforeLockDeadline(); - assertTrue(requestLock.isProverPaid()); - } - - function testIsProverPaid2() public { - requestLock.setProverPaidAfterLockDeadline(address(0x456)); - assertTrue(requestLock.isProverPaid()); - } - - function testIsSlashed() public { - requestLock.setSlashed(); - assertTrue(requestLock.isSlashed()); - } - - function testSetProverPaidAfterLockDeadlineThenSetSlashed() public { - address prover = address(0x456); - requestLock.setProverPaidAfterLockDeadline(prover); - requestLock.setSlashed(); - - assertTrue(requestLock.isProverPaidAfterLockDeadline()); - assertTrue(requestLock.isSlashed()); - } -} diff --git a/contracts/shanghai/variants/BoundlessMarket.sol b/contracts/shanghai/variants/BoundlessMarket.sol new file mode 100644 index 0000000000..1f69d1c717 --- /dev/null +++ b/contracts/shanghai/variants/BoundlessMarket.sol @@ -0,0 +1,1001 @@ +// Copyright 2026 Boundless Foundation, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.26; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; +import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; +import {ERC20} from "solmate/tokens/ERC20.sol"; +import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {IRiscZeroSetVerifier} from "risc0/IRiscZeroSetVerifier.sol"; + +import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; +import {IBoundlessMarketCallback} from "../../src/IBoundlessMarketCallback.sol"; +import {Account} from "../../src/types/Account.sol"; +import {Fulfillment, LegacyFulfillment} from "../../src/types/Fulfillment.sol"; +import {FulfillmentDataLibrary, FulfillmentDataType} from "../../src/types/FulfillmentData.sol"; +import {ProofRequest} from "../../src/types/ProofRequest.sol"; +import {LockRequestLibrary} from "../../src/types/LockRequest.sol"; +import {RequestId} from "../../src/types/RequestId.sol"; +import {RequestLock} from "../../src/types/RequestLock.sol"; +import {ProofRequestBatch} from "../../src/types/ProofRequestBatch.sol"; +import {SlimRequest, SlimRequestLibrary} from "../../src/types/SlimRequest.sol"; +import {FulfillmentBatch} from "../../src/types/FulfillmentBatch.sol"; +import {FulfillmentContext, FulfillmentContextLibrary} from "./FulfillmentContext.sol"; + +import {BoundlessMarketLib} from "../../src/libraries/BoundlessMarketLib.sol"; + +import {IBoundlessRouter} from "../../src/router/interfaces/IBoundlessRouter.sol"; + +error InvalidRouter(); +error InvalidCollateralToken(); +error InvalidLegacyImpl(); +error InvalidInitialOwner(); +error MismatchedRequestId(uint256 expected, uint256 received); + +contract BoundlessMarket is + IBoundlessMarket, + Initializable, + EIP712Upgradeable, + AccessControlUpgradeable, + UUPSUpgradeable, + Proxy +{ + using SafeCast for int256; + using SafeCast for uint256; + using SafeTransferLib for ERC20; + + /// @dev The version of the contract, with respect to upgrades. + uint64 public constant VERSION = 1; + + /// @notice Admin role identifier + bytes32 public constant ADMIN_ROLE = DEFAULT_ADMIN_ROLE; + + /// Mapping of request ID to lock-in state. Non-zero for requests that are locked in. + mapping(RequestId => RequestLock) public requestLocks; + /// Mapping of address to account state. + mapping(address => Account) internal accounts; + /// @dev Held the assessor guest URL in earlier implementations. The market + /// no longer reads or writes this field; the slot is preserved so the + /// storage layout doesn't shift across upgrades. Kept under its + /// original name so the OZ storage-layout check accepts the upgrade + /// without a rename annotation. + string private imageUrl; + + /// @notice Block number at which a fulfillment commitment was recorded, keyed by the commitment + /// hash `keccak256(abi.encode(FulfillmentBatch[]))`. Anti-front-running guard for the + /// open fulfillment paths (never-locked and after the lock deadline), which carry no + /// prior on-chain prover binding. + mapping(bytes32 => uint256) public fulfillmentCommitBlock; + + /// @notice The verification engine. The market calls `ROUTER.verifyBatch` + /// once per fulfillment batch and trusts whatever per-class adapter the + /// router dispatches to. + /// @dev Set in the constructor; pinned per implementation contract. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IBoundlessRouter public immutable ROUTER; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address public immutable COLLATERAL_TOKEN_CONTRACT; + + /// @notice Implementation address of the previous (legacy ABI) BoundlessMarket. + /// The fallback function delegate-calls into this address so the + /// pre-router ABI keeps working for in-flight transactions and old + /// broker clients during the migration window. + /// @dev On Base mainnet this is the impl pointed to by the proxy before + /// the upgrade. On dev/localnet a fresh deployment of + /// contracts/src/legacy/BoundlessMarketLegacy.sol. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address public immutable LEGACY_IMPL; + + /// @notice Max gas allowed for ERC1271 smart contract signature checks used for client auth. + /// @dev This constraint is applied to smart contract signatures used for authorizing proof + /// requests in order to make gas costs bounded. + uint256 public constant ERC1271_MAX_GAS_FOR_CHECK = 100000; + + /// @notice When a prover is slashed for failing to fulfill a request, a portion of the collateral + /// is burned, and the remaining portion is either send to the prover that ultimately fulfilled + /// the order, or to the market treasury. This fraction controls that ratio. + /// @dev The value is configured as a constant to avoid accessing storage and thus paying for the + /// gas of an SLOAD. Can only be changed via contract upgrade. + uint256 public constant SLASHING_BURN_BPS = 5000; + + /// @notice When an order is fulfilled, the market takes a fee based on the price of the order. + /// This fraction is multiplied by the price to decide the fee. + /// @dev The fee is configured as a constant to avoid accessing storage and thus paying for the + /// gas of an SLOAD. Can only be changed via contract upgrade. + uint96 public constant MARKET_FEE_BPS = 0; + + /// @notice Minimum number of blocks between a fulfillment commitment and its reveal on the open + /// fulfillment paths. A reveal at block R requires a commitment at block C with + /// `C + COMMIT_REVEAL_MIN_BLOCKS <= R`, so a front-runner who only learns the seal at reveal time + /// cannot have committed early enough to steal it. + uint256 public constant COMMIT_REVEAL_MIN_BLOCKS = 1; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(IBoundlessRouter router, address collateralTokenContract, address legacyImpl) { + if (address(router) == address(0)) revert InvalidRouter(); + if (collateralTokenContract == address(0)) revert InvalidCollateralToken(); + if (legacyImpl == address(0)) revert InvalidLegacyImpl(); + + ROUTER = router; + COLLATERAL_TOKEN_CONTRACT = collateralTokenContract; + LEGACY_IMPL = legacyImpl; + + _disableInitializers(); + } + + /// @notice OpenZeppelin {Proxy} hook: returns the address that selectors not + /// declared on this contract are delegate-called into. The inherited + /// `fallback()` forwards to it, preserving the caller, value, and the + /// proxy's storage context. + /// @dev This is the LEGACY ABI implementation, NOT this contract's ERC1967 + /// implementation (that lives in the proxy's storage slot). Keeping + /// the legacy ABI surface live this way avoids re-introducing the + /// legacy bodies into this implementation's bytecode. + function _implementation() internal view override returns (address) { + return LEGACY_IMPL; + } + + function initialize(address initialOwner) external initializer { + if (initialOwner == address(0)) { + revert InvalidInitialOwner(); + } + __AccessControl_init(); + __UUPSUpgradeable_init(); + __EIP712_init(BoundlessMarketLib.EIP712_DOMAIN, BoundlessMarketLib.EIP712_DOMAIN_VERSION); + _grantRole(ADMIN_ROLE, initialOwner); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyRole(ADMIN_ROLE) {} + + // NOTE: We could verify the client signature here, but this adds about 18k gas (with a naive + // implementation), doubling the cost of calling this method. It is not required for protocol + // safety as the signature is checked during lock, and during fulfillment (by the assessor). + function submitRequest(ProofRequest calldata request, bytes calldata clientSignature) external payable { + if (msg.value > 0) { + deposit(); + } + emit RequestSubmitted(request.id, request, clientSignature); + } + + /// @inheritdoc IBoundlessMarket + function lockRequest(ProofRequest calldata request, bytes calldata clientSignature) external { + (address client, uint32 idx) = request.id.clientAndIndex(); + (bytes32 requestHash,) = _verifyClientSignature(request, client, clientSignature); + (uint64 lockDeadline, uint64 deadline) = request.validate(); + + _lockRequest(request, clientSignature, requestHash, client, idx, msg.sender, lockDeadline, deadline); + } + + /// @inheritdoc IBoundlessMarket + function lockRequestWithSignature( + ProofRequest calldata request, + bytes calldata clientSignature, + bytes calldata proverSignature + ) external { + (address client, uint32 idx) = request.id.clientAndIndex(); + (bytes32 requestHash, bytes32 proofRequestEip712Digest) = + _verifyClientSignature(request, client, clientSignature); + bytes32 lockRequestHash = + _hashTypedDataV4(LockRequestLibrary.eip712DigestFromPrecomputedDigest(proofRequestEip712Digest)); + address prover = ECDSA.recover(lockRequestHash, proverSignature); + (uint64 lockDeadline, uint64 deadline) = request.validate(); + + _lockRequest(request, clientSignature, requestHash, client, idx, prover, lockDeadline, deadline); + } + + /// @notice Locks the request to the prover. Deducts funds from the client for payment + /// and funding from the prover for locking collateral. + function _lockRequest( + ProofRequest calldata request, + bytes calldata clientSignature, + bytes32 requestDigest, + address client, + uint32 idx, + address prover, + uint64 lockDeadline, + uint64 deadline + ) internal { + (bool locked, bool fulfilled) = accounts[client].requestFlags(idx); + if (locked) { + revert RequestIsLocked({requestId: request.id}); + } + if (fulfilled) { + revert RequestIsFulfilled({requestId: request.id}); + } + if (block.timestamp > lockDeadline) { + revert RequestLockIsExpired({requestId: request.id, lockDeadline: lockDeadline}); + } + + // Compute the current price offered by the reverse Dutch auction. + uint96 price = request.offer.priceAt(uint64(block.timestamp)).toUint96(); + + // Deduct payment from the client account and collateral from the prover account. + Account storage clientAccount = accounts[client]; + if (clientAccount.balance < price) { + revert InsufficientBalance(client); + } + Account storage proverAccount = accounts[prover]; + if (proverAccount.collateralBalance < request.offer.lockCollateral) { + revert InsufficientBalance(prover); + } + + unchecked { + clientAccount.balance -= price; + proverAccount.collateralBalance -= request.offer.lockCollateral.toUint96(); + } + + // Record the lock for the request and emit an event. + requestLocks[request.id] = RequestLock({ + prover: prover, + price: price, + requestLockFlags: 0, + lockDeadline: lockDeadline, + deadlineDelta: uint256(deadline - lockDeadline).toUint24(), + collateral: request.offer.lockCollateral.toUint96(), + requestDigest: requestDigest + }); + + clientAccount.setRequestLocked(idx); + emit RequestLocked(request.id, prover, request, clientSignature); + } + + /// Validates the request and records the price to transient storage such that it can be + /// fulfilled within the same transaction without taking a lock on it. + /// @inheritdoc IBoundlessMarket + function priceRequest(ProofRequest calldata request, bytes calldata clientSignature) public { + address client = request.id.client(); + + (bytes32 requestHash,) = _verifyClientSignature(request, client, clientSignature); + + (, uint64 deadline) = request.validate(); + bool expired = deadline < block.timestamp; + + // Compute the current price offered by the reverse Dutch auction. + uint96 price = request.offer.priceAt(uint64(block.timestamp)).toUint96(); + + // Record the price in transient storage, such that the order can be filled in this same transaction. + FulfillmentContext({valid: true, expired: expired, price: price}).store(requestHash); + } + + /// @dev Assert that the supplied `requestDigest` matches either the stored + /// lock digest or a valid `FulfillmentContext` entry from + /// `priceRequest`. Once this passes, the slim payload that produced + /// `requestDigest` is bound to a client-signed request and downstream + /// consumers (router, assessor adapter, callback dispatch) can trust + /// its fields without re-verification. + /// + /// `requestDigest` is the domain-bound digest (`_hashTypedDataV4` of + /// the EIP-712 struct hash produced by + /// `SlimRequestLibrary.reconstructRequestDigest`). `_lockRequest` and + /// `priceRequest` both write this same domain-bound value into + /// storage, so this function can compare without further hashing. + function _verifyBinding(RequestId id, bytes32 requestDigest) internal view { + if (requestLocks[id].requestDigest == requestDigest) { + return; + } + if (FulfillmentContextLibrary.load(requestDigest).valid) { + return; + } + revert RequestIsNotLockedOrPriced(id); + } + + /// @dev Per-batch helper: reconstruct each request's domain-bound digest, + /// verify the binding, and collect into an array for the router and assessor. + function _bindAndCollectDigests(SlimRequest[] calldata requests) + internal + view + returns (bytes32[] memory requestDigests) + { + uint256 n = requests.length; + requestDigests = new bytes32[](n); + for (uint256 i = 0; i < n; i++) { + bytes32 requestDigest = _hashTypedDataV4(SlimRequestLibrary.reconstructRequestDigest(requests[i])); + _verifyBinding(requests[i].id, requestDigest); + requestDigests[i] = requestDigest; + } + } + + /// @inheritdoc IBoundlessMarket + function priceAndFulfill( + ProofRequestBatch[] calldata requestBatches, + FulfillmentBatch[] calldata fulfillmentBatches + ) public returns (bytes[] memory paymentError) { + _priceAll(requestBatches); + paymentError = fulfill(fulfillmentBatches); + } + + /// @inheritdoc IBoundlessMarket + function fulfill(FulfillmentBatch[] calldata fulfillmentBatches) public returns (bytes[] memory paymentError) { + // Anti-front-running: the open fulfillment paths (never-locked, or locked-but-past-deadline) + // carry no prior on-chain prover binding, so a copied proof could be re-submitted under a + // different prover. Require a `commitFulfillment` recorded in a strictly earlier block — + // binding these exact batches (and their seals) — before settling any open-path fill. The + // locked-before-deadline path is already bound by `lock.prover` and needs no commitment. + if (_hasOpenPathFill(fulfillmentBatches)) { + _consumeCommitment(fulfillmentBatches); + } + + // Flatten payment-error output across fulfillment batches. + uint256 totalFills = 0; + for (uint256 j = 0; j < fulfillmentBatches.length; j++) { + totalFills += fulfillmentBatches[j].fills.length; + } + paymentError = new bytes[](totalFills); + + uint256 outIdx = 0; + for (uint256 j = 0; j < fulfillmentBatches.length; j++) { + FulfillmentBatch calldata batch = fulfillmentBatches[j]; + uint256 n = batch.fills.length; + if (n == 0) continue; + if (n > type(uint16).max) revert BatchSizeExceedsLimit(n, type(uint16).max); + if (batch.requests.length != n) revert BatchSizeExceedsLimit(batch.requests.length, n); + + // Bind every slim payload to a client-signed request (lock or + // priced), then dispatch verifier + assessor through the router + // and settle each fill. + bytes32[] memory requestDigests = _bindAndCollectDigests(batch.requests); + ROUTER.verifyBatch(batch, requestDigests); + outIdx = _settleBatch(batch, requestDigests, paymentError, outIdx); + } + } + + /// @notice Commit, ahead of time, to the exact fulfillment you will reveal on an open path. + /// @param commitment `keccak256(abi.encode(FulfillmentBatch[]))` — the exact `fulfillmentBatches` + /// argument of the upcoming `fulfill` / `priceAndFulfill` / `submitRoot…` call. Because it + /// binds the prover and every seal, it cannot be precomputed without already holding the + /// proof, and the reveal must land at least `COMMIT_REVEAL_MIN_BLOCKS` blocks later. + /// @dev First writer per commitment wins the block stamp; re-commits are no-ops. The commitment + /// reveals nothing (just a hash), so front-running this call is pointless. + function commitFulfillment(bytes32 commitment) external { + if (fulfillmentCommitBlock[commitment] == 0) { + fulfillmentCommitBlock[commitment] = block.number; + } + } + + /// @dev True if any fill in the call settles on an open path — never-locked, or locked but past + /// its lock deadline — i.e. a path with no prior on-chain prover binding. + function _hasOpenPathFill(FulfillmentBatch[] calldata fulfillmentBatches) internal view returns (bool) { + for (uint256 j = 0; j < fulfillmentBatches.length; j++) { + SlimRequest[] calldata requests = fulfillmentBatches[j].requests; + for (uint256 i = 0; i < requests.length; i++) { + (address client, uint32 idx) = requests[i].id.clientAndIndex(); + (bool locked,) = accounts[client].requestFlags(idx); + if (!locked) return true; // never-locked + if (requestLocks[requests[i].id].lockDeadline < block.timestamp) return true; // was-locked + } + } + return false; + } + + /// @dev Consume the commitment for these exact batches, requiring it was recorded at least + /// `COMMIT_REVEAL_MIN_BLOCKS` blocks earlier. Reverts if absent or too recent. One-shot. + function _consumeCommitment(FulfillmentBatch[] calldata fulfillmentBatches) internal { + bytes32 commitment = keccak256(abi.encode(fulfillmentBatches)); + uint256 committedBlock = fulfillmentCommitBlock[commitment]; + if (committedBlock == 0 || committedBlock + COMMIT_REVEAL_MIN_BLOCKS > block.number) { + revert MissingFulfillmentCommitment(); + } + delete fulfillmentCommitBlock[commitment]; + } + + /// @dev Per-fill settle pass for one already-verified `FulfillmentBatch`. + /// Walks every fill, charges/credits accounts via `_fulfillAndPay`, + /// and dispatches callbacks. Returns the updated flat-output index so + /// `fulfill` can keep packing payment errors across batches. + function _settleBatch( + FulfillmentBatch calldata batch, + bytes32[] memory requestDigests, + bytes[] memory paymentError, + uint256 outIdx + ) internal returns (uint256) { + address prover = batch.prover; + uint256 n = batch.fills.length; + for (uint256 i = 0; i < n; i++) { + Fulfillment calldata fill = batch.fills[i]; + SlimRequest calldata slim = batch.requests[i]; + bool expired; + (paymentError[outIdx], expired) = _fulfillAndPay(fill, slim.id, requestDigests[i], prover); + + if (!expired && slim.callback.addr != address(0)) { + if (fill.fulfillmentDataType == FulfillmentDataType.ImageIdAndJournal) { + (bytes32 imageId, bytes calldata journal) = + FulfillmentDataLibrary.decodePackedImageIdAndJournal(fill.fulfillmentData); + _executeCallback(slim.id, slim.callback.addr, slim.callback.gasLimit, imageId, journal, fill.seal); + } else { + revert UnfulfillableCallback(); + } + } + outIdx++; + } + return outIdx; + } + + /// @inheritdoc IBoundlessMarket + function priceAndFulfillAndWithdraw( + ProofRequestBatch[] calldata requestBatches, + FulfillmentBatch[] calldata fulfillmentBatches + ) public returns (bytes[] memory paymentError) { + _priceAll(requestBatches); + paymentError = fulfillAndWithdraw(fulfillmentBatches); + } + + /// @inheritdoc IBoundlessMarket + function fulfillAndWithdraw(FulfillmentBatch[] calldata fulfillmentBatches) + public + returns (bytes[] memory paymentError) + { + paymentError = fulfill(fulfillmentBatches); + + // Withdraw any remaining balance from each fulfillment batch's prover. + for (uint256 j = 0; j < fulfillmentBatches.length; j++) { + address prover = fulfillmentBatches[j].prover; + uint256 balance = accounts[prover].balance; + if (balance > 0) { + _withdraw(prover, balance); + } + } + } + + /// @dev Price every request in every group. Each `ProofRequestBatch` + /// carries the requests and matching client signatures that need + /// pricing — typically only the un-locked entries. Verified client + /// signatures populate `FulfillmentContext` keyed by `requestHash`, + /// which the subsequent `fulfill` step looks up via the slim + /// payload's reconstructed digest. + function _priceAll(ProofRequestBatch[] calldata requestBatches) internal { + for (uint256 j = 0; j < requestBatches.length; j++) { + ProofRequest[] calldata requests = requestBatches[j].requests; + bytes[] calldata sigs = requestBatches[j].signatures; + if (sigs.length != requests.length) { + revert BatchSizeExceedsLimit(sigs.length, requests.length); + } + for (uint256 i = 0; i < requests.length; i++) { + priceRequest(requests[i], sigs[i]); + } + } + } + + /// Complete the fulfillment logic after having verified the app and assessor + /// receipts. `requestDigest` is the verified EIP-712 digest reconstructed + /// from the slim payload; the caller has already asserted it matches the + /// stored binding via `_verifyBinding`. `id` comes from the trusted slim + /// payload (positionally paired with `fill`). + function _fulfillAndPay(Fulfillment calldata fill, RequestId id, bytes32 requestDigest, address prover) + internal + returns (bytes memory paymentError, bool expired) + { + (address client, uint32 idx) = id.clientAndIndex(); + Account storage clientAccount = accounts[client]; + (bool locked, bool fulfilled) = clientAccount.requestFlags(idx); + + // Fetch the lock and fulfillment information. + // NOTE: The `lock` should only be used in code paths where locked is true. + RequestLock memory lock; + if (locked) { + lock = requestLocks[id]; + } + FulfillmentContext memory context = FulfillmentContextLibrary.load(requestDigest); + // Release the priced context after reading it. The default profile uses transient storage, + // which clears automatically at the end of the transaction; this Shanghai variant uses + // persistent storage, so it must clear the slot explicitly to bound storage growth and to + // preserve the same single-transaction price-then-fulfill semantics (a priced context must + // not survive into a later transaction). `_verifyBinding` only reads via the non-clearing + // `load`, so this is the single consume point. + FulfillmentContextLibrary.clear(requestDigest); + + // First, check whether the request is known to be a valid signed request, and whether it is + // expired. If the request cannot be authenticated, revert. + // + // In the expired case, we return early here. We do not emit the ProofDelivered event, and + // we do not issue a callback. This makes interpretation of the ProofDelivered events + // simpler, as they cannot be emitted for an expired request. + if (context.valid) { + // Request has been validated in priceRequest, check the reported expiration. + if (context.expired) { + paymentError = abi.encodeWithSelector(RequestIsExpired.selector, RequestId.unwrap(id)); + emit PaymentRequirementsFailed(paymentError); + return (paymentError, true); + } + } else if (locked && lock.requestDigest == requestDigest) { + // Request was validated in lockRequest, check whether the request is fully expired. + if (lock.deadline() < block.timestamp) { + paymentError = abi.encodeWithSelector(RequestIsExpired.selector, RequestId.unwrap(id)); + emit PaymentRequirementsFailed(paymentError); + return (paymentError, true); + } + } else { + // Request is not validated by either price or lock step. We cannot determine that the + // request is authentic, so we revert. + // NOTE: We could loosen this slightly, only reverting when the id indicates this is a + // smart-contract authorized request. However, we'd need to handle the fact that we + // don't have a FulfillmentContext on this code path. + revert RequestIsNotLockedOrPriced(id); + } + + // NOTE: Every code path past this point must ensure the `fulfilled` flag is set, or + // revert. If this is not the case, then it will break the invariant that the first + // delivered proof (e.g. the first time `ProofDelivered` fires and the first time the + // callback is called) the fulfilled flag is set. + if (locked) { + if (lock.lockDeadline >= block.timestamp) { + paymentError = _fulfillAndPayLocked(lock, id, client, idx, requestDigest, fulfilled, prover); + } else { + // NOTE: If the request is not priced, the context will be all zeroes. We will have + // only reached this point if the request digest matches the lock, which is expired. + // In this case, the price will be zero, which is correct. + paymentError = + _fulfillAndPayWasLocked(lock, id, client, idx, context.price, requestDigest, fulfilled, prover); + } + } else { + paymentError = _fulfillAndPayNeverLocked(id, client, idx, context.price, requestDigest, fulfilled, prover); + } + + if (paymentError.length > 0) { + emit PaymentRequirementsFailed(paymentError); + } + + // `ProofDelivered` carries the legacy (pre-router) fulfillment shape — `id`/`requestDigest` + // embedded inline — so the event's topic0 and payload stay decodable by clients that have + // not upgraded their SDK. The current `Fulfillment` dropped those fields to save batch + // calldata, so reconstruct the legacy shape here from the request identity and the fill. + emit ProofDelivered( + id, + prover, + LegacyFulfillment({ + id: id, + requestDigest: requestDigest, + claimDigest: fill.claimDigest, + fulfillmentDataType: fill.fulfillmentDataType, + fulfillmentData: fill.fulfillmentData, + seal: fill.seal + }) + ); + } + + /// @notice For a request that is currently locked. Marks the request as fulfilled, and transfers payment if eligible. + /// @dev It is possible for anyone to fulfill a request at any time while the request has not expired. + /// If the request is currently locked, only the prover can fulfill it and receive payment + function _fulfillAndPayLocked( + RequestLock memory lock, + RequestId id, + address client, + uint32 idx, + bytes32 requestDigest, + bool fulfilled, + address assessorProver + ) internal returns (bytes memory paymentError) { + // NOTE: If the prover is paid, the fulfilled flag must be set. + if (lock.isProverPaid()) { + return abi.encodeWithSelector(RequestIsFulfilled.selector, RequestId.unwrap(id)); + } + + if (!fulfilled) { + accounts[client].setRequestFulfilled(idx); + emit RequestFulfilled(id, assessorProver, requestDigest); + } + + // At this point the request has been fulfilled. The remaining logic determines whether + // payment should be sent and to whom. + // While the request is locked, only the locker is eligible for payment, and only for the request that was locked. + if (lock.prover != assessorProver || lock.requestDigest != requestDigest) { + return abi.encodeWithSelector(RequestIsLocked.selector, RequestId.unwrap(id)); + } + requestLocks[id].setProverPaidBeforeLockDeadline(); + + uint96 price = lock.price; + if (MARKET_FEE_BPS > 0) { + price = _applyMarketFee(price); + } + accounts[assessorProver].balance += price; + accounts[assessorProver].collateralBalance += lock.collateral; + } + + /// @notice For a request that was locked, and now the lock has expired. Marks the request as fulfilled, + /// and transfers payment if eligible. + /// @dev It is possible for anyone to fulfill a request at any time while the request has not expired. + /// If the request was locked, and now the lock has expired, and the request as a whole has not expired, + /// anyone can fulfill it and receive payment. + function _fulfillAndPayWasLocked( + RequestLock memory lock, + RequestId id, + address client, + uint32 idx, + uint96 price, + bytes32 requestDigest, + bool fulfilled, + address assessorProver + ) internal returns (bytes memory paymentError) { + // NOTE: If the prover is paid, the fulfilled flag must be set. + if (lock.isProverPaid()) { + return abi.encodeWithSelector(RequestIsFulfilled.selector, RequestId.unwrap(id)); + } + + if (!fulfilled) { + accounts[client].setRequestFulfilled(idx); + emit RequestFulfilled(id, assessorProver, requestDigest); + } + + // Deduct any additionally owned funds from client account. The client was already charged + // for the price at lock time once when the request was locked. We only need to charge any + // additional price for the difference between the price of the fulfilled request, at the + // current block, and the price of the locked request. + // + // Note that although they have the same ID, the locked request and the fulfilled request + // could be different. If the request fulfilled is the same as the one locked, the + // price will be zero and the entire fee on the lock will be returned to the client. + Account storage clientAccount = accounts[client]; + + // If the request has the same id, but is different to the request that was locked, the fulfillment + // price could be either higher or lower than the price that was previously locked. + // If the price is higher, we charge the client the difference. + // If the price is lower, we refund the client the difference. + uint96 lockPrice = lock.price; + bool partialPayment = false; + uint96 finalPrice = price; + + if (price > lockPrice) { + uint96 clientOwes = price - lockPrice; + if (clientAccount.balance < clientOwes) { + // If the client does not have enough balance to cover the full amount owed, + // we will only charge them what they have available. + clientOwes = clientAccount.balance; + finalPrice = lockPrice + clientOwes; + partialPayment = true; + } + unchecked { + clientAccount.balance -= clientOwes; + } + } else { + uint96 clientOwed = lockPrice - price; + clientAccount.balance += clientOwed; + } + + requestLocks[id].setProverPaidAfterLockDeadline(assessorProver); + if (MARKET_FEE_BPS > 0) { + finalPrice = _applyMarketFee(finalPrice); + } + accounts[assessorProver].balance += finalPrice; + if (partialPayment) { + return abi.encodeWithSelector(PartialPayment.selector, price, finalPrice); + } + } + + /// @notice For a request that has never been locked. Marks the request as fulfilled, and transfers payment if eligible. + /// @dev If a never locked request is fulfilled, but client has not enough funds to cover the payment, no + /// payment can ever be rendered for this order in the future. + function _fulfillAndPayNeverLocked( + RequestId id, + address client, + uint32 idx, + uint96 price, + bytes32 requestDigest, + bool fulfilled, + address assessorProver + ) internal returns (bytes memory paymentError) { + // When never locked, the fulfilled flag _does_ indicate that we alrady attempted to + // transfer payment (which will only fail in the InsufficientBalance case below) so we + // return early here. + if (fulfilled) { + return abi.encodeWithSelector(RequestIsFulfilled.selector, RequestId.unwrap(id)); + } + + Account storage clientAccount = accounts[client]; + clientAccount.setRequestFulfilled(idx); + emit RequestFulfilled(id, assessorProver, requestDigest); + + // Deduct the funds from client account. + // NOTE: In the case of InsufficientBalance, the payment can never be transferred in the + // future. This is a simplifying choice. + if (clientAccount.balance < price) { + return abi.encodeWithSelector(InsufficientBalance.selector, client); + } + unchecked { + clientAccount.balance -= price; + } + + if (MARKET_FEE_BPS > 0) { + price = _applyMarketFee(price); + } + accounts[assessorProver].balance += price; + } + + function _applyMarketFee(uint96 proverPayment) internal returns (uint96) { + uint96 fee = proverPayment * MARKET_FEE_BPS / 10000; + accounts[address(this)].balance += fee; + return proverPayment - fee; + } + + /// @notice Execute the callback for a fulfilled request if one is specified + /// @dev This function is called after payment is processed and handles any callback specified in the request + /// @param id The ID of the request being fulfilled + /// @param callbackAddr The address of the callback contract + /// @param callbackGasLimit The gas limit to use for the callback + /// @param imageId The ID of the RISC Zero guest image that produced the proof + /// @param journal The output journal from the RISC Zero guest execution + /// @param seal The cryptographic seal proving correct execution + function _executeCallback( + RequestId id, + address callbackAddr, + uint96 callbackGasLimit, + bytes32 imageId, + bytes calldata journal, + bytes calldata seal + ) internal { + // Ensure sufficient gas for callback, accounting for EIP-150 (63/64 rule). + // The requestor is responsible for ensuring that the callback gas limit is sufficient to cover + // for any extra overhead that the caller pays (calldata copy, cold access, etc.). + if (gasleft() * 63 / 64 < callbackGasLimit) revert InsufficientGas(); + try IBoundlessMarketCallback(callbackAddr).handleProof{gas: callbackGasLimit}(imageId, journal, seal) {} + catch (bytes memory err) { + emit CallbackFailed(id, callbackAddr, err); + } + } + + /// @inheritdoc IBoundlessMarket + function submitRoot(address setVerifierAddress, bytes32 root, bytes calldata seal) external { + _submitRoot(setVerifierAddress, root, seal); + } + + /// @inheritdoc IBoundlessMarket + function submitRootAndFulfill( + address setVerifier, + bytes32 root, + bytes calldata seal, + FulfillmentBatch[] calldata fulfillmentBatches + ) external returns (bytes[] memory paymentError) { + _submitRoot(setVerifier, root, seal); + paymentError = fulfill(fulfillmentBatches); + } + + /// @inheritdoc IBoundlessMarket + function submitRootAndFulfillAndWithdraw( + address setVerifier, + bytes32 root, + bytes calldata seal, + FulfillmentBatch[] calldata fulfillmentBatches + ) external returns (bytes[] memory paymentError) { + _submitRoot(setVerifier, root, seal); + paymentError = fulfillAndWithdraw(fulfillmentBatches); + } + + /// @inheritdoc IBoundlessMarket + function submitRootAndPriceAndFulfill( + address setVerifier, + bytes32 root, + bytes calldata seal, + ProofRequestBatch[] calldata requestBatches, + FulfillmentBatch[] calldata fulfillmentBatches + ) external returns (bytes[] memory paymentError) { + _submitRoot(setVerifier, root, seal); + paymentError = priceAndFulfill(requestBatches, fulfillmentBatches); + } + + /// @inheritdoc IBoundlessMarket + function submitRootAndPriceAndFulfillAndWithdraw( + address setVerifier, + bytes32 root, + bytes calldata seal, + ProofRequestBatch[] calldata requestBatches, + FulfillmentBatch[] calldata fulfillmentBatches + ) external returns (bytes[] memory paymentError) { + _submitRoot(setVerifier, root, seal); + paymentError = priceAndFulfillAndWithdraw(requestBatches, fulfillmentBatches); + } + + /// @dev Shared dispatch for `submitRoot*` variants. Five call sites means + /// the optimizer should keep this factored instead of inlining the + /// external-call setup at each site. + function _submitRoot(address setVerifier, bytes32 root, bytes calldata seal) private { + IRiscZeroSetVerifier(setVerifier).submitMerkleRoot(root, seal); + } + + /// @inheritdoc IBoundlessMarket + function slash(RequestId requestId) external { + (address client, uint32 idx) = requestId.clientAndIndex(); + (bool locked,) = accounts[client].requestFlags(idx); + if (!locked) { + revert RequestIsNotLocked({requestId: requestId}); + } + + RequestLock memory lock = requestLocks[requestId]; + if (lock.isSlashed()) { + revert RequestIsSlashed({requestId: requestId}); + } + if (lock.isProverPaidBeforeLockDeadline()) { + revert RequestIsFulfilled({requestId: requestId}); + } + + // You can only slash a request after the request fully expires, so that if the request + // does get fulfilled, we know which prover should receive a portion of the collateral. + if (block.timestamp <= lock.deadline()) { + revert RequestIsNotExpired({requestId: requestId, deadline: lock.deadline()}); + } + + // Request was either fulfilled after the lock deadline or the request expired unfulfilled. + // In both cases the locker should be slashed. + requestLocks[requestId].setSlashed(); + + // Calculate the portion of collateral that should be burned vs sent to the prover. + uint256 burnValue = uint256(lock.collateral) * SLASHING_BURN_BPS / 10000; + + // If a prover fulfilled the request after the lock deadline, that prover + // receives the unburned portion of the collateral as a reward. + // Otherwise the request expired unfulfilled, unburnt collateral accrues to the market treasury, + // and we refund the client the price they paid for the request at lock time. + uint96 transferValue = (uint256(lock.collateral) - burnValue).toUint96(); + address collateralRecipient = lock.prover; + if (lock.isProverPaidAfterLockDeadline()) { + // At this point lock.prover is the prover that ultimately fulfilled the request, not + // the prover that locked the request. Transfer them the unburnt collateral. + accounts[collateralRecipient].collateralBalance += transferValue; + } else { + collateralRecipient = address(this); + accounts[collateralRecipient].collateralBalance += transferValue; + accounts[client].balance += lock.price; + } + + ERC20(COLLATERAL_TOKEN_CONTRACT).transfer(address(0xdEaD), burnValue); + (burnValue); + emit ProverSlashed(requestId, burnValue, transferValue, collateralRecipient); + } + + /// @inheritdoc IBoundlessMarket + function deposit() public payable { + accounts[msg.sender].balance += msg.value.toUint96(); + emit Deposit(msg.sender, msg.value); + } + + /// @inheritdoc IBoundlessMarket + function depositTo(address to) public payable { + accounts[to].balance += msg.value.toUint96(); + emit Deposit(to, msg.value); + } + + function _withdraw(address account, uint256 value) internal { + if (accounts[account].balance < value.toUint96()) { + revert InsufficientBalance(account); + } + unchecked { + accounts[account].balance -= value.toUint96(); + } + (bool sent,) = account.call{value: value}(""); + if (!sent) { + revert TransferFailed(); + } + emit Withdrawal(account, value); + } + + /// @inheritdoc IBoundlessMarket + function withdraw(uint256 value) public { + _withdraw(msg.sender, value); + } + + /// @inheritdoc IBoundlessMarket + function balanceOf(address addr) public view returns (uint256) { + return uint256(accounts[addr].balance); + } + + /// @inheritdoc IBoundlessMarket + function depositCollateral(uint256 value) external { + // Transfer tokens from user to market + _depositCollateral(msg.sender, msg.sender, value); + } + + /// @inheritdoc IBoundlessMarket + function depositCollateralTo(address to, uint256 value) external { + _depositCollateral(msg.sender, to, value); + } + + /// @inheritdoc IBoundlessMarket + function depositCollateralWithPermit(uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external { + // Transfer tokens from user to market + try ERC20(COLLATERAL_TOKEN_CONTRACT).permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + _depositCollateral(msg.sender, msg.sender, value); + } + + /// @inheritdoc IBoundlessMarket + function depositCollateralWithPermitTo(address to, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + { + try ERC20(COLLATERAL_TOKEN_CONTRACT).permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + _depositCollateral(msg.sender, to, value); + } + + function _depositCollateral(address from, address to, uint256 value) internal { + ERC20(COLLATERAL_TOKEN_CONTRACT).safeTransferFrom(from, address(this), value); + accounts[to].collateralBalance += value.toUint96(); + emit CollateralDeposit(to, value); + } + + /// @inheritdoc IBoundlessMarket + function withdrawCollateral(uint256 value) public { + if (accounts[msg.sender].collateralBalance < value.toUint96()) { + revert InsufficientBalance(msg.sender); + } + unchecked { + accounts[msg.sender].collateralBalance -= value.toUint96(); + } + // Transfer tokens from market to user + bool success = ERC20(COLLATERAL_TOKEN_CONTRACT).transfer(msg.sender, value); + if (!success) revert TransferFailed(); + + emit CollateralWithdrawal(msg.sender, value); + } + + /// @inheritdoc IBoundlessMarket + function balanceOfCollateral(address addr) public view returns (uint256) { + return uint256(accounts[addr].collateralBalance); + } + + /// @inheritdoc IBoundlessMarket + function requestIsFulfilled(RequestId id) public view returns (bool) { + (address client, uint32 idx) = id.clientAndIndex(); + (, bool fulfilled) = accounts[client].requestFlags(idx); + return fulfilled; + } + + /// @inheritdoc IBoundlessMarket + function requestIsLocked(RequestId id) public view returns (bool) { + (address client, uint32 idx) = id.clientAndIndex(); + (bool locked,) = accounts[client].requestFlags(idx); + return locked; + } + + /// @inheritdoc IBoundlessMarket + function requestIsSlashed(RequestId id) external view returns (bool) { + return requestLocks[id].isSlashed(); + } + + /// @inheritdoc IBoundlessMarket + function requestLockDeadline(RequestId id) external view returns (uint64) { + if (!requestIsLocked(id)) { + revert RequestIsNotLocked({requestId: id}); + } + return requestLocks[id].lockDeadline; + } + + /// @inheritdoc IBoundlessMarket + function requestDeadline(RequestId id) external view returns (uint64) { + if (!requestIsLocked(id)) { + revert RequestIsNotLocked({requestId: id}); + } + return requestLocks[id].deadline(); + } + + function _verifyClientSignature(ProofRequest calldata request, address addr, bytes calldata clientSignature) + internal + view + returns (bytes32, bytes32) + { + bytes32 eip712Digest = request.eip712Digest(); + bytes32 requestHash = _hashTypedDataV4(eip712Digest); + if (request.id.isSmartContractSigned()) { + if ( + IERC1271(addr).isValidSignature{gas: ERC1271_MAX_GAS_FOR_CHECK}(requestHash, clientSignature) + != IERC1271.isValidSignature.selector + ) { + revert IBoundlessMarket.InvalidSignature(); + } + } else { + if (ECDSA.recover(requestHash, clientSignature) != addr) { + revert IBoundlessMarket.InvalidSignature(); + } + } + return (requestHash, eip712Digest); + } + + /// @inheritdoc IBoundlessMarket + function eip712DomainSeparator() external view returns (bytes32) { + return _domainSeparatorV4(); + } +} diff --git a/contracts/shanghai/variants/FulfillmentContext.sol b/contracts/shanghai/variants/FulfillmentContext.sol new file mode 100644 index 0000000000..54bdc5a3ce --- /dev/null +++ b/contracts/shanghai/variants/FulfillmentContext.sol @@ -0,0 +1,77 @@ +// Copyright 2026 Boundless Foundation, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +pragma solidity ^0.8.26; + +using FulfillmentContextLibrary for FulfillmentContext global; + +/// @title FulfillmentContext +/// @notice A struct for storing validated fulfillment information in persistent storage. +/// @dev This struct is designed to be packed into a single uint256 for efficient storage. +/// Shanghai-compatible variant: uses sstore/sload instead of tstore/tload. Selected over the +/// transient-storage variant via the `fulfillment-context/` remapping in the `shanghai` profile. +struct FulfillmentContext { + /// @notice Boolean set to true to indicate the request is internally consistent and signed. + bool valid; + /// @notice Boolean set to true to indicate that the request is expired. + bool expired; + /// @notice The validated price for the request + uint96 price; +} + +library FulfillmentContextLibrary { + uint256 private constant VALID_MASK = 1 << 127; + uint256 private constant EXPIRED_MASK = 1 << 126; + uint256 private constant PRICE_MASK = (1 << 96) - 1; + + /// @notice Packs the struct into a single 256-bit slots and sets the flags. + /// @param x The FulfillmentContext struct to pack + /// @return Packed uint256 containing valid bit and price + function pack(FulfillmentContext memory x) internal pure returns (uint256) { + return (x.valid ? VALID_MASK : 0) | (x.expired ? EXPIRED_MASK : 0) | uint256(x.price); + } + + /// @notice Unpacks the struct from a single uint256 + /// @param packed Packed uint256 containing the flags and price + /// @return The unpacked FulfillmentContext struct + function unpack(uint256 packed) internal pure returns (FulfillmentContext memory) { + return FulfillmentContext({ + valid: (packed & VALID_MASK) != 0, expired: (packed & EXPIRED_MASK) != 0, price: uint96(packed & PRICE_MASK) + }); + } + + /// @notice Packs and stores the object to persistent storage + /// @param x The FulfillmentContext struct to store + /// @param requestDigest The storage key (used directly as the sstore slot) + function store(FulfillmentContext memory x, bytes32 requestDigest) internal { + uint256 packed = pack(x); + assembly { + sstore(requestDigest, packed) + } + } + + /// @notice Loads from persistent storage and unpacks to FulfillmentContext. + /// @dev Non-destructive (mirrors the transient `tload`), so the same digest may be read more + /// than once within a fulfillment. The slot is released separately via `clear`. + /// @param requestDigest The storage key to load from + /// @return The loaded and unpacked FulfillmentContext struct + function load(bytes32 requestDigest) internal view returns (FulfillmentContext memory) { + uint256 packed; + assembly { + packed := sload(requestDigest) + } + return unpack(packed); + } + + /// @notice Releases the stored context once it has been consumed. + /// @dev Clears the slot to bound permanent storage growth and to enforce the same + /// single-transaction price-then-fulfill semantics that transient storage provides + /// automatically (a context cannot survive into a later transaction). + /// @param requestDigest The storage key to release + function clear(bytes32 requestDigest) internal { + assembly { + sstore(requestDigest, 0) + } + } +} diff --git a/contracts/src/types/Predicate.sol b/contracts/src/types/Predicate.sol index 20c6a8f342..b6211ddc4f 100644 --- a/contracts/src/types/Predicate.sol +++ b/contracts/src/types/Predicate.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.26; import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; -import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol"; +import {Bytes} from "bytes-compat/Bytes.sol"; using PredicateLibrary for Predicate global; using ReceiptClaimLib for ReceiptClaim; diff --git a/contracts/test/BoundlessMarket.t.sol b/contracts/test/BoundlessMarket.t.sol index 4de9a66149..636c58993b 100644 --- a/contracts/test/BoundlessMarket.t.sol +++ b/contracts/test/BoundlessMarket.t.sol @@ -27,7 +27,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {HitPoints} from "../src/HitPoints.sol"; -import {BoundlessMarket} from "../src/BoundlessMarket.sol"; +import {BoundlessMarket} from "boundless-market/BoundlessMarket.sol"; import {BoundlessRouter} from "../src/router/BoundlessRouter.sol"; import {IBoundlessVerifier} from "../src/router/interfaces/IBoundlessVerifier.sol"; import {IBoundlessAssessor} from "../src/router/interfaces/IBoundlessAssessor.sol"; diff --git a/crates/boundless-market/src/contracts/artifacts/Predicate.sol b/crates/boundless-market/src/contracts/artifacts/Predicate.sol index 20c6a8f342..b6211ddc4f 100644 --- a/crates/boundless-market/src/contracts/artifacts/Predicate.sol +++ b/crates/boundless-market/src/contracts/artifacts/Predicate.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.26; import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; -import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol"; +import {Bytes} from "bytes-compat/Bytes.sol"; using PredicateLibrary for Predicate global; using ReceiptClaimLib for ReceiptClaim; diff --git a/foundry.toml b/foundry.toml index a11d04db53..819e93396e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,6 +13,19 @@ fs_permissions = [ libs = ["./lib"] script = "./contracts/scripts" test = "./contracts/test" +# EVM-version-divergent imports are routed through dedicated remapping prefixes so the shanghai +# profile can swap them (see [profile.shanghai]): +# - bytes-compat/ -> OpenZeppelin Bytes (mcopy) here; a no-mcopy shim under shanghai +# - boundless-market/ -> the transient-storage market here; a persistent-storage variant under +# shanghai (the market is the one contract whose body must differ) +# Defined here in config rather than remappings.txt because a profile cannot override a +# remappings.txt entry. Profiles that compile contracts/src but do not set their own `remappings` +# (povw-deploy, reference-contract, deployment-test) inherit these from the default profile. +remappings = [ + "bytes-compat/=lib/openzeppelin-contracts/contracts/utils/", + "boundless-market/=contracts/src/", + "boundless-market-legacy/=contracts/src/legacy/", +] ffi = true evm_version = 'cancun' via_ir = true @@ -94,14 +107,51 @@ fs_permissions = [ { access = "read", path = "contracts/reference-contract/out" }, ] -# Profile for Shanghai-compatible contract variant (no tstore/tload). +# Profile for the Shanghai-compatible contract variant (no tstore/tload/mcopy). +# Compiles the SAME ./contracts/src tree as the default profile, except the EVM-version-divergent +# files, which are swapped via the remappings below: +# - bytes-compat/ -> contracts/shanghai/compat/Bytes.sol (no mcopy) +# - boundless-market/ -> contracts/shanghai/variants/BoundlessMarket.sol (persistent storage + +# explicit FulfillmentContext.clear, importing the sstore variant) +# The Cancun-only originals (the transient-storage market + its FulfillmentContext) are skipped so +# they aren't compiled under the Shanghai EVM. The mainline legacy tree is Cancun-only and frozen; +# it is skipped here too (Taiko carries its own frozen legacy under contracts/shanghai/legacy). # TIP: You can select this profile by setting env var FOUNDRY_PROFILE=shanghai [profile.shanghai] -src = "./contracts/shanghai/src" +src = "./contracts/src" out = "./out-shanghai" +# Separate gas-snapshot dir so shanghai test runs don't clobber the default profile's cancun +# snapshots under contracts/snapshots (gitignored — the shanghai variant has no snapshot CI gate). +snapshots = "./out-shanghai/snapshots" libs = ["./lib"] -script = "./contracts/shanghai/scripts" -test = "./contracts/shanghai/test" +script = "./contracts/scripts" +test = "./contracts/test" +remappings = [ + "bytes-compat/=contracts/shanghai/compat/", + "boundless-market/=contracts/shanghai/variants/", + "boundless-market-legacy/=contracts/shanghai/legacy/", +] +skip = [ + "contracts/src/legacy/**", + "contracts/src/BoundlessMarket.sol", + "contracts/src/types/FulfillmentContext.sol", + # The Cancun/Base legacy ABI test suites exercise the mainline frozen legacy (tstore); the + # Taiko legacy is covered by the bytecode/storage parity checks instead. + "contracts/test/legacy/**", + # Unit test for the transient-storage FulfillmentContext library (uses tstore directly). The + # shanghai sstore/clear variant's behaviour is exercised end-to-end by the price-then-fulfill + # paths in BoundlessMarket.t.sol, which run under this profile. + "contracts/test/types/FulfillmentContext.t.sol", + # PoVW is a separate contract suite (rewards), not part of the market router work, and its zkc + # Supply library uses transient storage. It is out of scope for the Taiko market port; skip the + # PoVW contracts and their deploy scripts under shanghai. + "contracts/src/povw/**", + "contracts/scripts/Deploy.PoVW.s.sol", + "contracts/scripts/Manage.PoVW.s.sol", + # ZKC test fixtures pull in the transient zkc Supply library; only PoVW tests use them. + "contracts/test/MockZKC.sol", + "contracts/test/TestUtilsZKC.sol", +] ffi = true evm_version = 'shanghai' via_ir = true diff --git a/justfile b/justfile index de645bfa1c..b6dcb4a16e 100644 --- a/justfile +++ b/justfile @@ -26,6 +26,10 @@ test: test-foundry test-cargo test-foundry: forge test -vvv --isolate +# Run the contract suite under the Shanghai-EVM profile (Taiko variant) +test-foundry-shanghai: + FOUNDRY_PROFILE=shanghai forge test -vvv --isolate + # Run all Cargo tests test-cargo: test-cargo-root test-cargo-example test-cargo-db @@ -149,7 +153,7 @@ test-db action="setup": fi # Run all formatting and linting checks -check: check-links check-license check-format check-clippy check-legacy-bytecode check-storage-layout +check: check-links check-license check-format check-clippy check-legacy-bytecode check-storage-layout check-legacy-bytecode-shanghai check-storage-layout-shanghai check-main: check-format-main check-clippy-main check-license check-links @@ -165,6 +169,18 @@ check-storage-layout: forge build --silent uv run contracts/scripts/verify-storage-layout.py +# Verify contracts/shanghai/legacy/ still compiles to the deployed Taiko market bytecode +check-legacy-bytecode-shanghai: + @echo "Verifying Taiko (shanghai) legacy market bytecode parity..." + FOUNDRY_PROFILE=shanghai forge build --silent contracts/shanghai/legacy/BoundlessMarketLegacy.sol + BOUNDLESS_OUT_DIR=out-shanghai BOUNDLESS_LEGACY_SNAPSHOT_DIR=contracts/shanghai/legacy uv run contracts/scripts/verify-legacy-bytecode.py + +# Verify storage layout interop between the shanghai market variant and its frozen legacy +check-storage-layout-shanghai: + @echo "Verifying shanghai market/legacy storage layout interop..." + FOUNDRY_PROFILE=shanghai forge build --silent contracts/shanghai/variants/BoundlessMarket.sol contracts/shanghai/legacy/BoundlessMarketLegacy.sol + BOUNDLESS_OUT_DIR=out-shanghai uv run contracts/scripts/verify-storage-layout.py + # Check links in markdown files check-links: @echo "Checking links in markdown files..." diff --git a/license-check.py b/license-check.py index d4899bc9bb..303bcfc2dd 100755 --- a/license-check.py +++ b/license-check.py @@ -43,12 +43,10 @@ str(Path.cwd()) + "/contracts/src/libraries/UtilImageID.sol", str(Path.cwd()) + "/contracts/src/verifier/RiscZeroVerifierRouter.sol", str(Path.cwd()) + "/contracts/src/blake3-groth16/Groth16Verifier.sol", - str(Path.cwd()) + "/contracts/shanghai/src/SetBuilderImageID.sol", - str(Path.cwd()) + "/contracts/shanghai/src/libraries/AssessorImageID.sol", - str(Path.cwd()) + "/contracts/shanghai/src/libraries/UtilImageID.sol", - str(Path.cwd()) + "/contracts/shanghai/src/verifier/RiscZeroVerifierRouter.sol", - str(Path.cwd()) + "/contracts/shanghai/src/blake3-groth16/Groth16Verifier.sol", - str(Path.cwd()) + "/contracts/shanghai/src/compat/Bytes.sol", + # Shanghai variant: third-party-derived no-mcopy Bytes shim (kept under the variant and the + # frozen legacy). Same MIT-derived provenance as the OpenZeppelin Bytes it replaces. + str(Path.cwd()) + "/contracts/shanghai/compat/Bytes.sol", + str(Path.cwd()) + "/contracts/shanghai/legacy/compat/Bytes.sol", str(Path.cwd()) + "/crates/boundless-market/src/contracts/artifacts", str(Path.cwd()) + "/crates/boundless-market/src/contracts/bytecode.rs", str(Path.cwd()) + "/crates/povw/src/contracts/artifacts", @@ -65,12 +63,7 @@ str(Path.cwd()) + "/contracts/src/povw/IPovwAccounting.sol", str(Path.cwd()) + "/contracts/src/povw/IPovwMint.sol", str(Path.cwd()) + "/contracts/src/zkc/IStakingRewards.sol", - str(Path.cwd()) + "/contracts/shanghai/src/HitPoints.sol", - str(Path.cwd()) + "/contracts/shanghai/src/IBoundlessMarket.sol", - str(Path.cwd()) + "/contracts/shanghai/src/IHitPoints.sol", - str(Path.cwd()) + "/contracts/shanghai/src/povw/IPovwAccounting.sol", - str(Path.cwd()) + "/contracts/shanghai/src/povw/IPovwMint.sol", - str(Path.cwd()) + "/contracts/shanghai/src/zkc/IStakingRewards.sol", + str(Path.cwd()) + "/contracts/shanghai/legacy/IBoundlessMarketLegacy.sol", str(Path.cwd()) + "/crates/bench", str(Path.cwd()) + "/crates/boundless-backend", str(Path.cwd()) + "/crates/boundless-cli",