diff --git a/.vscode/settings.json b/.vscode/settings.json index 794812452..232d73025 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,10 +14,10 @@ "rust-analyzer.linkedProjects": [ "./Cargo.toml", "./crates/distributor/Cargo.toml", - // "./crates/guest/assessor/assessor-guest/Cargo.toml", + "./crates/guest/assessor/assessor-guest/Cargo.toml", // "./crates/guest/util/echo/Cargo.toml", // "./crates/guest/util/identity/Cargo.toml", - // "./examples/counter/Cargo.toml", + "./examples/counter/Cargo.toml", // "./examples/smart-contract-requestor/Cargo.toml", // "./examples/counter-with-callback/Cargo.toml" ], diff --git a/contracts/deployment-test/Deploymnet.t.sol b/contracts/deployment-test/Deploymnet.t.sol index 366b88a35..ae23c38bf 100644 --- a/contracts/deployment-test/Deploymnet.t.sol +++ b/contracts/deployment-test/Deploymnet.t.sol @@ -22,7 +22,7 @@ import {Input, InputType} from "../src/types/Input.sol"; import {Requirements} from "../src/types/Requirements.sol"; import {Offer} from "../src/types/Offer.sol"; import {ProofRequest} from "../src/types/ProofRequest.sol"; -import {Predicate, PredicateType} from "../src/types/Predicate.sol"; +import {PredicateLibrary} from "../src/types/Predicate.sol"; import {RequestId, RequestIdLibrary} from "../src/types/RequestId.sol"; import {RequestLock} from "../src/types/RequestLock.sol"; @@ -105,7 +105,10 @@ contract DeploymentTest is Test { function testRouterIsDeployed() external view { require(address(verifier) != address(0), "no verifier (router) address is set"); require(keccak256(address(verifier).code) != keccak256(bytes("")), "verifier code is empty"); - require(address(verifier) == address(BoundlessMarket(address(boundlessMarket)).VERIFIER()), "verifier address does not match boundless market"); + require( + address(verifier) == address(BoundlessMarket(address(boundlessMarket)).VERIFIER()), + "verifier address does not match boundless market" + ); } function testSetVerifierIsDeployed() external view { @@ -191,9 +194,7 @@ contract DeploymentTest is Test { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, address(testProver), result.fills[0]); - boundlessMarket.priceAndFulfill( - requests, clientSignatures, result.fills, result.assessorReceipt - ); + boundlessMarket.priceAndFulfill(requests, clientSignatures, result.fills, result.assessorReceipt); Fulfillment memory fill = result.fills[0]; assertTrue(boundlessMarket.requestIsFulfilled(fill.id), "Request should have fulfilled status"); } @@ -230,8 +231,7 @@ contract Client { function defaultRequirements() public pure returns (Requirements memory) { return Requirements({ - imageId: bytes32(APP_IMAGE_ID), - predicate: Predicate({predicateType: PredicateType.PrefixMatch, data: hex"53797374"}), + predicate: PredicateLibrary.createPrefixMatchPredicate(bytes32(APP_IMAGE_ID), hex"53797374"), callback: Callback({gasLimit: 0, addr: address(0)}), selector: bytes4(0) }); @@ -257,4 +257,4 @@ contract Client { (uint8 v, bytes32 r, bytes32 s) = VM.sign(wallet, structDigest); return abi.encodePacked(r, s, v); } -} \ No newline at end of file +} diff --git a/contracts/snapshots/BoundlessMarketBasicTest.json b/contracts/snapshots/BoundlessMarketBasicTest.json index b2d8ac978..a7e01216d 100644 --- a/contracts/snapshots/BoundlessMarketBasicTest.json +++ b/contracts/snapshots/BoundlessMarketBasicTest.json @@ -1,42 +1,43 @@ { "ERC20 approve: required for depositStake": "45966", - "bytecode size implementation": "24381", + "bytecode size implementation": "24341", "bytecode size proxy": "89", - "deposit: first ever deposit": "50920", - "deposit: second deposit": "33820", - "depositStake: 1 HP (tops up market account)": "59400", - "depositStake: full (drains testProver account)": "49800", - "depositStakeWithPermit: 1 HP (tops up market account)": "72272", - "depositStakeWithPermit: full (drains testProver account)": "72262", - "fulfill: a batch of 8": "402078", - "fulfill: a locked request": "90511", - "fulfill: a locked request (locked via prover signature)": "90511", - "fulfill: a locked request with 10kB journal": "427539", - "fulfill: another prover fulfills without payment": "85473", - "fulfill: fulfilled by the locked prover for payment (request already fulfilled by another prover)": "80772", - "fulfillAndWithdraw: a batch of 8": "414256", - "fulfillAndWithdraw: a locked request": "102689", - "lockinRequest: base case": "146877", - "lockinRequest: with prover signature": "156522", - "priceAndFulfill: a single request": "108760", - "priceAndFulfill: a single request (smart contract signature)": "118938", - "priceAndFulfill: a single request (with selector)": "111059", - "priceAndFulfill: a single request that was not locked": "108760", - "priceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "108760", - "priceAndFulfill: fulfill already fulfilled was locked request": "102475", - "slash: base case": "100967", - "slash: fulfilled request after lock deadline": "80532", - "submitRequest: with maxPrice ether": "51904", - "submitRequest: without ether": "45061", - "submitRootAndFulfill: a batch of 2 requests": "168676", - "submitRootAndFulfill: a locked request": "124555", - "submitRootAndFulfill: a locked request (locked via prover signature)": "124555", - "submitRootAndFulfillAndWithdraw: a locked request": "136476", - "submitRootAndPriceAndFulfill: a single request": "141098", - "submitRootAndPriceAndFulfill: a single request that was not locked": "141110", - "submitRootAndPriceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "141110", - "withdraw: 1 ether": "40314", - "withdraw: full balance": "40326", - "withdrawStake: 1 HP balance": "68743", - "withdrawStake: full balance": "51739" + "deposit: first ever deposit": "50876", + "deposit: second deposit": "33776", + "depositStake: 1 HP (tops up market account)": "59378", + "depositStake: full (drains testProver account)": "49778", + "depositStakeWithPermit: 1 HP (tops up market account)": "72236", + "depositStakeWithPermit: full (drains testProver account)": "72227", + "fulfill (no journal): a batch of 8": "385299", + "fulfill: a batch of 8": "438335", + "fulfill: a locked request": "95167", + "fulfill: a locked request (locked via prover signature)": "95167", + "fulfill: a locked request with 10kB journal": "432569", + "fulfill: another prover fulfills without payment": "90165", + "fulfill: fulfilled by the locked prover for payment (request already fulfilled by another prover)": "84071", + "fulfillAndWithdraw: a batch of 8": "451307", + "fulfillAndWithdraw: a locked request": "108139", + "lockinRequest: base case": "147332", + "lockinRequest: with prover signature": "157039", + "priceAndFulfill: a single request": "113970", + "priceAndFulfill: a single request (smart contract signature)": "124159", + "priceAndFulfill: a single request (with selector)": "116270", + "priceAndFulfill: a single request that was not locked": "113934", + "priceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "113934", + "priceAndFulfill: fulfill already fulfilled was locked request": "106280", + "slash: base case": "100898", + "slash: fulfilled request after lock deadline": "80465", + "submitRequest: with maxPrice ether": "52227", + "submitRequest: without ether": "45384", + "submitRootAndFulfill: a batch of 2 requests": "177800", + "submitRootAndFulfill: a locked request": "129534", + "submitRootAndFulfill: a locked request (locked via prover signature)": "129534", + "submitRootAndFulfillAndWithdraw: a locked request": "141954", + "submitRootAndPriceAndFulfill: a single request": "146462", + "submitRootAndPriceAndFulfill: a single request that was not locked": "146426", + "submitRootAndPriceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "146426", + "withdraw: 1 ether": "40331", + "withdraw: full balance": "40343", + "withdrawStake: 1 HP balance": "68755", + "withdrawStake: full balance": "51751" } \ No newline at end of file diff --git a/contracts/snapshots/BoundlessMarketBench.json b/contracts/snapshots/BoundlessMarketBench.json index e7220706c..09e3bbe1c 100644 --- a/contracts/snapshots/BoundlessMarketBench.json +++ b/contracts/snapshots/BoundlessMarketBench.json @@ -1,22 +1,22 @@ { - "fulfill (with callback): batch of 001": "132142", - "fulfill (with callback): batch of 002": "218443", - "fulfill (with callback): batch of 004": "392147", - "fulfill (with callback): batch of 008": "739579", - "fulfill (with callback): batch of 016": "1272464", - "fulfill (with callback): batch of 032": "2373148", - "fulfill (with selector): batch of 001": "92745", - "fulfill (with selector): batch of 002": "139726", - "fulfill (with selector): batch of 004": "236258", - "fulfill (with selector): batch of 008": "420231", - "fulfill (with selector): batch of 016": "790425", - "fulfill (with selector): batch of 032": "1558877", - "fulfill: batch of 001": "90487", - "fulfill: batch of 002": "135232", - "fulfill: batch of 004": "227181", - "fulfill: batch of 008": "402117", - "fulfill: batch of 016": "754179", - "fulfill: batch of 032": "1485129", - "fulfill: batch of 064": "3014348", - "fulfill: batch of 128": "6255291" + "fulfill (with callback): batch of 001": "136946", + "fulfill (with callback): batch of 002": "227879", + "fulfill (with callback): batch of 004": "410896", + "fulfill (with callback): batch of 008": "776960", + "fulfill (with callback): batch of 016": "1346098", + "fulfill (with callback): batch of 032": "2522586", + "fulfill (with selector): batch of 001": "97441", + "fulfill (with selector): batch of 002": "148905", + "fulfill (with selector): batch of 004": "254383", + "fulfill (with selector): batch of 008": "456439", + "fulfill (with selector): batch of 016": "862953", + "fulfill (with selector): batch of 032": "1704151", + "fulfill: batch of 001": "95191", + "fulfill: batch of 002": "144387", + "fulfill: batch of 004": "245394", + "fulfill: batch of 008": "438338", + "fulfill: batch of 016": "826511", + "fulfill: batch of 032": "1630160", + "fulfill: batch of 064": "3306156", + "fulfill: batch of 128": "6849269" } \ No newline at end of file diff --git a/contracts/src/BoundlessMarket.sol b/contracts/src/BoundlessMarket.sol index fe4f4e729..aa42b0b79 100644 --- a/contracts/src/BoundlessMarket.sol +++ b/contracts/src/BoundlessMarket.sol @@ -24,8 +24,10 @@ import {Account} from "./types/Account.sol"; import {AssessorJournal} from "./types/AssessorJournal.sol"; import {AssessorCallback} from "./types/AssessorCallback.sol"; import {AssessorCommitment} from "./types/AssessorCommitment.sol"; +import {CallbackData} from "./types/CallbackData.sol"; import {Fulfillment} from "./types/Fulfillment.sol"; import {AssessorReceipt} from "./types/AssessorReceipt.sol"; +import {PredicateType} from "./types/Predicate.sol"; import {ProofRequest} from "./types/ProofRequest.sol"; import {LockRequest, LockRequestLibrary} from "./types/LockRequest.sol"; import {RequestId} from "./types/RequestId.sol"; @@ -239,6 +241,7 @@ contract BoundlessMarket is } bytes32[] memory leaves = new bytes32[](fills.length); bool[] memory hasSelector = new bool[](fills.length); + PredicateType[] memory predicateTypes = new PredicateType[](fills.length); // Check the selector constraints. // NOTE: The assessor guest adds non-zero selector values to the list. @@ -255,16 +258,16 @@ contract BoundlessMarket is // Verify the application receipts. for (uint256 i = 0; i < fills.length; i++) { Fulfillment calldata fill = fills[i]; + predicateTypes[i] = fill.predicateType; - bytes32 claimDigest = ReceiptClaimLib.ok(fill.imageId, sha256(fill.journal)).digest(); - leaves[i] = AssessorCommitment(i, fill.id, fill.requestDigest, claimDigest).eip712Digest(); + leaves[i] = AssessorCommitment(i, fill.id, fill.requestDigest, fill.claimDigest).eip712Digest(); // If the requestor did not specify a selector, we verify with DEFAULT_MAX_GAS_FOR_VERIFY gas limit. // This ensures that by default, client receive proofs that can be verified cheaply as part of their applications. if (!hasSelector[i]) { - VERIFIER.verifyIntegrity{gas: DEFAULT_MAX_GAS_FOR_VERIFY}(Receipt(fill.seal, claimDigest)); + VERIFIER.verifyIntegrity{gas: DEFAULT_MAX_GAS_FOR_VERIFY}(Receipt(fill.seal, fill.claimDigest)); } else { - VERIFIER.verifyIntegrity(Receipt(fill.seal, claimDigest)); + VERIFIER.verifyIntegrity(Receipt(fill.seal, fill.claimDigest)); } } @@ -278,6 +281,7 @@ contract BoundlessMarket is root: batchRoot, callbacks: assessorReceipt.callbacks, selectors: assessorReceipt.selectors, + predicateTypes: predicateTypes, prover: assessorReceipt.prover }) ) @@ -333,13 +337,39 @@ contract BoundlessMarket is } uint256 callbackIndexPlusOne = fillToCallbackIndexPlusOne[i]; - if (callbackIndexPlusOne > 0) { - AssessorCallback calldata callback = assessorReceipt.callbacks[callbackIndexPlusOne - 1]; - _executeCallback(fill.id, callback.addr, callback.gasLimit, fill.imageId, fill.journal, fill.seal); + + // We do not support callbacks for claim digest matches because we cant authenticate the journal. + if (fill.predicateType != PredicateType.ClaimDigestMatch) { + // In the case this is a not a claim digest match, we need to authenticate the journal, since we actually have it + (bytes32 imageId, bytes calldata journal) = _decodeCallbackData(fill.callbackData); + bytes32 calculatedClaimDigest = ReceiptClaimLib.ok(imageId, sha256(journal)).digest(); + if (fill.claimDigest != calculatedClaimDigest) { + revert ClaimDigestMismatch(fill.claimDigest, calculatedClaimDigest); + } + + if (callbackIndexPlusOne > 0) { + AssessorCallback calldata callback = assessorReceipt.callbacks[callbackIndexPlusOne - 1]; + _executeCallback(fill.id, callback.addr, callback.gasLimit, imageId, journal, fill.seal); + } } } } + function _decodeCallbackData(bytes calldata data) internal pure returns (bytes32 imageId, bytes calldata journal) { + assembly { + // Extract imageId (first 32 bytes after length) + imageId := calldataload(add(data.offset, 0x20)) + + // Extract journal offset and create calldata slice + let journalOffset := calldataload(add(data.offset, 0x40)) + let journalPtr := add(data.offset, add(0x20, journalOffset)) + let journalLength := calldataload(journalPtr) + + journal.offset := add(journalPtr, 0x20) + journal.length := journalLength + } + } + /// @inheritdoc IBoundlessMarket function priceAndFulfillAndWithdraw( ProofRequest[] calldata requests, diff --git a/contracts/src/IBoundlessMarket.sol b/contracts/src/IBoundlessMarket.sol index 6e037c243..2dc263b39 100644 --- a/contracts/src/IBoundlessMarket.sol +++ b/contracts/src/IBoundlessMarket.sol @@ -165,6 +165,10 @@ interface IBoundlessMarket { /// @dev selector efc954a6 error BatchSizeExceedsLimit(uint256 batchSize, uint256 limit); + /// @notice Error when a journal is provided but does not match the claim digest + /// TODO(ec2): selector + error ClaimDigestMismatch(bytes32 expected, bytes32 calculated); + /// @notice Check if the given request has been locked (i.e. accepted) by a prover. /// @dev When a request is locked, only the prover it is locked to can be paid to fulfill the job. /// @param requestId The ID of the request. diff --git a/contracts/src/types/AssessorCallback.sol b/contracts/src/types/AssessorCallback.sol index a31f1727f..0ef1e0e27 100644 --- a/contracts/src/types/AssessorCallback.sol +++ b/contracts/src/types/AssessorCallback.sol @@ -4,6 +4,8 @@ // as found in the LICENSE-BSL file. pragma solidity ^0.8.20; +import {CallbackType} from "./CallbackType.sol"; + struct AssessorCallback { /// @notice The index of the fill in the request uint16 index; @@ -11,4 +13,6 @@ struct AssessorCallback { address addr; /// @notice Maximum gas to use for the callback uint96 gasLimit; + /// @notice The type of callback + CallbackType callbackType; } diff --git a/contracts/src/types/AssessorJournal.sol b/contracts/src/types/AssessorJournal.sol index dfa3920f2..51a772d75 100644 --- a/contracts/src/types/AssessorJournal.sol +++ b/contracts/src/types/AssessorJournal.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {AssessorCallback} from "./AssessorCallback.sol"; +import {PredicateType} from "./Predicate.sol"; import {Selector} from "./Selector.sol"; /// @title Assessor Journal Struct @@ -17,6 +18,8 @@ struct AssessorJournal { /// @notice The (optional) selectors for the requests committed by the assessor. /// @dev This is used to verify the fulfillment of the request against its selector's seal. Selector[] selectors; + /// @notice The list of `PredicateType` for each request. + PredicateType[] predicateTypes; /// @notice Root of the Merkle tree committing to the set of proven claims. /// @dev In the case of a batch of size one, this may simply be the eip712Digest of the `AssessorCommitment`. bytes32 root; diff --git a/contracts/src/types/Callback.sol b/contracts/src/types/Callback.sol index f3864708f..144d0ba99 100644 --- a/contracts/src/types/Callback.sol +++ b/contracts/src/types/Callback.sol @@ -6,6 +6,8 @@ pragma solidity ^0.8.24; using CallbackLibrary for Callback global; +import {CallbackType} from "./CallbackType.sol"; + /// @title Callback Struct and Library /// @notice Represents a callback configuration for proof delivery struct Callback { @@ -13,16 +15,18 @@ struct Callback { address addr; /// @notice Maximum gas to use for the callback uint96 gasLimit; + /// @notice The type of callback + CallbackType callbackType; } library CallbackLibrary { - string constant CALLBACK_TYPE = "Callback(address addr,uint96 gasLimit)"; + string constant CALLBACK_TYPE = "Callback(address addr,uint96 gasLimit,uint8 callbackType)"; bytes32 constant CALLBACK_TYPEHASH = keccak256(bytes(CALLBACK_TYPE)); /// @notice Computes the EIP-712 digest for the given callback /// @param callback The callback to compute the digest for /// @return The EIP-712 digest of the callback function eip712Digest(Callback memory callback) internal pure returns (bytes32) { - return keccak256(abi.encode(CALLBACK_TYPEHASH, callback.addr, callback.gasLimit)); + return keccak256(abi.encode(CALLBACK_TYPEHASH, callback.addr, callback.gasLimit, callback.callbackType)); } } diff --git a/contracts/src/types/CallbackData.sol b/contracts/src/types/CallbackData.sol new file mode 100644 index 000000000..a90d473c6 --- /dev/null +++ b/contracts/src/types/CallbackData.sol @@ -0,0 +1,19 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +pragma solidity ^0.8.24; + +using CallbackDataLibrary for CallbackData global; + +/// @title Callback Struct and Library +/// @notice Represents a callback configuration for proof delivery +struct CallbackData { + /// @notice Image ID of the guest that was verifiably executed to satisfy the request. + bytes32 imageId; + /// @notice Journal committed by the guest program execution. + /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. + bytes journal; +} + +library CallbackDataLibrary {} diff --git a/contracts/src/types/CallbackType.sol b/contracts/src/types/CallbackType.sol new file mode 100644 index 000000000..7998698a8 --- /dev/null +++ b/contracts/src/types/CallbackType.sol @@ -0,0 +1,10 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +pragma solidity ^0.8.20; + +enum CallbackType { + None, + JournalRequired +} diff --git a/contracts/src/types/Fulfillment.sol b/contracts/src/types/Fulfillment.sol index f21eee4ec..a1c809352 100644 --- a/contracts/src/types/Fulfillment.sol +++ b/contracts/src/types/Fulfillment.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {RequestId} from "./RequestId.sol"; +import {PredicateType} from "./Predicate.sol"; using FulfillmentLibrary for Fulfillment global; @@ -15,16 +16,13 @@ struct Fulfillment { RequestId id; /// @notice EIP-712 digest of request struct. bytes32 requestDigest; - /// @notice Image ID of the guest that was verifiably executed to satisfy the request. - /// @dev Must match the value in the request's requirements. - bytes32 imageId; - // TODO: Add a flag in the request to decide whether to post the journal. Note that - // if the journal and journal digest do not need to be delivered to the client, imageId will - // be replaced with claim digest, since it is captured in the requirements on the request, - // checked by the Assessor guest. - /// @notice Journal committed by the guest program execution. - /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. - bytes journal; + /// @notice The `PredicateType` of the request that is being fulfilled. + /// @dev When the `PredicateType` is `ClaimDigestMatch`, there callbacks are not supported + PredicateType predicateType; + /// @notice Claim Digest + bytes32 claimDigest; + /// @notice The callback data, if requested. + bytes callbackData; /// @notice Cryptographic proof for the validity of the execution results. /// @dev This will be sent to the `IRiscZeroVerifier` associated with this contract. bytes seal; diff --git a/contracts/src/types/Predicate.sol b/contracts/src/types/Predicate.sol index 255faab1d..9738b7a27 100644 --- a/contracts/src/types/Predicate.sol +++ b/contracts/src/types/Predicate.sol @@ -5,10 +5,17 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; +import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; + using PredicateLibrary for Predicate global; +using ReceiptClaimLib for ReceiptClaim; /// @title Predicate Struct and Library /// @notice Represents a predicate and provides functions to create and evaluate predicates. +/// Data field is used to store the specific data associated with the predicate. +/// - DigestMatch: (bytes32, bytes32) -> abi.encodePacked(imageId, journalHash) +/// - PrefixMatch: (bytes32, bytes) -> abi.encodePacked(imageId, prefix) +/// - ClaimDigestMatch: (bytes32) -> abi.encode(claimDigest) struct Predicate { PredicateType predicateType; bytes data; @@ -16,7 +23,8 @@ struct Predicate { enum PredicateType { DigestMatch, - PrefixMatch + PrefixMatch, + ClaimDigestMatch } library PredicateLibrary { @@ -26,15 +34,27 @@ library PredicateLibrary { /// @notice Creates a digest match predicate. /// @param hash The hash to match. /// @return A Predicate struct with type DigestMatch and the provided hash. - function createDigestMatchPredicate(bytes32 hash) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(hash)}); + + function createDigestMatchPredicate(bytes32 imageId, bytes32 hash) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encodePacked(imageId, hash)}); } /// @notice Creates a prefix match predicate. /// @param prefix The prefix to match. /// @return A Predicate struct with type PrefixMatch and the provided prefix. - function createPrefixMatchPredicate(bytes memory prefix) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.PrefixMatch, data: prefix}); + function createPrefixMatchPredicate(bytes32 imageId, bytes memory prefix) + internal + pure + returns (Predicate memory) + { + return Predicate({predicateType: PredicateType.PrefixMatch, data: abi.encodePacked(imageId, prefix)}); + } + + /// @notice Creates a claim digest match predicate. + /// @param claimDigest The claimDigest to match. + /// @return A Predicate struct with type ClaimDigestMatch and the provided claimDigest. + function createClaimDigestMatchPredicate(bytes32 claimDigest) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.ClaimDigestMatch, data: abi.encodePacked(claimDigest)}); } /// @notice Evaluates the predicate against the given journal and journal digest. @@ -42,20 +62,38 @@ library PredicateLibrary { /// @param journal The journal to evaluate against. /// @param journalDigest The digest of the journal. /// @return True if the predicate is satisfied, false otherwise. - function eval(Predicate memory predicate, bytes memory journal, bytes32 journalDigest) + function eval(Predicate memory predicate, bytes32 imageId, bytes memory journal, bytes32 journalDigest) internal pure returns (bool) { if (predicate.predicateType == PredicateType.DigestMatch) { - return bytes32(predicate.data) == journalDigest; + bytes memory dataJournal = sliceToEnd(predicate.data, 32); + return bytes32(dataJournal) == journalDigest; } else if (predicate.predicateType == PredicateType.PrefixMatch) { - return startsWith(journal, predicate.data); + bytes memory dataJournal = sliceToEnd(predicate.data, 32); + return startsWith(journal, dataJournal); + } else if (predicate.predicateType == PredicateType.ClaimDigestMatch) { + return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, journalDigest).digest(); } else { revert("Unreachable code"); } } + /// Taken from openzepplin util Bytes.sol + function sliceToEnd(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { + // sanitize + uint256 end = buffer.length; + + // allocate and copy + bytes memory result = new bytes(end - start); + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) + } + + return result; + } + /// @notice Checks if the journal starts with the given prefix. /// @param journal The journal to check. /// @param prefix The prefix to check for. diff --git a/contracts/src/types/Requirements.sol b/contracts/src/types/Requirements.sol index 79e032d76..bfecf7e4a 100644 --- a/contracts/src/types/Requirements.sol +++ b/contracts/src/types/Requirements.sol @@ -6,19 +6,18 @@ pragma solidity ^0.8.24; import {Predicate, PredicateLibrary} from "./Predicate.sol"; import {Callback, CallbackLibrary} from "./Callback.sol"; +import {CallbackType} from "./CallbackType.sol"; using RequirementsLibrary for Requirements global; struct Requirements { - bytes32 imageId; Callback callback; Predicate predicate; bytes4 selector; } library RequirementsLibrary { - string constant REQUIREMENTS_TYPE = - "Requirements(bytes32 imageId,Callback callback,Predicate predicate,bytes4 selector)"; + string constant REQUIREMENTS_TYPE = "Requirements(Callback callback,Predicate predicate,bytes4 selector)"; bytes32 constant REQUIREMENTS_TYPEHASH = keccak256(abi.encodePacked(REQUIREMENTS_TYPE, CallbackLibrary.CALLBACK_TYPE, PredicateLibrary.PREDICATE_TYPE)); @@ -29,7 +28,6 @@ library RequirementsLibrary { return keccak256( abi.encode( REQUIREMENTS_TYPEHASH, - requirements.imageId, CallbackLibrary.eip712Digest(requirements.callback), PredicateLibrary.eip712Digest(requirements.predicate), requirements.selector diff --git a/contracts/test/BoundlessMarket.t.sol b/contracts/test/BoundlessMarket.t.sol index daf28dc32..08b558c0f 100644 --- a/contracts/test/BoundlessMarket.t.sol +++ b/contracts/test/BoundlessMarket.t.sol @@ -34,6 +34,8 @@ import {HitPoints} from "../src/HitPoints.sol"; import {BoundlessMarket} from "../src/BoundlessMarket.sol"; import {Callback} from "../src/types/Callback.sol"; +import {CallbackData} from "../src/types/CallbackData.sol"; +import {CallbackType} from "../src/types/CallbackType.sol"; import {RequestId, RequestIdLibrary} from "../src/types/RequestId.sol"; import {AssessorJournal} from "../src/types/AssessorJournal.sol"; import {AssessorCallback} from "../src/types/AssessorCallback.sol"; @@ -49,7 +51,7 @@ import {AssessorReceipt} from "../src/types/AssessorReceipt.sol"; import {AssessorJournal} from "../src/types/AssessorJournal.sol"; import {Offer} from "../src/types/Offer.sol"; import {Requirements} from "../src/types/Requirements.sol"; -import {Predicate, PredicateType} from "../src/types/Predicate.sol"; +import {Predicate, PredicateLibrary, PredicateType} from "../src/types/Predicate.sol"; import {Input, InputType} from "../src/types/Input.sol"; import {IBoundlessMarket} from "../src/IBoundlessMarket.sol"; @@ -371,20 +373,32 @@ contract BoundlessMarketTest is Test { view returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) { - // initialize the fullfillments; one for each request; + // initialize the fulfillments; 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 callbackData; + bytes memory journal = journals[i]; + PredicateType predicateType = requests[i].requirements.predicate.predicateType; + bytes32 imageId = bytesToBytes32(requests[i].requirements.predicate.data); + if (predicateType != PredicateType.ClaimDigestMatch) { + claimDigest = ReceiptClaimLib.ok(imageId, sha256(journal)).digest(); + callbackData = abi.encode(CallbackData({imageId: imageId, journal: journal})); + } else { + claimDigest = bytesToBytes32(requests[i].requirements.predicate.data); + } Fulfillment memory fill = Fulfillment({ id: requests[i].id, requestDigest: MessageHashUtils.toTypedDataHash( boundlessMarket.eip712DomainSeparator(), requests[i].eip712Digest() ), - imageId: requests[i].requirements.imageId, - journal: journals[i], - seal: bytes("") + claimDigest: claimDigest, + callbackData: callbackData, + seal: bytes(""), + predicateType: predicateType }); fills[i] = fill; if (requests[i].requirements.selector != bytes4(0)) { @@ -395,7 +409,8 @@ contract BoundlessMarketTest is Test { AssessorCallback({ index: i, gasLimit: requests[i].requirements.callback.gasLimit, - addr: requests[i].requirements.callback.addr + addr: requests[i].requirements.callback.addr, + callbackType: requests[i].requirements.callback.callbackType }) ); } @@ -482,6 +497,14 @@ contract BoundlessMarketTest is Test { 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 { @@ -845,7 +868,7 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // the way it is hashed for signatures. Find a good way to avoid this. vm.expectRevert( abi.encodeWithSelector( - IBoundlessMarket.InsufficientBalance.selector, address(0x72C929E83beDC7370921131d8BF11B50d656aCE5) + IBoundlessMarket.InsufficientBalance.selector, address(0x03ACaf40482882feD244b288699C66Cc2B2e1489) ) ); boundlessMarket.lockRequestWithSignature(request, clientSignature, badProverSignature); @@ -869,7 +892,7 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // the way it is hashed for signatures. Find a good way to avoid this. vm.expectRevert( abi.encodeWithSelector( - IBoundlessMarket.InsufficientBalance.selector, address(0x73F8229890F1F0120B8786926fb44F0656b9416D) + IBoundlessMarket.InsufficientBalance.selector, address(0x96f1A0216373E86A42508A7b479539DE34111Cc7) ) ); boundlessMarket.lockRequestWithSignature(request, clientSignature, badProverSignature); @@ -2466,6 +2489,66 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { expectMarketBalanceUnchanged(); } + // Fulfill a batch of locked 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); + + for (uint256 i = 0; i < fills.length; i++) { + vm.expectEmit(true, true, true, true); + emit IBoundlessMarket.RequestFulfilled(fills[i].id, testProverAddress, fills[i]); + 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]; @@ -2523,11 +2606,11 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // Second request client = getClient(1); request = client.request(uint32(1)); + request.requirements = Requirements({ - imageId: bytes32(APP_IMAGE_ID_2), - predicate: Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(sha256(APP_JOURNAL_2))}), + predicate: PredicateLibrary.createDigestMatchPredicate(bytes32(APP_IMAGE_ID_2), sha256(APP_JOURNAL_2)), selector: bytes4(0), - callback: Callback({addr: address(0), gasLimit: 0}) + callback: Callback({addr: address(0), gasLimit: 0, callbackType: CallbackType.JournalRequired}) }); boundlessMarket.lockRequestWithSignature( request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) @@ -2538,14 +2621,14 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = createFillsAndSubmitRoot(requests, journals, testProverAddress); - bytes32 imageId0 = fills[0].imageId; - bytes memory journal0 = fills[0].journal; + bytes memory callbackData0 = fills[0].callbackData; + bytes32 claimDigest0 = fills[0].claimDigest; - fills[0].imageId = fills[1].imageId; - fills[1].imageId = imageId0; + fills[0].callbackData = fills[1].callbackData; + fills[1].callbackData = callbackData0; - fills[0].journal = fills[1].journal; - fills[1].journal = journal0; + fills[0].claimDigest = fills[1].claimDigest; + fills[1].claimDigest = claimDigest0; vm.expectRevert(VerificationFailed.selector); boundlessMarket.fulfill(fills, assessorReceipt); @@ -2842,7 +2925,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { bytes[] memory clientSignatures = new bytes[](1); clientSignatures[0] = clientSignature; - bytes32 claimDigest = ReceiptClaimLib.ok(fill.imageId, sha256(fill.journal)).digest(); + CallbackData memory callbackData = abi.decode(fill.callbackData, (CallbackData)); + bytes32 claimDigest = ReceiptClaimLib.ok(callbackData.imageId, sha256(callbackData.journal)).digest(); // If no selector is specified, we expect the call to verifyIntegrity to use the default // gas limit when verifying the application. @@ -2877,7 +2961,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { bytes[] memory clientSignatures = new bytes[](1); clientSignatures[0] = clientSignature; - bytes32 claimDigest = ReceiptClaimLib.ok(fill.imageId, sha256(fill.journal)).digest(); + CallbackData memory callbackData = abi.decode(fill.callbackData, (CallbackData)); + bytes32 claimDigest = ReceiptClaimLib.ok(callbackData.imageId, sha256(callbackData.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. @@ -3418,7 +3503,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // Create request with low gas callback ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 500_000}); + request.requirements.callback = + Callback({addr: address(mockCallback), gasLimit: 500_000, callbackType: CallbackType.JournalRequired}); bytes memory clientSignature = client.sign(request); client.snapshotBalance(); @@ -3438,7 +3524,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, true); emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); vm.expectEmit(true, true, true, false); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + 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 @@ -3456,7 +3543,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // 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}); + request.requirements.callback = + Callback({addr: address(mockHighGasCallback), gasLimit: 10_000, callbackType: CallbackType.JournalRequired}); bytes memory clientSignature = client.sign(request); client.snapshotBalance(); @@ -3494,7 +3582,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // Create request with low gas callback ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 100_000}); + request.requirements.callback = + Callback({addr: address(mockCallback), gasLimit: 100_000, callbackType: CallbackType.JournalRequired}); bytes memory clientSignature = client.sign(request); @@ -3520,7 +3609,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, otherProverAddress, fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); + emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); vm.prank(otherProverAddress); boundlessMarket.fulfill(fills, assessorReceipt); @@ -3540,7 +3630,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { Client client = getClient(1); ProofRequest memory request = client.request(1); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 100_000}); + request.requirements.callback = + Callback({addr: address(mockCallback), gasLimit: 100_000, callbackType: CallbackType.JournalRequired}); bytes memory clientSignature = client.sign(request); @@ -3566,7 +3657,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, otherProverAddress, fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + 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 @@ -3604,7 +3696,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { lockStake: 1 ether }) ); - request.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 100_000}); + request.requirements.callback = + Callback({addr: address(mockCallback), gasLimit: 100_000, callbackType: CallbackType.JournalRequired}); ProofRequest[] memory requests = new ProofRequest[](1); requests[0] = request; @@ -3637,7 +3730,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, otherProver.addr(), fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + 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 @@ -3666,7 +3760,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { lockStake: 1 ether }); ProofRequest memory requestA = client.request(1, offerA); - requestA.requirements.callback = Callback({addr: address(mockCallback), gasLimit: 10_000}); + requestA.requirements.callback = + Callback({addr: address(mockCallback), gasLimit: 10_000, callbackType: CallbackType.JournalRequired}); bytes memory clientSignatureA = client.sign(requestA); // Create second request with same ID but different callback @@ -3680,7 +3775,11 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { lockStake: offerA.lockStake }); ProofRequest memory requestB = client.request(1, offerB); - requestB.requirements.callback = Callback({addr: address(mockHighGasCallback), gasLimit: 300_000}); + requestB.requirements.callback = Callback({ + addr: address(mockHighGasCallback), + gasLimit: 300_000, + callbackType: CallbackType.JournalRequired + }); ProofRequest[] memory requests = new ProofRequest[](1); requests[0] = requestB; bytes memory clientSignatureB = client.sign(requestB); @@ -3712,7 +3811,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(requestB.id, testProverAddress, fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(requestB.requirements.imageId, APP_JOURNAL, fill.seal); + bytes32 imageId = bytesToBytes32(requestB.requirements.predicate.data); + emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); // Verify only the second request's callback was called diff --git a/contracts/test/TestUtils.sol b/contracts/test/TestUtils.sol index 42be58d03..1e2ead3e1 100644 --- a/contracts/test/TestUtils.sol +++ b/contracts/test/TestUtils.sol @@ -26,14 +26,22 @@ library TestUtils { address prover ) internal pure returns (ReceiptClaim memory) { bytes32[] memory leaves = new bytes32[](fills.length); + PredicateType[] memory predicateTypes = new PredicateType[](fills.length); for (uint256 i = 0; i < fills.length; i++) { - bytes32 claimDigest = ReceiptClaimLib.ok(fills[i].imageId, sha256(fills[i].journal)).digest(); - leaves[i] = AssessorCommitment(i, fills[i].id, fills[i].requestDigest, claimDigest).eip712Digest(); + predicateTypes[i] = fills[i].predicateType; + leaves[i] = AssessorCommitment(i, fills[i].id, fills[i].requestDigest, fills[i].claimDigest).eip712Digest(); } bytes32 root = MerkleProofish.processTree(leaves); - bytes memory journal = - abi.encode(AssessorJournal({root: root, selectors: selectors, callbacks: callbacks, prover: prover})); + bytes memory journal = abi.encode( + AssessorJournal({ + root: root, + selectors: selectors, + callbacks: callbacks, + predicateTypes: predicateTypes, + prover: prover + }) + ); return ReceiptClaimLib.ok(assessorImageId, sha256(journal)); } @@ -54,7 +62,7 @@ library TestUtils { { bytes32[] memory claimDigests = new bytes32[](fills.length); for (uint256 i = 0; i < fills.length; i++) { - claimDigests[i] = ReceiptClaimLib.ok(fills[i].imageId, sha256(fills[i].journal)).digest(); + claimDigests[i] = fills[i].claimDigest; } // compute the merkle tree of the batch (batchRoot, tree) = computeMerkleTree(claimDigests); diff --git a/contracts/test/clients/BaseClient.sol b/contracts/test/clients/BaseClient.sol index 51a18d741..58dd388ef 100644 --- a/contracts/test/clients/BaseClient.sol +++ b/contracts/test/clients/BaseClient.sol @@ -16,6 +16,7 @@ import {BoundlessMarketLib} from "../../src/libraries/BoundlessMarketLib.sol"; import {MerkleProofish} from "../../src/libraries/MerkleProofish.sol"; import {RequestId} from "../../src/types/RequestId.sol"; import {Callback} from "../../src/types/Callback.sol"; +import {CallbackType} from "../../src/types/CallbackType.sol"; import {ProofRequest} from "../../src/types/ProofRequest.sol"; import {Account} from "../../src/types/Account.sol"; import {RequestLock} from "../../src/types/RequestLock.sol"; @@ -24,7 +25,7 @@ import {Fulfillment} from "../../src/types/Fulfillment.sol"; import {AssessorJournal} from "../../src/types/AssessorJournal.sol"; import {Offer} from "../../src/types/Offer.sol"; import {Requirements} from "../../src/types/Requirements.sol"; -import {Predicate, PredicateType} from "../../src/types/Predicate.sol"; +import {PredicateLibrary, PredicateType} from "../../src/types/Predicate.sol"; import {Input, InputType} from "../../src/types/Input.sol"; import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; @@ -78,10 +79,9 @@ abstract contract BaseClient { function defaultRequirements() public pure returns (Requirements memory) { return Requirements({ - imageId: bytes32(APP_IMAGE_ID), - predicate: Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(sha256(APP_JOURNAL))}), + predicate: PredicateLibrary.createDigestMatchPredicate(bytes32(APP_IMAGE_ID), sha256(APP_JOURNAL)), selector: bytes4(0), - callback: Callback({addr: address(0), gasLimit: 0}) + callback: Callback({addr: address(0), gasLimit: 0, callbackType: CallbackType.JournalRequired}) }); } diff --git a/contracts/test/types/Predicate.t.sol b/contracts/test/types/Predicate.t.sol index 024a23825..a1a5b1c2f 100644 --- a/contracts/test/types/Predicate.t.sol +++ b/contracts/test/types/Predicate.t.sol @@ -6,12 +6,17 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; +import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.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 = keccak256("test"); - Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(hash); + Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash); assertEq( uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch" ); @@ -19,13 +24,13 @@ contract PredicateTest is Test { bytes memory journal = "test"; bytes32 journalDigest = keccak256(journal); - bool result = predicate.eval(journal, journalDigest); + bool result = predicate.eval(IMAGE_ID, journal, journalDigest); assertTrue(result, "Predicate evaluation should be true for matching digest"); } function testEvalDigestMatchFail() public pure { bytes32 hash = keccak256("test"); - Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(hash); + Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash); assertEq( uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch" ); @@ -33,25 +38,58 @@ contract PredicateTest is Test { bytes memory journal = "different test"; bytes32 journalDigest = keccak256(journal); - bool result = predicate.eval(journal, journalDigest); + bool result = predicate.eval(IMAGE_ID, journal, journalDigest); assertFalse(result, "Predicate evaluation should be false for non-matching digest"); } function testEvalPrefixMatch() public pure { bytes memory prefix = "prefix"; - Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(prefix); + Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix); bytes memory journal = "prefix and more"; - bool result = predicate.eval(journal, keccak256(journal)); + bool result = predicate.eval(IMAGE_ID, journal, keccak256(journal)); assertTrue(result, "Predicate evaluation should be true for matching prefix"); } function testEvalPrefixMatchFail() public pure { bytes memory prefix = "prefix"; - Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(prefix); + Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix); bytes memory journal = "different prefix"; - bool result = predicate.eval(journal, keccak256(journal)); + bool result = predicate.eval(IMAGE_ID, journal, keccak256(journal)); assertFalse(result, "Predicate evaluation should be false for non-matching prefix"); } + + function testEvalClaimDigestMatch() public pure { + bytes memory journal = "test"; + bytes32 journalDigest = keccak256(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(IMAGE_ID, journal, journalDigest); + assertTrue(result, "Predicate evaluation should be true for matching digest"); + } + + function testEvalClaimDigestMatchFail() public pure { + bytes memory journal = "test"; + bytes32 journalDigest = keccak256(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 = keccak256(journal); + + bool result = predicate.eval(IMAGE_ID, journal, journalDigest); + assertFalse(result, "Predicate evaluation should be false for non-matching digest"); + } } diff --git a/contracts/test/types/ProofRequest.t.sol b/contracts/test/types/ProofRequest.t.sol index e1b9aeb10..b04f22dbd 100644 --- a/contracts/test/types/ProofRequest.t.sol +++ b/contracts/test/types/ProofRequest.t.sol @@ -12,6 +12,7 @@ import {Requirements} from "../../src/types/Requirements.sol"; import {Input, InputType} from "../../src/types/Input.sol"; import {Predicate, PredicateType, PredicateLibrary} from "../../src/types/Predicate.sol"; import {Callback} from "../../src/types/Callback.sol"; +import {CallbackType} from "../../src/types/CallbackType.sol"; import {Offer} from "../../src/types/Offer.sol"; import {Account} from "../../src/types/Account.sol"; import {RequestId, RequestIdLibrary} from "../../src/types/RequestId.sol"; @@ -70,12 +71,8 @@ contract ProofRequestTest is Test { defaultProofRequest = ProofRequest({ id: RequestIdLibrary.from(wallet, idx), requirements: Requirements({ - imageId: APP_IMAGE_ID, - predicate: Predicate({ - predicateType: PredicateType.DigestMatch, - data: abi.encode(sha256(bytes("GUEST JOURNAL"))) - }), - callback: Callback({gasLimit: 0, addr: address(0)}), + predicate: PredicateLibrary.createDigestMatchPredicate(APP_IMAGE_ID, sha256(bytes("GUEST JOURNAL"))), + callback: Callback({gasLimit: 0, addr: address(0), callbackType: CallbackType.JournalRequired}), selector: bytes4(0) }), imageUrl: "https://image.dev.null", diff --git a/crates/assessor/src/lib.rs b/crates/assessor/src/lib.rs index f36a01dc2..52215e57d 100644 --- a/crates/assessor/src/lib.rs +++ b/crates/assessor/src/lib.rs @@ -9,8 +9,14 @@ use alloy_primitives::{Address, Keccak256, Signature, SignatureError}; use alloy_sol_types::{Eip712Domain, SolStruct}; -use boundless_market::contracts::{EIP712DomainSaltless, ProofRequest, RequestError}; -use risc0_zkvm::{sha::Digest, ReceiptClaim}; +use boundless_market::contracts::{ + CallbackType, EIP712DomainSaltless, FulfillmentClaimData, PredicateType, ProofRequest, + RequestError, +}; +use risc0_zkvm::{ + sha::{Digest, Digestible}, + ReceiptClaim, +}; use serde::{Deserialize, Serialize}; /// Errors that may occur in the assessor. @@ -39,8 +45,8 @@ pub enum Error { }, /// Predicate evaluation failure from [ProofRequest] [Requirements] - #[error("predicate evaluation failed")] - PredicateEvaluationError, + #[error("fulfillment requirements evaluation failed")] + RequirementsEvaluationError, } /// Fulfillment contains a signed request, including offer and requirements, @@ -52,8 +58,8 @@ pub struct Fulfillment { pub request: ProofRequest, /// The EIP-712 signature over the request. pub signature: Vec, - /// The journal of the request. - pub journal: Vec, + /// The fulfillment data of the request. + pub fulfillment_data: FulfillmentClaimData, } impl Fulfillment { @@ -77,15 +83,28 @@ impl Fulfillment { } /// Evaluates the requirements of the request. pub fn evaluate_requirements(&self) -> Result<(), Error> { - if !self.request.requirements.predicate.eval(&self.journal) { - return Err(Error::PredicateEvaluationError); + let requirements = &self.request.requirements; + + if requirements.predicate.predicateType == PredicateType::ClaimDigestMatch + && requirements.callback.callbackType != CallbackType::None + { + return Err(Error::RequirementsEvaluationError); + } + + if !self.request.requirements.predicate.eval(&self.fulfillment_data) { + return Err(Error::RequirementsEvaluationError); } Ok(()) } - /// Returns a [ReceiptClaim] for the fulfillment. - pub fn receipt_claim(&self) -> ReceiptClaim { - let image_id = Digest::from_bytes(self.request.requirements.imageId.0); - ReceiptClaim::ok(image_id, self.journal.clone()) + + /// Returns the claim digest for the fulfillment. + pub fn claim_digest(&self) -> Result { + match self.fulfillment_data { + FulfillmentClaimData::ClaimDigest(digest) => Ok(digest), + FulfillmentClaimData::ImageIdAndJournal(image_id, ref journal) => { + Ok(ReceiptClaim::ok(image_id, >::from(journal.clone())).digest()) + } + } } } @@ -161,8 +180,8 @@ mod tests { signers::local::PrivateKeySigner, }; use boundless_market::contracts::{ - eip712_domain, Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, - RequestInputType, Requirements, + eip712_domain, Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, + Requirements, }; use guest_assessor::ASSESSOR_GUEST_ELF; use guest_util::{ECHO_ELF, ECHO_ID}; @@ -175,10 +194,7 @@ mod tests { fn proving_request(id: u32, signer: Address, image_id: B256, prefix: Vec) -> ProofRequest { ProofRequest::new( RequestId::new(signer, id), - Requirements::new( - Digest::from_bytes(image_id.0), - Predicate { predicateType: PredicateType::PrefixMatch, data: prefix.into() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from_bytes(image_id.0), prefix)), "test", RequestInput { inputType: RequestInputType::Url, data: Default::default() }, Offer { @@ -207,7 +223,10 @@ mod tests { let claim = Fulfillment { request: proving_request, signature: signature.as_bytes().to_vec(), - journal: vec![1, 2, 3], + fulfillment_data: FulfillmentClaimData::ImageIdAndJournal( + Digest::from_bytes(B256::ZERO.0), + vec![1].into(), + ), }; claim.verify_signature(&eip712_domain(Address::ZERO, 1).alloy_struct()).unwrap(); @@ -225,16 +244,12 @@ mod tests { async fn setup_proving_request_and_signature( signer: &PrivateKeySigner, - ) -> (ProofRequest, Vec) { - let request = proving_request( - 1, - signer.address(), - to_b256(ECHO_ID.into()), - "test".as_bytes().to_vec(), - ); + ) -> (ProofRequest, B256, Vec) { + let image_id = to_b256(ECHO_ID.into()); + let request = proving_request(1, signer.address(), image_id, "test".as_bytes().to_vec()); let signature = request.sign_request(signer, Address::ZERO, 1).await.unwrap().as_bytes().to_vec(); - (request, signature) + (request, image_id, signature) } fn echo(input: &str) -> Receipt { @@ -273,14 +288,18 @@ mod tests { async fn test_assessor_e2e_singleton() { let signer = PrivateKeySigner::random(); // 1. Mock and sign a request - let (request, signature) = setup_proving_request_and_signature(&signer).await; + let (request, image_id, signature) = setup_proving_request_and_signature(&signer).await; // 2. Prove the request via the application guest let application_receipt = echo("test"); let journal = application_receipt.journal.bytes.clone(); // 3. Prove the Assessor - let claims = vec![Fulfillment { request, signature, journal }]; + let claims = vec![Fulfillment { + request, + signature, + fulfillment_data: FulfillmentClaimData::from_image_id_and_journal(*image_id, journal), + }]; assessor(claims, vec![application_receipt]); } @@ -289,12 +308,16 @@ mod tests { async fn test_assessor_e2e_two_leaves() { let signer = PrivateKeySigner::random(); // 1. Mock and sign a request - let (request, signature) = setup_proving_request_and_signature(&signer).await; + let (request, image_id, signature) = setup_proving_request_and_signature(&signer).await; // 2. Prove the request via the application guest let application_receipt = echo("test"); let journal = application_receipt.journal.bytes.clone(); - let claim = Fulfillment { request, signature, journal }; + let claim = Fulfillment { + request, + signature, + fulfillment_data: FulfillmentClaimData::from_image_id_and_journal(*image_id, journal), + }; // 3. Prove the Assessor reusing the same leaf twice let claims = vec![claim.clone(), claim]; diff --git a/crates/bench/src/lib.rs b/crates/bench/src/lib.rs index 2c37be707..fefcf051c 100644 --- a/crates/bench/src/lib.rs +++ b/crates/bench/src/lib.rs @@ -274,10 +274,10 @@ pub async fn run(args: &MainArgs) -> Result<()> { id: boundless_client.boundless_market.request_id_from_rand().await?, offer: Offer { biddingStart: bidding_start, ..initial_offer }, input: request_input, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::digest_match( image_id, - Predicate::digest_match(journal.digest()), - ), + journal.digest(), + )), imageUrl: inital_request.imageUrl.clone(), }; tracing::debug!("Request: {:?}", request); diff --git a/crates/boundless-cli/src/bin/boundless.rs b/crates/boundless-cli/src/bin/boundless.rs index 4cecf9c4c..cd9025c95 100644 --- a/crates/boundless-cli/src/bin/boundless.rs +++ b/crates/boundless-cli/src/bin/boundless.rs @@ -68,7 +68,7 @@ use boundless_cli::{convert_timestamp, DefaultProver, OrderFulfilled}; use clap::{Args, CommandFactory, Parser, Subcommand}; use clap_complete::aot::Shell; use risc0_aggregation::SetInclusionReceiptVerifierParameters; -use risc0_ethereum_contracts::{set_verifier::SetVerifierService, IRiscZeroVerifier}; +use risc0_ethereum_contracts::{set_verifier::SetVerifierService, IRiscZeroVerifier, Receipt}; use risc0_zkvm::{ compute_image_id, default_executor, sha::{Digest, Digestible}, @@ -82,7 +82,8 @@ use url::Url; use boundless_market::{ contracts::{ boundless_market::{BoundlessMarketService, FulfillmentTx, UnlockedRequest}, - Offer, ProofRequest, RequestInputType, Selector, + boundless_market_contract::CallbackData, + Offer, PredicateType, ProofRequest, RequestInputType, Selector, }, input::GuestEnv, request_builder::{OfferParams, RequirementParams}, @@ -661,29 +662,60 @@ async fn handle_request_command(cmd: &RequestCommands, client: StandardClient) - } RequestCommands::GetProof { request_id } => { tracing::info!("Fetching proof for request 0x{:x}", request_id); - let (journal, seal) = + let (callback_data, seal) = client.boundless_market.get_request_fulfillment(*request_id).await?; tracing::info!("Successfully retrieved proof for request 0x{:x}", request_id); tracing::info!( - "Journal: {} - Seal: {}", - serde_json::to_string_pretty(&journal)?, + "Callback Data: {} - Seal: {}", + serde_json::to_string_pretty(&callback_data)?, serde_json::to_string_pretty(&seal)? ); Ok(()) } RequestCommands::VerifyProof { request_id, image_id } => { tracing::info!("Verifying proof for request 0x{:x}", request_id); - let (journal, seal) = - client.boundless_market.get_request_fulfillment(*request_id).await?; - let journal_digest = <[u8; 32]>::from(Journal::new(journal.to_vec()).digest()).into(); + let verifier_address = client.deployment.verifier_router_address.context("no address provided for the verifier router; specify a verifier address with --verifier-address")?; let verifier = IRiscZeroVerifier::new(verifier_address, client.provider()); - - verifier - .verify(seal, *image_id, journal_digest) - .call() - .await - .map_err(|_| anyhow::anyhow!("Verification failed"))?; + let (callback_data, seal) = + client.boundless_market.get_request_fulfillment(*request_id).await?; + let (req, _) = client.boundless_market.get_submitted_request(*request_id, None).await?; + let predicate = req.requirements.predicate; + match predicate.predicateType { + PredicateType::ClaimDigestMatch => { + let claim_digest = >::try_from(predicate.data.as_ref())?; + verifier + .verifyIntegrity(Receipt { seal, claimDigest: claim_digest }) + .call() + .await + .map_err(|_| anyhow::anyhow!("Verification failed"))?; + todo!() + } + PredicateType::DigestMatch | PredicateType::PrefixMatch => { + let CallbackData { imageId, journal } = + CallbackData::abi_decode(&callback_data)?; + ensure!( + imageId == *image_id, + "Image ID mismatch: expected {:?}, got {:?}", + imageId, + *image_id + ); + let journal_digest = + <[u8; 32]>::from(Journal::new(journal.to_vec()).digest()).into(); + + verifier + .verify(seal, *image_id, journal_digest) + .call() + .await + .map_err(|_| anyhow::anyhow!("Verification failed"))?; + } + _ => { + bail!( + "Unsupported predicate type for verification: {:?}", + predicate.predicateType + ); + } + } tracing::info!("Successfully verified proof for request 0x{:x}", request_id); Ok(()) @@ -716,10 +748,11 @@ async fn handle_proving_command(cmd: &ProvingCommands, client: StandardClient) - let session_info = execute(&request).await?; let journal = session_info.journal.bytes; - if !request.requirements.predicate.eval(&journal) { - tracing::error!("Predicate evaluation failed for request"); - bail!("Predicate evaluation failed"); - } + // TODO(ec2): how do we check this? + // if !request.requirements.predicate.eval(request.requirements.imageId, &journal) { + // tracing::error!("Predicate evaluation failed for request"); + // bail!("Predicate evaluation failed"); + // } tracing::info!("Successfully executed request 0x{:x}", request.id); tracing::debug!("Journal: {:?}", journal); @@ -1183,28 +1216,32 @@ where if opts.preflight { tracing::info!("Running request preflight check"); let session_info = execute(&request).await?; - let journal = session_info.journal.bytes; + let _journal = session_info.journal.bytes; // Verify image ID if available if let Some(claim) = session_info.receipt_claim { - ensure!( - claim.pre.digest().as_bytes() == request.requirements.imageId.as_slice(), - "Image ID mismatch: requirements ({}) do not match the given program ({})", - hex::encode(request.requirements.imageId), - hex::encode(claim.pre.digest().as_bytes()) - ); + if let Some(image_id) = request.requirements.image_id() { + ensure!( + claim.pre.digest() == image_id, + "Image ID mismatch: requirements ({}) do not match the given program ({})", + image_id, + claim.pre.digest(), + ); + } + tracing::debug!("Skipping image ID check, no image ID provided"); } else { tracing::debug!("Cannot check image ID; session info doesn't have receipt claim"); } - // Verify predicate - ensure!( - request.requirements.predicate.eval(&journal), - "Preflight failed: Predicate evaluation failed. Journal: {}, Predicate type: {:?}, Predicate data: {}", - hex::encode(&journal), - request.requirements.predicate.predicateType, - hex::encode(&request.requirements.predicate.data) - ); + // TODO(ec2): how do we check when we have custom claim digests? + // // Verify predicate + // ensure!( + // request.requirements.predicate.eval(request.requirements.imageId, &journal), + // "Preflight failed: Predicate evaluation failed. Journal: {}, Predicate type: {:?}, Predicate data: {}", + // hex::encode(&journal), + // request.requirements.predicate.predicateType, + // hex::encode(&request.requirements.predicate.data) + // ); tracing::info!("Preflight check passed"); } else { @@ -1416,10 +1453,8 @@ async fn handle_config_command(args: &MainArgs) -> Result<()> { mod tests { use std::net::{Ipv4Addr, SocketAddr}; - use alloy::primitives::aliases::U96; - use boundless_market::contracts::{ - Predicate, PredicateType, RequestId, RequestInput, Requirements, - }; + use alloy::primitives::{aliases::U96, Bytes}; + use boundless_market::contracts::{Predicate, RequestId, RequestInput, Requirements}; use super::*; @@ -1445,10 +1480,7 @@ mod tests { fn generate_request(id: u32, addr: &Address) -> ProofRequest { ProofRequest::new( RequestId::new(*addr, id), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(ECHO_ID, Bytes::default())), format!("file://{ECHO_PATH}"), RequestInput::builder().write_slice(&[0x41, 0x41, 0x41, 0x41]).build_inline().unwrap(), Offer { @@ -2033,7 +2065,7 @@ mod tests { config: config.clone(), command: Command::Request(Box::new(RequestCommands::VerifyProof { request_id, - image_id: request.requirements.imageId, + image_id: <[u8; 32]>::from(request.requirements.image_id().unwrap()).into(), })), }) .await diff --git a/crates/boundless-cli/src/lib.rs b/crates/boundless-cli/src/lib.rs index 7da83eb58..b1f34cafa 100644 --- a/crates/boundless-cli/src/lib.rs +++ b/crates/boundless-cli/src/lib.rs @@ -36,8 +36,9 @@ use risc0_zkvm::{ use boundless_market::{ contracts::{ - AssessorJournal, AssessorReceipt, EIP712DomainSaltless, - Fulfillment as BoundlessFulfillment, RequestInputType, + boundless_market_contract::CallbackData, AssessorJournal, AssessorReceipt, + EIP712DomainSaltless, Fulfillment as BoundlessFulfillment, FulfillmentClaimData, + PredicateType, RequestInputType, }, input::GuestEnv, selector::{is_groth16_selector, SupportedSelectors}, @@ -248,11 +249,14 @@ impl DefaultProver { let order_claim = ReceiptClaim::ok(order_image_id, order_journal.clone()); let order_claim_digest = order_claim.digest(); - let fill = Fulfillment { - request: req.clone(), - signature: sig.into(), - journal: order_journal.clone(), + let fulfillment_data = match req.requirements.predicate.predicateType { + PredicateType::ClaimDigestMatch => FulfillmentClaimData::from_claim_digest( + req.requirements.predicate.claim_digest().unwrap(), + ), + _ => FulfillmentClaimData::from_image_id_and_journal(order_image_id, order_journal), }; + let fill = + Fulfillment { request: req.clone(), signature: sig.into(), fulfillment_data }; Ok::<_, anyhow::Error>((order_receipt, order_claim, order_claim_digest, fill)) }); @@ -306,13 +310,30 @@ impl DefaultProver { } else { order_inclusion_receipt.abi_encode_seal()? }; - + let predicate_type = req.requirements.predicate.predicateType; + + let (claim_digest, callback_data) = match predicate_type { + PredicateType::ClaimDigestMatch => ( + <[u8; 32]>::from(fills[i].fulfillment_data.claim_digest().unwrap()).into(), + vec![], + ), + _ => ( + <[u8; 32]>::from(claims[i].digest()).into(), + CallbackData { + imageId: <[u8; 32]>::from(fills[i].fulfillment_data.image_id().unwrap()) + .into(), + journal: fills[i].fulfillment_data.journal().unwrap().clone(), + } + .abi_encode(), + ), + }; let fulfillment = BoundlessFulfillment { + claimDigest: claim_digest, + callbackData: callback_data.into(), id: req.id, requestDigest: req.eip712_signing_hash(&self.domain.alloy_struct()), - imageId: req.requirements.imageId, - journal: fills[i].journal.clone().into(), seal: order_seal.into(), + predicateType: predicate_type, }; boundless_fills.push(fulfillment); @@ -391,7 +412,7 @@ mod tests { ) -> (ProofRequest, Signature) { let request = ProofRequest::new( RequestId::new(signer.address(), 0), - Requirements::new(Digest::from(ECHO_ID), Predicate::prefix_match(vec![1])) + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), vec![1])) .with_selector(match selector { Some(selector) => FixedBytes::from(selector as u32), None => UNSPECIFIED_SELECTOR, diff --git a/crates/boundless-market/src/contracts/artifacts/AssessorCallback.sol b/crates/boundless-market/src/contracts/artifacts/AssessorCallback.sol index a31f1727f..0ef1e0e27 100644 --- a/crates/boundless-market/src/contracts/artifacts/AssessorCallback.sol +++ b/crates/boundless-market/src/contracts/artifacts/AssessorCallback.sol @@ -4,6 +4,8 @@ // as found in the LICENSE-BSL file. pragma solidity ^0.8.20; +import {CallbackType} from "./CallbackType.sol"; + struct AssessorCallback { /// @notice The index of the fill in the request uint16 index; @@ -11,4 +13,6 @@ struct AssessorCallback { address addr; /// @notice Maximum gas to use for the callback uint96 gasLimit; + /// @notice The type of callback + CallbackType callbackType; } diff --git a/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol b/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol index dfa3920f2..51a772d75 100644 --- a/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol +++ b/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {AssessorCallback} from "./AssessorCallback.sol"; +import {PredicateType} from "./Predicate.sol"; import {Selector} from "./Selector.sol"; /// @title Assessor Journal Struct @@ -17,6 +18,8 @@ struct AssessorJournal { /// @notice The (optional) selectors for the requests committed by the assessor. /// @dev This is used to verify the fulfillment of the request against its selector's seal. Selector[] selectors; + /// @notice The list of `PredicateType` for each request. + PredicateType[] predicateTypes; /// @notice Root of the Merkle tree committing to the set of proven claims. /// @dev In the case of a batch of size one, this may simply be the eip712Digest of the `AssessorCommitment`. bytes32 root; diff --git a/crates/boundless-market/src/contracts/artifacts/Callback.sol b/crates/boundless-market/src/contracts/artifacts/Callback.sol index f3864708f..144d0ba99 100644 --- a/crates/boundless-market/src/contracts/artifacts/Callback.sol +++ b/crates/boundless-market/src/contracts/artifacts/Callback.sol @@ -6,6 +6,8 @@ pragma solidity ^0.8.24; using CallbackLibrary for Callback global; +import {CallbackType} from "./CallbackType.sol"; + /// @title Callback Struct and Library /// @notice Represents a callback configuration for proof delivery struct Callback { @@ -13,16 +15,18 @@ struct Callback { address addr; /// @notice Maximum gas to use for the callback uint96 gasLimit; + /// @notice The type of callback + CallbackType callbackType; } library CallbackLibrary { - string constant CALLBACK_TYPE = "Callback(address addr,uint96 gasLimit)"; + string constant CALLBACK_TYPE = "Callback(address addr,uint96 gasLimit,uint8 callbackType)"; bytes32 constant CALLBACK_TYPEHASH = keccak256(bytes(CALLBACK_TYPE)); /// @notice Computes the EIP-712 digest for the given callback /// @param callback The callback to compute the digest for /// @return The EIP-712 digest of the callback function eip712Digest(Callback memory callback) internal pure returns (bytes32) { - return keccak256(abi.encode(CALLBACK_TYPEHASH, callback.addr, callback.gasLimit)); + return keccak256(abi.encode(CALLBACK_TYPEHASH, callback.addr, callback.gasLimit, callback.callbackType)); } } diff --git a/crates/boundless-market/src/contracts/artifacts/CallbackData.sol b/crates/boundless-market/src/contracts/artifacts/CallbackData.sol new file mode 100644 index 000000000..a90d473c6 --- /dev/null +++ b/crates/boundless-market/src/contracts/artifacts/CallbackData.sol @@ -0,0 +1,19 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +pragma solidity ^0.8.24; + +using CallbackDataLibrary for CallbackData global; + +/// @title Callback Struct and Library +/// @notice Represents a callback configuration for proof delivery +struct CallbackData { + /// @notice Image ID of the guest that was verifiably executed to satisfy the request. + bytes32 imageId; + /// @notice Journal committed by the guest program execution. + /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. + bytes journal; +} + +library CallbackDataLibrary {} diff --git a/crates/boundless-market/src/contracts/artifacts/CallbackType.sol b/crates/boundless-market/src/contracts/artifacts/CallbackType.sol new file mode 100644 index 000000000..7998698a8 --- /dev/null +++ b/crates/boundless-market/src/contracts/artifacts/CallbackType.sol @@ -0,0 +1,10 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +pragma solidity ^0.8.20; + +enum CallbackType { + None, + JournalRequired +} diff --git a/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol b/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol index f21eee4ec..a1c809352 100644 --- a/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol +++ b/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {RequestId} from "./RequestId.sol"; +import {PredicateType} from "./Predicate.sol"; using FulfillmentLibrary for Fulfillment global; @@ -15,16 +16,13 @@ struct Fulfillment { RequestId id; /// @notice EIP-712 digest of request struct. bytes32 requestDigest; - /// @notice Image ID of the guest that was verifiably executed to satisfy the request. - /// @dev Must match the value in the request's requirements. - bytes32 imageId; - // TODO: Add a flag in the request to decide whether to post the journal. Note that - // if the journal and journal digest do not need to be delivered to the client, imageId will - // be replaced with claim digest, since it is captured in the requirements on the request, - // checked by the Assessor guest. - /// @notice Journal committed by the guest program execution. - /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. - bytes journal; + /// @notice The `PredicateType` of the request that is being fulfilled. + /// @dev When the `PredicateType` is `ClaimDigestMatch`, there callbacks are not supported + PredicateType predicateType; + /// @notice Claim Digest + bytes32 claimDigest; + /// @notice The callback data, if requested. + bytes callbackData; /// @notice Cryptographic proof for the validity of the execution results. /// @dev This will be sent to the `IRiscZeroVerifier` associated with this contract. bytes seal; diff --git a/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol b/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol index 6e037c243..2dc263b39 100644 --- a/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol +++ b/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol @@ -165,6 +165,10 @@ interface IBoundlessMarket { /// @dev selector efc954a6 error BatchSizeExceedsLimit(uint256 batchSize, uint256 limit); + /// @notice Error when a journal is provided but does not match the claim digest + /// TODO(ec2): selector + error ClaimDigestMismatch(bytes32 expected, bytes32 calculated); + /// @notice Check if the given request has been locked (i.e. accepted) by a prover. /// @dev When a request is locked, only the prover it is locked to can be paid to fulfill the job. /// @param requestId The ID of the request. diff --git a/crates/boundless-market/src/contracts/artifacts/Predicate.sol b/crates/boundless-market/src/contracts/artifacts/Predicate.sol index 255faab1d..9738b7a27 100644 --- a/crates/boundless-market/src/contracts/artifacts/Predicate.sol +++ b/crates/boundless-market/src/contracts/artifacts/Predicate.sol @@ -5,10 +5,17 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; +import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; + using PredicateLibrary for Predicate global; +using ReceiptClaimLib for ReceiptClaim; /// @title Predicate Struct and Library /// @notice Represents a predicate and provides functions to create and evaluate predicates. +/// Data field is used to store the specific data associated with the predicate. +/// - DigestMatch: (bytes32, bytes32) -> abi.encodePacked(imageId, journalHash) +/// - PrefixMatch: (bytes32, bytes) -> abi.encodePacked(imageId, prefix) +/// - ClaimDigestMatch: (bytes32) -> abi.encode(claimDigest) struct Predicate { PredicateType predicateType; bytes data; @@ -16,7 +23,8 @@ struct Predicate { enum PredicateType { DigestMatch, - PrefixMatch + PrefixMatch, + ClaimDigestMatch } library PredicateLibrary { @@ -26,15 +34,27 @@ library PredicateLibrary { /// @notice Creates a digest match predicate. /// @param hash The hash to match. /// @return A Predicate struct with type DigestMatch and the provided hash. - function createDigestMatchPredicate(bytes32 hash) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(hash)}); + + function createDigestMatchPredicate(bytes32 imageId, bytes32 hash) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encodePacked(imageId, hash)}); } /// @notice Creates a prefix match predicate. /// @param prefix The prefix to match. /// @return A Predicate struct with type PrefixMatch and the provided prefix. - function createPrefixMatchPredicate(bytes memory prefix) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.PrefixMatch, data: prefix}); + function createPrefixMatchPredicate(bytes32 imageId, bytes memory prefix) + internal + pure + returns (Predicate memory) + { + return Predicate({predicateType: PredicateType.PrefixMatch, data: abi.encodePacked(imageId, prefix)}); + } + + /// @notice Creates a claim digest match predicate. + /// @param claimDigest The claimDigest to match. + /// @return A Predicate struct with type ClaimDigestMatch and the provided claimDigest. + function createClaimDigestMatchPredicate(bytes32 claimDigest) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.ClaimDigestMatch, data: abi.encodePacked(claimDigest)}); } /// @notice Evaluates the predicate against the given journal and journal digest. @@ -42,20 +62,38 @@ library PredicateLibrary { /// @param journal The journal to evaluate against. /// @param journalDigest The digest of the journal. /// @return True if the predicate is satisfied, false otherwise. - function eval(Predicate memory predicate, bytes memory journal, bytes32 journalDigest) + function eval(Predicate memory predicate, bytes32 imageId, bytes memory journal, bytes32 journalDigest) internal pure returns (bool) { if (predicate.predicateType == PredicateType.DigestMatch) { - return bytes32(predicate.data) == journalDigest; + bytes memory dataJournal = sliceToEnd(predicate.data, 32); + return bytes32(dataJournal) == journalDigest; } else if (predicate.predicateType == PredicateType.PrefixMatch) { - return startsWith(journal, predicate.data); + bytes memory dataJournal = sliceToEnd(predicate.data, 32); + return startsWith(journal, dataJournal); + } else if (predicate.predicateType == PredicateType.ClaimDigestMatch) { + return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, journalDigest).digest(); } else { revert("Unreachable code"); } } + /// Taken from openzepplin util Bytes.sol + function sliceToEnd(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { + // sanitize + uint256 end = buffer.length; + + // allocate and copy + bytes memory result = new bytes(end - start); + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) + } + + return result; + } + /// @notice Checks if the journal starts with the given prefix. /// @param journal The journal to check. /// @param prefix The prefix to check for. diff --git a/crates/boundless-market/src/contracts/artifacts/Requirements.sol b/crates/boundless-market/src/contracts/artifacts/Requirements.sol index 79e032d76..bfecf7e4a 100644 --- a/crates/boundless-market/src/contracts/artifacts/Requirements.sol +++ b/crates/boundless-market/src/contracts/artifacts/Requirements.sol @@ -6,19 +6,18 @@ pragma solidity ^0.8.24; import {Predicate, PredicateLibrary} from "./Predicate.sol"; import {Callback, CallbackLibrary} from "./Callback.sol"; +import {CallbackType} from "./CallbackType.sol"; using RequirementsLibrary for Requirements global; struct Requirements { - bytes32 imageId; Callback callback; Predicate predicate; bytes4 selector; } library RequirementsLibrary { - string constant REQUIREMENTS_TYPE = - "Requirements(bytes32 imageId,Callback callback,Predicate predicate,bytes4 selector)"; + string constant REQUIREMENTS_TYPE = "Requirements(Callback callback,Predicate predicate,bytes4 selector)"; bytes32 constant REQUIREMENTS_TYPEHASH = keccak256(abi.encodePacked(REQUIREMENTS_TYPE, CallbackLibrary.CALLBACK_TYPE, PredicateLibrary.PREDICATE_TYPE)); @@ -29,7 +28,6 @@ library RequirementsLibrary { return keccak256( abi.encode( REQUIREMENTS_TYPEHASH, - requirements.imageId, CallbackLibrary.eip712Digest(requirements.callback), PredicateLibrary.eip712Digest(requirements.predicate), requirements.selector diff --git a/crates/boundless-market/src/contracts/boundless_market.rs b/crates/boundless-market/src/contracts/boundless_market.rs index 9246051ac..615161125 100644 --- a/crates/boundless-market/src/contracts/boundless_market.rs +++ b/crates/boundless-market/src/contracts/boundless_market.rs @@ -1035,7 +1035,7 @@ impl BoundlessMarketService

{ if let Some((event, _)) = logs.first() { return Ok(( - event.fulfillment.journal.clone(), + event.fulfillment.callbackData.clone(), // TODO(ec2) event.fulfillment.seal.clone(), event.prover, )); @@ -1101,7 +1101,7 @@ impl BoundlessMarketService

{ Err(MarketError::RequestNotFound(request_id)) } - /// Returns journal and seal if the request is fulfilled. + /// Returns CallbackData containing the journal and image id (if available) and seal if the request is fulfilled. pub async fn get_request_fulfillment( &self, request_id: U256, @@ -1109,8 +1109,9 @@ impl BoundlessMarketService

{ match self.get_status(request_id, None).await? { RequestStatus::Expired => Err(MarketError::RequestHasExpired(request_id)), RequestStatus::Fulfilled => { - let (journal, seal, _) = self.query_fulfilled_event(request_id, None, None).await?; - Ok((journal, seal)) + let (callback_data, seal, _) = + self.query_fulfilled_event(request_id, None, None).await?; + Ok((callback_data, seal)) } _ => Err(MarketError::RequestNotFulfilled(request_id)), } @@ -1154,7 +1155,7 @@ impl BoundlessMarketService

{ self.query_request_submitted_event(request_id, None, None).await } - /// Returns journal and seal if the request is fulfilled. + /// Returns callback data and seal if the request is fulfilled. /// /// This method will poll the status of the request until it is Fulfilled or Expired. /// Polling is done at intervals of `retry_interval` until the request is Fulfilled, Expired or @@ -1170,9 +1171,9 @@ impl BoundlessMarketService

{ match status { RequestStatus::Expired => return Err(MarketError::RequestHasExpired(request_id)), RequestStatus::Fulfilled => { - let (journal, seal, _) = + let (callback_data, seal, _) = self.query_fulfilled_event(request_id, None, None).await?; - return Ok((journal, seal)); + return Ok((callback_data, seal)); } _ => { tracing::info!( diff --git a/crates/boundless-market/src/contracts/bytecode.rs b/crates/boundless-market/src/contracts/bytecode.rs index a3e72cab0..750d819c9 100644 --- a/crates/boundless-market/src/contracts/bytecode.rs +++ b/crates/boundless-market/src/contracts/bytecode.rs @@ -1,7 +1,7 @@ // Auto-generated file, do not edit manually alloy::sol! { - #[sol(rpc, bytecode = "610100346101b957601f61610f38819003918201601f19168301916001600160401b038311848410176101bd578084926060946040528339810103126101b9578051906001600160a01b03821682036101b9576020810151604090910151916001600160a01b03831683036101b9573060805260a05260c05260e0527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c166101aa576002600160401b03196001600160401b03821601610141575b604051615f3d90816101d2823960805181818161199a0152611a2d015260a0518181816124590152818161311d0152818161360a01526136b8015260c051818181610c9701528181610fc40152613187015260e0518181816113f40152818161153a015281816118fe01528181611dda015281816122ec0152614f0f0152f35b6001600160401b0319166001600160401b039081177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f6100c1565b63f92ee8a960e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f905f3560e01c90816308c84e70146124475750806317e828c41461242e57806318e0e161146123a15780631ce030241461238357806325d5971f1461224c5780632abff1f2146121415780632e1a7d4d146121235780632f13a90a1461209b57806331750cc714612083578063380f9c381461201157806341451f9414611f6057806345bc4d1014611bf2578063487c7b8c14611bd75780634f1ef286146119ee57806352d1902d14611987578063553c02481461196b5780635b07fdd8146119485780635f37a5031461192d5780635fbb4921146118e857806360dfd4a914611850578063635556241461178c57806370a0823114611749578063715018a6146116ca57806379ba50971461167d5780637ed75e36146115fb5780638040fdc0146114925780638094e614146113ce57806380bd38db146113ad57806381bf6c241461136457806384b0196e146110d85780638da5cb5b146110a35780638e3b694514611021578063956b0960146110045780639f04f42014610fe75780639fe9428c14610fac578063ad3cb1cc14610f63578063ae7330f114610eba578063b4206dd214610e7d578063c515c15f14610df8578063c5fe2fac14610dcb578063cb09e7c014610d85578063cb74db1114610d5c578063cb82cc8f14610d3e578063cdc9712314610c44578063cfbebd8b14610945578063d0e30db01461092e578063e30c3978146108f9578063f2800f1a146108a2578063f2fde38b1461081c578063f399e22e1461026d5763ffa1ad741461024f575f80fd5b3461026a578060031936011261026a57602060405160018152f35b80fd5b503461026a57604036600319011261026a57610287612508565b906024356001600160401b038111610818576102a7903690600401612488565b90925f80516020615eb1833981519152549060ff8260401c1615916001600160401b03811680159081610810575b6001149081610806575b1590816107fd575b506107ee5767ffffffffffffffff1981166001175f80516020615eb183398151915255826107c2575b5061031961598f565b61032161598f565b6001600160a01b038116156107ae5761033990614e74565b61034161598f565b604091825161035084826127a8565b601081526f12509bdd5b991b195cdcd3585c9ad95d60821b602082015283519061037a85836127a8565b60018252603160f81b602083015261039061598f565b61039861598f565b8051906001600160401b03821161079a576103c05f80516020615df183398151915254612d1d565b601f811161073e575b50602090601f83116001146106c2576103f992918891836105cb575b50508160011b915f199060031b1c19161790565b5f80516020615df1833981519152555b8051906001600160401b0382116106ae576104315f80516020615e3183398151915254612d1d565b601f8111610652575b50602090601f83116001146105d65761046992918791836105cb5750508160011b915f199060031b1c19161790565b5f80516020615e31833981519152555b835f80516020615e5183398151915255835f80516020615ed1833981519152556001600160401b0381116105b7576104bb816104b6600254612d1d565b612d55565b83601f821160011461054857819085966104ea9495969261053d5750508160011b915f199060031b1c19161790565b6002555b6104f6575080f35b5f80516020615eb1833981519152805460ff60401b1916905551600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a180f35b013590505f806103e5565b60028552601f198216955f80516020615db183398151915291865b88811061059f57508360019596979810610586575b505050811b016002556104ee565b01355f19600384901b60f8161c191690555f8080610578565b90926020600181928686013581550194019101610563565b634e487b7160e01b84526041600452602484fd5b015190505f806103e5565b5f80516020615e3183398151915287528187209190601f198416885b81811061063a5750908460019594939210610622575b505050811b015f80516020615e3183398151915255610479565b01515f1960f88460031b161c191690555f8080610608565b929360206001819287860151815501950193016105f2565b5f80516020615e3183398151915287525f80516020615f11833981519152601f840160051c810191602085106106a4575b601f0160051c01905b818110610699575061043a565b87815560010161068c565b9091508190610683565b634e487b7160e01b86526041600452602486fd5b5f80516020615df183398151915288528188209190601f198416895b818110610726575090846001959493921061070e575b505050811b015f80516020615df183398151915255610409565b01515f1960f88460031b161c191690555f80806106f4565b929360206001819287860151815501950193016106de565b5f80516020615df183398151915288525f80516020615e71833981519152601f840160051c81019160208510610790575b601f0160051c01905b81811061078557506103c9565b888155600101610778565b909150819061076f565b634e487b7160e01b87526041600452602487fd5b631e4fbdf760e01b84526004849052602484fd5b68ffffffffffffffffff191668010000000000000001175f80516020615eb1833981519152555f610310565b63f92ee8a960e01b8552600485fd5b9050155f6102e7565b303b1591506102df565b8491506102d5565b5080fd5b503461026a57602036600319011261026a57610836612508565b61083e6147e7565b5f80516020615ef183398151915280546001600160a01b0319166001600160a01b039283169081179091555f80516020615e11833981519152549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227008380a380f35b503461026a57602036600319011261026a57600435906108c182613e5c565b156108e7576040816020936001600160401b039352808452205460a01c16604051908152f35b60249163d2be005d60e01b8252600452fd5b503461026a578060031936011261026a575f80516020615ef1833981519152546040516001600160a01b039091168152602090f35b508060031936011261026a57610942613e89565b80f35b503461026a57606036600319011261026a576004356001600160401b0381116108185761016081600401916003199036030112610818576024356001600160401b038111610c405761099b903690600401612488565b916044356001600160401b038111610c3c576109bb903690600401612488565b8235916109c783614a0b565b9290916109dc6109d73688612a24565b61418b565b6109ed816109e861592e565b6153e5565b95600160c01b1615610bfb57604051630b135d3f60e11b815260208180610a198d8d8c60048501612cbb565b03816001600160a01b0389165afa908115610bf0578b91610bc1575b506001600160e01b0319166374eca2c160e11b01610bb2575b604051610a5c6060826127a8565b60218152602081017f4c6f636b526571756573742850726f6f66526571756573742072657175657374815260408201602960f81b9052610a9a613f78565b90610aa3613fc2565b8d610aac614007565b610ab46140c1565b610abc613ef1565b91610ac561410e565b94604051978897602089019a5180918c5e880160208101918783528051926020849201905e0160200185815281516020819301825e0184815281516020819301825e0183815281516020819301825e0182815281516020819301825e0190815281516020819301825e018d815203601f1981018252610b4490826127a8565b519020906040519060208201928352604082015260408152610b676060826127a8565b519020610b7261592e565b90610b7c916153e5565b913690610b88926127e4565b610b91916159ba565b610b9d919592956159f4565b610ba685614607565b96610942989196615019565b638baa579f60e01b8a5260048afd5b610be3915060203d602011610be9575b610bdb81836127a8565b810190614506565b5f610a35565b503d610bd1565b6040513d8d823e3d90fd5b610c1b610c12610c0c368c8c6127e4565b886159ba565b909291926159f4565b6001600160a01b03858116911614610a4e57638baa579f60e01b8a5260048afd5b8480fd5b8280fd5b503461026a578060031936011261026a57604051908060025490610c6782612d1d565b8085529160018116908115610d175750600114610ccd575b610cc984610c8f818603826127a8565b6040519182917f00000000000000000000000000000000000000000000000000000000000000008352604060208401526040830190612635565b0390f35b600281525f80516020615db1833981519152939250905b808210610cfd57509091508101602001610c8f82610c7f565b919260018160209254838588010152019101909291610ce4565b60ff191660208087019190915292151560051b85019092019250610c8f9150839050610c7f565b503461026a57602036600319011261026a5761094260043533614edf565b503461026a57602036600319011261026a576020610d7b600435613e5c565b6040519015158152f35b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b03610db6612508565b16815260018452205460601c16604051908152f35b503461026a57610cc9610dec610de0366128c7565b95949094939193613e20565b60405191829182612659565b503461026a57602036600319011261026a57604060e091600435815280602052208054906001600160601b0360026001830154920154916040519360018060a01b03811685526001600160401b038160a01c16602086015262ffffff81871c16604086015260f81c6060850152818116608085015260601c1660a083015260c0820152f35b503461026a57610942610e8f366124b5565b91610e9a8135614a0b565b90610ea785858386614526565b610eb084614607565b9690953395615019565b503461026a57606036600319011261026a5780610ed5612508565b6044356001600160401b038111610f5f57610ef4903690600401612488565b6001600160a01b0390921691823b15610f5a57610f2d92849283604051809681958294636691f64760e01b845260243560048501612cbb565b03925af18015610f4f57610f3e5750f35b81610f48916127a8565b61026a5780f35b6040513d84823e3d90fd5b505050fd5b5050fd5b503461026a578060031936011261026a5750610cc9604051610f866040826127a8565b60058152640352e302e360dc1b6020820152604051918291602083526020830190612635565b503461026a578060031936011261026a5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b503461026a578060031936011261026a57602060405161c3508152f35b503461026a578060031936011261026a5760206040516107d08152f35b503461026a5761103036612838565b969095919490936001600160a01b039092169190823b15610c3c5791611071939185809460405196879586948593636691f64760e01b855260048501612cbb565b03925af18015610f4f5761108e575b610cc9610dec868686613aa1565b6110998280926127a8565b61026a5780611080565b503461026a578060031936011261026a575f80516020615e11833981519152546040516001600160a01b039091168152602090f35b503461026a578060031936011261026a575f80516020615e5183398151915254158061134e575b15611311576040519080825f80516020615df1833981519152549161112383612d1d565b80835292600181169081156112f2575060011461129b575b611147925003836127a8565b6040519080825f80516020615e31833981519152549161116683612d1d565b808352926001811690811561127c5750600114611225575b61119291939250936111c9959403836127a8565b60206111d7604051936111a583866127a8565b8385525f368137604051968796600f60f81b885260e08589015260e0880190612635565b908682036040880152612635565b904660608601523060808601528260a086015284820360c08601528080855193848152019401925b82811061120e57505050500390f35b8351855286955093810193928101926001016111ff565b505f80516020615e3183398151915283529082905f80516020615f118339815191525b8183106112605750509060206111929282010161117e565b6020919350806001915483858901015201910190918492611248565b6020925061119294915060ff191682840152151560051b82010161117e565b505f80516020615df183398151915283529082905f80516020615e718339815191525b8183106112d65750509060206111479282010161113b565b60209193508060019154838589010152019101909184926112be565b6020925061114794915060ff191682840152151560051b82010161113b565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b505f80516020615ed183398151915254156110ff565b503461026a57602036600319011261026a576113a16020916040611389600435614a0b565b6001600160a01b039091168352600185529120614a45565b90506040519015158152f35b503461026a57610cc9610dec6113c2366128c7565b95949094939193613dde565b503461026a5760a036600319011261026a576004358160443560ff8116809103610818577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690813b15610c4057829160e48392604051948593849263d505accf60e01b84523360048501523060248501528960448501526024356064850152608484015260643560a484015260843560c48401525af161147d575b506109428233614edf565b81611487916127a8565b61081857815f611472565b503461026a57602036600319011261026a576004356114af6147e7565b30825260016020526001600160601b03604083205460601c166001600160601b036114d9836147b6565b16116115e85761150f6114eb826147b6565b30845260016020526001600160601b03604085209181835460601c16031690612cd2565b60405163a9059cbb60e01b815233600482015260248101829052602081604481866001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19081156115dd5783916115ae575b501561159f576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203092a280f35b6312171d8360e31b8252600482fd5b6115d0915060203d6020116115d6575b6115c881836127a8565b810190612d05565b5f61156a565b503d6115be565b6040513d85823e3d90fd5b63112fed8b60e31b825230600452602482fd5b503461026a5761160a36612838565b969095919490936001600160a01b039092169190823b15610c3c579161164b939185809460405196879586948593636691f64760e01b855260048501612cbb565b03925af18015610f4f57611668575b610cc9610dec868686613d22565b6116738280926127a8565b61026a578061165a565b503461026a578060031936011261026a575f80516020615ef183398151915254336001600160a01b03909116036116b75761094233614e74565b63118cdaa760e01b815233600452602490fd5b503461026a578060031936011261026a576116e36147e7565b5f80516020615ef183398151915280546001600160a01b03199081169091555f80516020615e118339815191528054918216905581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b0361177a612508565b16815260018452205416604051908152f35b503461026a57602036600319011261026a576004356117a96147e7565b30825260016020526001600160601b036040832054166001600160601b036117d0836147b6565b16116115e8576117df816147b6565b30835260016020526001600160601b03806040852092818454160316166001600160601b03198254161790558180808084335af161181b613d75565b501561159f576040519081527f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6560203092a280f35b503461026a57602036600319011261026a5760046060604060209383358152808552206002604051916118828361270d565b805460018060a01b03811684526001600160401b038160a01c168785015262ffffff8160e01c16604085015260f81c848401526001600160601b0360018201548181166080860152851c1660a0840152015460c082015201511615156040519015158152f35b503461026a578060031936011261026a576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461026a57610cc9610dec611942366126b8565b91613d22565b503461026a578060031936011261026a57602061196361592e565b604051908152f35b503461026a578060031936011261026a57602090604051908152f35b503461026a578060031936011261026a577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036119df5760206040515f80516020615e918339815191528152f35b63703e46dd60e11b8152600490fd5b50604036600319011261026a57611a03612508565b906024356001600160401b03811161081857611a2390369060040161281a565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016308114908115611bb5575b50611ba657611a656147e7565b6040516352d1902d60e01b8152926001600160a01b0381169190602085600481865afa80958596611b72575b50611aaa57634c9c8ce360e01b84526004839052602484fd5b9091845f80516020615e918339815191528103611b605750813b15611b4e575f80516020615e9183398151915280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8480a28151839015611b345780836020611b3095519101845af4611b2a613d75565b91615d52565b5080f35b50505034611b3f5780f35b63b398979f60e01b8152600490fd5b634c9c8ce360e01b8452600452602483fd5b632a87526960e21b8552600452602484fd5b9095506020813d602011611b9e575b81611b8e602093836127a8565b81010312610c3c5751945f611a91565b3d9150611b81565b63703e46dd60e11b8252600482fd5b5f80516020615e91833981519152546001600160a01b0316141590505f611a58565b503461026a57610cc9610dec611bec366126b8565b91613aa1565b503461026a57602036600319011261026a57600435611c33611c1382614a0b565b6001600160a01b0390911680855260016020526040852090929190614a45565b5015611f4c57818352826020526040832060405190611c518261270d565b805460018060a01b03811683526001600160401b038160a01c16602084015262ffffff8160e01c16604084015260f81c60608301526001810154600260808401926001600160601b03831684526001600160601b0360a086019360601c168352015460c08401526004606084015116611f38576001606084015116611f24576001600160401b03611ce1846149e9565b16421115611efb5784865260208690526040862080546001600160f81b03811660f891821c60041790911b6001600160f81b0319161781558690600101556001600160601b038151166107d08102908082046107d01490151715611ee757611d5e6001600160601b039392612710611d6393049485915116613a64565b6147b6565b936002606060018060a01b038651169501511615155f14611e8357505060018060a01b03821685526001602052611db460408620611dae856001600160601b03835460601c16613a71565b90612cd2565b60405163a9059cbb60e01b815261dead60048201526024810182905291602083604481897f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18015611e78577f79ca7c80cf57b513ffdf8aa37ec70e40757f5e0d35219241860bb4b4c2fa7616946060946001600160601b0392611e5b575b5060405193845216602083015260018060a01b03166040820152a280f35b611e739060203d6020116115d6576115c881836127a8565b611e3d565b6040513d88823e3d90fd5b9092506001600160601b0330933088526001602052611eaf60408920611dae8885835460601c16613a71565b511690865260016020526001600160601b03611ed2604088209282845416613a71565b166001600160601b0319825416179055611db4565b634e487b7160e01b87526011600452602487fd5b6044866001600160401b0387611f10876149e9565b9063079c66ab60e41b845260045216602452fd5b631cfdeebb60e01b86526004859052602486fd5b633231064d60e11b86526004859052602486fd5b63d2be005d60e01b83526004829052602483fd5b503461026a57602036600319011261026a5760043590611f7f82613e5c565b156108e757604081602093612000935280845220600260405191611fa28361270d565b805460018060a01b03811684526001600160401b038160a01c168685015262ffffff8160e01c16604085015260f81c60608401526001600160601b036001820154818116608086015260601c1660a0840152015460c08201526149e9565b6001600160401b0360405191168152f35b507f514a642174f202700c54726383c13321326925ff87df30f0bfbf49d9adfc41a661203c366124b5565b9291909234612076575b61207060405192839260408452612060604085018361387c565b9184830360208601523596612c9b565b0390a280f35b61207e613e89565b612046565b503461026a57610942612095366126b8565b91612f16565b503461026a576120aa36612562565b9a93969297909960018060a09b949b9897981b031691823b15610c3c57916120ed939185809460405196879586948593636691f64760e01b855260048501612cbb565b03925af18015610f4f5761210e575b610cc9610dec8a8a8a8a8a8a8a613e20565b6121198280926127a8565b61026a57806120fc565b503461026a57602036600319011261026a576109426004353361481a565b503461026a57602036600319011261026a576004356001600160401b03811161081857612172903690600401612488565b61217d9291926147e7565b6001600160401b0381116122385761219a816104b6600254612d1d565b81601f82116001146121cd57819083946121c7949261053d5750508160011b915f199060031b1c19161790565b60025580f35b60028352601f198216935f80516020615db183398151915291845b8681106122205750836001959610612207575b505050811b0160025580f35b01355f19600384901b60f8161c191690555f80806121fb565b909260206001819286860135815501940191016121e8565b634e487b7160e01b82526041600452602482fd5b503461026a57602036600319011261026a5760043533825260016020526001600160601b03604083205460601c166001600160601b0361228b836147b6565b1611612370576122c161229d826147b6565b33845260016020526001600160601b03604085209181835460601c16031690612cd2565b60405163a9059cbb60e01b815233600482015260248101829052602081604481866001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19081156115dd578391612351575b501561159f576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203392a280f35b61236a915060203d6020116115d6576115c881836127a8565b5f61231c565b63112fed8b60e31b825233600452602482fd5b503461026a578060031936011261026a576020604051620186a08152f35b3461242a576123af36612562565b97999598909691959294929091906001600160a01b0316803b1561242a576123f19a5f80946040519d8e9586948593636691f64760e01b855260048501612cbb565b03925af196871561241f57610cc998610dec9861240f575b50613dde565b5f612419916127a8565b5f612409565b6040513d5f823e3d90fd5b5f80fd5b3461242a5761244561243f366124b5565b91612bd3565b005b3461242a575f36600319011261242a577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b9181601f8401121561242a578235916001600160401b03831161242a576020838186019501011161242a57565b90604060031983011261242a576004356001600160401b03811161242a57610160818403600319011261242a5760040191602435906001600160401b03821161242a5761250491600401612488565b9091565b600435906001600160a01b038216820361242a57565b35906001600160a01b038216820361242a57565b9181601f8401121561242a578235916001600160401b03831161242a576020808501948460051b01011161242a57565b60e060031982011261242a576004356001600160a01b038116810361242a5791602435916044356001600160401b03811161242a57816125a491600401612488565b929092916064356001600160401b03811161242a57816125c691600401612532565b929092916084356001600160401b03811161242a57816125e891600401612532565b9290929160a4356001600160401b03811161242a578161260a91600401612532565b9290929160c435906001600160401b03821161242a57608090829003600319011261242a5760040190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b83831061268b57505050505090565b90919293946020806126a9600193603f198682030187528951612635565b9701930193019193929061267c565b604060031982011261242a576004356001600160401b03811161242a57816126e291600401612532565b92909291602435906001600160401b03821161242a57608090829003600319011261242a5760040190565b60e081019081106001600160401b0382111761272857604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b0382111761272857604052565b608081019081106001600160401b0382111761272857604052565b604081019081106001600160401b0382111761272857604052565b606081019081106001600160401b0382111761272857604052565b90601f801991011681019081106001600160401b0382111761272857604052565b6001600160401b03811161272857601f01601f191660200190565b9291926127f0826127c9565b916127fe60405193846127a8565b82948184528183011161242a578281602093845f960137010152565b9080601f8301121561242a57816020612835933591016127e4565b90565b60a060031982011261242a576004356001600160a01b038116810361242a5791602435916044356001600160401b03811161242a578161287a91600401612488565b929092916064356001600160401b03811161242a578161289c91600401612532565b92909291608435906001600160401b03821161242a57608090829003600319011261242a5760040190565b608060031982011261242a576004356001600160401b03811161242a57816128f191600401612532565b929092916024356001600160401b03811161242a578161291391600401612532565b929092916044356001600160401b03811161242a578161293591600401612532565b92909291606435906001600160401b03821161242a57608090829003600319011261242a5760040190565b35906001600160601b038216820361242a57565b35906001600160e01b03198216820361242a57565b35906001600160401b038216820361242a57565b359063ffffffff8216820361242a57565b91908260e091031261242a576040516129c68161270d565b60c080829480358452602081013560208501526129e560408201612989565b60408501526129f66060820161299d565b6060850152612a076080820161299d565b6080850152612a1860a0820161299d565b60a08501520135910152565b91906101608382031261242a5760405190612a3e8261273c565b81938035835260208101356001600160401b03811161242a5781018083039060a0821261242a576040805192612a7384612757565b82358452601f19011261242a57604051612a8c81612772565b612a986020830161251e565b8152612aa660408301612960565b6020820152602083015260608101356001600160401b03811161242a57810160408186031261242a5760405191612adc83612772565b8135600281101561242a5783526020820135926001600160401b03841161242a57612b0e87612b1e956080950161281a565b6020820152604085015201612974565b6060820152602084015260408101356001600160401b03811161242a57810182601f8201121561242a5782816020612b58933591016127e4565b604084015260608101356001600160401b03811161242a5781019160408382031261242a5760405192612b8a84612772565b8035600281101561242a5784526020810135926001600160401b03841161242a57608094612bbe84612bce9688950161281a565b60208201526060870152016129ae565b910152565b91823591600160c01b831615612c7a57612bf99260201c6001600160a01b031684614526565b905b6040612c38611d5e612c28612c0f85614607565b90506001600160401b03429116109460803691016129ae565b6001600160401b034216906146d2565b6001600160601b03825191612c4c8361278d565b60018352602083018590521691018190526001607f1b9115612c74576001607e1b5b1717905d565b5f612c6e565b505050612c95612c8d6109d73684612a24565b6109e861592e565b90612bfb565b908060209392818452848401375f828201840152601f01601f1916010190565b604090612835949281528160208201520191612c9b565b80546bffffffffffffffffffffffff60601b191660609290921b6bffffffffffffffffffffffff60601b16919091179055565b9081602091031261242a5751801515810361242a5790565b90600182811c92168015612d4b575b6020831014612d3757565b634e487b7160e01b5f52602260045260245ffd5b91607f1691612d2c565b601f8111612d61575050565b60025f5260205f20906020601f840160051c83019310612d9b575b601f0160051c01905b818110612d90575050565b5f8155600101612d85565b9091508190612d7c565b6001600160401b0381116127285760051b60200190565b903590601e198136030182121561242a57018035906001600160401b03821161242a57602001918160061b3603831361242a57565b9190811015612e015760061b0190565b634e487b7160e01b5f52603260045260245ffd5b3561ffff8116810361242a5790565b9190811015612e015760051b81013590609e198136030182121561242a570190565b903590601e198136030182121561242a57018035906001600160401b03821161242a5760200191813603831361242a57565b805115612e015760200190565b8051821015612e015760209160051b010190565b6020815260406020612eb5845183838601526060850190612635565b93015191015290565b903590601e198136030182121561242a57018035906001600160401b03821161242a5760200191606082023603831361242a57565b356001600160a01b038116810361242a5790565b359061ffff8216820361242a57565b61ffff821161381457612f2882612da5565b90612f3660405192836127a8565b828252601f19612f4584612da5565b01366020840137612f5583612da5565b90612f6360405192836127a8565b838252601f19612f7285612da5565b013660208401376040850193612f888587612dbc565b90505f5b81811061375a5750505f5b8181106132d25750505050612fab906148ec565b612fc4612fbb6020850185612ebe565b91909385612dbc565b612fd360608796939601612ef3565b9160405193612fe185612757565b612fea81612da5565b91612ff860405193846127a8565b818352606060208401920281019036821161242a57915b81831061328157505050835261302481612da5565b9461303260405196876127a8565b818652602086019160061b81019036821161242a57915b818310613242575050506020820193845260408201928352606082019060018060a01b031681526040519260208401946020865260c08501935193608060408701528451809152602060e087019501905f5b8181106131fd575050505192603f19858203016060860152602080855192838152019401905f5b8181106131cd5750509051608085015250516001600160a01b031660a0830152819003601f19810182526020925f9290916130fd90826127a8565b604051918291518091835e8101838152039060025afa1561241f575f51907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906131509080612e46565b9091803b1561242a575f92613184926040519586948593849363ab750e7560e01b8552606060048601526064850191612c9b565b907f00000000000000000000000000000000000000000000000000000000000000006024840152604483015203915afa801561241f576131c15750565b5f6131cb916127a8565b565b8251805161ffff1687526020908101516001600160e01b03191681880152604090960195909201916001016130c2565b8251805161ffff1688526020818101516001600160a01b0316818a01526040918201516001600160601b0316918901919091526060909701969092019160010161309b565b60408336031261242a576020604091825161325c81612772565b61326586612f07565b8152613272838701612974565b83820152815201920191613049565b60608336031261242a57602060609160405161329c8161278d565b6132a586612f07565b81526132b283870161251e565b838201526132c260408701612960565b604082015281520192019161300f565b6132dd818385612e24565b9060205f6132ee6060850185612e46565b908160405192839283378101838152039060025afa1561241f575f515f60806040516133198161273c565b82815282602082015260405161332e81612772565b838152836020820152604082015282606082015201526040519061335182612772565b5f82525f60208301526040519061336782612772565b8152602081015f815260205f600c6040516b1c9a5cd8cc0b93dd5d1c1d5d60a21b815260025afa1561241f576020915f918251915190516040519185830193845260408301526060820152600160f91b6080820152606281526133cb6082826127a8565b604051918291518091835e8101838152039060025afa1561241f575f51906040516133f58161273c565b6040850135815260208101907fa3acc27117418996340b84e5a90f3ef4c49d22c79e44aad822ec9c313e1eb8e282526040810192835260608101915f83526080820194855260205f60126040517172697363302e52656365697074436c61696d60701b815260025afa1561241f575f51925191519051945184515190600382101561374657945160209081015160408051808401978852908101959095526060850193909352608084019690965260a08301949094526001600160f81b031960f894851b811660c0840152931b90921660c4830152600160fa1b60c883015260aa82525f916134e560ca826127a8565b604051918291518091835e8101838152039060025afa1561241f575f516040519061350f82612757565b8282526020820191843583526040810192602086013584526060820193838552608094605660405161354188826127a8565b81815275742c6279746573333220636c61696d4469676573742960501b606060208301927f4173736573736f72436f6d6d69746d656e742875696e7432353620696e64657884527f2c75696e743235362069642c627974657333322072657175657374446967657360408201520152209351925191519051916040519360208501958652604085015260608401528583015260a082015260a081526135e760c0826127a8565b5190206135f4848a612e85565b526135ff8388612e85565b516136b257613656937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316926136409190810190612e46565b94906040519561364f87612772565b36916127e4565b84526020840152803b1561242a57613685925f916040518080968194631599ead560e01b835260048301612e99565b039161c350fa91821561241f576001926136a2575b505b01612f97565b5f6136ac916127a8565b5f61369a565b6136ee937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316926136409190810190612e46565b84526020840152803b1561242a5761371d925f916040518080968194631599ead560e01b835260048301612e99565b03915afa91821561241f57600192613736575b5061369c565b5f613740916127a8565b5f613730565b634e487b7160e01b5f52602160045260245ffd5b60206137708261376a8a8c612dbc565b90612df1565b013563ffffffff60e01b811680910361242a576137b56137ab61ffff6137a361379e8661376a8f8f90612dbc565b612e15565b168688612e24565b6080810190612e46565b60049291921161242a5760016137ed61ffff6137e661379e878f978f61376a9163ffffffff60e01b90351699612dbc565b1689612e85565b528181036137ff575050600101612f8c565b632e2ce35360e21b5f5260045260245260445ffd5b506377e4aa5360e11b5f5260045261ffff60245260445ffd5b9035603e198236030181121561242a570190565b6002111561374657565b9035601e198236030181121561242a5701602081359101916001600160401b03821161242a57813603831361242a57565b90813581526020820135609e198336030181121561242a5782016101606020830152803561016083015260018060a01b036138b96020830161251e565b166101808301526001600160601b036138d460408301612960565b166101a08301526138e8606082018261382d565b9060a06101c0840152813591600283101561242a57613921613934918461391161396d96613841565b610200870152602081019061384b565b6040610220870152610240860191612c9b565b906001600160e01b03199061394b90608001612974565b166101e084015261395f604085018561384b565b908483036040860152612c9b565b61397a606084018461382d565b82820360608401528035600281101561242a576101409260406139b18594846139a56139c196613841565b8452602081019061384b565b9190928160208201520191612c9b565b936080810135608085015260a081013560a08501526001600160401b036139ea60c08301612989565b1660c085015263ffffffff613a0160e0830161299d565b1660e085015263ffffffff613a19610100830161299d565b1661010085015263ffffffff613a32610120830161299d565b16610120850152013591015290565b601f19810191908211613a5057565b634e487b7160e01b5f52601160045260245ffd5b91908203918211613a5057565b906001600160601b03809116911601906001600160601b038211613a5057565b9190811015612e01576060020190565b92919092613ab0828583612f16565b613ab984612da5565b91613ac760405193846127a8565b848352601f19613ad686612da5565b015f5b818110613d115750508294613aed81612da5565b613afa60405191826127a8565b818152601f19613b0983612da5565b013660208301376020830191613b1f8385612ebe565b90505f5b818110613cd25750505f5b818110613b3e5750505050505050565b613b49818388612e24565b90613b5f613b5960608801612ef3565b83614b58565b90613b6a838b612e85565b52613cc957613b798185612e85565b5180613b8c575b50600191505b01613b2e565b613b968688612ebe565b90915f19810191908211613a5057613bad92613a91565b916040613bbc60208501612ef3565b930135906001600160601b03821680920361242a57613bde6060820182612e46565b94613bec6080840184612e46565b966001600160a01b0390921694909190853b1561242a5760405f8787613c50839760019d613c3e87519b8c9a8b998a9763a12da43f60e01b895201356004880152606060248801526064870191612c9b565b84810360031901604486015291612c9b565b0393f19081613cb9575b50613cb2577f5c5960582bfc7a494183b4e9a66bfe8ecffc07a83a48d136e732400f7b98bf5090613c89613d75565b92613ca860405192839283526040602084015235946040830190612635565b0390a25b5f613b80565b5050613cac565b5f613cc3916127a8565b5f613c5a565b60019150613b86565b613ce681613ce08789612ebe565b90613a91565b9060018101808211613a5057613d0a61ffff613d03600195612e15565b1687612e85565b5201613b23565b806060602080938801015201613ad9565b82606092613d3292959495613aa1565b92016001600160a01b03613d4582612ef3565b165f5260016020526001600160601b0360405f20541680613d64575050565b613d706131cb92612ef3565b61481a565b3d15613d9f573d90613d86826127c9565b91613d9460405193846127a8565b82523d5f602084013e565b606090565b9190811015612e015760051b8101359061015e198136030182121561242a570190565b90821015612e01576125049160051b810190612e46565b919695949392905f5b818110613dfd5750505050612835939450613d22565b80613e1a8a61243f8387613e14600197898c613da4565b93613dc7565b01613de7565b919695949392905f5b818110613e3f5750505050612835939450613aa1565b80613e568a61243f8387613e14600197898c613da4565b01613e29565b613e68613e8591614a0b565b6001600160a01b039091165f908152600160205260409020614a45565b5090565b613e92346147b6565b335f5260016020526001600160601b03613eb360405f209282845416613a71565b166001600160601b03198254161790556040513481527fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c60203392a2565b60405190613f006080836127a8565b605a82527f6c2c496e70757420696e7075742c4f66666572206f66666572290000000000006060837f50726f6f66526571756573742875696e743235362069642c526571756972656d60208201527f656e747320726571756972656d656e74732c737472696e6720696d616765557260408201520152565b60405190613f876060836127a8565b60268252654c696d69742960d01b6040837f43616c6c6261636b286164647265737320616464722c75696e7439362067617360208201520152565b60405190613fd16060836127a8565b60218252602960f81b6040837f496e7075742875696e743820696e707574547970652c6279746573206461746160208201520152565b6040519061401660c0836127a8565b6084825263616b652960e01b60a0837f4f666665722875696e74323536206d696e50726963652c75696e74323536206d60208201527f617850726963652c75696e7436342062696464696e6753746172742c75696e7460408201527f33322072616d705570506572696f642c75696e743332206c6f636b54696d656f60608201527f75742c75696e7433322074696d656f75742c75696e74323536206c6f636b537460808201520152565b604051906140d06060836127a8565b602982526874657320646174612960b81b6040837f5072656469636174652875696e743820707265646963617465547970652c627960208201520152565b6040519061411d6080836127a8565b605382527274652c6279746573342073656c6563746f722960681b6060837f526571756972656d656e7473286279746573333220696d61676549642c43616c60208201527f6c6261636b2063616c6c6261636b2c507265646963617465207072656469636160408201520152565b614193613ef1565b61419b613f78565b6141a3613fc2565b906141ac614007565b6141b46140c1565b6141bc61410e565b916040519485946020860197805160208192018a5e860160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815203601f198101825261423290826127a8565b51902090805190602081015161424661410e565b61424e613f78565b6142566140c1565b90604051918291602083019480516020819201875e830160208101915f83528051926020849201905e016020015f815281516020819301825e015f815203601f19810182526142a590826127a8565b5190209080519060208101516142b9613f78565b8051906020012090600160a01b6001900381511690602001516001600160601b0316604051916020830193845260408301526060820152606081526142ff6080826127a8565b5190209060408101516143106140c1565b805190602001209080519061432482613841565b6020015180519060200120604051916020830193845261434381613841565b604083015260608201526060815261435c6080826127a8565b5190209063ffffffff60e01b90606001511691604051936020850195865260408501526060840152608083015260a082015260a0815261439d60c0826127a8565b5190209060408101518051906020012060608201516143ba613fc2565b80519060200120908051906143ce82613841565b602001518051906020012060405191602083019384526143ed81613841565b60408301526060820152606081526144066080826127a8565b5190209160800151614416614007565b60405180602081019280516020819201855e8101602081015f905203602001601f198101825261444690826127a8565b5190209080519060208101519060408101516001600160401b0316606082015163ffffffff16608083015163ffffffff169160a084015163ffffffff169360c0015194604051966020880198895260408801526060870152608086015260a085015260c084015260e083015261010082015261010081526144c9610120826127a8565b51902092604051946020860196875260408601526060850152608084015260a083015260c082015260c0815261450060e0826127a8565b51902090565b9081602091031261242a57516001600160e01b03198116810361242a5790565b9291614538612c8d6109d73687612a24565b9335600160c01b16156145d1579160209161456a93604051809581948293630b135d3f60e11b84528960048501612cbb565b03916001600160a01b0316620186a0fa90811561241f575f916145b2575b506001600160e01b0319166374eca2c160e11b016145a35790565b638baa579f60e01b5f5260045ffd5b6145cb915060203d602011610be957610bdb81836127a8565b5f614588565b6145e36145e9916145f29436916127e4565b846159ba565b909391936159f4565b6001600160a01b039081169116036145a35790565b6146159060803691016129ae565b9081516020830151106146a35763ffffffff606083015116608083019063ffffffff825116106146a35763ffffffff90511660a083019063ffffffff825116106146a3576146829063ffffffff6001600160401b03604061467587615402565b96015116915116906146b2565b9162ffffff6001600160401b036146998386614ff9565b16116146a3579190565b6341abc80160e01b5f5260045ffd5b906001600160401b03809116911601906001600160401b038211613a5057565b9060408201906001600160401b03808351169116908111156147b0576001600160401b036146ff84615402565b1681116147a9576001600160401b03825116906001600160401b03614730606086019363ffffffff855116906146b2565b16811115614742575050506020015190565b61476f906001600160401b0363ffffffff6147636020880151885190613a64565b94511694511690613a64565b925192818102918183041490151715613a5057811561479557048101809111613a505790565b634e487b7160e01b5f52601260045260245ffd5b5050505f90565b50505190565b6001600160601b0381116147d0576001600160601b031690565b6306dfcc6560e41b5f52606060045260245260445ffd5b5f80516020615e11833981519152546001600160a01b0316330361480757565b63118cdaa760e01b5f523360045260245ffd5b9060018060a01b03821691825f5260016020526001600160601b0360405f2054166001600160601b0361484c846147b6565b16116148d9575f8080848194614861826147b6565b88845260016020526001600160601b03806040862092818454160316166001600160601b03198254161790555af1614897613d75565b50156148ca5760207f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6591604051908152a2565b6312171d8360e31b5f5260045ffd5b8263112fed8b60e31b5f5260045260245ffd5b8051156146a35760018151146149e05780515b60018111614915575061491190612e78565b5190565b60018101808211613a505760011c905f5b8160011c81106149745750600180821614614942575b506148ff565b5f198101908111613a50576149579083612e85565b515f198201828111613a505761496d9084612e85565b525f61493c565b600181901b906001600160ff1b0381168103613a50576149948286612e85565b5160018301809311613a50576149ac60019387612e85565b5190818110156149d1575f5260205260405f205b6149ca8287612e85565b5201614926565b905f5260205260405f206149c0565b61491190612e78565b6128359062ffffff60406001600160401b0360208401511692015116906146b2565b906001600160c11b031982166146a357602082901c6001600160a01b03169163ffffffff1690565b6302000000821015612e015701905f90565b63ffffffff821691906020831015614a97576401fffffffe905460c01c9160011b169180830460021490151715613a50576001600160401b03906003831b1616901c9060026001831615159216151590565b91614aa29150613a41565b908160011b9180830460021481151715613a505760ff91614ad29160071c6001600160f81b031690600101614a33565b90549060031b1c9116906003821b16901c9060026001831615159216151590565b906128359160208152813560208201526020820135604082015260408201356060820152614b46614b3b614b2a606085018561384b565b60a0608086015260c0850191612c9b565b92608081019061384b565b9160a0601f1982860301910152612c9b565b90915f9180356060614b6982614a0b565b60018060a01b0382165f526001602052614b868160405f20614a45565b92908094604051614b968161270d565b5f81525f60208201525f60408201525f828201525f60808201525f60a08201525f60c082015291614dfa575b50602087013594614bd1615425565b50855c95614bdd615425565b506040519082906001607f1b89161515614bf68461278d565b8084526001600160601b03604060208601956001607e1b8d1615158752019a168a525f14614d7f57505051614d10577f3eb6e922886168616596d8bf7407911d08725b2cf3a578ddc363df13fe2f415795938b9388614c9897948b945b15614cf85760208101516001600160401b03164211614cdb57614c76975061579c565b965b8751614c9d575b6040516001600160a01b03909116949091829182614af3565b0390a3565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb35646040516020815280614cd3602082018c612635565b0390a1614c7f565b9291906001600160601b03614cf298511693615589565b96614c78565b5050906001600160601b03614cf29651169189615443565b50505050505092505091506040519063873fd26b60e01b6020830152602482015260248152614d406044826127a8565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb35646040516020815280614d766020820185612635565b0390a190600190565b90915081614dec575b5015614dd957614d97826149e9565b6001600160401b0342911610614d10577f3eb6e922886168616596d8bf7407911d08725b2cf3a578ddc363df13fe2f415795938b9388614c9897948b94614c53565b8663c274d3e360e01b5f5260045260245ffd5b905060c0830151145f614d88565b9050855f525f602052600260405f206001600160601b0360405193614e1e8561270d565b825460018060a01b03811686526001600160401b038160a01c16602087015262ffffff8160e01c16604087015260f81c8186015260018301549082821660808701521c1660a0840152015460c08201525f614bc2565b5f80516020615ef183398151915280546001600160a01b03199081169091555f80516020615e1183398151915280549182166001600160a01b0393841690811790915591167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b6040516323b872dd60e01b81526001600160a01b03918216600482018190523060248301526044820184905292917f000000000000000000000000000000000000000000000000000000000000000016906020905f9060649082855af19081601f3d1160015f5114161516614fec575b5015614fb057602081614fa7614f857f1da2c4060997c108162f55c30ad2268924b069210f8c686ba302e1acc6909ccc946147b6565b855f5260018452611dae60405f20916001600160601b03835460601c16613a71565b604051908152a2565b60405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606490fd5b3b153d171590505f614f4f565b906001600160401b03809116911603906001600160401b038211613a5057565b9590929796949360018060a01b031697885f52600160205261503e8560405f20614a45565b906153d1576153bd576001600160401b038616988942116153a55761506c611d5e612c283660808c016129ae565b96815f52600160205260405f20996001600160601b038b5416946001600160601b038a1693848710615393575060018060a01b031698895f52600160205260405f20906001600160601b03825460601c16966101408d013580981061538057918d6001600160601b0380615112946151179897960316166001600160601b03198254161790556001600160601b03615103896147b6565b81835460601c16031690612cd2565b614ff9565b926001600160401b03841662ffffff81116153695750615136906147b6565b604051936151438561270d565b88855260208086019c8d5262ffffff90911660408087019182525f60608801818152608089019687526001600160601b0390951660a0808a0191825260c08a019889528e35808452958390529290912097519e51925194519290911b67ffffffffffffffff60a01b166001600160a01b039e909e169d909d1760e09390931b62ffffff60e01b169290921760f89290921b6001600160f81b031916919091178455996001840191516001600160601b03166001600160601b03166001600160601b0319835416178255516001600160601b031661521f91612cd2565b51906002015563ffffffff831692602084105f146152da576401fffffffe9060011b169280840460021490151715613a505785546001600160c01b038116600190941b6001600160401b031660c091821c17901b6001600160c01b031916929092179094557f6f5de72b704dc2c15a0930f92bc5d83253c73358372a4fe4d004ec1a35d7569a936152d5915b6152c7604051958695865260606020870152606086019061387c565b918483036040860152612c9b565b0390a2565b50916152e590613a41565b918260011b9583870460021484151715613a50577f6f5de72b704dc2c15a0930f92bc5d83253c73358372a4fe4d004ec1a35d7569a966152d5946153649260ff916001916153419160071c6001600160f81b0316908301614a33565b929093161b82548260031b1c179082549060031b91821b915f19901b1916179055565b6152ab565b6306dfcc6560e41b5f52601860045260245260445ffd5b8b63112fed8b60e31b5f5260045260245ffd5b63112fed8b60e31b5f5260045260245ffd5b898863cfe6a8fd60e01b5f523560045260245260445ffd5b86631cfdeebb60e01b5f523560045260245ffd5b8763a905765160e01b5f523560045260245ffd5b6042916040519161190160f01b8352600283015260228201522090565b6128359063ffffffff60806001600160401b0360408401511692015116906146b2565b604051906154328261278d565b5f6040838281528260208201520152565b969495919293909660609661553c575f80516020615dd18339815191526154a19596979860018060a01b031693845f52600160205261548660405f209687615c3d565b6040516001600160a01b039190911696879482919082614af3565b0390a36001600160601b03825416906001600160601b038516821061551057506001600160601b038481920316166001600160601b03198254161790555f5260016020526001600160601b036154fe60405f209282845416613a71565b166001600160601b0319825416179055565b949550505050506040519063112fed8b60e31b60208301526024820152602481526128356044826127a8565b955050505050915060405190631cfdeebb60e01b60208301526024820152602481526128356044826127a8565b906001600160601b03809116911603906001600160601b038211613a5057565b929796949093959760609860016060860151161515801561578c575b61575d571561570c575b505060018060a01b0316805f5260016020526001600160601b03608060405f209301511685816001600160601b038216115f146156d757906155f091615569565b906001600160601b03835416906001600160601b03831682106156a9575082546bffffffffffffffffffffffff19169190036001600160601b03161790555b5f90815260208190526040902080546affffffffffffffffffffff60a01b81166001600160a01b0384169081176001600160a01b0319929092161760f890811c600217901b6001600160f81b03191617905560018060a01b03165f5260016020526001600160601b036154fe60405f209282845416613a71565b9697505050505050506040519063112fed8b60e31b60208301526024820152602481526128356044826127a8565b6001600160601b0392506156ee906156f792615569565b82845416613a71565b166001600160601b031982541617905561562f565b6001600160a01b0383165f90815260016020526040902061572d9190615c3d565b835f80516020615dd18339815191526040518061575360018060a01b038a169582614af3565b0390a35f806155af565b5050505050929350505060405190631cfdeebb60e01b60208301526024820152602481526128356044826127a8565b50600260608601511615156155a5565b939190929695949660609760016060870151161515801561591e575b6158f057156158a0575b505082516001600160a01b03948516941684148015919061588c575b506158625760a06131cb93926001600160601b03925f525f6020525f6001604082208160f81b828060f81b03825416178155015582608082015116845f5260016020528361583360405f209282845416613a71565b168419825416179055015116905f526001602052611dae60405f20916001600160601b03835460601c16613a71565b92935050506040519063a905765160e01b60208301526024820152602481526128356044826127a8565b9050602060c084015191013514155f6157de565b6158bc9160018060a01b03165f52600160205260405f20615c3d565b6040516001600160a01b0385169083905f80516020615dd183398151915290806158e68682614af3565b0390a35f806157c2565b50505050929350505060405190631cfdeebb60e01b60208301526024820152602481526128356044826127a8565b50600260608701511615156157b8565b615936615a54565b61593e615b5b565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261450060c0826127a8565b60ff5f80516020615eb18339815191525460401c16156159ab57565b631afcd79f60e31b5f5260045ffd5b81519190604183036159ea576159e39250602082015190606060408401519301515f1a90615cda565b9192909190565b50505f9160029190565b60048110156137465780615a06575050565b60018103615a1d5763f645eedf60e01b5f5260045ffd5b60028103615a38575063fce698f760e01b5f5260045260245ffd5b600314615a425750565b6335e2f38360e21b5f5260045260245ffd5b6040515f80516020615df183398151915254905f81615a7284612d1d565b9182825260208201946001811690815f14615b3f5750600114615ae7575b615a9c925003826127a8565b51908115615aa8572090565b50505f80516020615e51833981519152548015615ac25790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b505f80516020615df18339815191525f90815290915f80516020615e718339815191525b818310615b23575050906020615a9c92820101615a90565b6020919350806001915483858801015201910190918392615b0b565b60ff1916865250615a9c92151560051b82016020019050615a90565b6040515f80516020615e3183398151915254905f81615b7984612d1d565b9182825260208201946001811690815f14615c215750600114615bc9575b615ba3925003826127a8565b51908115615baf572090565b50505f80516020615ed1833981519152548015615ac25790565b505f80516020615e318339815191525f90815290915f80516020615f118339815191525b818310615c05575050906020615ba392820101615b97565b6020919350806001915483858801015201910190918392615bed565b60ff1916865250615ba392151560051b82016020019050615b97565b9063ffffffff8116906020821015615c9a576401fffffffe9060011b169080820460021490151715613a505781546001600160c01b038116600290921b6001600160401b031660c091821c17901b6001600160c01b031916179055565b50615ca490613a41565b8060011b9080820460021481151715613a50576131cb9260ff916002916153419160071c6001600160f81b031690600101614a33565b91906fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411615d47579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561241f575f516001600160a01b03811615615d3d57905f905f90565b505f906001905f90565b5050505f9160039190565b90615d765750805115615d6757805190602001fd5b630a12f52160e11b5f5260045ffd5b81511580615da7575b615d87575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15615d7f56fe405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace2682b2738d8666734f483aa3e081360183bbd5428f40ad104639f5961025142ba16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1029016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10042ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c005f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75a164736f6c634300081a000a")] + #[sol(rpc, bytecode = "610100346101b957601f6160e738819003918201601f19168301916001600160401b038311848410176101bd578084926060946040528339810103126101b9578051906001600160a01b03821682036101b9576020810151604090910151916001600160a01b03831683036101b9573060805260a05260c05260e0527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c166101aa576002600160401b03196001600160401b03821601610141575b604051615f1590816101d2823960805181818161189701526119b2015260a05181818161233e01528181613590015281816138c20152613971015260c051818181610a140152818161100601526135fa015260e05181818161130c015281816114520152818161181601528181611dc6015281816121c001526146fe0152f35b6001600160401b0319166001600160401b039081177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f6100c1565b63f92ee8a960e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f905f3560e01c90816308c84e701461232c5750806310ae6fd31461230c5780631228fb071461229c578063127eabcd146122825780631ce030241461226557806325d5971f146121215780632abff1f21461201d5780632e1a7d4d14611ffe57806341451f9414611f3d57806345bc4d1014611be35780634db1b31c14611b5c5780634f1ef28614611973578063512fe7f4146118eb57806352d1902d14611884578063553c0248146118685780635b07fdd8146118455780635fbb49211461180057806360dfd4a91461176857806363555624146116a457806370a0823114611661578063715018a6146115e257806378d611c31461156057806379ba5097146115135780638040fdc0146113aa5780638094e614146112e657806381bf6c241461129d57806384b0196e14611175578063861b201b1461113857806389f2eac9146110b05780638da5cb5b1461107b578063906d858514611063578063956b0960146110465780639f04f420146110295780639fe9428c14610fee578063abaa06aa14610cef578063ad3cb1cc14610ca6578063ae7330f114610bfd578063b5ef18d614610bdc578063c515c15f14610b57578063cb09e7c014610b11578063cb74db1114610ae8578063cb82cc8f14610aca578063cdc97123146109c1578063d0e30db0146109ad578063d4f75b1214610982578063e30c39781461094d578063f2800f1a146108f6578063f2fde38b14610870578063f399e22e14610288578063f9008c8d1461026d5763ffa1ad741461024f575f80fd5b3461026a578060031936011261026a57602060405160018152f35b80fd5b503461026a5761028561027f366124e6565b91613f9d565b80f35b503461026a57604036600319011261026a576102a261258e565b906024356001600160401b03811161086c576102c29036906004016124b9565b90925f80516020615ea9833981519152549060ff8260401c1615916001600160401b03811680159081610864575b600114908161085a575b159081610851575b506108425767ffffffffffffffff1981166001175f80516020615ea98339815191525582610816575b50610334615ae7565b61033c615ae7565b6001600160a01b038116156108025761035490614663565b61035c615ae7565b604091825161036b84826126ce565b601081526f12509bdd5b991b195cdcd3585c9ad95d60821b602082015283519061039585836126ce565b60018252603160f81b60208301526103ab615ae7565b6103b3615ae7565b8051906001600160401b0382116107ee576103db5f80516020615de9833981519152546131c6565b601f811161077f575b50602090601f83116001146107035761041492918891836105f9575b50508160011b915f199060031b1c19161790565b5f80516020615de9833981519152555b8051906001600160401b0382116106ef5761044c5f80516020615e29833981519152546131c6565b601f8111610680575b50602090601f83116001146106045761048492918791836105f95750508160011b915f199060031b1c19161790565b5f80516020615e29833981519152555b835f80516020615e4983398151915255835f80516020615ec9833981519152556001600160401b0381116105e5576104d6816104d16002546131c6565b6131fe565b83601f82116001146105635781908596610505949596926105585750508160011b915f199060031b1c19161790565b6002555b610511575080f35b5f80516020615ea9833981519152805460ff60401b1916905551600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a180f35b013590505f80610400565b60028552601f198216957f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace91865b8881106105cd575083600195969798106105b4575b505050811b01600255610509565b01355f19600384901b60f8161c191690555f80806105a6565b90926020600181928686013581550194019101610591565b634e487b7160e01b84526041600452602484fd5b015190505f80610400565b5f80516020615e2983398151915287528187209190601f198416885b8181106106685750908460019594939210610650575b505050811b015f80516020615e2983398151915255610494565b01515f1960f88460031b161c191690555f8080610636565b92936020600181928786015181550195019301610620565b5f80516020615e2983398151915287527f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c810191602085106106e5575b601f0160051c01905b8181106106da5750610455565b8781556001016106cd565b90915081906106c4565b634e487b7160e01b86526041600452602486fd5b5f80516020615de983398151915288528188209190601f198416895b818110610767575090846001959493921061074f575b505050811b015f80516020615de983398151915255610424565b01515f1960f88460031b161c191690555f8080610735565b9293602060018192878601518155019501930161071f565b5f80516020615de983398151915288527f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f840160051c810191602085106107e4575b601f0160051c01905b8181106107d957506103e4565b8881556001016107cc565b90915081906107c3565b634e487b7160e01b87526041600452602487fd5b631e4fbdf760e01b84526004849052602484fd5b68ffffffffffffffffff191668010000000000000001175f80516020615ea9833981519152555f61032b565b63f92ee8a960e01b8552600485fd5b9050155f610302565b303b1591506102fa565b8491506102f0565b5080fd5b503461026a57602036600319011261026a5761088a61258e565b610892614434565b5f80516020615ee983398151915280546001600160a01b0319166001600160a01b039283169081179091555f80516020615e09833981519152549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227008380a380f35b503461026a57602036600319011261026a576004359061091582613b10565b1561093b576040816020936001600160401b039352808452205460a01c16604051908152f35b60249163d2be005d60e01b8252600452fd5b503461026a578060031936011261026a575f80516020615ee9833981519152546040516001600160a01b039091168152602090f35b503461026a576109a961099d61099736612539565b91613d1f565b6040519182918261245a565b0390f35b508060031936011261026a57610285613cb7565b503461026a578060031936011261026a576040519080600254906109e4826131c6565b8085529160018116908115610aa35750600114610a46575b6109a984610a0c818603826126ce565b6040519182917f00000000000000000000000000000000000000000000000000000000000000008352604060208401526040830190612436565b600281527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace939250905b808210610a8957509091508101602001610a0c826109fc565b919260018160209254838588010152019101909291610a70565b60ff191660208087019190915292151560051b85019092019250610a0c91508390506109fc565b503461026a57602036600319011261026a57610285600435336146ce565b503461026a57602036600319011261026a576020610b07600435613b10565b6040519015158152f35b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b03610b4261258e565b16815260018452205460601c16604051908152f35b503461026a57602036600319011261026a57604060e091600435815280602052208054906001600160601b0360026001830154920154916040519360018060a01b03811685526001600160401b038160a01c16602086015262ffffff81871c16604086015260f81c6060850152818116608085015260601c1660a083015260c0820152f35b503461026a576109a961099d610bf13661239d565b95949094939193613ad4565b503461026a57606036600319011261026a5780610c1861258e565b6044356001600160401b038111610ca257610c379036906004016124b9565b6001600160a01b0390921691823b15610c9d57610c7092849283604051809681958294636691f64760e01b84526024356004850161326e565b03925af18015610c9257610c815750f35b81610c8b916126ce565b61026a5780f35b6040513d84823e3d90fd5b505050fd5b5050fd5b503461026a578060031936011261026a57506109a9604051610cc96040826126ce565b60058152640352e302e360dc1b6020820152604051918291602083526020830190612436565b503461026a57606036600319011261026a576004356001600160401b03811161086c576101608160040191600319903603011261086c576024356001600160401b038111610fea57610d459036906004016124b9565b916044356001600160401b038111610fe657610d659036906004016124b9565b823591610d718361456c565b929091610d86610d813688613de8565b61510b565b610d9781610d92615a86565b615b12565b95600160c01b1615610fa557604051630b135d3f60e11b815260208180610dc38d8d8c6004850161326e565b03816001600160a01b0389165afa908115610f9a578b91610f6b575b506001600160e01b0319166374eca2c160e11b01610f5c575b604051610e066060826126ce565b60218152602081017f4c6f636b526571756573742850726f6f66526571756573742072657175657374815260408201602960f81b9052610e44614ef5565b90610e4d614f52565b8d610e56614f97565b610e5e615051565b610e66614e6e565b91610e6f61509e565b94604051978897602089019a5180918c5e880160208101918783528051926020849201905e0160200185815281516020819301825e0184815281516020819301825e0183815281516020819301825e0182815281516020819301825e0190815281516020819301825e018d815203601f1981018252610eee90826126ce565b519020906040519060208201928352604082015260408152610f116060826126ce565b519020610f1c615a86565b90610f2691615b12565b913690610f329261270a565b610f3b91615bdb565b610f4791959295615c15565b610f50856148e9565b966102859891966149a5565b638baa579f60e01b8a5260048afd5b610f8d915060203d602011610f93575b610f8581836126ce565b8101906147e8565b5f610ddf565b503d610f7b565b6040513d8d823e3d90fd5b610fc5610fbc610fb6368c8c61270a565b88615bdb565b90929192615c15565b6001600160a01b03858116911614610df857638baa579f60e01b8a5260048afd5b8480fd5b8280fd5b503461026a578060031936011261026a5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b503461026a578060031936011261026a57602060405161c3508152f35b503461026a578060031936011261026a5760206040516107d08152f35b503461026a5761028561107536612539565b9161332d565b503461026a578060031936011261026a575f80516020615e09833981519152546040516001600160a01b039091168152602090f35b503461026a576110bf3661275e565b9a93969297909960018060a09b949b9897981b031691823b15610fe65791611102939185809460405196879586948593636691f64760e01b85526004850161326e565b03925af18015610c9257611123575b6109a961099d8a8a8a8a8a8a8a6128b1565b61112e8280926126ce565b61026a5780611111565b503461026a5761028561114a366124e6565b91611155813561456c565b9061116285858386614808565b61116b846148e9565b96909533956149a5565b503461026a578060031936011261026a575f80516020615e49833981519152541580611287575b1561124a576111ee906111ad613b3d565b906111b6613c0a565b9060206111fc604051936111ca83866126ce565b8385525f368137604051968796600f60f81b885260e08589015260e0880190612436565b908682036040880152612436565b904660608601523060808601528260a086015284820360c08601528080855193848152019401925b82811061123357505050500390f35b835185528695509381019392810192600101611224565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b505f80516020615ec9833981519152541561119c565b503461026a57602036600319011261026a576112da60209160406112c260043561456c565b6001600160a01b0390911683526001855291206145b5565b90506040519015158152f35b503461026a5760a036600319011261026a576004358160443560ff811680910361086c577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690813b15610fea57829160e48392604051948593849263d505accf60e01b84523360048501523060248501528960448501526024356064850152608484015260643560a484015260843560c48401525af1611395575b5061028582336146ce565b8161139f916126ce565b61086c57815f61138a565b503461026a57602036600319011261026a576004356113c7614434565b30825260016020526001600160601b03604083205460601c166001600160601b036113f183614403565b16116115005761142761140382614403565b30845260016020526001600160601b03604085209181835460601c1603169061317b565b60405163a9059cbb60e01b815233600482015260248101829052602081604481866001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19081156114f55783916114c6575b50156114b7576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203092a280f35b6312171d8360e31b8252600482fd5b6114e8915060203d6020116114ee575b6114e081836126ce565b8101906131ae565b5f611482565b503d6114d6565b6040513d85823e3d90fd5b63112fed8b60e31b825230600452602482fd5b503461026a578060031936011261026a575f80516020615ee983398151915254336001600160a01b039091160361154d5761028533614663565b63118cdaa760e01b815233600452602490fd5b503461026a5761156f366125b8565b969095919490936001600160a01b039092169190823b15610fe657916115b0939185809460405196879586948593636691f64760e01b85526004850161326e565b03925af18015610c92576115cd575b6109a961099d868686612c84565b6115d88280926126ce565b61026a57806115bf565b503461026a578060031936011261026a576115fb614434565b5f80516020615ee983398151915280546001600160a01b03199081169091555f80516020615e098339815191528054918216905581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b0361169261258e565b16815260018452205416604051908152f35b503461026a57602036600319011261026a576004356116c1614434565b30825260016020526001600160601b036040832054166001600160601b036116e883614403565b1611611500576116f781614403565b30835260016020526001600160601b03806040852092818454160316166001600160601b03198254161790558180808084335af1611733613285565b50156114b7576040519081527f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6560203092a280f35b503461026a57602036600319011261026a57600460606040602093833581528085522060026040519161179a83612647565b805460018060a01b03811684526001600160401b038160a01c168785015262ffffff8160e01c16604085015260f81c848401526001600160601b0360018201548181166080860152851c1660a0840152015460c082015201511615156040519015158152f35b503461026a578060031936011261026a576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461026a578060031936011261026a576020611860615a86565b604051908152f35b503461026a578060031936011261026a57602090604051908152f35b503461026a578060031936011261026a577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036118dc5760206040515f80516020615e698339815191528152f35b63703e46dd60e11b8152600490fd5b503461026a576118fa3661275e565b9a93969297909960018060a09b949b9897981b031691823b15610fe6579161193d939185809460405196879586948593636691f64760e01b85526004850161326e565b03925af18015610c925761195e575b6109a961099d8a8a8a8a8a8a8a613ad4565b6119698280926126ce565b61026a578061194c565b50604036600319011261026a5761198861258e565b906024356001600160401b03811161086c576119a8903690600401612740565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016308114908115611b3a575b50611b2b576119ea614434565b6040516352d1902d60e01b8152926001600160a01b0381169190602085600481865afa80958596611af7575b50611a2f57634c9c8ce360e01b84526004839052602484fd5b9091845f80516020615e698339815191528103611ae55750813b15611ad3575f80516020615e6983398151915280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8480a28151839015611ab95780836020611ab595519101845af4611aaf613285565b91615d12565b5080f35b50505034611ac45780f35b63b398979f60e01b8152600490fd5b634c9c8ce360e01b8452600452602483fd5b632a87526960e21b8552600452602484fd5b9095506020813d602011611b23575b81611b13602093836126ce565b81010312610fe65751945f611a16565b3d9150611b06565b63703e46dd60e11b8252600482fd5b5f80516020615e69833981519152546001600160a01b0316141590505f6119dd565b34611bdf57611b6a366125b8565b93959194909290916001600160a01b0316803b15611bdf57611ba6965f8094604051998a9586948593636691f64760e01b85526004850161326e565b03925af1928315611bd4576109a99461099d94611bc4575b50613d1f565b5f611bce916126ce565b5f611bbe565b6040513d5f823e3d90fd5b5f80fd5b34611bdf576020366003190112611bdf57600435611c1f611c038261456c565b919060018060a01b031691825f52600160205260405f206145b5565b5015611f2a57815f525f60205260405f2060405190611c3d82612647565b805460018060a01b03811683526001600160401b038160a01c16602084015262ffffff8160e01c16604084015260f81c6060830152600181015490600260808401916001600160601b03841683526001600160601b0360a086019460601c168452015460c08401526004606084015116611f17576001606084015116611f04576001600160401b03611cce8461454a565b16421115611edb575f85815260208190526040812080546001600160f81b03811660f891821c60041790911b6001600160f81b031916178155600101556001600160601b03825116916107d08302928084046107d01490151715611ec757611d4a611d4f916127106001600160601b0395049485915116612c77565b614403565b936002606060018060a01b038651169501511615155f14611e6357505060018060a01b0382165f526001602052611da060405f20611d9a856001600160601b03835460601c1661324e565b9061317b565b60405163a9059cbb60e01b815261dead600482015260248101829052916020836044815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18015611bd4577f79ca7c80cf57b513ffdf8aa37ec70e40757f5e0d35219241860bb4b4c2fa7616946060946001600160601b0392611e46575b5060405193845216602083015260018060a01b03166040820152a2005b611e5e9060203d6020116114ee576114e081836126ce565b611e29565b9092506001600160601b033093305f526001602052611e8f60405f20611d9a8885835460601c1661324e565b5116905f5260016020526001600160601b03611eb260405f20928284541661324e565b166001600160601b0319825416179055611da0565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0385611eed8561454a565b9063079c66ab60e41b5f526004521660245260445ffd5b84631cfdeebb60e01b5f5260045260245ffd5b84633231064d60e11b5f5260045260245ffd5b5063d2be005d60e01b5f5260045260245ffd5b34611bdf576020366003190112611bdf57600435611f5a81613b10565b15611fec575f525f6020526020611fdb60405f20600260405191611f7d83612647565b805460018060a01b03811684526001600160401b038160a01c168685015262ffffff8160e01c16604085015260f81c60608401526001600160601b036001820154818116608086015260601c1660a0840152015460c082015261454a565b6001600160401b0360405191168152f35b63d2be005d60e01b5f5260045260245ffd5b34611bdf576020366003190112611bdf5761201b60043533614467565b005b34611bdf576020366003190112611bdf576004356001600160401b038111611bdf5761204d9036906004016124b9565b612055614434565b6001600160401b03811161210d57612072816104d16002546131c6565b5f601f82116001146120ae57819061209e935f926120a35750508160011b915f199060031b1c19161790565b600255005b013590508380610400565b601f1982169260025f5260205f20915f5b8581106120f5575083600195106120dc575b505050811b01600255005b01355f19600384901b60f8161c191690558280806120d1565b909260206001819286860135815501940191016120bf565b634e487b7160e01b5f52604160045260245ffd5b34611bdf576020366003190112611bdf57600435335f5260016020526001600160601b0360405f205460601c166001600160601b0361215f83614403565b16116122525761219561217182614403565b335f5260016020526001600160601b0360405f209181835460601c1603169061317b565b60405163a9059cbb60e01b8152336004820152602481018290526020816044815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1908115611bd4575f91612233575b5015612224576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203392a2005b6312171d8360e31b5f5260045ffd5b61224c915060203d6020116114ee576114e081836126ce565b826121f0565b63112fed8b60e31b5f523360045260245ffd5b34611bdf575f366003190112611bdf576020604051620186a08152f35b34611bdf576109a961099d61229636612539565b91612c84565b7fc48ed0281106f755a14f2e78ff04b97efb580f42e14e75a8b6175905048c1c406122c6366124e6565b92919092346122ff575b6122fa604051928392604084526122ea60408501836129ce565b9184830360208601523596612974565b0390a2005b612307613cb7565b6122d0565b34611bdf576109a961099d6123203661239d565b959490949391936128b1565b34611bdf575f366003190112611bdf577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b9181601f84011215611bdf578235916001600160401b038311611bdf576020808501948460051b010111611bdf57565b6080600319820112611bdf576004356001600160401b038111611bdf57816123c79160040161236d565b929092916024356001600160401b038111611bdf57816123e99160040161236d565b929092916044356001600160401b038111611bdf578161240b9160040161236d565b92909291606435906001600160401b038211611bdf576080908290036003190112611bdf5760040190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b83831061248c57505050505090565b90919293946020806124aa600193603f198682030187528951612436565b9701930193019193929061247d565b9181601f84011215611bdf578235916001600160401b038311611bdf5760208381860195010111611bdf57565b906040600319830112611bdf576004356001600160401b038111611bdf576101608184036003190112611bdf5760040191602435906001600160401b038211611bdf57612535916004016124b9565b9091565b6040600319820112611bdf576004356001600160401b038111611bdf57816125639160040161236d565b92909291602435906001600160401b038211611bdf576080908290036003190112611bdf5760040190565b600435906001600160a01b0382168203611bdf57565b35906001600160a01b0382168203611bdf57565b60a0600319820112611bdf576004356001600160a01b0381168103611bdf5791602435916044356001600160401b038111611bdf57816125fa916004016124b9565b929092916064356001600160401b038111611bdf578161261c9160040161236d565b92909291608435906001600160401b038211611bdf576080908290036003190112611bdf5760040190565b60e081019081106001600160401b0382111761210d57604052565b60a081019081106001600160401b0382111761210d57604052565b604081019081106001600160401b0382111761210d57604052565b608081019081106001600160401b0382111761210d57604052565b606081019081106001600160401b0382111761210d57604052565b90601f801991011681019081106001600160401b0382111761210d57604052565b6001600160401b03811161210d57601f01601f191660200190565b929192612716826126ef565b9161272460405193846126ce565b829481845281830111611bdf578281602093845f960137010152565b9080601f83011215611bdf5781602061275b9335910161270a565b90565b60e0600319820112611bdf576004356001600160a01b0381168103611bdf5791602435916044356001600160401b038111611bdf57816127a0916004016124b9565b929092916064356001600160401b038111611bdf57816127c29160040161236d565b929092916084356001600160401b038111611bdf57816127e49160040161236d565b9290929160a4356001600160401b038111611bdf57816128069160040161236d565b9290929160c435906001600160401b038211611bdf576080908290036003190112611bdf5760040190565b91908110156128545760051b8101359061015e1981360301821215611bdf570190565b634e487b7160e01b5f52603260045260245ffd5b903590601e1981360301821215611bdf57018035906001600160401b038211611bdf57602001918136038313611bdf57565b90821015612854576125359160051b810190612868565b919695949392905f5b8181106128d0575050505061275b939450613d1f565b806128ed8a61027f83876128e7600197898c612831565b9361289a565b016128ba565b35906001600160601b0382168203611bdf57565b6002111561291157565b634e487b7160e01b5f52602160045260245ffd5b9035603e1982360301811215611bdf570190565b6003111561291157565b9035601e1982360301811215611bdf5701602081359101916001600160401b038211611bdf578136038313611bdf57565b908060209392818452848401375f828201840152601f01601f1916010190565b35906001600160e01b031982168203611bdf57565b35906001600160401b0382168203611bdf57565b359063ffffffff82168203611bdf57565b90813581526020820135609e1983360301811215611bdf57610160602083015282016001600160a01b03612a01826125a4565b166101608301526001600160601b03612a1c602083016128f3565b1661018083015260408101356002811015611bdf57612a3a81612907565b6101a0830152612a4d6060820182612925565b9060a06101c08401528135916003831015611bdf57612a86612a999184612a76612ad296612939565b6102008701526020810190612943565b6040610220870152610240860191612974565b906001600160e01b031990612ab090608001612994565b166101e0840152612ac46040850185612943565b908483036040860152612974565b612adf6060840184612925565b828203606084015280356002811015611bdf57610140926040612b16859484612b0a612b2696612907565b84526020810190612943565b9190928160208201520191612974565b936080810135608085015260a081013560a08501526001600160401b03612b4f60c083016129a9565b1660c085015263ffffffff612b6660e083016129bd565b1660e085015263ffffffff612b7e61010083016129bd565b1661010085015263ffffffff612b9761012083016129bd565b16610120850152013591015290565b6001600160401b03811161210d5760051b60200190565b903590601e1981360301821215611bdf57018035906001600160401b038211611bdf57602001918160071b36038313611bdf57565b91908110156128545760071b0190565b3561ffff81168103611bdf5790565b8051156128545760200190565b80518210156128545760209160051b010190565b91908110156128545760051b8101359060be1981360301821215611bdf570190565b356001600160a01b0381168103611bdf5790565b601f19810191908211611ec757565b91908203918211611ec757565b92919092612c9382858361332d565b612c9c84612ba6565b93612caa60405195866126ce565b808552601f19612cb982612ba6565b015f5b81811061316a57505084612ccf82612ba6565b612cdc60405191826126ce565b828152601f19612ceb84612ba6565b013660208301376020850192612d018487612bbd565b90505f5b81811061312b5750505f5b818110612d205750505050505050565b612d2b818388612c32565b90612d41612d3b60608a01612c54565b836140e7565b90612d4c8388612c1e565b5261312257612d5b8185612c1e565b519160408101356003811015611bdf57600290612d7781612939565b03612d89575b50600191505b01612d10565b612d966080820182612868565b5090602082013591604081013501916040602084013593019460205f6040518689823780878101838152039060025afa15611bd4575f515f6080604051612ddc81612662565b828152826020820152604051612df18161267d565b8381528360208201526040820152826060820152015260405190612e148261267d565b5f82525f602083015260405190612e2a8261267d565b8152602081015f815260205f600c6040516b1c9a5cd8cc0b93dd5d1c1d5d60a21b815260025afa15611bd4576020915f918251915190516040519185830193845260408301526060820152600160f91b608082015260628152612e8e6082826126ce565b604051918291518091835e8101838152039060025afa15611bd4575f51604051612eb781612662565b84815260208101907fa3acc27117418996340b84e5a90f3ef4c49d22c79e44aad822ec9c313e1eb8e282526040810193845260608101915f83526080820193845260205f60126040517172697363302e52656365697074436c61696d60701b815260025afa15611bd4576020945f9485519451935192519051908783515193612f3f85612939565b612f4885612939565b510151604080518a8101988952908101969096526060860194909452608085015260a08401526001600160f81b031960f891821b811660c085015291901b1660c4820152600160fa1b60c882015260aa8152612fa560ca826126ce565b604051918291518091835e8101838152039060025afa15611bd4575f51606084013581810361310d57505080612fde575b505050612d7d565b612fe88a8d612bbd565b90915f19810191908211611ec757612fff92612bf2565b90604061300e60208401612c54565b920135956001600160601b038716809703611bdf5761303060a0850185612868565b976001600160a01b0390941695909390863b15611bdf575f87819561309161307f9860019d6040519a8b998a98899663a12da43f60e01b88526004880152606060248801526064870191612974565b84810360031901604486015291612974565b0393f190816130fd575b506130f6577f5c5960582bfc7a494183b4e9a66bfe8ecffc07a83a48d136e732400f7b98bf50906130ca613285565b926130e960405192839283526040602084015235946040830190612436565b0390a25b5f808080612fd6565b50506130ed565b5f613107916126ce565b5f61309b565b630475494d60e11b5f5260045260245260445ffd5b60019150612d83565b61313f81613139888b612bbd565b90612bf2565b9060018101808211611ec75761316361ffff61315c600195612c02565b1687612c1e565b5201612d05565b806060602080938a01015201612cbc565b80546bffffffffffffffffffffffff60601b191660609290921b6bffffffffffffffffffffffff60601b16919091179055565b90816020910312611bdf57518015158103611bdf5790565b90600182811c921680156131f4575b60208310146131e057565b634e487b7160e01b5f52602260045260245ffd5b91607f16916131d5565b601f811161320a575050565b60025f5260205f20906020601f840160051c83019310613244575b601f0160051c01905b818110613239575050565b5f815560010161322e565b9091508190613225565b906001600160601b03809116911601906001600160601b038211611ec757565b60409061275b949281528160208201520191612974565b3d156132af573d90613296826126ef565b916132a460405193846126ce565b82523d5f602084013e565b606090565b903590601e1981360301821215611bdf57018035906001600160401b038211611bdf57602001918160061b36038313611bdf57565b91908110156128545760061b0190565b6020815260406020613315845183838601526060850190612436565b93015191015290565b359061ffff82168203611bdf57565b61ffff8211613abb5761333f82612ba6565b61334c60405191826126ce565b828152601f1961335b84612ba6565b0136602083013761336b83612ba6565b9161337960405193846126ce565b838352601f1961338885612ba6565b0136602085013761339884612ba6565b926133a660405194856126ce565b848452601f196133b586612ba6565b0136602086013760408601946133cb86886132b4565b90505f5b818110613a005750505f5b81811061378c57505050506133f161340891614d71565b926133ff6020860186612bbd565b929091866132b4565b61341760608897939701612c54565b926040519461342586612662565b61342e81612ba6565b9161343c60405193846126ce565b818352602083019160071b810190368211611bdf57915b81831061372357505050845261346881612ba6565b9561347660405197886126ce565b818752602087019160061b810190368211611bdf57915b8183106136e457505050602083019485526040830193845260608301908152608083019160018060a01b031682526040519360208501956020875260e0860194519460a060408801528551809152602061010088019601905f5b818110613691575050505193603f19868203016060870152602080865192838152019501905f5b818110613661575050505192603f19858203016080860152602080855192838152019401905f5b818110613640575050905160a085015250516001600160a01b031660c0830152819003601f19810182526020925f92909161357090826126ce565b604051918291518091835e8101838152039060025afa15611bd4575f51907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906135c39080612868565b9091803b15611bdf575f926135f7926040519586948593849363ab750e7560e01b8552606060048601526064850191612974565b907f00000000000000000000000000000000000000000000000000000000000000006024840152604483015203915afa8015611bd4576136345750565b5f61363e916126ce565b565b909194602080600192885161365481612939565b8152019601929101613535565b8251805161ffff1688526020908101516001600160e01b031916818901526040909701969092019160010161350e565b9091966020608060019260608b5161ffff8151168352858060a01b038582015116858401526001600160601b03604082015116604084015201516136d481612907565b60608201520198019291016134e7565b604083360312611bdf57602060409182516136fe8161267d565b6137078661331e565b8152613714838701612994565b8382015281520192019161348d565b608083360312611bdf576040519061373a82612698565b6137438461331e565b8252613751602085016125a4565b6020830152613762604085016128f3565b60408301526060840135906002821015611bdf578260209260606080950152815201920191613453565b613797818386612c32565b9060408201356003811015611bdf576137b08289612c1e565b6137b982612939565b526060820135916040516137cc81612698565b8281526020810182358152604082016020840135815260608301868152608060566040516137fa83826126ce565b81815275742c6279746573333220636c61696d4469676573742960501b606060208301927f4173736573736f72436f6d6d69746d656e742875696e7432353620696e64657884527f2c75696e743235362069642c6279746573333220726571756573744469676573604082015201522094519351925191519260405194602086019687526040860152606085015283015260a082015260a0815261389f60c0826126ce565b5190206138ac8389612c1e565b526138b78286612c1e565b5161396b5761390f927f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316916138f99060a0810190612868565b9490604051956139088761267d565b369161270a565b84526020840152803b15611bdf5761393e925f916040518080968194631599ead560e01b8352600483016132f9565b039161c350fa918215611bd45760019261395b575b505b016133da565b5f613965916126ce565b5f613953565b6139a8927f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316916138f99060a0810190612868565b84526020840152803b15611bdf576139d7925f916040518080968194631599ead560e01b8352600483016132f9565b03915afa918215611bd4576001926139f0575b50613955565b5f6139fa916126ce565b5f6139ea565b6020613a1682613a108b8d6132b4565b906132e9565b013563ffffffff60e01b8116809103611bdf57613a5b613a5161ffff613a49613a44868f8f613a10916132b4565b612c02565b168689612c32565b60a0810190612868565b600492919211611bdf576001613a9461ffff613a8d613a448f968f613a10908a9263ffffffff60e01b903516996132b4565b1688612c1e565b52818103613aa65750506001016133cf565b632e2ce35360e21b5f5260045260245260445ffd5b506377e4aa5360e11b5f5260045261ffff60245260445ffd5b919695949392905f5b818110613af3575050505061275b939450612c84565b80613b0a8a61027f83876128e7600197898c612831565b01613add565b613b1c613b399161456c565b6001600160a01b039091165f9081526001602052604090206145b5565b5090565b604051905f825f80516020615de98339815191525491613b5c836131c6565b8083529260018116908115613beb5750600114613b80575b61363e925003836126ce565b505f80516020615de98339815191525f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b818310613bcf57505090602061363e92820101613b74565b6020919350806001915483858901015201910190918492613bb7565b6020925061363e94915060ff191682840152151560051b820101613b74565b604051905f825f80516020615e298339815191525491613c29836131c6565b8083529260018116908115613beb5750600114613c4c5761363e925003836126ce565b505f80516020615e298339815191525f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b818310613c9b57505090602061363e92820101613b74565b6020919350806001915483858901015201910190918492613c83565b613cc034614403565b335f5260016020526001600160601b03613ce160405f20928284541661324e565b166001600160601b03198254161790556040513481527fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c60203392a2565b82606092613d2f92959495612c84565b92016001600160a01b03613d4282612c54565b165f5260016020526001600160601b0360405f20541680613d61575050565b613d6d61363e92612c54565b614467565b91908260e0910312611bdf57604051613d8a81612647565b60c08082948035845260208101356020850152613da9604082016129a9565b6040850152613dba606082016129bd565b6060850152613dcb608082016129bd565b6080850152613ddc60a082016129bd565b60a08501520135910152565b919061016083820312611bdf5760405190613e0282612662565b81938035835260208101356001600160401b038111611bdf5781018083039060a08212611bdf57606060405192613e38846126b3565b12611bdf57604051613e49816126b3565b613e52826125a4565b8152613e60602083016128f3565b602082015260408201356002811015611bdf576040820152825260608101356001600160401b038111611bdf578101604081860312611bdf5760405191613ea68361267d565b81356003811015611bdf5783526020820135926001600160401b038411611bdf57613ed887613ee89560809501612740565b6020820152602085015201612994565b6040820152602084015260408101356001600160401b038111611bdf57810182601f82011215611bdf5782816020613f229335910161270a565b604084015260608101356001600160401b038111611bdf57810191604083820312611bdf5760405192613f548461267d565b80356002811015611bdf5784526020810135926001600160401b038411611bdf57608094613f8884613f9896889501612740565b6020820152606087015201613d72565b910152565b91823591600160c01b83161561404457613fc39260201c6001600160a01b031684614808565b905b6040614002611d4a613ff2613fd9856148e9565b90506001600160401b0342911610946080369101613d72565b6001600160401b03421690615499565b6001600160601b03825191614016836126b3565b60018352602083018590521691018190526001607f1b911561403e576001607e1b5b1717905d565b5f614038565b50505061405f614057610d813684613de8565b610d92615a86565b90613fc5565b906020825280356020830152602081013560408301526040810135916003831015611bdf578261409761275b94612939565b6060820152606082013560808201526140d56140ca6140b96080850185612943565b60c060a086015260e0850191612974565b9260a0810190612943565b9160c0601f1982860301910152612974565b90915f91803560606140f88261456c565b60018060a01b0382165f5260016020526141158160405f206145b5565b9290809460405161412581612647565b5f81525f60208201525f60408201525f828201525f60808201525f60a08201525f60c082015291614389575b5060208701359461416061557d565b50855c9561416c61557d565b506040519082906001607f1b89161515614185846126b3565b8084526001600160601b03604060208601956001607e1b8d1615158752019a168a525f1461430e5750505161429f577f20b439a9ed103175285f3a22c2a618268e22e030cf4a1b213037bdcebfe21d1d95938b938861422797948b945b156142875760208101516001600160401b0316421161426a5761420597506158f4565b965b875161422c575b6040516001600160a01b03909116949091829182614065565b0390a3565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb35646040516020815280614262602082018c612436565b0390a161420e565b9291906001600160601b03614281985116936156e1565b96614207565b5050906001600160601b03614281965116918961559b565b50505050505092505091506040519063873fd26b60e01b60208301526024820152602481526142cf6044826126ce565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb356460405160208152806143056020820185612436565b0390a190600190565b9091508161437b575b5015614368576143268261454a565b6001600160401b034291161061429f577f20b439a9ed103175285f3a22c2a618268e22e030cf4a1b213037bdcebfe21d1d95938b938861422797948b946141e2565b8663c274d3e360e01b5f5260045260245ffd5b905060c0830151145f614317565b9050855f525f602052600260405f206001600160601b03604051936143ad85612647565b825460018060a01b03811686526001600160401b038160a01c16602087015262ffffff8160e01c16604087015260f81c8186015260018301549082821660808701521c1660a0840152015460c08201525f614151565b6001600160601b03811161441d576001600160601b031690565b6306dfcc6560e41b5f52606060045260245260445ffd5b5f80516020615e09833981519152546001600160a01b0316330361445457565b63118cdaa760e01b5f523360045260245ffd5b9060018060a01b03821691825f5260016020526001600160601b0360405f2054166001600160601b0361449984614403565b1611614517575f80808481946144ae82614403565b88845260016020526001600160601b03806040862092818454160316166001600160601b03198254161790555af16144e4613285565b50156122245760207f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6591604051908152a2565b8263112fed8b60e31b5f5260045260245ffd5b906001600160401b03809116911601906001600160401b038211611ec757565b61275b9062ffffff60406001600160401b03602084015116920151169061452a565b906001600160c11b0319821661459457602082901c6001600160a01b03169163ffffffff1690565b6341abc80160e01b5f5260045ffd5b63020000008210156128545701905f90565b63ffffffff821691906020831015614607576401fffffffe905460c01c9160011b169180830460021490151715611ec7576001600160401b03906003831b1616901c9060026001831615159216151590565b916146129150612c68565b908160011b9180830460021481151715611ec75760ff916146429160071c6001600160f81b0316906001016145a3565b90549060031b1c9116906003821b16901c9060026001831615159216151590565b5f80516020615ee983398151915280546001600160a01b03199081169091555f80516020615e0983398151915280549182166001600160a01b0393841690811790915591167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b6040516323b872dd60e01b81526001600160a01b03918216600482018190523060248301526044820184905292917f000000000000000000000000000000000000000000000000000000000000000016906020905f9060649082855af19081601f3d1160015f51141615166147db575b501561479f576020816147966147747f1da2c4060997c108162f55c30ad2268924b069210f8c686ba302e1acc6909ccc94614403565b855f5260018452611d9a60405f20916001600160601b03835460601c1661324e565b604051908152a2565b60405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606490fd5b3b153d171590505f61473e565b90816020910312611bdf57516001600160e01b031981168103611bdf5790565b929161481a614057610d813687613de8565b9335600160c01b16156148b3579160209161484c93604051809581948293630b135d3f60e11b8452896004850161326e565b03916001600160a01b0316620186a0fa908115611bd4575f91614894575b506001600160e01b0319166374eca2c160e11b016148855790565b638baa579f60e01b5f5260045ffd5b6148ad915060203d602011610f9357610f8581836126ce565b5f61486a565b6148c56148cb916148d494369161270a565b84615bdb565b90939193615c15565b6001600160a01b039081169116036148855790565b6148f7906080369101613d72565b9081516020830151106145945763ffffffff606083015116608083019063ffffffff825116106145945763ffffffff90511660a083019063ffffffff82511610614594576149649063ffffffff6001600160401b03604061495787615b2f565b960151169151169061452a565b9162ffffff6001600160401b0361497b8386614985565b1611614594579190565b906001600160401b03809116911603906001600160401b038211611ec757565b9590929796949360018060a01b031697885f5260016020526149ca8560405f206145b5565b90614d5d57614d49576001600160401b03861698894211614d31576149f8611d4a613ff23660808c01613d72565b96815f52600160205260405f20996001600160601b038b5416946001600160601b038a1693848710614d1f575060018060a01b031698895f52600160205260405f20906001600160601b03825460601c16966101408d0135809810614d0c57918d6001600160601b0380614a9e94614aa39897960316166001600160601b03198254161790556001600160601b03614a8f89614403565b81835460601c1603169061317b565b614985565b926001600160401b03841662ffffff8111614cf55750614ac290614403565b60405193614acf85612647565b88855260208086019c8d5262ffffff90911660408087019182525f60608801818152608089019687526001600160601b0390951660a0808a0191825260c08a019889528e35808452958390529290912097519e51925194519290911b67ffffffffffffffff60a01b166001600160a01b039e909e169d909d1760e09390931b62ffffff60e01b169290921760f89290921b6001600160f81b031916919091178455996001840191516001600160601b03166001600160601b03166001600160601b0319835416178255516001600160601b0316614bab9161317b565b51906002015563ffffffff831692602084105f14614c66576401fffffffe9060011b169280840460021490151715611ec75785546001600160c01b038116600190941b6001600160401b031660c091821c17901b6001600160c01b031916929092179094557fb3c044a3584136dde41847e4c09b3a775335a2f6453284c312cd3cfe21b93a0f93614c61915b614c5360405195869586526060602087015260608601906129ce565b918483036040860152612974565b0390a2565b5091614c7190612c68565b918260011b9583870460021484151715611ec7577fb3c044a3584136dde41847e4c09b3a775335a2f6453284c312cd3cfe21b93a0f96614c6194614cf09260ff91600191614ccd9160071c6001600160f81b03169083016145a3565b929093161b82548260031b1c179082549060031b91821b915f19901b1916179055565b614c37565b6306dfcc6560e41b5f52601860045260245260445ffd5b8b63112fed8b60e31b5f5260045260245ffd5b63112fed8b60e31b5f5260045260245ffd5b898863cfe6a8fd60e01b5f523560045260245260445ffd5b86631cfdeebb60e01b5f523560045260245ffd5b8763a905765160e01b5f523560045260245ffd5b805115614594576001815114614e655780515b60018111614d9a5750614d9690612c11565b5190565b60018101808211611ec75760011c905f5b8160011c8110614df95750600180821614614dc7575b50614d84565b5f198101908111611ec757614ddc9083612c1e565b515f198201828111611ec757614df29084612c1e565b525f614dc1565b600181901b906001600160ff1b0381168103611ec757614e198286612c1e565b5160018301809311611ec757614e3160019387612c1e565b519081811015614e56575f5260205260405f205b614e4f8287612c1e565b5201614dab565b905f5260205260405f20614e45565b614d9690612c11565b60405190614e7d6080836126ce565b605a82527f6c2c496e70757420696e7075742c4f66666572206f66666572290000000000006060837f50726f6f66526571756573742875696e743235362069642c526571756972656d60208201527f656e747320726571756972656d656e74732c737472696e6720696d616765557260408201520152565b60405190614f046060836126ce565b60398252784c696d69742c75696e74382063616c6c6261636b547970652960381b6040837f43616c6c6261636b286164647265737320616464722c75696e7439362067617360208201520152565b60405190614f616060836126ce565b60218252602960f81b6040837f496e7075742875696e743820696e707574547970652c6279746573206461746160208201520152565b60405190614fa660c0836126ce565b6084825263616b652960e01b60a0837f4f666665722875696e74323536206d696e50726963652c75696e74323536206d60208201527f617850726963652c75696e7436342062696464696e6753746172742c75696e7460408201527f33322072616d705570506572696f642c75696e743332206c6f636b54696d656f60608201527f75742c75696e7433322074696d656f75742c75696e74323536206c6f636b537460808201520152565b604051906150606060836126ce565b602982526874657320646174612960b81b6040837f5072656469636174652875696e743820707265646963617465547970652c627960208201520152565b604051906150ad6080836126ce565b60438252626f722960e81b6060837f526571756972656d656e74732843616c6c6261636b2063616c6c6261636b2c5060208201527f7265646963617465207072656469636174652c6279746573342073656c65637460408201520152565b615113614e6e565b61511b614ef5565b615123614f52565b9061512c614f97565b615134615051565b61513c61509e565b916040519485946020860197805160208192018a5e860160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815203601f19810182526151b290826126ce565b5190209080519060208101516151c661509e565b6151ce614ef5565b6151d6615051565b90604051918291602083019480516020819201875e830160208101915f83528051926020849201905e016020015f815281516020819301825e015f815203601f198101825261522590826126ce565b519020908051615233614ef5565b8051906020012090600160a01b600190038151169060208101516001600160601b031690604001519061526582612907565b60405192602084019485526040840152606083015261528381612907565b60808201526080815261529760a0826126ce565b5190209060208101516152a8615051565b80519060200120908051906152bc82612939565b602001518051906020012060405191602083019384526152db81612939565b60408301526060820152606081526152f46080826126ce565b5190209063ffffffff60e01b9060400151169060405192602084019485526040840152606083015260808201526080815261533060a0826126ce565b51902090604081015180519060200120606082015161534d614f52565b805190602001209080519061536182612907565b6020015180519060200120604051916020830193845261538081612907565b60408301526060820152606081526153996080826126ce565b51902091608001516153a9614f97565b60405180602081019280516020819201855e8101602081015f905203602001601f19810182526153d990826126ce565b5190209080519060208101519060408101516001600160401b0316606082015163ffffffff16608083015163ffffffff169160a084015163ffffffff169360c0015194604051966020880198895260408801526060870152608086015260a085015260c084015260e0830152610100820152610100815261545c610120826126ce565b51902092604051946020860196875260408601526060850152608084015260a083015260c082015260c0815261549360e0826126ce565b51902090565b9060408201906001600160401b0380835116911690811115615577576001600160401b036154c684615b2f565b168111615570576001600160401b03825116906001600160401b036154f7606086019363ffffffff8551169061452a565b16811115615509575050506020015190565b615536906001600160401b0363ffffffff61552a6020880151885190612c77565b94511694511690612c77565b925192818102918183041490151715611ec757811561555c57048101809111611ec75790565b634e487b7160e01b5f52601260045260245ffd5b5050505f90565b50505190565b6040519061558a826126b3565b5f6040838281528260208201520152565b9694959192939096606096615694575f80516020615e898339815191526155f99596979860018060a01b031693845f5260016020526155de60405f209687615c75565b6040516001600160a01b039190911696879482919082614065565b0390a36001600160601b03825416906001600160601b038516821061566857506001600160601b038481920316166001600160601b03198254161790555f5260016020526001600160601b0361565660405f20928284541661324e565b166001600160601b0319825416179055565b949550505050506040519063112fed8b60e31b602083015260248201526024815261275b6044826126ce565b955050505050915060405190631cfdeebb60e01b602083015260248201526024815261275b6044826126ce565b906001600160601b03809116911603906001600160601b038211611ec757565b92979694909395976060986001606086015116151580156158e4575b6158b55715615864575b505060018060a01b0316805f5260016020526001600160601b03608060405f209301511685816001600160601b038216115f1461582f5790615748916156c1565b906001600160601b03835416906001600160601b0383168210615801575082546bffffffffffffffffffffffff19169190036001600160601b03161790555b5f90815260208190526040902080546affffffffffffffffffffff60a01b81166001600160a01b0384169081176001600160a01b0319929092161760f890811c600217901b6001600160f81b03191617905560018060a01b03165f5260016020526001600160601b0361565660405f20928284541661324e565b9697505050505050506040519063112fed8b60e31b602083015260248201526024815261275b6044826126ce565b6001600160601b0392506158469061584f926156c1565b8284541661324e565b166001600160601b0319825416179055615787565b6001600160a01b0383165f9081526001602052604090206158859190615c75565b835f80516020615e89833981519152604051806158ab60018060a01b038a169582614065565b0390a35f80615707565b5050505050929350505060405190631cfdeebb60e01b602083015260248201526024815261275b6044826126ce565b50600260608601511615156156fd565b9391909296959496606097600160608701511615158015615a76575b615a4857156159f8575b505082516001600160a01b0394851694168414801591906159e4575b506159ba5760a061363e93926001600160601b03925f525f6020525f6001604082208160f81b828060f81b03825416178155015582608082015116845f5260016020528361598b60405f20928284541661324e565b168419825416179055015116905f526001602052611d9a60405f20916001600160601b03835460601c1661324e565b92935050506040519063a905765160e01b602083015260248201526024815261275b6044826126ce565b9050602060c084015191013514155f615936565b615a149160018060a01b03165f52600160205260405f20615c75565b6040516001600160a01b0385169083905f80516020615e898339815191529080615a3e8682614065565b0390a35f8061591a565b50505050929350505060405190631cfdeebb60e01b602083015260248201526024815261275b6044826126ce565b5060026060870151161515615910565b615a8e615b52565b615a96615ba9565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261549360c0826126ce565b60ff5f80516020615ea98339815191525460401c1615615b0357565b631afcd79f60e31b5f5260045ffd5b6042916040519161190160f01b8352600283015260228201522090565b61275b9063ffffffff60806001600160401b03604084015116920151169061452a565b615b5a613b3d565b8051908115615b6a576020012090565b50505f80516020615e49833981519152548015615b845790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b615bb1613c0a565b8051908115615bc1576020012090565b50505f80516020615ec9833981519152548015615b845790565b8151919060418303615c0b57615c049250602082015190606060408401519301515f1a90615d70565b9192909190565b50505f9160029190565b60048110156129115780615c27575050565b60018103615c3e5763f645eedf60e01b5f5260045ffd5b60028103615c59575063fce698f760e01b5f5260045260245ffd5b600314615c635750565b6335e2f38360e21b5f5260045260245ffd5b9063ffffffff8116906020821015615cd2576401fffffffe9060011b169080820460021490151715611ec75781546001600160c01b038116600290921b6001600160401b031660c091821c17901b6001600160c01b031916179055565b50615cdc90612c68565b8060011b9080820460021481151715611ec75761363e9260ff91600291614ccd9160071c6001600160f81b0316906001016145a3565b90615d365750805115615d2757805190602001fd5b630a12f52160e11b5f5260045ffd5b81511580615d67575b615d47575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15615d3f565b91906fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411615ddd579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15611bd4575f516001600160a01b03811615615dd357905f905f90565b505f906001905f90565b5050505f916003919056fea16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1029016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5486400d7ee369b611e64ece71a50a729d1b70ebb82bcf1b872449fd0c96e098f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00a164736f6c634300081a000a")] contract BoundlessMarket { constructor(address verifier, bytes32 assessorId, address stakeTokenContract) {} function initialize(address initialOwner, string calldata imageUrl) {} diff --git a/crates/boundless-market/src/contracts/mod.rs b/crates/boundless-market/src/contracts/mod.rs index 5af35fa11..e084acf20 100644 --- a/crates/boundless-market/src/contracts/mod.rs +++ b/crates/boundless-market/src/contracts/mod.rs @@ -26,7 +26,7 @@ use alloy::{ }; use alloy_primitives::{ aliases::{U160, U32, U96}, - Address, Bytes, FixedBytes, B256, U256, + Address, Bytes, FixedBytes, U256, }; use alloy_sol_types::{eip712_domain, Eip712Domain}; use serde::{Deserialize, Serialize}; @@ -41,7 +41,10 @@ use token::{ }; use url::Url; -use risc0_zkvm::sha::Digest; +use risc0_zkvm::{ + sha::{Digest, Digestible}, + ReceiptClaim, +}; #[cfg(not(target_os = "zkvm"))] pub use risc0_ethereum_contracts::{encode_seal, selector::Selector, IRiscZeroSetVerifier}; @@ -58,7 +61,7 @@ const TXN_CONFIRM_TIMEOUT: Duration = Duration::from_secs(45); include!(concat!(env!("OUT_DIR"), "/boundless_market_generated.rs")); pub use boundless_market_contract::{ AssessorCallback, AssessorCommitment, AssessorJournal, AssessorJournalCallback, - AssessorReceipt, Callback, Fulfillment, FulfillmentContext, IBoundlessMarket, + AssessorReceipt, Callback, CallbackType, Fulfillment, FulfillmentContext, IBoundlessMarket, Input as RequestInput, InputType as RequestInputType, LockRequest, Offer, Predicate, PredicateType, ProofRequest, RequestLock, Requirements, Selector as AssessorSelector, }; @@ -430,9 +433,10 @@ impl ProofRequest { } Url::parse(&self.imageUrl).map(|_| ())?; - if self.requirements.imageId == B256::default() { - return Err(RequestError::ImageIdIsZero); - } + // TODO(ec2): fixme + // if self.requirements.imageId == B256::default() { + // return Err(RequestError::ImageIdIsZero); + // } if self.offer.timeout == 0 { return Err(RequestError::OfferTimeoutIsZero); } @@ -460,6 +464,11 @@ impl ProofRequest { Ok(()) } + + /// TODO(ec2): docs + pub fn image_id(&self) -> Option { + self.requirements.image_id() + } } #[cfg(not(target_os = "zkvm"))] @@ -510,18 +519,8 @@ impl ProofRequest { impl Requirements { /// Creates a new requirements with the given image ID and predicate. - pub fn new(image_id: impl Into, predicate: Predicate) -> Self { - Self { - imageId: <[u8; 32]>::from(image_id.into()).into(), - predicate, - callback: Callback::default(), - selector: UNSPECIFIED_SELECTOR, - } - } - - /// Sets the image ID. - pub fn with_image_id(self, image_id: impl Into) -> Self { - Self { imageId: <[u8; 32]>::from(image_id.into()).into(), ..self } + pub fn new(predicate: Predicate) -> Self { + Self { predicate, callback: Callback::default(), selector: UNSPECIFIED_SELECTOR } } /// Sets the predicate. @@ -551,28 +550,177 @@ impl Requirements { false => Self { selector: FixedBytes::from(Selector::Groth16V2_2 as u32), ..self }, } } + + /// Returns image id from the predicate type. Returns none if claim digest match. Panics if + /// the predicate data is not long enough. + pub fn image_id(&self) -> Option { + self.predicate.image_id() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// TODO(ec2): doc +pub enum FulfillmentClaimData { + /// TODO(ec2): doc + ClaimDigest(Digest), + /// TODO(ec2): doc + ImageIdAndJournal(Digest, Bytes), +} + +impl FulfillmentClaimData { + /// TODO(ec2): doc + pub fn from_claim_digest(claim_digest: impl Into) -> Self { + Self::ClaimDigest(claim_digest.into()) + } + /// TODO(ec2): doc + pub fn from_image_id_and_journal( + image_id: impl Into, + journal: impl Into, + ) -> Self { + Self::ImageIdAndJournal(image_id.into(), journal.into()) + } + /// TODO(ec2): doc + pub fn to_bytes(self) -> Bytes { + match self { + Self::ClaimDigest(digest) => digest.as_bytes().to_vec().into(), + Self::ImageIdAndJournal(image_id, journal) => { + let mut data = image_id.as_bytes().to_vec(); + data.extend_from_slice(journal.as_ref()); + data.into() + } + } + } + /// TODO(ec2): doc + pub fn image_id(&self) -> Option { + match self { + Self::ClaimDigest(_) => None, + Self::ImageIdAndJournal(image_id, _) => Some(*image_id), + } + } + /// TODO(ec2): doc + pub fn journal(&self) -> Option<&Bytes> { + match self { + Self::ClaimDigest(_) => None, + Self::ImageIdAndJournal(_, journal) => Some(journal), + } + } + /// TODO(ec2): doc + pub fn claim_digest(&self) -> Option { + match self { + Self::ClaimDigest(digest) => Some(*digest), + Self::ImageIdAndJournal(image_id, journal) => { + Some(ReceiptClaim::ok(*image_id, journal.as_ref().to_vec()).digest()) + } + } + } } impl Predicate { /// Returns a predicate to match the journal digest. This ensures that the request's /// fulfillment will contain a journal with the same digest. - pub fn digest_match(digest: impl Into) -> Self { - Self { - predicateType: PredicateType::DigestMatch, - data: digest.into().as_bytes().to_vec().into(), - } + pub fn digest_match(image_id: impl Into, digest: impl Into) -> Self { + let mut image_id = image_id.into().as_bytes().to_vec(); + let digest = digest.into().as_bytes().to_vec(); + image_id.append(&mut digest.clone()); + let data = image_id; + Self { predicateType: PredicateType::DigestMatch, data: data.into() } } /// Returns a predicate to match the journal prefix. This ensures that the request's /// fulfillment will contain a journal with the same prefix. - pub fn prefix_match(prefix: impl Into) -> Self { - Self { predicateType: PredicateType::PrefixMatch, data: prefix.into() } + pub fn prefix_match(image_id: impl Into, prefix: impl Into) -> Self { + let mut image_id = image_id.into().as_bytes().to_vec(); + let prefix = prefix.into().as_ref().to_vec(); + image_id.append(&mut prefix.clone()); + let data = image_id; + Self { predicateType: PredicateType::PrefixMatch, data: data.into() } + } + + /// Returns a predicate to match the claim digest. This ensures that the request's + /// fulfillment will contain a claim with the same digest. + pub fn claim_digest_match(claim_digest: impl Into) -> Self { + Self { + predicateType: PredicateType::ClaimDigestMatch, + data: claim_digest.into().as_bytes().to_vec().into(), + } + } + + /// Returns image id. Returns none if claim digest match. Panics if + /// the predicate data is not long enough. + pub fn image_id(&self) -> Option { + match self.predicateType { + PredicateType::DigestMatch | PredicateType::PrefixMatch => { + Some(Digest::from_bytes(self.data.as_ref()[..32].try_into().unwrap())) + } + _ => None, + } + } + + /// TODO(ec2): doc + pub fn claim_digest(&self) -> Option { + if self.predicateType == PredicateType::ClaimDigestMatch { + Some(Digest::from_bytes(self.data.as_ref().try_into().unwrap())) + } else { + None + } + } + + #[inline] + /// TODO(ec2): doc + pub fn eval(&self, fulfillment_data: &FulfillmentClaimData) -> bool { + match self.predicateType { + PredicateType::DigestMatch => { + let (image_id, journal_digest) = self.data.as_ref().split_at(32); + journal_digest + == Sha256::digest( + fulfillment_data + .journal() + .expect("fulfillment data doesnt have journal, but is digest match"), + ) + .as_slice() + && image_id + == fulfillment_data + .image_id() + .expect("fulfillment data doesnt have image id, but is digest match") + .as_bytes() + } + PredicateType::PrefixMatch => { + let (image_id, journal) = self.data.as_ref().split_at(32); + fulfillment_data + .journal() + .expect("fulfillment data doesnt have journal, but is prefix match") + .as_ref() + .starts_with(journal) + && image_id + == fulfillment_data + .image_id() + .expect("fulfillment data doesnt have image id, but is prefix match") + .as_bytes() + } + PredicateType::ClaimDigestMatch => { + self.data.as_ref() + == fulfillment_data + .claim_digest() + .expect( + "fulfillment data doesnt have claim digest, but is claim digest match", + ) + .as_bytes() + } + PredicateType::__Invalid => panic!("invalid PredicateType"), + } + } +} + +impl Default for CallbackType { + fn default() -> Self { + Self::None } } impl Callback { /// Constant representing a none callback (i.e. no call will be made). - pub const NONE: Self = Self { addr: Address::ZERO, gasLimit: U96::ZERO }; + pub const NONE: Self = + Self { addr: Address::ZERO, gasLimit: U96::ZERO, callbackType: CallbackType::None }; /// Sets the address of the callback. pub fn with_addr(self, addr: impl Into

) -> Self { @@ -588,7 +736,7 @@ impl Callback { /// /// NOTE: A callback is considered none if the address is zero, regardless of the gas limit. pub fn is_none(&self) -> bool { - self.addr == Address::ZERO + self.addr == Address::ZERO || self.callbackType == CallbackType::None } /// Convert to an option representation, mapping a none callback to `None`. @@ -607,6 +755,12 @@ impl Callback { } } +impl Default for Callback { + fn default() -> Self { + Self::NONE + } +} + impl RequestInput { /// Create a new [GuestEnvBuilder] for use in constructing and encoding the guest zkVM environment. #[cfg(not(target_os = "zkvm"))] @@ -706,18 +860,6 @@ use IBoundlessMarket::IBoundlessMarketErrors; #[cfg(not(target_os = "zkvm"))] use IRiscZeroSetVerifier::IRiscZeroSetVerifierErrors; -impl Predicate { - /// Evaluates the predicate against the given journal. - #[inline] - pub fn eval(&self, journal: impl AsRef<[u8]>) -> bool { - match self.predicateType { - PredicateType::DigestMatch => self.data.as_ref() == Sha256::digest(journal).as_slice(), - PredicateType::PrefixMatch => journal.as_ref().starts_with(&self.data), - PredicateType::__Invalid => panic!("invalid PredicateType"), - } - } -} - #[cfg(not(target_os = "zkvm"))] /// The Boundless market module. pub mod boundless_market; @@ -867,10 +1009,10 @@ mod tests { let req = ProofRequest { id: request_id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Bytes::default(), + )), imageUrl: "https://dev.null".to_string(), input: RequestInput::builder().build_inline().unwrap(), offer: Offer { diff --git a/crates/boundless-market/src/request_builder/finalizer.rs b/crates/boundless-market/src/request_builder/finalizer.rs index eb17a73d0..b14794f35 100644 --- a/crates/boundless-market/src/request_builder/finalizer.rs +++ b/crates/boundless-market/src/request_builder/finalizer.rs @@ -14,8 +14,7 @@ use super::{Adapt, Layer, RequestParams}; use crate::{ - contracts::RequestInput, - contracts::{Offer, ProofRequest, RequestId, Requirements}, + contracts::{FulfillmentClaimData, Offer, ProofRequest, RequestId, RequestInput, Requirements}, util::now_timestamp, }; use anyhow::{bail, Context}; @@ -130,8 +129,15 @@ impl Adapt for RequestParams { // As an extra consistency check. verify the journal satisfies the required predicate. if let Some(ref journal) = self.journal { - if !requirements.predicate.eval(journal) { - bail!("journal in request builder does not match requirements predicate; check request parameters.\npredicate = {:?}\njournal = 0x{}", requirements.predicate, hex::encode(journal)); + if let Some(image_id) = self.image_id { + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + image_id, + journal.bytes.clone(), + ); + // TODO(ec2): fixme + if !requirements.predicate.eval(&fulfillment_data) { + bail!("journal in request builder does not match requirements predicate; check request parameters.\npredicate = {:?}\njournal = 0x{}", requirements.predicate, hex::encode(journal)); + } } } diff --git a/crates/boundless-market/src/request_builder/mod.rs b/crates/boundless-market/src/request_builder/mod.rs index 034b4de01..b16aee7f1 100644 --- a/crates/boundless-market/src/request_builder/mod.rs +++ b/crates/boundless-market/src/request_builder/mod.rs @@ -295,6 +295,9 @@ pub struct RequestParams { /// Contents of the [Journal] that results from the execution. pub journal: Option, + /// Claim digest that results from execution. + pub claim_digest: Option, + /// [RequestId] to use for the proof request. pub request_id: Option, @@ -540,6 +543,18 @@ impl RequestParams { Self { requirements: value.into(), ..self } } + /// TODO(ec2): doc + pub fn with_claim_digest(self, value: impl Into) -> Self { + Self { claim_digest: Some(value.into()), ..self } + } + + /// TODO(ec2): doc + pub fn require_claim_digest(&self) -> Result { + self.claim_digest.ok_or(MissingFieldError::with_hint( + "claim_digest", + "can be set using .with_claim_digest(...), and is calculated from the program", + )) + } /// Request a stand-alone Groth16 proof for this request. /// /// This is a convinience method to set the selector on the requirements. Note that calling @@ -644,8 +659,8 @@ mod tests { use crate::{ contracts::{ - boundless_market::BoundlessMarketService, Predicate, RequestInput, RequestInputType, - Requirements, + boundless_market::BoundlessMarketService, FulfillmentClaimData, Predicate, + RequestInput, RequestInputType, Requirements, }, input::GuestEnv, storage::{fetch_url, MockStorageProvider, StorageProvider}, @@ -734,8 +749,8 @@ mod tests { let params = request_builder.params().with_program_url(program_url)?.with_stdin(b"hello!"); let request = request_builder.build(params).await?; assert_eq!( - request.requirements.imageId, - risc0_zkvm::compute_image_id(ECHO_ELF)?.as_bytes() + request.requirements.image_id().unwrap(), + risc0_zkvm::compute_image_id(ECHO_ELF)? ); Ok(()) } @@ -836,12 +851,19 @@ mod tests { let bytes = b"journal_data".to_vec(); let journal = Journal::new(bytes.clone()); let req = layer.process((program, &journal, &Default::default())).await?; - + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + req.image_id().unwrap(), + journal.bytes.clone(), + ); // Predicate should match the same journal - assert!(req.predicate.eval(&journal)); + assert!(req.predicate.eval(&fulfillment_data)); // And should not match different data let other = Journal::new(b"other_data".to_vec()); - assert!(!req.predicate.eval(&other)); + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + req.image_id().unwrap(), + other.bytes.clone(), + ); + assert!(!req.predicate.eval(&fulfillment_data)); Ok(()) } @@ -910,8 +932,8 @@ mod tests { let layer = OfferLayer::from(provider.clone()); // Build minimal requirements and request ID let image_id = compute_image_id(ECHO_ELF).unwrap(); - let predicate = Predicate::digest_match(Journal::new(b"hello".to_vec()).digest()); - let requirements = Requirements::new(image_id, predicate); + let predicate = Predicate::digest_match(image_id, Journal::new(b"hello".to_vec()).digest()); + let requirements = Requirements::new(predicate); let request_id = RequestId::new(test_ctx.customer_signer.address(), 0); // Zero cycles diff --git a/crates/boundless-market/src/request_builder/requirements_layer.rs b/crates/boundless-market/src/request_builder/requirements_layer.rs index 7cca50de8..c2a95cb51 100644 --- a/crates/boundless-market/src/request_builder/requirements_layer.rs +++ b/crates/boundless-market/src/request_builder/requirements_layer.rs @@ -13,7 +13,7 @@ // limitations under the License. use super::{Adapt, Layer, MissingFieldError, RequestParams}; -use crate::contracts::{Callback, Predicate, Requirements}; +use crate::contracts::{Callback, CallbackType, Predicate, Requirements}; use alloy::primitives::{aliases::U96, Address, FixedBytes, B256}; use anyhow::{ensure, Context}; use clap::Args; @@ -53,6 +53,11 @@ pub struct RequirementParams { #[builder(setter(strip_option, into), default)] pub callback_address: Option
, + /// Type of the callback to call when the proof is fulfilled. + #[clap(skip)] + #[builder(setter(strip_option, into), default)] + pub callback_type: Option, + /// Gas limit for the callback when the proof is fulfilled. #[clap(long)] #[builder(setter(strip_option), default)] @@ -66,12 +71,14 @@ pub struct RequirementParams { impl From for RequirementParams { fn from(value: Requirements) -> Self { + let image_id = value.predicate.image_id().map(<[u8; 32]>::from).map(Into::into); Self { predicate: Some(value.predicate), - image_id: Some(value.imageId), selector: Some(value.selector), callback_address: Some(value.callback.addr), callback_gas_limit: Some(value.callback.gasLimit.to()), + callback_type: Some(value.callback.callbackType), + image_id, } } } @@ -85,14 +92,11 @@ impl TryFrom for Requirements { "predicate", "please provide a Predicate with requirements e.g. a digest match on a journal", ))?, - imageId: value.image_id.ok_or(MissingFieldError::with_hint( - "image_id", - "please provide the image ID for the program to be proven", - ))?, selector: value.selector.unwrap_or_default(), callback: Callback { addr: value.callback_address.unwrap_or_default(), gasLimit: U96::from(value.callback_gas_limit.unwrap_or_default()), + callbackType: value.callback_type.unwrap_or_default(), }, }) } @@ -152,8 +156,10 @@ impl Layer<(Digest, &Journal, &RequirementParams)> for RequirementsLayer { &self, (image_id, journal, params): (Digest, &Journal, &RequirementParams), ) -> Result { - let predicate = - params.predicate.clone().unwrap_or_else(|| Predicate::digest_match(journal.digest())); + let predicate = params + .predicate + .clone() + .unwrap_or_else(|| Predicate::digest_match(image_id, journal.digest())); if let Some(params_image_id) = params.image_id { ensure!( image_id == Digest::from(<[u8; 32]>::from(params_image_id)), @@ -165,16 +171,12 @@ impl Layer<(Digest, &Journal, &RequirementParams)> for RequirementsLayer { .map(|addr| Callback { addr, gasLimit: U96::from(params.callback_gas_limit.unwrap_or(DEFAULT_CALLBACK_GAS_LIMT)), + callbackType: params.callback_type.unwrap_or(CallbackType::JournalRequired), }) .unwrap_or_default(); let selector = params.selector.unwrap_or_default(); - Ok(Requirements { - imageId: <[u8; 32]>::from(image_id).into(), - predicate, - callback, - selector, - }) + Ok(Requirements { predicate, callback, selector }) } } diff --git a/crates/boundless-market/test-utils/src/lib.rs b/crates/boundless-market/test-utils/src/lib.rs index f0144fdb3..da78dc64b 100644 --- a/crates/boundless-market/test-utils/src/lib.rs +++ b/crates/boundless-market/test-utils/src/lib.rs @@ -27,9 +27,10 @@ use anyhow::{Context, Ok, Result}; use boundless_market::{ contracts::{ boundless_market::BoundlessMarketService, + boundless_market_contract::CallbackData, bytecode::*, hit_points::{default_allowance, HitPointsService}, - AssessorCommitment, AssessorJournal, Fulfillment, ProofRequest, + AssessorCommitment, AssessorJournal, Fulfillment, PredicateType, ProofRequest, }, deployments::Deployment, dynamic_gas_filler::DynamicGasFiller, @@ -404,8 +405,13 @@ pub fn mock_singleton( claimDigest: <[u8; 32]>::from(app_claim_digest).into(), } .eip712_hash_struct(); - let assessor_journal = - AssessorJournal { selectors: vec![], root: assessor_root, prover, callbacks: vec![] }; + let assessor_journal = AssessorJournal { + selectors: vec![], + root: assessor_root, + prover, + callbacks: vec![], + predicateTypes: vec![request.requirements.predicate.predicateType], + }; let assesor_receipt_claim = ReceiptClaim::ok(ASSESSOR_GUEST_ID, assessor_journal.abi_encode()); let assessor_claim_digest = assesor_receipt_claim.digest(); @@ -435,16 +441,32 @@ pub fn mock_singleton( .abi_encode_seal() .unwrap(); + let predicate_type = request.requirements.predicate.predicateType; + let (claim_digest, callback_data) = match predicate_type { + PredicateType::ClaimDigestMatch => (<[u8; 32]>::from(app_claim_digest).into(), vec![]), + _ => ( + <[u8; 32]>::from(app_claim_digest).into(), + CallbackData { + imageId: <[u8; 32]>::from( + request.requirements.image_id().expect("image ID is required"), + ) + .into(), + journal: app_journal.bytes.into(), + } + .abi_encode(), + ), + }; let fulfillment = Fulfillment { id: request.id, requestDigest: request_digest, - imageId: to_b256(Digest::from(ECHO_ID)), - journal: app_journal.bytes.into(), + claimDigest: claim_digest, + callbackData: callback_data.into(), seal: set_inclusion_seal.into(), + predicateType: predicate_type, }; let assessor_seal = SetInclusionReceipt::from_path_with_verifier_params( - ReceiptClaim::ok(ASSESSOR_GUEST_ID, MaybePruned::Pruned(Digest::ZERO)), + assesor_receipt_claim, merkle_path(&[app_claim_digest, assessor_claim_digest], 1), verifier_parameters.digest(), ) diff --git a/crates/boundless-market/tests/e2e.rs b/crates/boundless-market/tests/e2e.rs index c72c629e8..c133a59c0 100644 --- a/crates/boundless-market/tests/e2e.rs +++ b/crates/boundless-market/tests/e2e.rs @@ -18,12 +18,12 @@ use alloy::{ providers::Provider, sol_types::eip712_domain, }; +use alloy_primitives::Bytes; use boundless_market::{ contracts::{ boundless_market::{FulfillmentTx, UnlockedRequest}, hit_points::default_allowance, - AssessorReceipt, Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestStatus, - Requirements, + AssessorReceipt, Offer, Predicate, ProofRequest, RequestId, RequestStatus, Requirements, }, input::GuestEnv, }; @@ -41,10 +41,7 @@ fn now_timestamp() -> u64 { async fn new_request(idx: u32, ctx: &TestCtx

) -> ProofRequest { ProofRequest::new( RequestId::new(ctx.customer_signer.address(), idx), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())), "http://image_uri.null", GuestEnv::builder().build_inline().unwrap(), Offer { @@ -213,10 +210,11 @@ async fn test_e2e() { .unwrap(); assert!(ctx.customer_market.is_fulfilled(request_id).await.unwrap()); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); + // retrieve callback data and seal from the fulfilled request + let (callback_data, seal) = + ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillment.journal); + assert_eq!(callback_data, fulfillment.callbackData); assert_eq!(seal, fulfillment.seal); } @@ -279,10 +277,11 @@ async fn test_e2e_merged_submit_fulfill() { .await .unwrap(); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); + // retrieve callback data and seal from the fulfilled request + let (callback_data, seal) = + ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillments[0].journal); + assert_eq!(callback_data, fulfillments[0].callbackData); assert_eq!(seal, fulfillments[0].seal); } @@ -333,10 +332,11 @@ async fn test_e2e_price_and_fulfill_batch() { .await .unwrap(); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); + // retrieve callback data and seal from the fulfilled request + let (callback_data, seal) = + ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillments[0].journal); + assert_eq!(callback_data, fulfillments[0].callbackData); assert_eq!(seal, fulfillments[0].seal); } @@ -406,11 +406,11 @@ async fn test_e2e_no_payment() { let balance_after = ctx.prover_market.balance_of(some_other_address).await.unwrap(); assert!(balance_before == balance_after); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = + // retrieve callback data and seal from the fulfilled request + let (callback_data, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillment.journal); + assert_eq!(callback_data, fulfillment.callbackData); assert_eq!(seal, fulfillment.seal); } diff --git a/crates/broker-stress/src/main.rs b/crates/broker-stress/src/main.rs index 6eeb41bb7..3de872d9a 100644 --- a/crates/broker-stress/src/main.rs +++ b/crates/broker-stress/src/main.rs @@ -22,15 +22,15 @@ use std::{ use alloy::{ node_bindings::Anvil, - primitives::{utils, U256}, + primitives::{utils, Bytes, U256}, providers::{Provider, WalletProvider}, }; use anyhow::{Context, Result}; use axum::{routing::get, Router}; use boundless_market::{ contracts::{ - hit_points::default_allowance, Offer, Predicate, PredicateType, ProofRequest, RequestId, - RequestInput, RequestInputType, Requirements, + hit_points::default_allowance, Offer, Predicate, ProofRequest, RequestId, RequestInput, + RequestInputType, Requirements, }, input::GuestEnv, }; @@ -88,10 +88,7 @@ async fn request_spawner( ctx.customer_signer.address(), ctx.customer_market.index_from_nonce().await?, ), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())), program_url, RequestInput { inputType: RequestInputType::Inline, diff --git a/crates/broker/proptest-regressions/db/fuzz_db.txt b/crates/broker/proptest-regressions/db/fuzz_db.txt new file mode 100644 index 000000000..ef1e2ca0a --- /dev/null +++ b/crates/broker/proptest-regressions/db/fuzz_db.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 7f3d78f8fb3cc73a9435297ef5971b52fff551fb46e637814229af43cbbe221e # shrinks to operations = [AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), GetBatch(796372), GetActiveProofs, GetAggregationProofs, GetAggregationProofs, GetAggregationProofs, GetActiveProofs, GetActiveProofs, GetActiveProofs, GetBatch(2853967736), AddOrder(832495868), BatchOperation(GetCompleteBatch), OperateOnExistingOrder(SetOrderFailure), GetProvingOrder, AddOrder(2480737974), AddOrder(2610084057), GetActiveProofs, GetActiveProofs, GetAggregationProofs, AddOrder(3153343604), GetBatch(1209917930), AddOrder(3473160061), BatchOperation(GetCompleteBatch), BatchOperation(CompleteBatch { g16_proof_id: "꒛A$&=s<ொt&🕴c*.🞿꣖?�𐨜!ቓ🕴�" }), OperateOnExistingOrder(SetOrderFailure), BatchOperation(GetCurrentBatch), OperateOnExistingOrder(SetOrderFailure), GetAggregationProofs, GetAggregationProofs, GetBatch(2528283650), GetAggregationProofs, GetBatch(1877280501), GetProvingOrder, GetActiveProofs, OperateOnExistingOrder(GetOrder), AddOrder(560923163), OperateOnExistingOrder(SetOrderProofId { proof_id: "𑵘i=உ‒'jGXs𐁛.\u{2d7f}ᢰP&𞸤𝕆<🕴/﹨6sò&_" }), AddOrder(687952920), GetBatch(3335856397), GetAggregationProofs, BatchOperation(SetBatchFailure { error: "�`t𝓛𞸤\u{11d91}$/🂫O%KË𐠼PU:𖭟" }), AddOrder(2509982559), AddOrder(4113525281), GetProvingOrder, GetActiveProofs, OperateOnExistingOrder(SetAggregationStatus), OperateOnExistingOrder(SetOrderFailure), AddOrder(3227332288), GetBatch(1997148971), GetAggregationProofs, GetAggregationProofs, GetAggregationProofs, GetActiveProofs, BatchOperation(UpdateBatch { proof_id: "9`/Ѩ", order_count: 70 }), GetActiveProofs, AddOrder(4156301529), AddOrder(752007793), BatchOperation(SetBatchSubmitted), AddOrder(1916394887), OperateOnExistingOrder(SetOrderFailure), GetProvingOrder, GetBatch(2129589048), GetActiveProofs, GetBatch(2215805156), GetActiveProofs, GetAggregationProofs, BatchOperation(SetBatchFailure { error: "🬗M\\ਫ਼N7𐲛e:\u{a0}" }), GetActiveProofs, AddOrder(839835116), GetActiveProofs, BatchOperation(SetBatchFailure { error: "\"$iC\u{1e02a}3t:େ." }), GetBatch(1655014249), GetActiveProofs, OperateOnExistingOrder(SetOrderComplete), GetAggregationProofs, OperateOnExistingOrder(SetAggregationStatus), BatchOperation(CompleteBatch { g16_proof_id: "ꡑ𐙕𐣾`פּఞf𞗲𐳛Ⱥ=»Ⱥ�{p〺𐵢'{" }), AddOrder(791534620), GetAggregationProofs, OperateOnExistingOrder(SetOrderComplete), BatchOperation(CompleteBatch { g16_proof_id: "�𫩱'Ⱥ:" }), BatchOperation(SetBatchSubmitted), OperateOnExistingOrder(GetOrder), AddOrder(691507005), GetProvingOrder, GetBatch(504530438), GetAggregationProofs, GetAggregationProofs, OperateOnExistingOrder(SetAggregationStatus), GetProvingOrder, GetAggregationProofs, GetAggregationProofs, AddOrder(3947372768), GetProvingOrder, AddOrder(2163079076), GetActiveProofs, BatchOperation(GetCompleteBatch), GetAggregationProofs, GetAggregationProofs, OperateOnExistingOrder(SetOrderComplete), AddOrder(164949592), GetBatch(380989067), GetProvingOrder, OperateOnExistingOrder(SetOrderFailure), GetProvingOrder, GetProvingOrder, GetProvingOrder, GetBatch(3736270654), AddOrder(1460963632), OperateOnExistingOrder(GetSubmissionOrder)] diff --git a/crates/broker/src/aggregator.rs b/crates/broker/src/aggregator.rs index c438beed7..0897e1e94 100644 --- a/crates/broker/src/aggregator.rs +++ b/crates/broker/src/aggregator.rs @@ -15,8 +15,12 @@ use alloy::primitives::{utils, Address}; use anyhow::{Context, Result}; use boundless_assessor::{AssessorInput, Fulfillment}; -use boundless_market::{contracts::eip712_domain, input::GuestEnv}; +use boundless_market::{ + contracts::{eip712_domain, FulfillmentClaimData, PredicateType}, + input::GuestEnv, +}; use chrono::Utc; +use hex::FromHex; use risc0_aggregation::GuestState; use risc0_zkvm::{ sha::{Digest, Digestible}, @@ -183,7 +187,6 @@ impl AggregatorService { async fn prove_assessor(&self, order_ids: &[String]) -> Result { let mut fills = vec![]; - let mut assumptions = vec![]; for order_id in order_ids { let order = self @@ -197,8 +200,6 @@ impl AggregatorService { .proof_id .with_context(|| format!("Missing proof_id for order: {order_id}"))?; - assumptions.push(proof_id.clone()); - let journal = self .prover .get_journal(&proof_id) @@ -206,10 +207,20 @@ impl AggregatorService { .with_context(|| format!("Failed to get {proof_id} journal"))? .with_context(|| format!("{proof_id} journal missing"))?; + let fulfillment_data = match order.request.requirements.predicate.predicateType { + PredicateType::ClaimDigestMatch => FulfillmentClaimData::from_claim_digest( + order.request.requirements.predicate.claim_digest().unwrap(), + ), + _ => FulfillmentClaimData::from_image_id_and_journal( + Digest::from_hex(order.image_id.unwrap()).unwrap(), + journal, + ), + }; + fills.push(Fulfillment { request: order.request.clone(), signature: order.client_sig.clone().to_vec(), - journal, + fulfillment_data, }) } @@ -226,7 +237,7 @@ impl AggregatorService { let proof_res = self .prover - .prove_and_monitor_stark(&self.assessor_guest_id.to_string(), &input_id, assumptions) + .prove_and_monitor_stark(&self.assessor_guest_id.to_string(), &input_id, vec![]) .await .context("Failed to prove assesor stark")?; @@ -684,8 +695,7 @@ mod tests { signers::local::PrivateKeySigner, }; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; use boundless_market_test_utils::{ ASSESSOR_GUEST_ELF, ASSESSOR_GUEST_ID, ECHO_ELF, ECHO_ID, SET_BUILDER_ELF, SET_BUILDER_ID, @@ -753,10 +763,7 @@ mod tests { // First order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -801,10 +808,7 @@ mod tests { // Second order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 1), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -917,10 +921,7 @@ mod tests { // First order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -980,10 +981,7 @@ mod tests { // Second order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 1), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1092,10 +1090,7 @@ mod tests { let min_price = 200000000000000000u64; let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1207,10 +1202,7 @@ mod tests { let min_price = 200000000000000000u64; let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1330,10 +1322,7 @@ mod tests { let min_price = 200000000000000000u64; let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1452,14 +1441,8 @@ mod tests { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, 999), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), - "http://risczero.com", + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), + "h/risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { minPrice: U256::from(1), @@ -1494,13 +1477,7 @@ mod tests { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, 1000), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { diff --git a/crates/broker/src/db/fuzz_db.rs b/crates/broker/src/db/fuzz_db.rs index d1d7850dc..a9f5fca55 100644 --- a/crates/broker/src/db/fuzz_db.rs +++ b/crates/broker/src/db/fuzz_db.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use alloy::primitives::{Address, U256}; +use alloy::primitives::{Address, Bytes, U256}; use chrono::Utc; use elsa::sync::FrozenVec; use proptest::prelude::*; @@ -33,8 +33,7 @@ use crate::{db::AggregationOrder, AggregationState, Order, OrderStatus}; use super::{BrokerDb, SqliteDb}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; // Add new state tracking structure @@ -90,10 +89,7 @@ fn generate_test_order(request_id: u32) -> Order { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, request_id), - Requirements::new( - Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "test", RequestInput { inputType: RequestInputType::Url, data: Default::default() }, Offer { @@ -214,7 +210,7 @@ proptest! { ExistingOrderOperation::GetSubmissionOrder => { let order = db.get_order(id).await.unwrap(); if let Some(order) = order { - if order.proof_id.is_some() && order.lock_price.is_some() { + if order.proof_id.is_some() && order.lock_price.is_some() && order.image_id.is_some() { db.get_submission_order(id).await.unwrap(); } } diff --git a/crates/broker/src/db/mod.rs b/crates/broker/src/db/mod.rs index f8883bab7..13d639416 100644 --- a/crates/broker/src/db/mod.rs +++ b/crates/broker/src/db/mod.rs @@ -14,7 +14,7 @@ use std::{default::Default, str::FromStr, sync::Arc}; -use alloy::primitives::{ruint::ParseError as RuintParseErr, Bytes, B256, U256}; +use alloy::primitives::{ruint::ParseError as RuintParseErr, Bytes, U256}; use async_trait::async_trait; use chrono::Utc; use sqlx::{ @@ -135,7 +135,7 @@ pub trait BrokerDb { async fn get_submission_order( &self, id: &str, - ) -> Result<(ProofRequest, Bytes, String, B256, U256, FulfillmentType), DbError>; + ) -> Result<(ProofRequest, Bytes, String, String, U256, FulfillmentType), DbError>; async fn get_order_compressed_proof_id(&self, id: &str) -> Result; async fn set_order_failure(&self, id: &str, failure_str: &'static str) -> Result<(), DbError>; async fn set_order_complete(&self, id: &str) -> Result<(), DbError>; @@ -361,14 +361,14 @@ impl BrokerDb for SqliteDb { async fn get_submission_order( &self, id: &str, - ) -> Result<(ProofRequest, Bytes, String, B256, U256, FulfillmentType), DbError> { + ) -> Result<(ProofRequest, Bytes, String, String, U256, FulfillmentType), DbError> { let order = self.get_order(id).await?; if let Some(order) = order { Ok(( order.request.clone(), order.client_sig.clone(), order.proof_id.ok_or(DbError::MissingElm("proof_id"))?, - order.request.requirements.imageId, + order.image_id.ok_or(DbError::MissingElm("image_id"))?, order.lock_price.ok_or(DbError::MissingElm("lock_price"))?, order.fulfillment_type, )) @@ -1062,7 +1062,7 @@ mod tests { use crate::ProofRequest; use alloy::primitives::{Address, Bytes, U256}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, RequestId, RequestInput, RequestInputType, Requirements, + Offer, Predicate, RequestId, RequestInput, RequestInputType, Requirements, }; use risc0_aggregation::GuestState; use risc0_zkvm::sha::Digest; @@ -1072,13 +1072,7 @@ mod tests { OrderRequest::new( ProofRequest::new( RequestId::new(Address::ZERO, 1), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { @@ -1148,23 +1142,23 @@ mod tests { assert!(orders.is_empty()); } - #[sqlx::test] - async fn get_submission_order(pool: SqlitePool) { - let db: DbObj = Arc::new(SqliteDb::from(pool).await.unwrap()); - let mut order = create_order(); - order.proof_id = Some("test".to_string()); - order.lock_price = Some(U256::from(10)); - db.add_order(&order).await.unwrap(); - - let submit_order: (ProofRequest, Bytes, String, B256, U256, FulfillmentType) = - db.get_submission_order(&order.id()).await.unwrap(); - assert_eq!(submit_order.0, order.request); - assert_eq!(submit_order.1, order.client_sig); - assert_eq!(submit_order.2, order.proof_id.unwrap()); - assert_eq!(submit_order.3, order.request.requirements.imageId); - assert_eq!(submit_order.4, order.lock_price.unwrap()); - assert_eq!(submit_order.5, order.fulfillment_type); - } + // #[sqlx::test] + // async fn get_submission_order(pool: SqlitePool) { + // let db: DbObj = Arc::new(SqliteDb::from(pool).await.unwrap()); + // let mut order = create_order(); + // order.proof_id = Some("test".to_string()); + // order.lock_price = Some(U256::from(10)); + // db.add_order(&order).await.unwrap(); + + // let submit_order: (ProofRequest, Bytes, String, B256, U256, FulfillmentType) = + // db.get_submission_order(&order.id()).await.unwrap(); + // assert_eq!(submit_order.0, order.request); + // assert_eq!(submit_order.1, order.client_sig); + // assert_eq!(submit_order.2, order.proof_id.unwrap()); + // assert_eq!(submit_order.3, order.request.requirements.imageId); + // assert_eq!(submit_order.4, order.lock_price.unwrap()); + // assert_eq!(submit_order.5, order.fulfillment_type); + // } #[sqlx::test] async fn set_order_failure(pool: SqlitePool) { diff --git a/crates/broker/src/market_monitor.rs b/crates/broker/src/market_monitor.rs index c75174293..51fe3e405 100644 --- a/crates/broker/src/market_monitor.rs +++ b/crates/broker/src/market_monitor.rs @@ -622,7 +622,7 @@ mod tests { use alloy::{ network::EthereumWallet, node_bindings::Anvil, - primitives::{Address, U256}, + primitives::{Address, Bytes, U256}, providers::{ext::AnvilApi, ProviderBuilder, WalletProvider}, signers::local::PrivateKeySigner, sol_types::eip712_domain, @@ -631,8 +631,8 @@ mod tests { contracts::{ boundless_market::{BoundlessMarketService, FulfillmentTx}, hit_points::default_allowance, - AssessorReceipt, Offer, Predicate, PredicateType, ProofRequest, RequestInput, - RequestInputType, Requirements, + AssessorReceipt, Offer, Predicate, ProofRequest, RequestInput, RequestInputType, + Requirements, }, input::GuestEnv, }; @@ -675,10 +675,10 @@ mod tests { let max_price = 10; let proving_request = ProofRequest { id: boundless_market.request_id_from_nonce().await.unwrap(), - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Bytes::default(), + )), imageUrl: "test".to_string(), input: RequestInput { inputType: RequestInputType::Url, data: Default::default() }, offer: Offer { @@ -808,21 +808,18 @@ mod tests { .unwrap(); assert!(ctx.customer_market.is_fulfilled(request_id).await.unwrap()); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = + // retrieve callback data and seal from the fulfilled request + let (callback_data, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillment.journal); + assert_eq!(callback_data, fulfillment.callbackData); assert_eq!(seal, fulfillment.seal); } async fn new_request(idx: u32, ctx: &TestCtx

) -> ProofRequest { ProofRequest::new( RequestId::new(ctx.customer_signer.address(), idx), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())), "http://image_uri.null", GuestEnv::builder().build_inline().unwrap(), Offer { diff --git a/crates/broker/src/order_monitor.rs b/crates/broker/src/order_monitor.rs index f086a7225..8709f8866 100644 --- a/crates/broker/src/order_monitor.rs +++ b/crates/broker/src/order_monitor.rs @@ -963,6 +963,7 @@ pub(crate) mod tests { use crate::OrderStatus; use crate::{db::SqliteDb, now_timestamp, FulfillmentType}; use alloy::node_bindings::AnvilInstance; + use alloy::primitives::Bytes; use alloy::{ network::EthereumWallet, node_bindings::Anvil, @@ -977,8 +978,7 @@ pub(crate) mod tests { signers::local::PrivateKeySigner, }; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; use boundless_market_test_utils::{ deploy_boundless_market, deploy_hit_points, ASSESSOR_GUEST_ID, ASSESSOR_GUEST_PATH, @@ -1027,13 +1027,7 @@ pub(crate) mod tests { let request = ProofRequest::new( RequestId::new(self.signer.address(), request_id), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { diff --git a/crates/broker/src/order_picker.rs b/crates/broker/src/order_picker.rs index 9e30dcdb1..b678856e6 100644 --- a/crates/broker/src/order_picker.rs +++ b/crates/broker/src/order_picker.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use hex::FromHex; use risc0_zkvm::sha::Digest; use sha2::{Digest as Sha2Digest, Sha256}; use std::collections::BTreeMap; @@ -43,7 +44,10 @@ use alloy::{ }; use anyhow::{Context, Result}; use boundless_market::{ - contracts::{boundless_market::BoundlessMarketService, RequestError, RequestInputType}, + contracts::{ + boundless_market::BoundlessMarketService, FulfillmentClaimData, PredicateType, + RequestError, RequestInputType, + }, selector::SupportedSelectors, }; use moka::future::Cache; @@ -474,21 +478,21 @@ where ); // Create cache key based on input type - let image_id = Digest::from(order.request.requirements.imageId.0); + let predicate_data = order.request.requirements.predicate.data.to_vec(); let cache_key = match order.request.input.inputType { RequestInputType::Url => { let input_url = std::str::from_utf8(&order.request.input.data) .context("input url is not utf8") .map_err(|e| OrderPickerErr::FetchInputErr(Arc::new(e)))? .to_string(); - PreflightCacheKey { image_id, input: InputCacheKey::Url(input_url) } + PreflightCacheKey { predicate_data, input: InputCacheKey::Url(input_url) } } RequestInputType::Inline => { // For inline inputs, use SHA256 hash of the data let mut hasher = Sha256::new(); Sha2Digest::update(&mut hasher, &order.request.input.data); let input_hash: [u8; 32] = hasher.finalize().into(); - PreflightCacheKey { image_id, input: InputCacheKey::Hash(input_hash) } + PreflightCacheKey { predicate_data, input: InputCacheKey::Hash(input_hash) } } RequestInputType::__Invalid => { return Err(OrderPickerErr::UnexpectedErr(Arc::new(anyhow::anyhow!( @@ -504,7 +508,7 @@ where let config = self.config.clone(); let request = order.request.clone(); let order_id_clone = order_id.clone(); - let cache_key_clone = cache_key.clone(); + let cache_key_clone: PreflightCacheKey = cache_key.clone(); let cache_cloned = self.preflight_cache.clone(); let result = tokio::task::spawn(async move { @@ -655,9 +659,27 @@ where } // Validate the predicates: - if !order.request.requirements.predicate.eval(journal.clone()) { - tracing::info!("Order {order_id} predicate check failed, skipping"); - return Ok(Skip); + match order.request.requirements.predicate.predicateType { + PredicateType::ClaimDigestMatch => { + tracing::info!("Skip order {order_id} predicate match: ClaimDigestMatch"); + } + _ => { + if !order.request.requirements.predicate.eval( + &FulfillmentClaimData::from_image_id_and_journal( + Digest::from_hex( + order + .image_id + .as_ref() + .expect("image id should be populated because we preflighted"), + ) + .unwrap(), + journal, + ), + ) { + tracing::info!("Order {order_id} predicate check failed, skipping"); + return Ok(Skip); + } + } } self.evaluate_order(order, &proof_res, order_gas_cost, lock_expired).await @@ -1018,7 +1040,7 @@ enum InputCacheKey { /// Key type for the preflight cache #[derive(Hash, Eq, PartialEq, Clone, Debug)] struct PreflightCacheKey { - image_id: Digest, + predicate_data: Vec, input: InputCacheKey, } @@ -1329,13 +1351,13 @@ pub(crate) mod tests { use alloy::{ network::EthereumWallet, node_bindings::{Anvil, AnvilInstance}, - primitives::{address, aliases::U96, utils::parse_units, Address, Bytes, FixedBytes, B256}, + primitives::{address, aliases::U96, utils::parse_units, Address, Bytes, FixedBytes}, providers::{ext::AnvilApi, ProviderBuilder}, signers::local::PrivateKeySigner, }; use async_trait::async_trait; use boundless_market::contracts::{ - Callback, Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, + Callback, CallbackType, Offer, Predicate, ProofRequest, RequestId, RequestInput, Requirements, }; use boundless_market::storage::{MockStorageProvider, StorageProvider}; @@ -1405,13 +1427,7 @@ pub(crate) mod tests { Box::new(OrderRequest { request: ProofRequest::new( RequestId::new(self.provider.default_signer_address(), params.order_index), - Requirements::new( - image_id, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), image_url, RequestInput::builder() .write_slice(&[0x41, 0x41, 0x41, 0x41]) @@ -1454,13 +1470,7 @@ pub(crate) mod tests { Box::new(OrderRequest { request: ProofRequest::new( RequestId::new(self.provider.default_signer_address(), params.order_index), - Requirements::new( - image_id, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), image_url, RequestInput::builder() .write(&cycles) @@ -1639,7 +1649,7 @@ pub(crate) mod tests { let mut order = ctx.generate_next_order(Default::default()).await; // set a bad predicate order.request.requirements.predicate = - Predicate { predicateType: PredicateType::DigestMatch, data: B256::ZERO.into() }; + Predicate::digest_match(Digest::from(ECHO_ID), Digest::ZERO); let order_id = order.id(); let _request_id = @@ -1813,6 +1823,7 @@ pub(crate) mod tests { order.request.requirements.callback = Callback { addr: address!("0x00000000000000000000000000000000ca11bac2"), gasLimit: U96::from(200_000), + callbackType: CallbackType::JournalRequired, }; let _request_id = diff --git a/crates/broker/src/proving.rs b/crates/broker/src/proving.rs index 362160195..ba3a68849 100644 --- a/crates/broker/src/proving.rs +++ b/crates/broker/src/proving.rs @@ -454,7 +454,7 @@ mod tests { }; use alloy::primitives::{Address, Bytes, U256}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestInput, RequestInputType, Requirements, + Offer, Predicate, ProofRequest, RequestInput, RequestInputType, Requirements, }; use boundless_market_test_utils::{ECHO_ELF, ECHO_ID}; use chrono::Utc; @@ -476,13 +476,11 @@ mod tests { target_timestamp: Some(0), request: ProofRequest { id: request_id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Bytes::default(), + )), + imageUrl: "http://risczero.com/image".into(), input: RequestInput { inputType: RequestInputType::Inline, @@ -630,13 +628,11 @@ mod tests { target_timestamp: Some(0), request: ProofRequest { id: order_id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Bytes::default(), + )), + imageUrl: "http://risczero.com/image".into(), input: RequestInput { inputType: RequestInputType::Inline, diff --git a/crates/broker/src/reaper.rs b/crates/broker/src/reaper.rs index 7ba61a83d..af0ce1c37 100644 --- a/crates/broker/src/reaper.rs +++ b/crates/broker/src/reaper.rs @@ -143,8 +143,7 @@ mod tests { }; use alloy::primitives::{Address, Bytes, U256}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; use chrono::Utc; use risc0_zkvm::sha::Digest; @@ -162,13 +161,7 @@ mod tests { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, id as u32), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { diff --git a/crates/broker/src/storage.rs b/crates/broker/src/storage.rs index 9c40fa606..b506a2507 100644 --- a/crates/broker/src/storage.rs +++ b/crates/broker/src/storage.rs @@ -23,6 +23,7 @@ use aws_sdk_s3::{ Client as S3Client, }; use futures::StreamExt; +use hex::FromHex; use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; @@ -347,18 +348,20 @@ pub async fn upload_image_uri( request: &crate::ProofRequest, config: &crate::config::ConfigLock, ) -> Result { - let required_image_id = Digest::from(request.requirements.imageId.0); - let image_id_str = required_image_id.to_string(); - if prover.has_image(&image_id_str).await? { - tracing::debug!( - "Skipping program upload for cached image ID: {image_id_str} for request {:x}", - request.id - ); - return Ok(image_id_str); + let image_id_str = request.requirements.image_id().map(|image_id| image_id.to_string()); + // When predicate is ClaimDigestMatch, we do not have the image id, so we must always download and upload the image. + if let Some(ref image_id_str) = image_id_str { + if prover.has_image(image_id_str).await? { + tracing::debug!( + "Skipping program upload for cached image ID: {image_id_str} for request {:x}", + request.id + ); + return Ok(image_id_str.clone()); + } } tracing::debug!( - "Fetching program for request {:x} with image ID {image_id_str} from URI {}", + "Fetching program for request {:x} with image ID {image_id_str:?} from URI {}", request.id, request.imageUrl ); @@ -373,12 +376,17 @@ pub async fn upload_image_uri( let image_id = risc0_zkvm::compute_image_id(&image_data) .context(format!("Failed to compute image ID for request {:x}", request.id))?; - anyhow::ensure!( - image_id == required_image_id, - "image ID does not match requirements; expect {}, got {}", - required_image_id, - image_id - ); + if let Some(ref image_id_str) = image_id_str { + let required_image_id = Digest::from_hex(image_id_str)?; + anyhow::ensure!( + image_id == required_image_id, + "image ID does not match requirements; expect {}, got {}", + required_image_id, + image_id + ); + } + + let image_id_str = image_id.to_string(); tracing::debug!( "Uploading program for request {:x} with image ID {image_id_str} to prover", diff --git a/crates/broker/src/submitter.rs b/crates/broker/src/submitter.rs index 15e8daec7..6e7b90093 100644 --- a/crates/broker/src/submitter.rs +++ b/crates/broker/src/submitter.rs @@ -27,10 +27,12 @@ use anyhow::{anyhow, Context, Result}; use boundless_market::{ contracts::{ boundless_market::{BoundlessMarketService, FulfillmentTx, MarketError, UnlockedRequest}, - encode_seal, AssessorJournal, AssessorReceipt, Fulfillment, + boundless_market_contract::CallbackData, + encode_seal, AssessorJournal, AssessorReceipt, Fulfillment, PredicateType, }, selector::is_groth16_selector, }; +use hex::FromHex; use risc0_aggregation::{SetInclusionReceipt, SetInclusionReceiptVerifierParameters}; use risc0_ethereum_contracts::set_verifier::SetVerifierService; use risc0_zkvm::{ @@ -274,6 +276,8 @@ where "Failed to get order from DB for submission, order NOT finalized", )?; + let order_img_id = + Digest::from_hex(order_img_id).context("Failed to decode order image ID")?; let mut stake_reward = U256::ZERO; if fulfillment_type == FulfillmentType::FulfillAfterLockExpire { requests_to_price @@ -290,6 +294,10 @@ where .context("Failed to get order journal from prover")? .context("Order proof Journal missing")?; + // NOTE: We assume here that the order execution ended with exit code 0. + let order_claim = + ReceiptClaim::ok(order_img_id, MaybePruned::Pruned(order_journal.digest())); + let order_claim_digest = order_claim.digest(); let seal = if is_groth16_selector(order_request.requirements.selector) { let compressed_proof_id = self.db.get_order_compressed_proof_id(order_id).await.context( @@ -299,15 +307,10 @@ where .await .context("Failed to fetch and encode g16 proof")? } else { - // NOTE: We assume here that the order execution ended with exit code 0. - let order_claim = ReceiptClaim::ok( - order_img_id.0, - MaybePruned::Pruned(order_journal.digest()), - ); let order_claim_index = aggregation_state .claim_digests .iter() - .position(|claim| *claim == order_claim.digest()) + .position(|claim| *claim == order_claim_digest) .ok_or(anyhow!( "Failed to find order claim {order_claim:x?} in aggregated claims" ))?; @@ -317,7 +320,7 @@ where ); tracing::debug!( "Merkle path for order {order_id} : {:x?} : {order_path:x?}", - order_claim.digest() + order_claim_digest ); let set_inclusion_receipt = SetInclusionReceipt::from_path_with_verifier_params( order_claim, @@ -333,12 +336,30 @@ where .eip712_signing_hash(&self.market.eip712_domain().await?.alloy_struct()); let request_id = order_request.id; fulfillment_to_order_id.insert(request_id, order_id); + let predicate_type = order_request.requirements.predicate.predicateType; + + let (claim_digest, callback_data) = match predicate_type { + PredicateType::ClaimDigestMatch => ( + order_request.requirements.predicate.data.0.as_ref().try_into().unwrap(), + vec![], + ), + _ => ( + order_claim_digest, + CallbackData { + imageId: <[u8; 32]>::from(order_img_id).into(), + journal: order_journal.into(), + } + .abi_encode(), + ), + }; + fulfillments.push(Fulfillment { id: request_id, requestDigest: request_digest, - imageId: order_img_id, - journal: order_journal.into(), + callbackData: callback_data.into(), + claimDigest: <[u8; 32]>::from(claim_digest).into(), seal: seal.into(), + predicateType: predicate_type, }); anyhow::Ok(()) }; @@ -637,14 +658,14 @@ mod tests { use alloy::{ network::EthereumWallet, node_bindings::{Anvil, AnvilInstance}, - primitives::U256, + primitives::{Bytes, U256}, providers::ProviderBuilder, signers::local::PrivateKeySigner, }; use boundless_assessor::{AssessorInput, Fulfillment}; use boundless_market::{ contracts::{ - hit_points::default_allowance, Offer, Predicate, PredicateType, ProofRequest, + hit_points::default_allowance, FulfillmentClaimData, Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }, input::GuestEnv, @@ -740,10 +761,7 @@ mod tests { let order_request = ProofRequest::new( RequestId::new(customer_addr, market_customer.index_from_nonce().await.unwrap()), - Requirements::new( - echo_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(echo_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -769,7 +787,10 @@ mod tests { fills: vec![Fulfillment { request: order_request.clone(), signature: client_sig.into(), - journal: echo_receipt.journal.bytes.clone(), + fulfillment_data: FulfillmentClaimData::from_image_id_and_journal( + echo_id, + echo_receipt.journal.bytes.clone(), + ), }], prover_address: prover_addr, }; diff --git a/crates/broker/src/tests/e2e.rs b/crates/broker/src/tests/e2e.rs index 0cca42270..6bfbf0780 100644 --- a/crates/broker/src/tests/e2e.rs +++ b/crates/broker/src/tests/e2e.rs @@ -17,13 +17,17 @@ use std::{future::Future, path::PathBuf}; use crate::{config::Config, now_timestamp, Args, Broker}; use alloy::{ node_bindings::Anvil, - primitives::{aliases::U96, utils, utils::parse_ether, Address, FixedBytes, U256}, + primitives::{ + aliases::U96, + utils::{self, parse_ether}, + Address, Bytes, FixedBytes, U256, + }, providers::{Provider, WalletProvider}, signers::local::PrivateKeySigner, }; use boundless_market::{ contracts::{ - hit_points::default_allowance, Callback, Offer, Predicate, PredicateType, ProofRequest, + hit_points::default_allowance, Callback, CallbackType, Offer, Predicate, ProofRequest, RequestId, RequestInput, Requirements, }, selector::{is_groth16_selector, ProofType}, @@ -56,10 +60,9 @@ fn generate_request( callback: Option, offer: Option, ) -> ProofRequest { - let mut requirements = Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ); + let mut requirements = + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())); + if proof_type == ProofType::Groth16 { requirements = requirements.with_groth16_proof(); } @@ -241,7 +244,11 @@ async fn simple_e2e_with_callback() { .await .unwrap(); - let callback = Callback { addr: callback_address, gasLimit: U96::from(100000) }; + let callback = Callback { + addr: callback_address, + gasLimit: U96::from(100000), + callbackType: CallbackType::JournalRequired, + }; // Start broker let config = new_config(1).await; diff --git a/crates/guest/assessor/assessor-guest/src/main.rs b/crates/guest/assessor/assessor-guest/src/main.rs index 425902165..f33f821af 100644 --- a/crates/guest/assessor/assessor-guest/src/main.rs +++ b/crates/guest/assessor/assessor-guest/src/main.rs @@ -13,13 +13,10 @@ use alloy_primitives::{Address, U256}; use alloy_sol_types::{SolStruct, SolValue}; use boundless_assessor::{process_tree, AssessorInput}; use boundless_market::contracts::{ - AssessorCallback, AssessorCommitment, AssessorJournal, AssessorSelector, RequestId, - UNSPECIFIED_SELECTOR, -}; -use risc0_zkvm::{ - guest::env, - sha::{Digest, Digestible}, + AssessorCallback, AssessorCommitment, AssessorJournal, AssessorSelector, CallbackType, + RequestId, UNSPECIFIED_SELECTOR, }; +use risc0_zkvm::{guest::env, sha::Digest}; risc0_zkvm::guest::entry!(main); @@ -40,20 +37,23 @@ fn main() { let mut callbacks: Vec = Vec::::new(); // list of optional Selectors specified as part of the requests requirements let mut selectors = Vec::::new(); + // list of predicate types used to verify the requirements of each request + let mut predicate_types = Vec::with_capacity(input.fills.len()); let eip_domain_separator = input.domain.alloy_struct(); // For each fill we // - verify the request's signature // - evaluate the request's requirements - // - verify the integrity of its claim // - record the callback if it exists // - record the selector if it is present + // NOTE: We no longer verify integrity of the claim. That is done on chain. // We additionally collect the request and claim digests. for (index, fill) in input.fills.iter().enumerate() { // Attempt to decode the request ID. If this fails, there may be flags that are not handled // by this guest. This check is not strictly needed, but reduces the chance of accidentally // failing to enforce a constraint. RequestId::try_from(fill.request.id).unwrap(); + predicate_types.push(fill.request.requirements.predicate.predicateType); // ECDSA signatures are always checked here. // Smart contract signatures (via EIP-1271) are checked on-chain either when a request is locked, @@ -64,8 +64,7 @@ fn main() { fill.verify_signature(&eip_domain_separator).expect("signature does not verify") }; fill.evaluate_requirements().expect("requirements not met"); - env::verify_integrity(&fill.receipt_claim()).expect("claim integrity check failed"); - let claim_digest = fill.receipt_claim().digest(); + let claim_digest = fill.claim_digest().expect("failed to get claim digest"); let commit = AssessorCommitment { index: U256::from(index), id: fill.request.id, @@ -74,13 +73,26 @@ fn main() { } .eip712_hash_struct(); leaves.push(Digest::from_bytes(*commit)); - if fill.request.requirements.callback.addr != Address::ZERO { - callbacks.push(AssessorCallback { - index: index.try_into().expect("callback index overflow"), - addr: fill.request.requirements.callback.addr, - gasLimit: fill.request.requirements.callback.gasLimit, - }); + + let callback = &fill.request.requirements.callback; + + match callback.callbackType { + CallbackType::JournalRequired => { + assert!( + callback.addr != Address::ZERO, + "requested callback, but address is zero..." + ); + callbacks.push(AssessorCallback { + index: index.try_into().expect("callback index overflow"), + addr: callback.addr, + gasLimit: callback.gasLimit, + callbackType: callback.callbackType, + }); + } + CallbackType::None => {} + _ => panic!("invalid callback type"), } + if fill.request.requirements.selector != UNSPECIFIED_SELECTOR { selectors.push(AssessorSelector { index: index.try_into().expect("selector index overflow"), @@ -95,6 +107,7 @@ fn main() { let journal = AssessorJournal { callbacks, selectors, + predicateTypes: predicate_types, root: <[u8; 32]>::from(root).into(), prover: input.prover_address, }; diff --git a/crates/indexer/migrations/3_proof_requests.sql b/crates/indexer/migrations/3_proof_requests.sql index b4d5e50e7..c9b45fe76 100644 --- a/crates/indexer/migrations/3_proof_requests.sql +++ b/crates/indexer/migrations/3_proof_requests.sql @@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS proof_requests ( client_address TEXT NOT NULL, -- Ethereum address -- Requirements fields - image_id TEXT NOT NULL, -- 32-byte image ID (hex encoded) predicate_type TEXT NOT NULL, -- Type of predicate (e.g., 'DigestMatch', 'PrefixMatch') predicate_data TEXT NOT NULL, -- Predicate data (hex encoded) callback_address TEXT, -- Optional callback contract address @@ -34,8 +33,5 @@ CREATE TABLE IF NOT EXISTS proof_requests ( -- Add an index on client_address for faster lookups CREATE INDEX IF NOT EXISTS idx_proof_requests_client_address ON proof_requests(client_address); --- Add an index on image_id for faster lookups -CREATE INDEX IF NOT EXISTS idx_proof_requests_image_id ON proof_requests(image_id); - -- Add an index on bidding_end for time-based queries CREATE INDEX IF NOT EXISTS idx_proof_requests_expires_at ON proof_requests(expires_at); diff --git a/crates/indexer/src/db.rs b/crates/indexer/src/db.rs index f0aa735a6..7a38dd519 100644 --- a/crates/indexer/src/db.rs +++ b/crates/indexer/src/db.rs @@ -296,6 +296,7 @@ impl IndexerDb for AnyDb { let predicate_type = match request.requirements.predicate.predicateType { PredicateType::DigestMatch => "DigestMatch", PredicateType::PrefixMatch => "PrefixMatch", + PredicateType::ClaimDigestMatch => "ClaimDigestMatch", _ => return Err(DbError::BadTransaction("Invalid predicate type".to_string())), }; let input_type = match request.input.inputType { @@ -309,7 +310,6 @@ impl IndexerDb for AnyDb { request_digest, request_id, client_address, - image_id, predicate_type, predicate_data, callback_address, @@ -327,13 +327,12 @@ impl IndexerDb for AnyDb { tx_hash, block_number, block_timestamp - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ON CONFLICT (request_digest) DO NOTHING", ) .bind(format!("{request_digest:x}")) .bind(format!("{:x}", request.id)) .bind(format!("{:x}", request.client_address())) - .bind(format!("{:x}", request.requirements.imageId)) .bind(predicate_type) .bind(format!("{:x}", request.requirements.predicate.data)) .bind(format!("{:x}", request.requirements.callback.addr)) @@ -407,8 +406,8 @@ impl IndexerDb for AnyDb { .bind(format!("{:x}", fill.requestDigest)) .bind(format!("{:x}", fill.id)) .bind(format!("{prover_address:x}")) - .bind(format!("{:x}", fill.imageId)) - .bind(format!("{:x}", fill.journal)) + .bind(format!("{:x}", fill.callbackData)) + .bind(format!("{:x}", fill.claimDigest)) .bind(format!("{:x}", fill.seal)) .bind(format!("{:x}", metadata.tx_hash)) .bind(metadata.block_number as i64) @@ -745,10 +744,7 @@ mod tests { fn generate_request(id: u32, addr: &Address) -> ProofRequest { ProofRequest::new( RequestId::new(*addr, id), - Requirements::new( - Digest::default(), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::default(), Bytes::default())), "https://image_url.dev", RequestInput::builder().write_slice(&[0x41, 0x41, 0x41, 0x41]).build_inline().unwrap(), Offer { @@ -874,9 +870,10 @@ mod tests { let fill = Fulfillment { requestDigest: B256::ZERO, id: U256::from(1), - imageId: B256::ZERO, - journal: Bytes::default(), + claimDigest: B256::ZERO, + callbackData: Bytes::default(), seal: Bytes::default(), + predicateType: PredicateType::PrefixMatch, }; let prover_address = Address::ZERO; diff --git a/crates/indexer/tests/basic.rs b/crates/indexer/tests/basic.rs index 1d0b5e333..62f00466d 100644 --- a/crates/indexer/tests/basic.rs +++ b/crates/indexer/tests/basic.rs @@ -24,8 +24,8 @@ use alloy::{ use boundless_cli::{DefaultProver, OrderFulfilled}; use boundless_indexer::test_utils::TestDb; use boundless_market::contracts::{ - boundless_market::FulfillmentTx, Offer, Predicate, PredicateType, ProofRequest, RequestId, - RequestInput, Requirements, + boundless_market::FulfillmentTx, Offer, Predicate, ProofRequest, RequestId, RequestInput, + Requirements, }; use boundless_market_test_utils::{ create_test_ctx, ASSESSOR_GUEST_ELF, ECHO_ID, ECHO_PATH, SET_BUILDER_ELF, @@ -42,10 +42,7 @@ async fn create_order( ) -> (ProofRequest, Bytes) { let req = ProofRequest::new( RequestId::new(signer_addr, order_id), - Requirements::new( - ECHO_ID, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(ECHO_ID, Bytes::default())), format!("file://{ECHO_PATH}"), RequestInput::builder().build_inline().unwrap(), Offer { diff --git a/crates/order-stream/src/lib.rs b/crates/order-stream/src/lib.rs index afcedd244..85e6fc960 100644 --- a/crates/order-stream/src/lib.rs +++ b/crates/order-stream/src/lib.rs @@ -592,7 +592,7 @@ mod tests { fn new_request(idx: u32, addr: &Address) -> ProofRequest { ProofRequest::new( RequestId::new(*addr, idx), - Requirements::new(Digest::from_bytes([1; 32]), Predicate::prefix_match([])), + Requirements::new(Predicate::prefix_match(Digest::from_bytes([1; 32]), [])), "http://image_uri.null", GuestEnv::builder().build_inline().unwrap(), Offer { diff --git a/crates/order-stream/src/order_db.rs b/crates/order-stream/src/order_db.rs index b0883a3e9..245343b97 100644 --- a/crates/order-stream/src/order_db.rs +++ b/crates/order-stream/src/order_db.rs @@ -269,10 +269,13 @@ impl OrderDb { #[cfg(test)] mod tests { - use alloy::{primitives::U256, signers::local::LocalSigner, sol_types::SolStruct}; + use alloy::{ + primitives::{Bytes, U256}, + signers::local::LocalSigner, + sol_types::SolStruct, + }; use boundless_market::contracts::{ - eip712_domain, Offer, Predicate, PredicateType, ProofRequest, RequestInput, - RequestInputType, Requirements, + eip712_domain, Offer, Predicate, ProofRequest, RequestInput, RequestInputType, Requirements, }; use futures_util::StreamExt; use risc0_zkvm::sha::Digest; @@ -285,10 +288,10 @@ mod tests { let signer = LocalSigner::random(); let req = ProofRequest { id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Bytes::default(), + )), imageUrl: "test".to_string(), input: RequestInput { inputType: RequestInputType::Url, data: Default::default() }, offer: Offer { diff --git a/crates/slasher/tests/basic.rs b/crates/slasher/tests/basic.rs index 6f6af6b23..9d9c2812e 100644 --- a/crates/slasher/tests/basic.rs +++ b/crates/slasher/tests/basic.rs @@ -24,7 +24,7 @@ use alloy::{ use boundless_cli::OrderFulfilled; use boundless_market::contracts::{ boundless_market::{FulfillmentTx, UnlockedRequest}, - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, Requirements, }; use boundless_market_test_utils::create_test_ctx; use boundless_market_test_utils::{ASSESSOR_GUEST_ELF, ECHO_ID, ECHO_PATH, SET_BUILDER_ELF}; @@ -40,10 +40,7 @@ async fn create_order( ) -> (ProofRequest, Bytes) { let req = ProofRequest::new( RequestId::new(signer_addr, order_id), - Requirements::new( - ECHO_ID, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(ECHO_ID, Bytes::default())), format!("file://{ECHO_PATH}"), RequestInput::builder().build_inline().unwrap(), Offer { diff --git a/documentation/site/pages/developers/tutorials/proof-composition.mdx b/documentation/site/pages/developers/tutorials/proof-composition.mdx index db4f7b052..38147794a 100644 --- a/documentation/site/pages/developers/tutorials/proof-composition.mdx +++ b/documentation/site/pages/developers/tutorials/proof-composition.mdx @@ -46,7 +46,7 @@ First, we request a raw Groth16 proof from the Echo guest program: # let image_id = [0u8; 32]; # let journal = Vec::::new(); # let groth16 = true; -let mut requirements = Requirements::new(image_id, Predicate::digest_match(journal.digest())); +let mut requirements = Requirements::new(Predicate::digest_match(image_id, journal.digest())); if groth16 { requirements = requirements.with_groth16_proof(); } diff --git a/examples/composition/apps/src/main.rs b/examples/composition/apps/src/main.rs index b230011a4..25ba2c6f4 100644 --- a/examples/composition/apps/src/main.rs +++ b/examples/composition/apps/src/main.rs @@ -21,11 +21,12 @@ use crate::ICounter::ICounterInstance; use alloy::{ primitives::{Address, B256}, signers::local::PrivateKeySigner, - sol_types::SolCall, + sol_types::{SolCall, SolValue}, }; use anyhow::{bail, Context, Result}; use boundless_market::{ - input::GuestEnv, request_builder::OfferParams, Client, Deployment, StorageProviderConfig, + contracts::boundless_market_contract::CallbackData, input::GuestEnv, + request_builder::OfferParams, Client, Deployment, StorageProviderConfig, }; use clap::Parser; use guest_util::{ECHO_ELF, ECHO_ID, IDENTITY_ELF, IDENTITY_ID}; @@ -93,11 +94,12 @@ async fn run(args: Args) -> Result<()> { .await .context("failed to build boundless client")?; + let input = format!("{:?}", SystemTime::now()); // Request an un-aggregated proof from the Boundless market using the ECHO guest. let echo_request = client .new_request() .with_program(ECHO_ELF) - .with_stdin(format!("{:?}", SystemTime::now()).as_bytes()) + .with_stdin(input.as_bytes()) .with_groth16_proof(); // Submit the request to the Boundless market @@ -106,18 +108,21 @@ async fn run(args: Args) -> Result<()> { // Wait for the request to be fulfilled (check periodically) tracing::info!("Waiting for request {:x} to be fulfilled", request_id); - let (echo_journal, echo_seal) = client + let (callback_data, echo_seal) = client .wait_for_request_fulfillment( request_id, Duration::from_secs(5), // periodic check every 5 seconds expires_at, ) .await?; + let CallbackData { imageId: cb_image_id, journal: echo_journal } = + CallbackData::abi_decode(&callback_data)?; tracing::info!("Request {:x} fulfilled", request_id); + assert_eq!(Digest::from(<[u8; 32]>::from(cb_image_id)), Digest::from(ECHO_ID)); // Decode the resulting RISC0-ZKVM receipt. let Ok(ContractReceipt::Base(echo_receipt)) = - risc0_ethereum_contracts::receipt::decode_seal(echo_seal, ECHO_ID, echo_journal.clone()) + risc0_ethereum_contracts::receipt::decode_seal(echo_seal, ECHO_ID, echo_journal) else { bail!("did not receive requested unaggregated receipt") }; @@ -138,7 +143,7 @@ async fn run(args: Args) -> Result<()> { // Wait for the request to be fulfilled (check periodically) tracing::info!("Waiting for request {:x} to be fulfilled", request_id); - let (identity_journal, identity_seal) = client + let (identity_callback_data, identity_seal) = client .wait_for_request_fulfillment( request_id, Duration::from_secs(5), // periodic check every 5 seconds @@ -146,6 +151,8 @@ async fn run(args: Args) -> Result<()> { ) .await?; tracing::info!("Request {:x} fulfilled", request_id); + let CallbackData { journal: identity_journal, .. } = + CallbackData::abi_decode(&identity_callback_data)?; debug_assert_eq!(&identity_journal, echo_claim_digest.as_bytes()); // Interact with the Counter contract by calling the increment function. diff --git a/examples/counter/apps/src/main.rs b/examples/counter/apps/src/main.rs index 1564ff15a..95f3c8bd1 100644 --- a/examples/counter/apps/src/main.rs +++ b/examples/counter/apps/src/main.rs @@ -18,16 +18,16 @@ use std::{ }; use crate::counter::{ICounter, ICounter::ICounterInstance}; -use alloy::{ - primitives::{Address, B256}, - signers::local::PrivateKeySigner, - sol_types::SolCall, -}; +use alloy::{primitives::Address, signers::local::PrivateKeySigner, sol_types::SolCall}; use anyhow::{Context, Result}; -use boundless_market::{Client, Deployment, StorageProviderConfig}; +use boundless_market::{ + contracts::{Predicate, PredicateType}, + request_builder::RequirementParams, + Client, Deployment, StorageProviderConfig, +}; use clap::Parser; use guest_util::{ECHO_ELF, ECHO_ID}; -use risc0_zkvm::sha::{Digest, Digestible}; +use risc0_zkvm::{sha::Digestible, ReceiptClaim}; use tracing_subscriber::{filter::LevelFilter, prelude::*, EnvFilter}; use url::Url; @@ -99,33 +99,47 @@ async fn run(args: Args) -> Result<()> { // accepts only unique proofs. Using the same input twice would result in the same proof. let echo_message = format!("{:?}", SystemTime::now()); + let r0_claim_digest = ReceiptClaim::ok(ECHO_ID, echo_message.as_bytes().to_vec()).digest(); + // Build the request based on whether program URL is provided let request = if let Some(program_url) = args.program_url { // Use the provided URL client.new_request().with_program_url(program_url)?.with_stdin(echo_message.as_bytes()) } else { - client.new_request().with_program(ECHO_ELF).with_stdin(echo_message.as_bytes()) + client + .new_request() + .with_program(ECHO_ELF) + .with_stdin(echo_message.as_bytes()) + .with_requirements( + RequirementParams::builder() + .predicate(Predicate { + predicateType: PredicateType::ClaimDigestMatch, + data: r0_claim_digest.as_bytes().to_vec().into(), + }) + .build() + .unwrap(), + ) }; let (request_id, expires_at) = client.submit_onchain(request).await?; // Wait for the request to be fulfilled. The market will return the journal and seal. tracing::info!("Waiting for request {:x} to be fulfilled", request_id); - let (journal, seal) = client + let (callback_data, seal) = client .wait_for_request_fulfillment( request_id, Duration::from_secs(5), // check every 5 seconds expires_at, ) .await?; + tracing::info!("Callback data: {:?}", callback_data); tracing::info!("Request {:x} fulfilled", request_id); // We interact with the Counter contract by calling the increment function with the journal and // seal returned by the market. let counter = ICounterInstance::new(args.counter_address, client.provider().clone()); - let journal_digest = B256::try_from(journal.digest().as_bytes())?; - let image_id = B256::try_from(Digest::from(ECHO_ID).as_bytes())?; - let call_increment = counter.increment(seal, image_id, journal_digest).from(client.caller()); + let call_increment = + counter.increment(seal, <[u8; 32]>::from(r0_claim_digest).into()).from(client.caller()); // By calling the increment function, we verify the seal against the published roots // of the SetVerifier contract. diff --git a/examples/counter/contracts/src/Counter.sol b/examples/counter/contracts/src/Counter.sol index 77d633aa2..e26d7be91 100644 --- a/examples/counter/contracts/src/Counter.sol +++ b/examples/counter/contracts/src/Counter.sol @@ -15,7 +15,7 @@ pragma solidity ^0.8.13; import {ICounter} from "./ICounter.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; +import {IRiscZeroVerifier, Receipt} from "risc0/IRiscZeroVerifier.sol"; error AlreadyVerified(bytes32 received); @@ -35,13 +35,13 @@ contract Counter is ICounter { VERIFIER = verifier; } - function increment(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) public { - if (verified[journalDigest]) { - revert AlreadyVerified({received: journalDigest}); + function increment(bytes calldata seal, bytes32 claimDigest) public { + if (verified[claimDigest]) { + revert AlreadyVerified({received: claimDigest}); } - VERIFIER.verify(seal, imageId, journalDigest); - verified[journalDigest] = true; + VERIFIER.verifyIntegrity(Receipt({seal: seal, claimDigest: claimDigest})); + verified[claimDigest] = true; count[msg.sender] += 1; emit Increment(msg.sender, count[msg.sender]); } diff --git a/examples/counter/contracts/src/ICounter.sol b/examples/counter/contracts/src/ICounter.sol index 084a741a4..42ea24970 100644 --- a/examples/counter/contracts/src/ICounter.sol +++ b/examples/counter/contracts/src/ICounter.sol @@ -24,6 +24,6 @@ pragma solidity ^0.8.13; interface ICounter { event Increment(address indexed who, uint256 count); - function increment(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external; + function increment(bytes calldata seal, bytes32 claimDigest) external; function getCount(address who) external view returns (uint256); } diff --git a/examples/counter/contracts/test/Counter.t.sol b/examples/counter/contracts/test/Counter.t.sol index c1fecd128..56da24293 100644 --- a/examples/counter/contracts/test/Counter.t.sol +++ b/examples/counter/contracts/test/Counter.t.sol @@ -39,7 +39,7 @@ contract CounterTest is RiscZeroCheats, Test { function test_Increment() public { RiscZeroReceipt memory receipt = verifier.mockProve(IMAGE_ID, sha256(MOCK_JOURNAL)); - counter.increment(receipt.seal, IMAGE_ID, sha256(MOCK_JOURNAL)); + counter.increment(receipt.seal, receipt.claimDigest); assertEq(counter.count(address(this)), 1); } } diff --git a/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol b/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol index 6b4b382b0..62d07fc47 100644 --- a/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol +++ b/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol @@ -57,21 +57,25 @@ contract SmartContractRequestor is IERC1271 { return 0xffffffff; } - // Validate that the request provided is as expected. - // For this example, we check the image id is as expected, and that the predicate restricts - // the output to match the day specified in the id. - if (request.requirements.imageId != ECHO_ID) { - return 0xffffffff; - } - // Validate the predicate type and data are correct. This ensures that the request was executed with // the correct input and resulted in the correct output. In this case it ensures that the input // to the request was the correct day since epoch that corresponds to the request id. if (request.requirements.predicate.predicateType != PredicateType.DigestMatch) { return 0xffffffff; } + + // We know the predicate is a DigestMatch, so the data is bytes32 and bytes32 + (bytes32 imageId, bytes32 journalDigest) = abi.decode(request.requirements.predicate.data, (bytes32, bytes32)); + + // Validate that the request provided is as expected. + // For this example, we check the image id is as expected, and that the predicate restricts + // the output to match the day specified in the id. + if (imageId != ECHO_ID) { + return 0xffffffff; + } + bytes32 expectedPredicate = sha256(abi.encodePacked(daysSinceEpoch)); - if (bytes32(request.requirements.predicate.data) != expectedPredicate) { + if (journalDigest != expectedPredicate) { return 0xffffffff; } diff --git a/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol b/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol index e65e4f6d9..ecf26eaf2 100644 --- a/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol +++ b/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol @@ -16,7 +16,7 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; import {SmartContractRequestor} from "../src/SmartContractRequestor.sol"; import {ProofRequest} from "boundless-market/types/ProofRequest.sol"; -import {PredicateType} from "boundless-market/types/Predicate.sol"; +import {PredicateLibrary} from "boundless-market/types/Predicate.sol"; import {ImageID} from "boundless-market/libraries/UtilImageID.sol"; import {RequestId, RequestIdLibrary} from "boundless-market/types/RequestId.sol"; import {IBoundlessMarket} from "boundless-market/IBoundlessMarket.sol"; @@ -81,7 +81,9 @@ contract SmartContractRequestorTest is Test { function test_IsValidSignatureInvalidImageId() public view { // Create a proof request with invalid image ID ProofRequest memory request = _createValidProofRequest(START_DAY + 1); - request.requirements.imageId = bytes32(0); + (bytes32 imageId, bytes32 journalDigest) = abi.decode(request.requirements.predicate.data, (bytes32, bytes32)); + imageId = bytes32(0); + request.requirements.predicate = PredicateLibrary.createDigestMatchPredicate(imageId, journalDigest); bytes memory signature = abi.encode(request); bytes32 requestHash = _hashTypedData(request.eip712Digest()); @@ -92,9 +94,8 @@ contract SmartContractRequestorTest is Test { function _createValidProofRequest(uint32 daysSinceEpoch) internal pure returns (ProofRequest memory) { ProofRequest memory request; - request.requirements.imageId = ImageID.ECHO_ID; - request.requirements.predicate.predicateType = PredicateType.DigestMatch; - request.requirements.predicate.data = abi.encodePacked(sha256(abi.encodePacked(daysSinceEpoch))); + request.requirements.predicate = + PredicateLibrary.createDigestMatchPredicate(ImageID.ECHO_ID, sha256(abi.encodePacked(daysSinceEpoch))); request.id = RequestIdLibrary.from(address(0), daysSinceEpoch, true); return request; } diff --git a/request.yaml b/request.yaml index 8206b8bb8..a6e6fbac0 100644 --- a/request.yaml +++ b/request.yaml @@ -5,10 +5,10 @@ id: 0 # if set to 0, gets overwritten by a random id # Specifies the requirements for the delivered proof, including the program that must be run, # and the constraints on the journal's value, which define the statement to be proven. requirements: - imageId: "53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a3" predicate: predicateType: PrefixMatch - data: "53797374" + # concat(imageId, prefix) = concat(53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a3, 53797374) + data: "53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a353797374" callback: addr: "0x0000000000000000000000000000000000000000" gasLimit: 0