This commit is contained in:
r4bbit 2024-08-16 14:30:18 +02:00
parent a27da9738a
commit e45724f825
No known key found for this signature in database
GPG Key ID: E95F1E9447DC91A9
4 changed files with 87 additions and 19 deletions

View File

@ -5,12 +5,12 @@
"contracts/Groth16Verifier.sol", "contracts/Groth16Verifier.sol",
"certora/helpers/ERC20A.sol", "certora/helpers/ERC20A.sol",
], ],
"parametric_contracts": ["Marketplace"], "parametric_contracts": ["MarketplaceHarness"],
"link" : [ "link" : [
"Marketplace:_token=ERC20A", "MarketplaceHarness:_token=ERC20A",
"Marketplace:_verifier=Groth16Verifier" "MarketplaceHarness:_verifier=Groth16Verifier"
], ],
"msg": "Verifying Marketplace", "msg": "Verifying MarketplaceHarness",
"rule_sanity": "basic", "rule_sanity": "basic",
"verify": "MarketplaceHarness:certora/specs/Marketplace.spec", "verify": "MarketplaceHarness:certora/specs/Marketplace.spec",
"optimistic_loop": true, "optimistic_loop": true,

View File

@ -6,7 +6,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IGroth16Verifier} from "../../contracts/Groth16.sol"; import {IGroth16Verifier} from "../../contracts/Groth16.sol";
import {MarketplaceConfig} from "../../contracts/Configuration.sol"; import {MarketplaceConfig} from "../../contracts/Configuration.sol";
import {Marketplace} from "../../contracts/Marketplace.sol"; import {Marketplace} from "../../contracts/Marketplace.sol";
import {RequestId} from "../../contracts/Requests.sol"; import {RequestId, SlotId} from "../../contracts/Requests.sol";
contract MarketplaceHarness is Marketplace { contract MarketplaceHarness is Marketplace {
constructor(MarketplaceConfig memory config, IERC20 token, IGroth16Verifier verifier) Marketplace(config, token, verifier) {} constructor(MarketplaceConfig memory config, IERC20 token, IGroth16Verifier verifier) Marketplace(config, token, verifier) {}
@ -15,6 +15,10 @@ contract MarketplaceHarness is Marketplace {
return _requestContexts[requestId]; return _requestContexts[requestId];
} }
function slots(SlotId slotId) public returns (Marketplace.Slot memory) {
return _slots[slotId];
}
function publicPeriodEnd(Period period) public view returns (uint256) { function publicPeriodEnd(Period period) public view returns (uint256) {
return _periodEnd(period); return _periodEnd(period);
} }

View File

@ -93,7 +93,8 @@ function canCancelRequest(method f) returns bool {
} }
function canStartRequest(method f) returns bool { function canStartRequest(method f) returns bool {
return f.selector == sig:fillSlot(Marketplace.RequestId, uint256, Marketplace.Groth16Proof).selector; return f.selector == sig:fillSlot(Marketplace.RequestId, uint256, Marketplace.Groth16Proof).selector ||
f.selector == sig:freeSlot(Marketplace.SlotId).selector;
} }
function canFinishRequest(method f) returns bool { function canFinishRequest(method f) returns bool {
@ -113,7 +114,11 @@ invariant totalSupplyIsSumOfBalances()
to_mathint(Token.totalSupply()) == sumOfBalances; to_mathint(Token.totalSupply()) == sumOfBalances;
invariant requestStartedWhenSlotsFilled(env e, Marketplace.RequestId requestId, Marketplace.SlotId slotId) invariant requestStartedWhenSlotsFilled(env e, Marketplace.RequestId requestId, Marketplace.SlotId slotId)
to_mathint(currentContract.requestContext(e, requestId).slotsFilled) == to_mathint(currentContract.getRequest(e, requestId).ask.slots) => currentContract.requestState(e, requestId) == Marketplace.RequestState.Started; currentContract.requestState(e, requestId) == Marketplace.RequestState.Started => to_mathint(currentContract.getRequest(e, requestId).ask.slots) - to_mathint(currentContract.requestContext(e, requestId).slotsFilled) <= to_mathint(currentContract.getRequest(e, requestId).ask.maxSlotLoss);
// { preserved {
// require to_mathint(currentContract.requestContext(e, requestId).slotsFilled) <= to_mathint(currentContract.getRequest(e, requestId).ask.slots);
// }
// }
// STATUS - verified // STATUS - verified
// can set missing if period was passed // can set missing if period was passed
@ -121,6 +126,20 @@ invariant requestStartedWhenSlotsFilled(env e, Marketplace.RequestId requestId,
invariant cantBeMissedIfInPeriod(MarketplaceHarness.SlotId slotId, Periods.Period period) invariant cantBeMissedIfInPeriod(MarketplaceHarness.SlotId slotId, Periods.Period period)
lastBlockTimestampGhost <= publicPeriodEnd(period) => !_missingMirror[slotId][period]; lastBlockTimestampGhost <= publicPeriodEnd(period) => !_missingMirror[slotId][period];
// STATUS - verified
// cancelled request is always expired
// https://prover.certora.com/output/6199/df88c16b9fb144ec88292df2346adb21?anonymousKey=2c76bd226b246bdd1b667d16c387519beaf94487
invariant cancelledRequestAlwaysExpired(env e, Marketplace.RequestId requestId)
currentContract.requestState(e, requestId) == Marketplace.RequestState.Cancelled =>
currentContract.requestExpiry(e, requestId) < lastBlockTimestampGhost;
// STATUS - verified
// failed request is always ended
// https://prover.certora.com/output/6199/902ffe4a83a9438e9860655446b46a74?anonymousKey=47b344024bbfe84a649bd1de44d7d243ce8dbc21
invariant failedRequestAlwaysEnded(env e, Marketplace.RequestId requestId)
currentContract.requestState(e, requestId) == Marketplace.RequestState.Failed =>
currentContract.requestContext(e, requestId).endsAt < lastBlockTimestampGhost;
/*-------------------------------------------- /*--------------------------------------------
| Properties | | Properties |
--------------------------------------------*/ --------------------------------------------*/
@ -154,10 +173,28 @@ rule totalSentCannotDecrease(env e, method f) {
assert total_after >= total_before; assert total_after >= total_before;
} }
rule slotFilledIFRequestIsFailed(env e, method f) {
calldataarg args;
Marketplace.SlotId slotId;
require currentContract.requestState(e, currentContract.slots(slotId).requestId) != Marketplace.RequestState.Failed;
f(e, args);
require currentContract.requestState(e, currentContract.slots(slotId).requestId) == Marketplace.RequestState.Failed;
assert currentContract.slotState(slotId) == Marketplace.SlotState.Failed;
}
rule allowedRequestStateChanges(env e, method f) { rule allowedRequestStateChanges(env e, method f) {
calldataarg args; calldataarg args;
Marketplace.RequestId requestId; Marketplace.RequestId requestId;
//requireInvariant cancelledRequestAlwaysExpired(e, requestId);
// requireInvariant failedRequestAlwaysEnded(e, requestId);
//require currentContract.requestContext(e, requestId).expiresAt < currentContract.requestContext(e, requestId).endsAt;
// require currentContract.slotState(e, slotId) == Marketplace.SlotState.Finished => currentContract.requestState(e, requestId) == Marketplace.RequestState.Finished;
Marketplace.RequestState requestStateBefore = currentContract.requestState(e, requestId); Marketplace.RequestState requestStateBefore = currentContract.requestState(e, requestId);
f(e, args); f(e, args);
Marketplace.RequestState requestStateAfter = currentContract.requestState(e, requestId); Marketplace.RequestState requestStateAfter = currentContract.requestState(e, requestId);
@ -179,17 +216,36 @@ rule functionsCausingRequestStateChanges(env e, method f) {
// RequestState.New -> RequestState.Started // RequestState.New -> RequestState.Started
assert requestStateBefore == Marketplace.RequestState.New && requestStateAfter == Marketplace.RequestState.Started => canStartRequest(f); assert requestStateBefore == Marketplace.RequestState.New && requestStateAfter == Marketplace.RequestState.Started => canStartRequest(f);
// RequestState.New -> RequestState.Cancelled
assert requestStateBefore == Marketplace.RequestState.New && requestStateAfter == Marketplace.RequestState.Cancelled => canCancelRequest(f);
// RequestState.Started -> RequestState.Finished // RequestState.Started -> RequestState.Finished
assert requestStateBefore == Marketplace.RequestState.Started && requestStateAfter == Marketplace.RequestState.Finished => canFinishRequest(f); assert requestStateBefore == Marketplace.RequestState.Started && requestStateAfter == Marketplace.RequestState.Finished => canFinishRequest(f);
// RequestState.Started -> RequestState.Failed // RequestState.Started -> RequestState.Failed
assert requestStateBefore == Marketplace.RequestState.Started && requestStateAfter == Marketplace.RequestState.Failed => canFailRequest(f); assert requestStateBefore == Marketplace.RequestState.Started && requestStateAfter == Marketplace.RequestState.Failed => canFailRequest(f);
// RequestState.New -> RequestState.Cancelled // RequestState.Finished -> RequestState.Started
assert requestStateBefore == Marketplace.RequestState.New && requestStateAfter == Marketplace.RequestState.Cancelled => canCancelRequest(f); assert requestStateBefore == Marketplace.RequestState.Finished && requestStateAfter == Marketplace.RequestState.Started => canStartRequest(f);
} }
rule finishedRequestCannotBeStartedAgain(env e, method f) { rule cancelledRequestsStayCancelled(env e, method f) {
calldataarg args;
Marketplace.RequestId requestId;
Marketplace.RequestState requestStateBefore = currentContract.requestState(e, requestId);
require requestStateBefore == Marketplace.RequestState.Cancelled;
requireInvariant cancelledRequestAlwaysExpired(e, requestId);
f(e, args);
Marketplace.RequestState requestStateAfter = currentContract.requestState(e, requestId);
assert requestStateAfter == requestStateBefore;
}
rule finishedRequestsStayFinished(env e, method f) {
calldataarg args; calldataarg args;
Marketplace.RequestId requestId; Marketplace.RequestId requestId;

View File

@ -118,7 +118,7 @@ contract Marketplace is Proofs, StateRetrieval, Endian {
RequestId requestId, RequestId requestId,
uint256 slotIndex, uint256 slotIndex,
Groth16Proof calldata proof Groth16Proof calldata proof
) public requestIsKnown(requestId) { ) public requestIsKnown(requestId) requestNotFinished(requestId) {
Request storage request = _requests[requestId]; Request storage request = _requests[requestId];
require(slotIndex < request.ask.slots, "Invalid slot"); require(slotIndex < request.ask.slots, "Invalid slot");
@ -127,7 +127,7 @@ contract Marketplace is Proofs, StateRetrieval, Endian {
slot.requestId = requestId; slot.requestId = requestId;
slot.slotIndex = slotIndex; slot.slotIndex = slotIndex;
require(slotState(slotId) == SlotState.Free, "Slot is not free"); require(slotState(slotId) == SlotState.Free, "Slot is not free"); // revert
_startRequiringProofs(slotId, request.ask.proofProbability); _startRequiringProofs(slotId, request.ask.proofProbability);
submitProof(slotId, proof); submitProof(slotId, proof);
@ -251,7 +251,7 @@ contract Marketplace is Proofs, StateRetrieval, Endian {
context.state == RequestState.Started context.state == RequestState.Started
) { ) {
context.state = RequestState.Failed; context.state = RequestState.Failed;
context.endsAt = block.timestamp - 1; context.endsAt = block.timestamp - 1; // Ended
emit RequestFailed(requestId); emit RequestFailed(requestId);
// TODO: send client remaining funds // TODO: send client remaining funds
@ -331,6 +331,14 @@ contract Marketplace is Proofs, StateRetrieval, Endian {
_; _;
} }
modifier requestNotFinished(RequestId requestId) {
require(
_requestContexts[requestId].state != RequestState.Finished,
"Request is cancelled or finished"
);
_;
}
function getRequest( function getRequest(
RequestId requestId RequestId requestId
) public view requestIsKnown(requestId) returns (Request memory) { ) public view requestIsKnown(requestId) returns (Request memory) {
@ -377,32 +385,32 @@ contract Marketplace is Proofs, StateRetrieval, Endian {
function requestState( function requestState(
RequestId requestId RequestId requestId
) public view requestIsKnown(requestId) returns (RequestState) { ) public view requestIsKnown(requestId) returns (RequestState) {
RequestContext storage context = _requestContexts[requestId]; RequestContext storage context = _requestContexts[requestId]; // Cancelled
if ( if (
context.state == RequestState.New && context.state == RequestState.New &&
block.timestamp > requestExpiry(requestId) block.timestamp > requestExpiry(requestId)
) { ) {
return RequestState.Cancelled; return RequestState.Cancelled;
} else if ( } else if (
context.state == RequestState.Started && block.timestamp > context.endsAt (context.state == RequestState.Started || context.state == RequestState.New) && block.timestamp > context.endsAt
) { ) {
return RequestState.Finished; return RequestState.Finished;
} else { } else {
return context.state; return context.state; // Cancelled
} }
} }
function slotState(SlotId slotId) public view override returns (SlotState) { function slotState(SlotId slotId) public view override returns (SlotState) {
Slot storage slot = _slots[slotId]; Slot storage slot = _slots[slotId]; // Free
if (RequestId.unwrap(slot.requestId) == 0) { if (RequestId.unwrap(slot.requestId) == 0) {
return SlotState.Free; return SlotState.Free;
} }
RequestState reqState = requestState(slot.requestId);
if (slot.state == SlotState.Paid) { if (slot.state == SlotState.Paid) {
return SlotState.Paid; return SlotState.Paid;
} }
RequestState reqState = requestState(slot.requestId); // Failed
if (reqState == RequestState.Cancelled) { if (reqState == RequestState.Cancelled) {
return SlotState.Cancelled; return SlotState.Cancelled; // Cancelled
} }
if (reqState == RequestState.Finished) { if (reqState == RequestState.Finished) {
return SlotState.Finished; return SlotState.Finished;