dagger-contracts/certora/specs/Marketplace.spec
r4bbit 1d36256230 chore(certora): add invariant that proofs cant be missing when in period
This invariant verifies that any given proof cannot be marked as missing
if the slot period has not passed yet.
2024-08-13 09:39:29 +02:00

183 lines
6.9 KiB
Ruby

using ERC20A as Token;
methods {
function Token.balanceOf(address) external returns (uint256) envfree;
function Token.totalSupply() external returns (uint256) envfree;
function publicPeriodEnd(Periods.Period) external returns (uint256) envfree;
}
/*--------------------------------------------
| Ghosts and hooks |
--------------------------------------------*/
ghost mathint sumOfBalances {
init_state axiom sumOfBalances == 0;
}
hook Sload uint256 balance Token._balances[KEY address addr] {
require sumOfBalances >= to_mathint(balance);
}
hook Sstore Token._balances[KEY address addr] uint256 newValue (uint256 oldValue) {
sumOfBalances = sumOfBalances - oldValue + newValue;
}
ghost mathint totalReceived;
hook Sload uint256 defaultValue currentContract._marketplaceTotals.received {
require totalReceived >= to_mathint(defaultValue);
}
hook Sstore currentContract._marketplaceTotals.received uint256 defaultValue (uint256 defaultValue_old) {
totalReceived = totalReceived + defaultValue - defaultValue_old;
}
ghost mathint totalSent;
hook Sload uint256 defaultValue currentContract._marketplaceTotals.sent {
require totalSent >= to_mathint(defaultValue);
}
hook Sstore currentContract._marketplaceTotals.sent uint256 defaultValue (uint256 defaultValue_old) {
totalSent = totalSent + defaultValue - defaultValue_old;
}
ghost uint256 lastBlockTimestampGhost;
hook TIMESTAMP uint v {
require lastBlockTimestampGhost <= v;
lastBlockTimestampGhost = v;
}
ghost mapping(MarketplaceHarness.SlotId => mapping(Periods.Period => bool)) _missingMirror {
init_state axiom forall MarketplaceHarness.SlotId a.
forall Periods.Period b.
_missingMirror[a][b] == false;
}
hook Sload bool defaultValue _missing[KEY MarketplaceHarness.SlotId slotId][KEY Periods.Period period] {
require _missingMirror[slotId][period] == defaultValue;
}
hook Sstore _missing[KEY MarketplaceHarness.SlotId slotId][KEY Periods.Period period] bool defaultValue {
_missingMirror[slotId][period] = defaultValue;
}
/*--------------------------------------------
| Helper functions |
--------------------------------------------*/
function canCancelRequest(method f) returns bool {
return f.selector == sig:withdrawFunds(Marketplace.RequestId).selector;
}
function canStartRequest(method f) returns bool {
return f.selector == sig:fillSlot(Marketplace.RequestId, uint256, Marketplace.Groth16Proof).selector;
}
function canFinishRequest(method f) returns bool {
return f.selector == sig:freeSlot(Marketplace.SlotId).selector;
}
function canFailRequest(method f) returns bool {
return f.selector == sig:markProofAsMissing(Marketplace.SlotId, Periods.Period).selector ||
f.selector == sig:freeSlot(Marketplace.SlotId).selector;
}
/*--------------------------------------------
| Invariants |
--------------------------------------------*/
invariant totalSupplyIsSumOfBalances()
to_mathint(Token.totalSupply()) == sumOfBalances;
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;
// STATUS - verified
// can set missing if period was passed
// https://prover.certora.com/output/3106/026b36c118e44ad0824a51c50647c497/?anonymousKey=29879706f3d343555bb6122d071c9409d4e9876d
invariant cantBeMissedIfInPeriod(MarketplaceHarness.SlotId slotId, Periods.Period period)
lastBlockTimestampGhost <= publicPeriodEnd(period) => !_missingMirror[slotId][period];
/*--------------------------------------------
| Properties |
--------------------------------------------*/
rule sanity(env e, method f) {
calldataarg args;
f(e, args);
assert true;
satisfy true;
}
rule totalReceivedCannotDecrease(env e, method f) {
mathint total_before = totalReceived;
calldataarg args;
f(e, args);
mathint total_after = totalReceived;
assert total_after >= total_before;
}
rule totalSentCannotDecrease(env e, method f) {
mathint total_before = totalSent;
calldataarg args;
f(e, args);
mathint total_after = totalSent;
assert total_after >= total_before;
}
rule allowedRequestStateChanges(env e, method f) {
calldataarg args;
Marketplace.RequestId requestId;
Marketplace.RequestState requestStateBefore = currentContract.requestState(e, requestId);
f(e, args);
Marketplace.RequestState requestStateAfter = currentContract.requestState(e, requestId);
assert requestStateBefore != requestStateAfter && requestStateAfter == Marketplace.RequestState.Started => requestStateBefore == Marketplace.RequestState.New;
assert requestStateBefore != requestStateAfter && requestStateAfter == Marketplace.RequestState.Finished => requestStateBefore == Marketplace.RequestState.Started;
assert requestStateBefore != requestStateAfter && requestStateAfter == Marketplace.RequestState.Failed => requestStateBefore == Marketplace.RequestState.Started;
assert requestStateBefore != requestStateAfter && requestStateAfter == Marketplace.RequestState.Cancelled => requestStateBefore == Marketplace.RequestState.New;
}
rule functionsCausingRequestStateChanges(env e, method f) {
calldataarg args;
Marketplace.RequestId requestId;
Marketplace.RequestState requestStateBefore = currentContract.requestState(e, requestId);
f(e, args);
Marketplace.RequestState requestStateAfter = currentContract.requestState(e, requestId);
// RequestState.New -> RequestState.Started
assert requestStateBefore == Marketplace.RequestState.New && requestStateAfter == Marketplace.RequestState.Started => canStartRequest(f);
// RequestState.Started -> RequestState.Finished
assert requestStateBefore == Marketplace.RequestState.Started && requestStateAfter == Marketplace.RequestState.Finished => canFinishRequest(f);
// RequestState.Started -> RequestState.Failed
assert requestStateBefore == Marketplace.RequestState.Started && requestStateAfter == Marketplace.RequestState.Failed => canFailRequest(f);
// RequestState.New -> RequestState.Cancelled
assert requestStateBefore == Marketplace.RequestState.New && requestStateAfter == Marketplace.RequestState.Cancelled => canCancelRequest(f);
}
rule finishedRequestCannotBeStartedAgain(env e, method f) {
calldataarg args;
Marketplace.RequestId requestId;
Marketplace.RequestState requestStateBefore = currentContract.requestState(e, requestId);
require requestStateBefore == Marketplace.RequestState.Finished;
f(e, args);
Marketplace.RequestState requestStateAfter = currentContract.requestState(e, requestId);
assert requestStateBefore == requestStateAfter;
}