2
2
//
3
3
// All rights reserved.
4
4
5
- pragma solidity ^ 0.8.20 ;
5
+ pragma solidity ^ 0.8.24 ;
6
6
7
7
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol " ;
8
8
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol " ;
@@ -97,12 +97,32 @@ struct RequestLock {
97
97
uint96 stake;
98
98
}
99
99
100
+ /// Struct encoding the validated price for a request, intended for use with transient storage.
101
+ struct TransientPrice {
102
+ /// Boolean set to true to indicate the request was validated.
103
+ bool valid;
104
+ uint96 price;
105
+ }
106
+
107
+ library TransientPriceLib {
108
+ /// Packs the struct into a uint256.
109
+ function pack (TransientPrice memory x ) internal pure returns (uint256 ) {
110
+ return (uint256 (x.valid ? 1 : 0 ) << 96 ) | uint256 (x.price);
111
+ }
112
+
113
+ /// Unpacks the struct from a uint256.
114
+ function unpack (uint256 packed ) internal pure returns (TransientPrice memory ) {
115
+ return TransientPrice ({valid: (packed & (1 << 96 )) > 0 , price: uint96 (packed & uint256 (type (uint96 ).max))});
116
+ }
117
+ }
118
+
100
119
contract ProofMarket is IProofMarket , EIP712 {
101
120
using AccountLib for Account;
102
121
using ProofMarketLib for Offer;
103
122
using ProofMarketLib for ProvingRequest;
104
123
using ReceiptClaimLib for ReceiptClaim;
105
124
using SafeCast for uint256 ;
125
+ using TransientPriceLib for TransientPrice;
106
126
107
127
// Mapping of request ID to lock-in state. Non-zero for requests that are locked in.
108
128
mapping (uint192 => RequestLock) public requestLocks;
@@ -196,13 +216,19 @@ contract ProofMarket is IProofMarket, EIP712 {
196
216
_lockinAuthed (request, client, idx, prover);
197
217
}
198
218
199
- function _lockinAuthed (ProvingRequest calldata request , address client , uint32 idx , address prover ) internal {
219
+ /// Check that the request is valid, and not already locked or fulfilled by another prover.
220
+ /// Returns the auction price and deadline for the request.
221
+ function _validateRequestForLockin (ProvingRequest calldata request , address client , uint32 idx )
222
+ internal
223
+ view
224
+ returns (uint96 price , uint64 deadline )
225
+ {
200
226
// Check that the request is internally consistent and is not expired.
201
227
request.offer.requireValid ();
202
228
203
229
// We are ending the reverse Dutch auction at the current price.
204
- uint96 price = request.offer.priceAtBlock (uint64 (block .number ));
205
- uint64 deadline = request.offer.deadline ();
230
+ price = request.offer.priceAtBlock (uint64 (block .number ));
231
+ deadline = request.offer.deadline ();
206
232
if (deadline < block .number ) {
207
233
revert RequestIsExpired ({requestId: request.id, deadline: deadline});
208
234
}
@@ -216,6 +242,12 @@ contract ProofMarket is IProofMarket, EIP712 {
216
242
revert RequestIsFulfilled ({requestId: request.id});
217
243
}
218
244
245
+ return (price, deadline);
246
+ }
247
+
248
+ function _lockinAuthed (ProvingRequest calldata request , address client , uint32 idx , address prover ) internal {
249
+ (uint96 price , uint64 deadline ) = _validateRequestForLockin (request, client, idx);
250
+
219
251
// Lock the request such that only the given prover can fulfill it (or else face a penalty).
220
252
Account storage clientAccount = accounts[client];
221
253
clientAccount.setRequestLocked (idx);
@@ -236,19 +268,39 @@ contract ProofMarket is IProofMarket, EIP712 {
236
268
clientAccount.balance -= price;
237
269
proverAccount.balance -= request.offer.lockinStake;
238
270
}
239
- accounts[client] = clientAccount;
240
271
241
272
emit RequestLockedin (request.id, prover);
242
273
}
243
274
275
+ /// Validates the request and records the price to transient storage such that it can be
276
+ /// fulfilled within the same transaction without taking a lock on it.
277
+ function priceRequest (ProvingRequest calldata request , bytes calldata clientSignature ) public {
278
+ (address client , uint32 idx ) = (ProofMarketLib.requestFrom (request.id), ProofMarketLib.requestIndex (request.id));
279
+
280
+ // Recover the prover address and require the client address to equal the address part of the ID.
281
+ bytes32 structHash = _hashTypedDataV4 (request.eip712Digest ());
282
+ require (ECDSA.recover (structHash, clientSignature) == client, "Invalid client signature " );
283
+
284
+ (uint96 price ,) = _validateRequestForLockin (request, client, idx);
285
+ uint192 requestId = request.id;
286
+
287
+ // Record the price in transient storage, such that the order can be filled in this same transaction.
288
+ // NOTE: Since transient storage is cleared at the end of the transaction, we know that this
289
+ // price will not become stale, and the request cannot expire, while this price is recorded.
290
+ // TODO(#165): Also record a requirements checksum here when solving #165.
291
+ uint256 packed = TransientPrice ({valid: true , price: price}).pack ();
292
+ assembly {
293
+ tstore (requestId, packed)
294
+ }
295
+ }
296
+
244
297
/// Verify the application and assessor receipts, ensuring that the provided fulfillment
245
298
/// satisfies the request.
246
299
// TODO(#165) Return or check the request checksum here.
247
300
function verifyDelivery (Fulfillment calldata fill , bytes calldata assessorSeal , address prover ) public view {
248
- // Verify the application guest proof. We need to verify it here, even though the market
249
- // guest already verified that the prover has knowledge of a verifying receipt, because
250
- // we need to make sure the _delivered_ seal is valid.
251
- // TODO(victor): Support journals hashed with keccak instead of SHA-256.
301
+ // Verify the application guest proof. We need to verify it here, even though the assesor
302
+ // already verified that the prover has knowledge of a verifying receipt, because we need to
303
+ // make sure the _delivered_ seal is valid.
252
304
bytes32 claimDigest = ReceiptClaimLib.ok (fill.imageId, sha256 (fill.journal)).digest ();
253
305
VERIFIER.verifyIntegrity {gas: FULFILL_MAX_GAS_FOR_VERIFY}(Receipt (fill.seal, claimDigest));
254
306
@@ -304,7 +356,7 @@ contract ProofMarket is IProofMarket, EIP712 {
304
356
305
357
function fulfill (Fulfillment calldata fill , bytes calldata assessorSeal , address prover ) external {
306
358
verifyDelivery (fill, assessorSeal, prover);
307
- _fulfillVerified (fill.id);
359
+ _fulfillVerified (fill.id, prover );
308
360
309
361
// TODO(victor): Potentially this should be (re)combined with RequestFulfilled. It would make
310
362
// the logic to watch for a proof a bit more complex, but the gas usage a little less (by
@@ -319,40 +371,88 @@ contract ProofMarket is IProofMarket, EIP712 {
319
371
// batch update to storage. However, updating the the same storage slot twice only costs 100 gas, so
320
372
// this savings is marginal, and will be outweighed by complicated memory management if not careful.
321
373
for (uint256 i = 0 ; i < fills.length ; i++ ) {
322
- _fulfillVerified (fills[i].id);
374
+ _fulfillVerified (fills[i].id, prover );
323
375
324
376
emit ProofDelivered (fills[i].id, fills[i].journal, fills[i].seal);
325
377
}
326
378
}
327
379
380
+ function priceAndFulfillBatch (
381
+ ProvingRequest[] calldata requests ,
382
+ bytes [] calldata clientSignatures ,
383
+ Fulfillment[] calldata fills ,
384
+ bytes calldata assessorSeal ,
385
+ address prover
386
+ ) external {
387
+ for (uint256 i = 0 ; i < requests.length ; i++ ) {
388
+ priceRequest (requests[i], clientSignatures[i]);
389
+ }
390
+ fulfillBatch (fills, assessorSeal, prover);
391
+ }
392
+
328
393
/// Complete the fulfillment logic after having verified the app and assessor receipts.
329
- function _fulfillVerified (uint192 id ) internal {
394
+ function _fulfillVerified (uint192 id , address assesorProver ) internal {
330
395
address client = ProofMarketLib.requestFrom (id);
331
396
uint32 idx = ProofMarketLib.requestIndex (id);
332
397
333
- // Check that the request is not locked to a different prover .
398
+ // Check that the request is not fulfilled .
334
399
(bool locked , bool fulfilled ) = accounts[client].requestFlags (idx);
335
400
336
- // Ensure the request is locked, and fetch the lock.
337
- if (! locked) {
338
- revert RequestIsNotLocked ({requestId: id});
339
- }
340
401
if (fulfilled) {
341
402
revert RequestIsFulfilled ({requestId: id});
342
403
}
343
404
344
- RequestLock memory lock = requestLocks[id];
405
+ address prover;
406
+ uint96 price;
407
+ uint96 stake;
408
+ if (locked) {
409
+ RequestLock memory lock = requestLocks[id];
410
+
411
+ if (lock.deadline < block .number ) {
412
+ revert RequestIsExpired ({requestId: id, deadline: lock.deadline});
413
+ }
414
+
415
+ prover = lock.prover;
416
+ price = lock.price;
417
+ stake = lock.stake;
418
+ } else {
419
+ uint256 packed;
420
+ assembly {
421
+ packed := tload (id)
422
+ }
423
+ TransientPrice memory tprice = TransientPriceLib.unpack (packed);
345
424
346
- if (lock.deadline < block .number ) {
347
- revert RequestIsExpired ({requestId: id, deadline: lock.deadline});
425
+ // Check that a price has actually been set, rather than this being default.
426
+ // NOTE: Maybe "request is not locked or priced" would be more accurate, but seems
427
+ // like that would be a confusing message.
428
+ if (! tprice.valid) {
429
+ revert RequestIsNotLocked ({requestId: id});
430
+ }
431
+
432
+ prover = assesorProver;
433
+ price = tprice.price;
434
+ stake = 0 ;
348
435
}
349
436
350
- // Zero out the lock to get a bit of a refund on gas.
351
- requestLocks[id] = RequestLock (address (0 ), uint96 (0 ), uint64 (0 ), uint96 (0 ));
437
+ if (locked) {
438
+ // Zero-out the lock to get a bit of a refund on gas.
439
+ requestLocks[id] = RequestLock (address (0 ), uint96 (0 ), uint64 (0 ), uint96 (0 ));
440
+ }
441
+
442
+ Account storage clientAccount = accounts[client];
443
+ if (! locked) {
444
+ // Deduct the funds from client account.
445
+ if (clientAccount.balance < price) {
446
+ revert InsufficientBalance (client);
447
+ }
448
+ unchecked {
449
+ clientAccount.balance -= price;
450
+ }
451
+ }
352
452
353
453
// Mark the request as fulfilled and pay the prover.
354
- accounts[client] .setRequestFulfilled (idx);
355
- accounts[lock. prover].balance += lock. price + lock. stake;
454
+ clientAccount .setRequestFulfilled (idx);
455
+ accounts[prover].balance += price + stake;
356
456
357
457
emit RequestFulfilled (id);
358
458
}
0 commit comments