From c00152e6213a3ad4e6760a670213bfae22b0aabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Uhl=C3=AD=C5=99?= Date: Thu, 20 Feb 2025 06:54:41 +0100 Subject: [PATCH] perf: optimizing parameters sizing (#207) * perf: optimizing parameters sizing * chore: feedback Co-authored-by: markspanbroek * 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 Co-authored-by: Arnaud Co-authored-by: Eric <5089238+emizzle@users.noreply.github.com> --- certora/harness/MarketplaceHarness.sol | 4 +- certora/specs/Marketplace.spec | 33 +++++------ configuration/configuration.js | 1 + .../networks/hardhat/configuration.js | 1 + contracts/Configuration.sol | 8 +-- contracts/FuzzMarketplace.sol | 2 +- contracts/Marketplace.sol | 55 ++++++++++--------- contracts/Periods.sol | 14 ++--- contracts/Proofs.sol | 8 +-- contracts/Requests.sol | 10 ++-- contracts/SlotReservations.sol | 6 +- test/examples.js | 2 +- test/ids.js | 10 ++-- 13 files changed, 80 insertions(+), 74 deletions(-) diff --git a/certora/harness/MarketplaceHarness.sol b/certora/harness/MarketplaceHarness.sol index f61ac57..a22ea08 100644 --- a/certora/harness/MarketplaceHarness.sol +++ b/certora/harness/MarketplaceHarness.sol @@ -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); } } diff --git a/certora/specs/Marketplace.spec b/certora/specs/Marketplace.spec index 2771657..64ba147 100644 --- a/certora/specs/Marketplace.spec +++ b/certora/specs/Marketplace.spec @@ -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, diff --git a/configuration/configuration.js b/configuration/configuration.js index 61c3c6e..2d0ec3d 100644 --- a/configuration/configuration.js +++ b/configuration/configuration.js @@ -15,6 +15,7 @@ const DEFAULT_CONFIGURATION = { timeout: 30, // seconds downtime: 64, // number of blocks downtimeProduct: 67, // number of blocks + zkeyHash: "", }, reservations: { maxReservations: 3, diff --git a/configuration/networks/hardhat/configuration.js b/configuration/networks/hardhat/configuration.js index f7a1f29..d8ffafe 100644 --- a/configuration/networks/hardhat/configuration.js +++ b/configuration/networks/hardhat/configuration.js @@ -12,6 +12,7 @@ module.exports = { timeout: 30, // seconds downtime: 96, // number of blocks downtimeProduct: 97, // number of blocks + zkeyHash: "", }, reservations: { maxReservations: 3, diff --git a/contracts/Configuration.sol b/contracts/Configuration.sol index 65cf862..e443410 100644 --- a/contracts/Configuration.sol +++ b/contracts/Configuration.sol @@ -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 { diff --git a/contracts/FuzzMarketplace.sol b/contracts/FuzzMarketplace.sol index f73cd66..dfcfe70 100644 --- a/contracts/FuzzMarketplace.sol +++ b/contracts/FuzzMarketplace.sol @@ -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 ), diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index 297eaa1..283a393 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -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 { diff --git a/contracts/Periods.sol b/contracts/Periods.sol index 7085c31..ba51358 100644 --- a/contracts/Periods.sol +++ b/contracts/Periods.sol @@ -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)); } diff --git a/contracts/Proofs.sol b/contracts/Proofs.sol index 4a70fa3..6550d1a 100644 --- a/contracts/Proofs.sol +++ b/contracts/Proofs.sol @@ -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); } /** diff --git a/contracts/Requests.sol b/contracts/Requests.sol index aca6ef4..4f3bdb2 100644 --- a/contracts/Requests.sol +++ b/contracts/Requests.sol @@ -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))); } diff --git a/contracts/SlotReservations.sol b/contracts/SlotReservations.sol index 22442e4..c2425b2 100644 --- a/contracts/SlotReservations.sol +++ b/contracts/SlotReservations.sol @@ -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); } diff --git a/test/examples.js b/test/examples.js index b03ef9a..85503af 100644 --- a/test/examples.js +++ b/test/examples.js @@ -13,8 +13,8 @@ const exampleConfiguration = () => ({ period: 10, timeout: 5, downtime: 64, - zkeyHash: "", downtimeProduct: 67, + zkeyHash: "", }, reservations: { maxReservations: 3, diff --git a/test/ids.js b/test/ids.js index 549daa1..4547565 100644 --- a/test/ids.js +++ b/test/ids.js @@ -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, ] }