Skip to content

Commit 699383d

Browse files
caposselenategraf
andauthored
BM-277: Commit prover address to assessor journal (github#99)
Co-authored-by: Victor Graf <[email protected]>
1 parent 7220bbc commit 699383d

File tree

10 files changed

+187
-65
lines changed

10 files changed

+187
-65
lines changed

contracts/src/IProofMarket.sol

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ struct AssessorJournal {
8888
// Root of the Merkle tree committing to the set of proven claims.
8989
// In the case of a batch of size one, this may simply be a claim digest.
9090
bytes32 root;
91-
// EIP712 domain separator
91+
// EIP712 domain separator.
9292
bytes32 eip712DomainSeparator;
93+
// The address of the prover that produced the assessor receipt.
94+
address prover;
9395
}
9496

9597
interface IProofMarket {
@@ -161,20 +163,27 @@ interface IProofMarket {
161163
bytes calldata proverSignature
162164
) external;
163165

164-
/// Fulfill a locked request by delivering the proof for the application.
165-
/// Upon proof verification, the prover will be paid.
166-
function fulfill(Fulfillment calldata fill, bytes calldata assessorSeal) external;
167-
168-
/// Fulfills a batch of locked requests
169-
function fulfillBatch(Fulfillment[] calldata fills, bytes calldata assessorSeal) external;
170-
171-
/// Optional path to combine submitting a new merkle root to the set-verifier and then calling fulfillBatch
172-
/// Useful to reduce the transaction count for fulfillments
166+
/// @notice Fulfill a locked request by delivering the proof for the application.
167+
/// Upon proof verification, the prover that locked the request will be paid.
168+
/// @param fill The fulfillment information, including the journal and seal.
169+
/// @param assessorSeal The seal from the Assessor guest, which is verified to confirm the
170+
/// request's requirements are met.
171+
/// @param prover The address of the prover that produced the fulfillment.
172+
/// Note that this can differ from the address of the prover that locked the
173+
/// request. When they differ, the locked-in prover is the one that received payment.
174+
function fulfill(Fulfillment calldata fill, bytes calldata assessorSeal, address prover) external;
175+
176+
/// @notice Fulfills a batch of locked requests. See IProofMarket.fulfill for more information.
177+
function fulfillBatch(Fulfillment[] calldata fills, bytes calldata assessorSeal, address prover) external;
178+
179+
/// @notice Combined function to submit a new merkle root to the set-verifier and call fulfillBatch.
180+
/// @dev Useful to reduce the transaction count for fulfillments
173181
function submitRootAndFulfillBatch(
174182
bytes32 root,
175183
bytes calldata seal,
176184
Fulfillment[] calldata fills,
177-
bytes calldata assessorSeal
185+
bytes calldata assessorSeal,
186+
address prover
178187
) external;
179188

180189
/// When a prover fails to fulfill a request by the deadline, this method can be used to burn

contracts/src/ProofMarket.sol

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ contract ProofMarket is IProofMarket, EIP712 {
242242
}
243243

244244
// TODO(victor): Add a path that allows a prover to fuilfill a request without first sending a lock-in.
245-
function fulfill(Fulfillment calldata fill, bytes calldata assessorSeal) external {
245+
function fulfill(Fulfillment calldata fill, bytes calldata assessorSeal, address prover) external {
246246
// Verify the application guest proof. We need to verify it here, even though the market
247247
// guest already verified that the prover has knowledge of a verifying receipt, because
248248
// we need to make sure the _delivered_ seal is valid.
@@ -256,7 +256,12 @@ contract ProofMarket is IProofMarket, EIP712 {
256256
ids[0] = fill.id;
257257
bytes32 assessorJournalDigest = sha256(
258258
abi.encode(
259-
AssessorJournal({requestIds: ids, root: claimDigest, eip712DomainSeparator: _domainSeparatorV4()})
259+
AssessorJournal({
260+
requestIds: ids,
261+
root: claimDigest,
262+
eip712DomainSeparator: _domainSeparatorV4(),
263+
prover: prover
264+
})
260265
)
261266
);
262267
// Verification of the assessor seal does not need to comply with FULFILL_MAX_GAS_FOR_VERIFY.
@@ -267,7 +272,7 @@ contract ProofMarket is IProofMarket, EIP712 {
267272
emit RequestFulfilled(fill.id, fill.journal, fill.seal);
268273
}
269274

270-
function fulfillBatch(Fulfillment[] calldata fills, bytes calldata assessorSeal) public {
275+
function fulfillBatch(Fulfillment[] calldata fills, bytes calldata assessorSeal, address prover) public {
271276
// TODO(victor): Figure out how much the memory here is costing. If it's significant, we can do some tricks to reduce memory pressure.
272277
bytes32[] memory claimDigests = new bytes32[](fills.length);
273278
uint192[] memory ids = new uint192[](fills.length);
@@ -281,7 +286,14 @@ contract ProofMarket is IProofMarket, EIP712 {
281286
// Verify the assessor, which ensures the application proof fulfills a valid request with the given ID.
282287
// NOTE: Signature checks and recursive verification happen inside the assessor.
283288
bytes32 assessorJournalDigest = sha256(
284-
abi.encode(AssessorJournal({requestIds: ids, root: batchRoot, eip712DomainSeparator: _domainSeparatorV4()}))
289+
abi.encode(
290+
AssessorJournal({
291+
requestIds: ids,
292+
root: batchRoot,
293+
eip712DomainSeparator: _domainSeparatorV4(),
294+
prover: prover
295+
})
296+
)
285297
);
286298
// Verification of the assessor seal does not need to comply with FULFILL_MAX_GAS_FOR_VERIFY.
287299
VERIFIER.verify(assessorSeal, ASSESSOR_ID, assessorJournalDigest);
@@ -368,11 +380,12 @@ contract ProofMarket is IProofMarket, EIP712 {
368380
bytes32 root,
369381
bytes calldata seal,
370382
Fulfillment[] calldata fills,
371-
bytes calldata assessorSeal
383+
bytes calldata assessorSeal,
384+
address prover
372385
) external {
373386
IRiscZeroSetVerifier setVerifier = IRiscZeroSetVerifier(address(VERIFIER));
374387
setVerifier.submitMerkleRoot(root, seal);
375-
fulfillBatch(fills, assessorSeal);
388+
fulfillBatch(fills, assessorSeal, prover);
376389
}
377390
}
378391

contracts/test/ProofMarket.t.sol

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -148,20 +148,21 @@ contract ProofMarketTest is Test {
148148
);
149149
}
150150

151-
function fulfillRequest(ProvingRequest memory request, bytes memory journal)
151+
function fulfillRequest(ProvingRequest memory request, bytes memory journal, address prover)
152152
internal
153153
returns (Fulfillment memory, bytes memory assessorSeal)
154154
{
155155
ProvingRequest[] memory requests = new ProvingRequest[](1);
156156
requests[0] = request;
157157
bytes[] memory journals = new bytes[](1);
158158
journals[0] = journal;
159-
(Fulfillment[] memory fills, bytes memory seal) = fulfillRequestBatch(requests, journals);
159+
(Fulfillment[] memory fills, bytes memory seal) = fulfillRequestBatch(requests, journals, prover);
160160
return (fills[0], seal);
161161
}
162162

163-
function createFills(ProvingRequest[] memory requests, bytes[] memory journals)
163+
function createFills(ProvingRequest[] memory requests, bytes[] memory journals, address prover)
164164
internal
165+
view
165166
returns (Fulfillment[] memory fills, bytes memory assessorSeal, bytes32 root)
166167
{
167168
// initialize the fullfillments; one for each request;
@@ -179,7 +180,7 @@ contract ProofMarketTest is Test {
179180

180181
// compute the assessor claim
181182
ReceiptClaim memory assessorClaim =
182-
TestUtils.mockAssessor(fills, ASSESSOR_IMAGE_ID, proofMarket.eip712DomainSeparator());
183+
TestUtils.mockAssessor(fills, ASSESSOR_IMAGE_ID, proofMarket.eip712DomainSeparator(), prover);
183184
// compute the batchRoot of the batch Merkle Tree (without the assessor)
184185
(bytes32 batchRoot, bytes32[][] memory tree) = TestUtils.mockSetBuilder(fills);
185186

@@ -193,12 +194,12 @@ contract ProofMarketTest is Test {
193194
return (fills, assessorSeal, root);
194195
}
195196

196-
function fulfillRequestBatch(ProvingRequest[] memory requests, bytes[] memory journals)
197+
function fulfillRequestBatch(ProvingRequest[] memory requests, bytes[] memory journals, address prover)
197198
internal
198199
returns (Fulfillment[] memory fills, bytes memory assessorSeal)
199200
{
200201
bytes32 root;
201-
(fills, assessorSeal, root) = createFills(requests, journals);
202+
(fills, assessorSeal, root) = createFills(requests, journals, prover);
202203
// submit the root to the set verifier
203204
publishRoot(root);
204205
return (fills, assessorSeal);
@@ -428,8 +429,8 @@ contract ProofMarketTest is Test {
428429

429430
vm.startPrank(PROVER_WALLET.addr);
430431
proofMarket.lockin(request, clientSignature);
431-
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL);
432-
proofMarket.fulfill(fill, assessorSeal);
432+
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL, PROVER_WALLET.addr);
433+
proofMarket.fulfill(fill, assessorSeal, PROVER_WALLET.addr);
433434
// console2.log("fulfill - Gas used:", vm.gasUsed());
434435
vm.stopPrank();
435436

@@ -473,8 +474,8 @@ contract ProofMarketTest is Test {
473474

474475
// Note that this does not come from any particular address.
475476
proofMarket.lockinWithSig(request, clientSignature, proverSignature);
476-
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL);
477-
proofMarket.fulfill(fill, assessorSeal);
477+
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL, PROVER_WALLET.addr);
478+
proofMarket.fulfill(fill, assessorSeal, PROVER_WALLET.addr);
478479

479480
// Check that the proof was submitted
480481
assertTrue(proofMarket.requestIsFulfilled(fill.id), "Request should have fulfilled status");
@@ -523,9 +524,9 @@ contract ProofMarketTest is Test {
523524
}
524525
}
525526

526-
(Fulfillment[] memory fills, bytes memory assessorSeal) = fulfillRequestBatch(requests, journals);
527-
528-
proofMarket.fulfillBatch(fills, assessorSeal);
527+
(Fulfillment[] memory fills, bytes memory assessorSeal) =
528+
fulfillRequestBatch(requests, journals, PROVER_WALLET.addr);
529+
proofMarket.fulfillBatch(fills, assessorSeal, PROVER_WALLET.addr);
529530

530531
for (uint256 i = 0; i < fills.length; i++) {
531532
// Check that the proof was submitted
@@ -539,18 +540,68 @@ contract ProofMarketTest is Test {
539540
checkProofMarketBalance();
540541
}
541542

543+
// Test that when the prover that produces the assessor receipt and the one that locked the
544+
// request are different, the one that locked the request gets paid.
545+
function testFulfillDistinctProvers() public {
546+
Vm.Wallet memory client = createClient(1);
547+
548+
ProvingRequest memory request = defaultRequest(client.addr, 3);
549+
550+
bytes memory clientSignature = signRequest(client, request);
551+
bytes memory proverSignature = signRequest(PROVER_WALLET, request);
552+
553+
uint256 balanceBefore = proofMarket.balanceOf(PROVER_WALLET.addr);
554+
console2.log("Prover balance before:", balanceBefore);
555+
556+
// Note that this does not come from any particular address.
557+
proofMarket.lockinWithSig(request, clientSignature, proverSignature);
558+
// address(3) is just a standin for some other address.
559+
address mockOtherProverAddr = address(uint160(3));
560+
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL, mockOtherProverAddr);
561+
proofMarket.fulfill(fill, assessorSeal, mockOtherProverAddr);
562+
563+
// Check that the proof was submitted
564+
assertTrue(proofMarket.requestIsFulfilled(fill.id), "Request should have fulfilled status");
565+
566+
uint256 balanceAfter = proofMarket.balanceOf(PROVER_WALLET.addr);
567+
console2.log("Prover balance after:", balanceAfter);
568+
assertEq(balanceBefore + 1 ether, balanceAfter);
569+
570+
checkProofMarketBalance();
571+
}
572+
573+
function testFulfillFulfillProverAddrDoesNotMatchAssessorReceipt() public {
574+
Vm.Wallet memory client = createClient(1);
575+
576+
ProvingRequest memory request = defaultRequest(client.addr, 3);
577+
578+
bytes memory clientSignature = signRequest(client, request);
579+
bytes memory proverSignature = signRequest(PROVER_WALLET, request);
580+
581+
uint256 balanceBefore = proofMarket.balanceOf(PROVER_WALLET.addr);
582+
console2.log("Prover balance before:", balanceBefore);
583+
584+
// Note that this does not come from any particular address.
585+
proofMarket.lockinWithSig(request, clientSignature, proverSignature);
586+
// address(3) is just a standin for some other address.
587+
address mockOtherProverAddr = address(uint160(3));
588+
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL, PROVER_WALLET.addr);
589+
590+
vm.expectRevert();
591+
proofMarket.fulfill(fill, assessorSeal, mockOtherProverAddr);
592+
}
593+
542594
function testFulfillAlreadyFulfilled() public {
543595
// Submit request and fulfill it
544596
Vm.Wallet memory client = createClient(1);
545597
ProvingRequest memory request = defaultRequest(client.addr, 1);
546598
testFulfill();
547599

548-
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL);
600+
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL, PROVER_WALLET.addr);
549601
// Attempt to fulfill a request already fulfilled
550602
// should revert with "RequestIsFulfilled({requestId: request.id})"
551603
vm.expectRevert(abi.encodeWithSelector(IProofMarket.RequestIsFulfilled.selector, request.id));
552-
vm.prank(PROVER_WALLET.addr);
553-
proofMarket.fulfill(fill, assessorSeal);
604+
proofMarket.fulfill(fill, assessorSeal, PROVER_WALLET.addr);
554605

555606
checkProofMarketBalance();
556607
}
@@ -559,13 +610,12 @@ contract ProofMarketTest is Test {
559610
// Attempt to prove a non-existent request
560611
Vm.Wallet memory client = createClient(1);
561612
ProvingRequest memory request = defaultRequest(client.addr, 1);
562-
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL);
613+
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL, PROVER_WALLET.addr);
563614

564615
// Attempt to fulfill a request not lockeed
565616
// should revert with "RequestIsNotLocked({requestId: request.id})"
566617
vm.expectRevert(abi.encodeWithSelector(IProofMarket.RequestIsNotLocked.selector, request.id));
567-
vm.prank(PROVER_WALLET.addr);
568-
proofMarket.fulfill(fill, assessorSeal);
618+
proofMarket.fulfill(fill, assessorSeal, PROVER_WALLET.addr);
569619

570620
checkProofMarketBalance();
571621
}
@@ -585,7 +635,7 @@ contract ProofMarketTest is Test {
585635

586636
vm.startPrank(PROVER_WALLET.addr);
587637
proofMarket.lockin(request, clientSignature);
588-
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL);
638+
(Fulfillment memory fill, bytes memory assessorSeal) = fulfillRequest(request, APP_JOURNAL, PROVER_WALLET.addr);
589639

590640
vm.roll(2);
591641

@@ -594,7 +644,7 @@ contract ProofMarketTest is Test {
594644
vm.expectRevert(
595645
abi.encodeWithSelector(IProofMarket.RequestIsExpired.selector, request.id, request.offer.deadline())
596646
);
597-
proofMarket.fulfill(fill, assessorSeal);
647+
proofMarket.fulfill(fill, assessorSeal, PROVER_WALLET.addr);
598648
vm.stopPrank();
599649

600650
checkProofMarketBalance();
@@ -709,10 +759,11 @@ contract ProofMarketTest is Test {
709759

710760
function benchFulfillBatch(uint256 batchSize) public {
711761
(ProvingRequest[] memory requests, bytes[] memory journals) = newBatch(batchSize);
712-
(Fulfillment[] memory fills, bytes memory assessorSeal) = fulfillRequestBatch(requests, journals);
762+
(Fulfillment[] memory fills, bytes memory assessorSeal) =
763+
fulfillRequestBatch(requests, journals, PROVER_WALLET.addr);
713764

714765
uint256 gasBefore = gasleft();
715-
proofMarket.fulfillBatch(fills, assessorSeal);
766+
proofMarket.fulfillBatch(fills, assessorSeal, PROVER_WALLET.addr);
716767
uint256 gasAfter = gasleft();
717768
// Calculate the gas used
718769
uint256 gasUsed = gasBefore - gasAfter;
@@ -761,11 +812,12 @@ contract ProofMarketTest is Test {
761812

762813
function testsubmitRootAndFulfillBatch() public {
763814
(ProvingRequest[] memory requests, bytes[] memory journals) = newBatch(2);
764-
(Fulfillment[] memory fills, bytes memory assessorSeal, bytes32 root) = createFills(requests, journals);
815+
(Fulfillment[] memory fills, bytes memory assessorSeal, bytes32 root) =
816+
createFills(requests, journals, PROVER_WALLET.addr);
765817

766818
bytes memory seal =
767819
verifier.mockProve(SET_BUILDER_IMAGE_ID, sha256(abi.encodePacked(SET_BUILDER_IMAGE_ID, root))).seal;
768-
proofMarket.submitRootAndFulfillBatch(root, seal, fills, assessorSeal);
820+
proofMarket.submitRootAndFulfillBatch(root, seal, fills, assessorSeal, PROVER_WALLET.addr);
769821

770822
for (uint256 j = 0; j < fills.length; j++) {
771823
assertTrue(proofMarket.requestIsFulfilled(fills[j].id), "Request should have fulfilled status");

contracts/test/TestUtils.sol

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import "../src/ProofMarket.sol";
1111
library TestUtils {
1212
using ReceiptClaimLib for ReceiptClaim;
1313

14-
function mockAssessor(Fulfillment[] memory fills, bytes32 assessorImageId, bytes32 eip712DomainSeparator)
15-
internal
16-
pure
17-
returns (ReceiptClaim memory)
18-
{
14+
function mockAssessor(
15+
Fulfillment[] memory fills,
16+
bytes32 assessorImageId,
17+
bytes32 eip712DomainSeparator,
18+
address prover
19+
) internal pure returns (ReceiptClaim memory) {
1920
bytes32[] memory claimDigests = new bytes32[](fills.length);
2021
uint192[] memory ids = new uint192[](fills.length);
2122
for (uint256 i = 0; i < fills.length; i++) {
@@ -24,8 +25,9 @@ library TestUtils {
2425
}
2526
bytes32 root = MerkleProofish.processTree(claimDigests);
2627

27-
bytes memory journal =
28-
abi.encode(AssessorJournal({requestIds: ids, root: root, eip712DomainSeparator: eip712DomainSeparator}));
28+
bytes memory journal = abi.encode(
29+
AssessorJournal({requestIds: ids, root: root, eip712DomainSeparator: eip712DomainSeparator, prover: prover})
30+
);
2931
return ReceiptClaimLib.ok(assessorImageId, sha256(journal));
3032
}
3133

crates/assessor/src/lib.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// All rights reserved.
44

5-
use alloy_primitives::Signature;
5+
use alloy_primitives::{Address, Signature};
66
use alloy_sol_types::{Eip712Domain, SolStruct};
77
use anyhow::{bail, Result};
88
use boundless_market::contracts::{EIP721DomainSaltless, ProvingRequest};
@@ -54,6 +54,8 @@ pub struct AssessorInput {
5454
// This smart contract address is used solely to construct the EIP-712 Domain
5555
// and complete signature checks on the requests.
5656
pub domain: EIP721DomainSaltless,
57+
// The address of the prover.
58+
pub prover_address: Address,
5759
}
5860

5961
impl AssessorInput {
@@ -235,8 +237,11 @@ mod tests {
235237
}
236238

237239
fn assessor(claims: Vec<Fulfillment>, assumption_receipt: Receipt) {
238-
let assessor_input =
239-
AssessorInput { domain: eip712_domain(Address::ZERO, 1), fills: claims };
240+
let assessor_input = AssessorInput {
241+
domain: eip712_domain(Address::ZERO, 1),
242+
fills: claims,
243+
prover_address: Address::ZERO,
244+
};
240245
let env = ExecutorEnv::builder()
241246
.write_slice(&assessor_input.to_vec())
242247
.add_assumption(assumption_receipt)

0 commit comments

Comments
 (0)