perf: optimizing parameters sizing (#207)

* perf: optimizing parameters sizing

* chore: feedback

Co-authored-by: markspanbroek <mark@spanbroek.net>

* style: formatting

* perf: more optimizations

* chore: fixes

* chore: fix certora spec

* chore: more fixes for certora spec

* chore: more and more fixes for certora spec

* fix: ends type

* test(certora): timestamp conversion

* test(certora): timestamp conversion again

* test(certora): timestamp conversion revert to assert_uint64

* test(certora): timestamp with mathint

* test(certora): timestamp back with uint64 with require

* Add missing configuration

* Fix previous merge

* Update StorageRequested to use int64 for expiry

* requestDurationLimit => uint64

---------

Co-authored-by: markspanbroek <mark@spanbroek.net>
Co-authored-by: Arnaud <arnaud@status.im>
Co-authored-by: Eric <5089238+emizzle@users.noreply.github.com>
This commit is contained in:
Adam Uhlíř 2025-02-20 06:54:41 +01:00 committed by GitHub
parent ff82c26b36
commit c00152e621
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 80 additions and 74 deletions

View File

@ -14,7 +14,7 @@ contract MarketplaceHarness is Marketplace {
Marketplace(config, token, verifier)
{}
function publicPeriodEnd(Period period) public view returns (uint256) {
function publicPeriodEnd(Period period) public view returns (uint64) {
return _periodEnd(period);
}
@ -22,7 +22,7 @@ contract MarketplaceHarness is Marketplace {
return _slots[slotId];
}
function generateSlotId(RequestId requestId, uint256 slotIndex) public pure returns (SlotId) {
function generateSlotId(RequestId requestId, uint64 slotIndex) public pure returns (SlotId) {
return Requests.slotId(requestId, slotIndex);
}
}

View File

@ -5,8 +5,8 @@ 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;
function generateSlotId(Marketplace.RequestId, uint256) external returns (Marketplace.SlotId) envfree;
function publicPeriodEnd(Periods.Period) external returns (uint64) envfree;
function generateSlotId(Marketplace.RequestId, uint64) external returns (Marketplace.SlotId) envfree;
}
/*--------------------------------------------
@ -45,11 +45,12 @@ hook Sstore currentContract._marketplaceTotals.sent uint256 defaultValue (uint25
totalSent = totalSent + defaultValue - defaultValue_old;
}
ghost uint256 lastBlockTimestampGhost;
ghost uint64 lastBlockTimestampGhost;
hook TIMESTAMP uint v {
require lastBlockTimestampGhost <= v;
lastBlockTimestampGhost = v;
require v < max_uint64;
require lastBlockTimestampGhost <= assert_uint64(v);
lastBlockTimestampGhost = assert_uint64(v);
}
ghost mapping(MarketplaceHarness.SlotId => mapping(Periods.Period => bool)) _missingMirror {
@ -58,7 +59,7 @@ ghost mapping(MarketplaceHarness.SlotId => mapping(Periods.Period => bool)) _mis
_missingMirror[a][b] == false;
}
ghost mapping(MarketplaceHarness.SlotId => uint256) _missedMirror {
ghost mapping(MarketplaceHarness.SlotId => uint64) _missedMirror {
init_state axiom forall MarketplaceHarness.SlotId a.
_missedMirror[a] == 0;
}
@ -79,11 +80,11 @@ hook Sstore _missing[KEY MarketplaceHarness.SlotId slotId][KEY Periods.Period pe
}
}
hook Sload uint256 defaultValue _missed[KEY MarketplaceHarness.SlotId slotId] {
hook Sload uint64 defaultValue _missed[KEY MarketplaceHarness.SlotId slotId] {
require _missedMirror[slotId] == defaultValue;
}
hook Sstore _missed[KEY MarketplaceHarness.SlotId slotId] uint256 defaultValue {
hook Sstore _missed[KEY MarketplaceHarness.SlotId slotId] uint64 defaultValue {
_missedMirror[slotId] = defaultValue;
if (defaultValue == 0) {
_missedCalculated[slotId] = 0;
@ -110,23 +111,23 @@ hook Sstore _slots[KEY Marketplace.SlotId slotId].state Marketplace.SlotState ne
}
}
ghost mapping(MarketplaceHarness.RequestId => uint256) slotsFilledGhost;
ghost mapping(MarketplaceHarness.RequestId => uint64) slotsFilledGhost;
hook Sload uint256 defaultValue _requestContexts[KEY MarketplaceHarness.RequestId RequestId].slotsFilled {
hook Sload uint64 defaultValue _requestContexts[KEY MarketplaceHarness.RequestId RequestId].slotsFilled {
require slotsFilledGhost[RequestId] == defaultValue;
}
hook Sstore _requestContexts[KEY MarketplaceHarness.RequestId RequestId].slotsFilled uint256 defaultValue {
hook Sstore _requestContexts[KEY MarketplaceHarness.RequestId RequestId].slotsFilled uint64 defaultValue {
slotsFilledGhost[RequestId] = defaultValue;
}
ghost mapping(MarketplaceHarness.RequestId => uint256) endsAtGhost;
ghost mapping(MarketplaceHarness.RequestId => uint64) endsAtGhost;
hook Sload uint256 defaultValue _requestContexts[KEY MarketplaceHarness.RequestId RequestId].endsAt {
hook Sload uint64 defaultValue _requestContexts[KEY MarketplaceHarness.RequestId RequestId].endsAt {
require endsAtGhost[RequestId] == defaultValue;
}
hook Sstore _requestContexts[KEY MarketplaceHarness.RequestId RequestId].endsAt uint256 defaultValue {
hook Sstore _requestContexts[KEY MarketplaceHarness.RequestId RequestId].endsAt uint64 defaultValue {
endsAtGhost[RequestId] = defaultValue;
}
@ -139,7 +140,7 @@ function canCancelRequest(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, uint64, Marketplace.Groth16Proof).selector;
}
function canFinishRequest(method f) returns bool {
@ -159,7 +160,7 @@ function canMakeSlotPaid(method f) returns bool {
}
function canFillSlot(method f) returns bool {
return f.selector == sig:fillSlot(Marketplace.RequestId, uint256, Marketplace.Groth16Proof).selector;
return f.selector == sig:fillSlot(Marketplace.RequestId, uint64, Marketplace.Groth16Proof).selector;
}
// The slot identified by `slotId` must have requestId and slotIndex set to 0,

View File

@ -15,6 +15,7 @@ const DEFAULT_CONFIGURATION = {
timeout: 30, // seconds
downtime: 64, // number of blocks
downtimeProduct: 67, // number of blocks
zkeyHash: "",
},
reservations: {
maxReservations: 3,

View File

@ -12,6 +12,7 @@ module.exports = {
timeout: 30, // seconds
downtime: 96, // number of blocks
downtimeProduct: 97, // number of blocks
zkeyHash: "",
},
reservations: {
maxReservations: 3,

View File

@ -7,7 +7,7 @@ struct MarketplaceConfig {
CollateralConfig collateral;
ProofConfig proofs;
SlotReservationsConfig reservations;
uint256 requestDurationLimit;
uint64 requestDurationLimit;
}
struct CollateralConfig {
@ -19,14 +19,14 @@ struct CollateralConfig {
}
struct ProofConfig {
uint256 period; // proofs requirements are calculated per period (in seconds)
uint256 timeout; // mark proofs as missing before the timeout (in seconds)
uint64 period; // proofs requirements are calculated per period (in seconds)
uint64 timeout; // mark proofs as missing before the timeout (in seconds)
uint8 downtime; // ignore this much recent blocks for proof requirements
string zkeyHash; // hash of the zkey file which is linked to the verifier
// Ensures the pointer does not remain in downtime for many consecutive
// periods. For each period increase, move the pointer `pointerProduct`
// blocks. Should be a prime number to ensure there are no cycles.
uint8 downtimeProduct;
string zkeyHash; // hash of the zkey file which is linked to the verifier
}
struct SlotReservationsConfig {

View File

@ -10,7 +10,7 @@ contract FuzzMarketplace is Marketplace {
Marketplace(
MarketplaceConfig(
CollateralConfig(10, 5, 10, 20),
ProofConfig(10, 5, 64, "", 67),
ProofConfig(10, 5, 64, 67, ""),
SlotReservationsConfig(20),
60 * 60 * 24 * 30 // 30 days
),

View File

@ -56,7 +56,6 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
struct RequestContext {
RequestState state;
uint256 slotsFilled;
/// @notice Tracks how much funds should be returned to the client as not all funds might be used for hosting the request
/// @dev The sum starts with the full reward amount for the request and is reduced every time a host fills a slot.
/// The reduction is calculated from the duration of time between the slot being filled and the request's end.
@ -65,9 +64,10 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
/// This is possible, because technically it is not possible for this variable to reach 0 in "natural" way as
/// that would require all the slots to be filled at the same block as the request was created.
uint256 fundsToReturnToClient;
uint256 startedAt;
uint256 endsAt;
uint256 expiresAt;
uint64 slotsFilled;
uint64 startedAt;
uint64 endsAt;
uint64 expiresAt;
}
struct Slot {
@ -76,8 +76,8 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
/// @notice Timestamp that signals when slot was filled
/// @dev Used for calculating payouts as hosts are paid
/// based on time they actually host the content
uint256 filledAt;
uint256 slotIndex;
uint64 filledAt;
uint64 slotIndex;
/// @notice Tracks the current amount of host's collateral that is
/// to be payed out at the end of Slot's lifespan.
/// @dev When Slot is filled, the collateral is collected in amount
@ -92,7 +92,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
struct ActiveSlot {
Request request;
uint256 slotIndex;
uint64 slotIndex;
}
constructor(
@ -160,8 +160,10 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
}
_requests[id] = request;
_requestContexts[id].endsAt = block.timestamp + request.ask.duration;
_requestContexts[id].expiresAt = block.timestamp + request.expiry;
_requestContexts[id].endsAt =
uint64(block.timestamp) +
request.ask.duration;
_requestContexts[id].expiresAt = uint64(block.timestamp) + request.expiry;
_addToMyRequests(request.client, id);
@ -174,7 +176,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
}
/**
* @notice Fills a slot. Reverts if an invalid proof of the slot data is
* @notice Fills a slot. Reverts if an invalid proof of the slot data is
provided.
* @param requestId RequestId identifying the request containing the slot to
fill.
@ -183,7 +185,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
*/
function fillSlot(
RequestId requestId,
uint256 slotIndex,
uint64 slotIndex,
Groth16Proof calldata proof
) public requestIsKnown(requestId) {
Request storage request = _requests[requestId];
@ -210,7 +212,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
submitProof(slotId, proof);
slot.host = msg.sender;
slot.filledAt = block.timestamp;
slot.filledAt = uint64(block.timestamp);
context.slotsFilled += 1;
context.fundsToReturnToClient -= _slotPayout(requestId, slot.filledAt);
@ -242,7 +244,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
context.state == RequestState.New // Only New requests can "start" the requests
) {
context.state = RequestState.Started;
context.startedAt = block.timestamp;
context.startedAt = uint64(block.timestamp);
emit RequestFulfilled(requestId);
}
}
@ -384,7 +386,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
context.state == RequestState.Started
) {
context.state = RequestState.Failed;
context.endsAt = block.timestamp - 1;
context.endsAt = uint64(block.timestamp) - 1;
emit RequestFailed(requestId);
}
}
@ -555,17 +557,17 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
return _slots[slotId].state == SlotState.Free;
}
function requestEnd(RequestId requestId) public view returns (uint256) {
uint256 end = _requestContexts[requestId].endsAt;
function requestEnd(RequestId requestId) public view returns (uint64) {
uint64 end = _requestContexts[requestId].endsAt;
RequestState state = requestState(requestId);
if (state == RequestState.New || state == RequestState.Started) {
return end;
} else {
return Math.min(end, block.timestamp - 1);
return uint64(Math.min(end, block.timestamp - 1));
}
}
function requestExpiry(RequestId requestId) public view returns (uint256) {
function requestExpiry(RequestId requestId) public view returns (uint64) {
return _requestContexts[requestId].expiresAt;
}
@ -578,7 +580,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
*/
function _slotPayout(
RequestId requestId,
uint256 startingTimestamp
uint64 startingTimestamp
) private view returns (uint256) {
return
_slotPayout(
@ -591,8 +593,8 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
/// @notice Calculates the amount that should be paid out to a host based on the specified time frame.
function _slotPayout(
RequestId requestId,
uint256 startingTimestamp,
uint256 endingTimestamp
uint64 startingTimestamp,
uint64 endingTimestamp
) private view returns (uint256) {
Request storage request = _requests[requestId];
if (startingTimestamp >= endingTimestamp)
@ -612,12 +614,13 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
RequestContext storage context = _requestContexts[requestId];
if (
context.state == RequestState.New &&
block.timestamp > requestExpiry(requestId)
uint64(block.timestamp) > requestExpiry(requestId)
) {
return RequestState.Cancelled;
} else if (
(context.state == RequestState.Started ||
context.state == RequestState.New) && block.timestamp > context.endsAt
context.state == RequestState.New) &&
uint64(block.timestamp) > context.endsAt
) {
return RequestState.Finished;
} else {
@ -661,11 +664,11 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
revert Marketplace_TransferFailed();
}
event StorageRequested(RequestId requestId, Ask ask, uint256 expiry);
event StorageRequested(RequestId requestId, Ask ask, uint64 expiry);
event RequestFulfilled(RequestId indexed requestId);
event RequestFailed(RequestId indexed requestId);
event SlotFilled(RequestId indexed requestId, uint256 slotIndex);
event SlotFreed(RequestId indexed requestId, uint256 slotIndex);
event SlotFilled(RequestId indexed requestId, uint64 slotIndex);
event SlotFreed(RequestId indexed requestId, uint64 slotIndex);
event RequestCancelled(RequestId indexed requestId);
struct MarketplaceTotals {

View File

@ -4,34 +4,34 @@ pragma solidity 0.8.23;
contract Periods {
error Periods_InvalidSecondsPerPeriod();
type Period is uint256;
type Period is uint64;
uint256 internal immutable _secondsPerPeriod;
uint64 internal immutable _secondsPerPeriod;
constructor(uint256 secondsPerPeriod) {
constructor(uint64 secondsPerPeriod) {
if (secondsPerPeriod == 0) {
revert Periods_InvalidSecondsPerPeriod();
}
_secondsPerPeriod = secondsPerPeriod;
}
function _periodOf(uint256 timestamp) internal view returns (Period) {
function _periodOf(uint64 timestamp) internal view returns (Period) {
return Period.wrap(timestamp / _secondsPerPeriod);
}
function _blockPeriod() internal view returns (Period) {
return _periodOf(block.timestamp);
return _periodOf(uint64(block.timestamp));
}
function _nextPeriod(Period period) internal pure returns (Period) {
return Period.wrap(Period.unwrap(period) + 1);
}
function _periodStart(Period period) internal view returns (uint256) {
function _periodStart(Period period) internal view returns (uint64) {
return Period.unwrap(period) * _secondsPerPeriod;
}
function _periodEnd(Period period) internal view returns (uint256) {
function _periodEnd(Period period) internal view returns (uint64) {
return _periodStart(_nextPeriod(period));
}

View File

@ -39,8 +39,8 @@ abstract contract Proofs is Periods {
_verifier = verifier;
}
mapping(SlotId => uint256) private _slotStarts; // TODO: Should be smaller than uint256
mapping(SlotId => uint256) private _missed; // TODO: Should be smaller than uint256
mapping(SlotId => uint64) private _slotStarts;
mapping(SlotId => uint64) private _missed;
mapping(SlotId => mapping(Period => bool)) private _received;
mapping(SlotId => mapping(Period => bool)) private _missing;
@ -55,7 +55,7 @@ abstract contract Proofs is Periods {
/**
* @return Number of missed proofs since Slot was Filled
*/
function missingProofs(SlotId slotId) public view returns (uint256) {
function missingProofs(SlotId slotId) public view returns (uint64) {
return _missed[slotId];
}
@ -73,7 +73,7 @@ abstract contract Proofs is Periods {
* and saves the required probability.
*/
function _startRequiringProofs(SlotId id) internal {
_slotStarts[id] = block.timestamp;
_slotStarts[id] = uint64(block.timestamp);
}
/**

View File

@ -8,17 +8,17 @@ struct Request {
address client;
Ask ask;
Content content;
uint256 expiry; // amount of seconds since start of the request at which this request expires
uint64 expiry; // amount of seconds since start of the request at which this request expires
bytes32 nonce; // random nonce to differentiate between similar requests
}
struct Ask {
uint64 slots; // the number of requested slots
uint256 slotSize; // amount of storage per slot (in number of bytes)
uint256 duration; // how long content should be stored (in seconds)
uint256 proofProbability; // how often storage proofs are required
uint256 pricePerBytePerSecond; // amount of tokens paid per second per byte to hosts
uint256 collateralPerByte; // amount of tokens per byte required to be deposited by the hosts in order to fill the slot
uint64 slots; // the number of requested slots
uint64 slotSize; // amount of storage per slot (in number of bytes)
uint64 duration; // how long content should be stored (in seconds)
uint64 maxSlotLoss; // Max slots that can be lost without data considered to be lost
}
@ -66,7 +66,7 @@ library Requests {
function slotId(
RequestId requestId,
uint256 slotIndex
uint64 slotIndex
) internal pure returns (SlotId) {
return SlotId.wrap(keccak256(abi.encode(requestId, slotIndex)));
}

View File

@ -18,7 +18,7 @@ abstract contract SlotReservations {
function _slotIsFree(SlotId slotId) internal view virtual returns (bool);
function reserveSlot(RequestId requestId, uint256 slotIndex) public {
function reserveSlot(RequestId requestId, uint64 slotIndex) public {
if (!canReserveSlot(requestId, slotIndex))
revert SlotReservations_ReservationNotAllowed();
@ -32,7 +32,7 @@ abstract contract SlotReservations {
function canReserveSlot(
RequestId requestId,
uint256 slotIndex
uint64 slotIndex
) public view returns (bool) {
address host = msg.sender;
SlotId slotId = Requests.slotId(requestId, slotIndex);
@ -43,5 +43,5 @@ abstract contract SlotReservations {
(!_reservations[slotId].contains(host));
}
event SlotReservationsFull(RequestId indexed requestId, uint256 slotIndex);
event SlotReservationsFull(RequestId indexed requestId, uint64 slotIndex);
}

View File

@ -13,8 +13,8 @@ const exampleConfiguration = () => ({
period: 10,
timeout: 5,
downtime: 64,
zkeyHash: "",
downtimeProduct: 67,
zkeyHash: "",
},
reservations: {
maxReservations: 3,

View File

@ -2,21 +2,21 @@ const { ethers } = require("hardhat")
const { keccak256, defaultAbiCoder } = ethers.utils
function requestId(request) {
const Ask = "tuple(int64, uint256, uint256, uint256, uint256, uint256, int64)"
const Ask = "tuple(uint256, uint256, uint256, uint64, uint64, uint64, int64)"
const Content = "tuple(bytes, bytes32)"
const Request =
"tuple(address, " + Ask + ", " + Content + ", uint256, bytes32)"
"tuple(address, " + Ask + ", " + Content + ", uint64, bytes32)"
return keccak256(defaultAbiCoder.encode([Request], requestToArray(request)))
}
function askToArray(ask) {
return [
ask.slots,
ask.slotSize,
ask.duration,
ask.proofProbability,
ask.pricePerBytePerSecond,
ask.collateralPerByte,
ask.slots,
ask.slotSize,
ask.duration,
ask.maxSlotLoss,
]
}