mirror of
https://github.com/codex-storage/codex-contracts-eth.git
synced 2025-02-09 10:03:27 +00:00
[fuzzing] Enable fuzzing for Marketplace
Replaces runtime invariant checks with fuzzing tests, simplifying the contract code and lowering gas costs.
This commit is contained in:
parent
d57cfc69cd
commit
3390e21071
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ node_modules
|
||||
cache
|
||||
artifacts
|
||||
deployment-localhost.json
|
||||
crytic-export
|
||||
|
32
contracts/FuzzMarketplace.sol
Normal file
32
contracts/FuzzMarketplace.sol
Normal file
@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./TestToken.sol";
|
||||
import "./Marketplace.sol";
|
||||
|
||||
contract FuzzMarketplace is Marketplace {
|
||||
constructor()
|
||||
Marketplace(
|
||||
new TestToken(),
|
||||
MarketplaceConfig(CollateralConfig(10, 5, 3, 10), ProofConfig(10, 5, 64))
|
||||
)
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Properties to be tested through fuzzing
|
||||
|
||||
MarketplaceTotals private _lastSeenTotals;
|
||||
|
||||
function neverDecreaseTotals() public {
|
||||
assert(_marketplaceTotals.received >= _lastSeenTotals.received);
|
||||
assert(_marketplaceTotals.sent >= _lastSeenTotals.sent);
|
||||
_lastSeenTotals = _marketplaceTotals;
|
||||
}
|
||||
|
||||
function neverLoseFunds() public view {
|
||||
uint256 total = _marketplaceTotals.received - _marketplaceTotals.sent;
|
||||
assert(token.balanceOf(address(this)) >= total);
|
||||
}
|
||||
}
|
@ -16,11 +16,12 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
IERC20 public immutable token;
|
||||
MarketplaceConfig public config;
|
||||
|
||||
MarketplaceFunds private _funds;
|
||||
mapping(RequestId => Request) private _requests;
|
||||
mapping(RequestId => RequestContext) private _requestContexts;
|
||||
mapping(SlotId => Slot) internal _slots;
|
||||
|
||||
MarketplaceTotals internal _marketplaceTotals;
|
||||
|
||||
struct RequestContext {
|
||||
RequestState state;
|
||||
uint256 slotsFilled;
|
||||
@ -48,7 +49,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
constructor(
|
||||
IERC20 token_,
|
||||
MarketplaceConfig memory configuration
|
||||
) Proofs(configuration.proofs) marketplaceInvariant {
|
||||
) Proofs(configuration.proofs) {
|
||||
token = token_;
|
||||
|
||||
require(configuration.collateral.repairRewardPercentage <= 100, "Must be less than 100");
|
||||
@ -57,9 +58,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
config = configuration;
|
||||
}
|
||||
|
||||
function requestStorage(
|
||||
Request calldata request
|
||||
) public marketplaceInvariant {
|
||||
function requestStorage(Request calldata request) public {
|
||||
require(request.client == msg.sender, "Invalid client address");
|
||||
|
||||
RequestId id = request.id();
|
||||
@ -71,8 +70,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
_addToMyRequests(request.client, id);
|
||||
|
||||
uint256 amount = request.price();
|
||||
_funds.received += amount;
|
||||
_funds.balance += amount;
|
||||
_marketplaceTotals.received += amount;
|
||||
_transferFrom(msg.sender, amount);
|
||||
|
||||
emit StorageRequested(id, request.ask);
|
||||
@ -104,8 +102,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
// Collect collateral
|
||||
uint256 collateralAmount = request.ask.collateral;
|
||||
_transferFrom(msg.sender, collateralAmount);
|
||||
_funds.received += collateralAmount;
|
||||
_funds.balance += collateralAmount;
|
||||
_marketplaceTotals.received += collateralAmount;
|
||||
slot.currentCollateral = collateralAmount;
|
||||
|
||||
_addToMySlots(slot.host, slotId);
|
||||
@ -142,9 +139,6 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
if (missingProofs(slotId) % config.collateral.slashCriterion == 0) {
|
||||
uint256 slashedAmount = (request.ask.collateral * config.collateral.slashPercentage) / 100;
|
||||
slot.currentCollateral -= slashedAmount;
|
||||
_funds.slashed += slashedAmount;
|
||||
_funds.balance -= slashedAmount;
|
||||
|
||||
if (missingProofs(slotId) / config.collateral.slashCriterion >= config.collateral.maxNumberOfSlashes) {
|
||||
// When the number of slashings is at or above the allowed amount,
|
||||
// free the slot.
|
||||
@ -153,7 +147,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
}
|
||||
}
|
||||
|
||||
function _forciblyFreeSlot(SlotId slotId) internal marketplaceInvariant {
|
||||
function _forciblyFreeSlot(SlotId slotId) internal {
|
||||
Slot storage slot = _slots[slotId];
|
||||
RequestId requestId = slot.requestId;
|
||||
RequestContext storage context = _requestContexts[requestId];
|
||||
@ -182,7 +176,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
function _payoutSlot(
|
||||
RequestId requestId,
|
||||
SlotId slotId
|
||||
) private requestIsKnown(requestId) marketplaceInvariant {
|
||||
) private requestIsKnown(requestId) {
|
||||
RequestContext storage context = _requestContexts[requestId];
|
||||
Request storage request = _requests[requestId];
|
||||
context.state = RequestState.Finished;
|
||||
@ -192,8 +186,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
_removeFromMySlots(slot.host, slotId);
|
||||
|
||||
uint256 amount = _requests[requestId].pricePerSlot() + slot.currentCollateral;
|
||||
_funds.sent += amount;
|
||||
_funds.balance -= amount;
|
||||
_marketplaceTotals.sent += amount;
|
||||
slot.state = SlotState.Paid;
|
||||
require(token.transfer(slot.host, amount), "Payment failed");
|
||||
}
|
||||
@ -201,7 +194,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
/// @notice Withdraws storage request funds back to the client that deposited them.
|
||||
/// @dev Request must be expired, must be in RequestState.New, and the transaction must originate from the depositer address.
|
||||
/// @param requestId the id of the request
|
||||
function withdrawFunds(RequestId requestId) public marketplaceInvariant {
|
||||
function withdrawFunds(RequestId requestId) public {
|
||||
Request storage request = _requests[requestId];
|
||||
require(block.timestamp > request.expiry, "Request not yet timed out");
|
||||
require(request.client == msg.sender, "Invalid client address");
|
||||
@ -219,8 +212,7 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
// fill a slot. The amount that we paid to hosts will then have to be
|
||||
// deducted from the price.
|
||||
uint256 amount = request.price();
|
||||
_funds.sent += amount;
|
||||
_funds.balance -= amount;
|
||||
_marketplaceTotals.sent += amount;
|
||||
require(token.transfer(msg.sender, amount), "Withdraw failed");
|
||||
}
|
||||
|
||||
@ -322,19 +314,8 @@ contract Marketplace is Proofs, StateRetrieval {
|
||||
event SlotFreed(RequestId indexed requestId, SlotId slotId);
|
||||
event RequestCancelled(RequestId indexed requestId);
|
||||
|
||||
modifier marketplaceInvariant() {
|
||||
MarketplaceFunds memory oldFunds = _funds;
|
||||
_;
|
||||
assert(_funds.received >= oldFunds.received);
|
||||
assert(_funds.sent >= oldFunds.sent);
|
||||
assert(_funds.slashed >= oldFunds.slashed);
|
||||
assert(_funds.received == _funds.balance + _funds.sent + _funds.slashed);
|
||||
}
|
||||
|
||||
struct MarketplaceFunds {
|
||||
uint256 balance;
|
||||
struct MarketplaceTotals {
|
||||
uint256 received;
|
||||
uint256 sent;
|
||||
uint256 slashed;
|
||||
}
|
||||
}
|
||||
|
3
fuzzing/corpus/.gitignore
vendored
Normal file
3
fuzzing/corpus/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!.keep
|
0
fuzzing/corpus/.keep
Normal file
0
fuzzing/corpus/.keep
Normal file
6
fuzzing/echidna.yaml
Normal file
6
fuzzing/echidna.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
# configure Echidna fuzzing tests
|
||||
|
||||
testMode: "assertion" # check that solidity asserts are never triggered
|
||||
multi-abi: true # allow calls to e.g. TestToken in test scenarios
|
||||
corpusDir: "fuzzing/corpus" # collect coverage maximizing corpus in this dir
|
||||
format: "text" # disable interactive ui
|
3
fuzzing/fuzz.sh
Executable file
3
fuzzing/fuzz.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echidna-test . --contract FuzzMarketplace --config fuzzing/echidna.yaml
|
@ -3,6 +3,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "npm run lint && hardhat test",
|
||||
"fuzz": "fuzzing/fuzz.sh",
|
||||
"start": "hardhat node --export deployment-localhost.json",
|
||||
"compile": "hardhat compile",
|
||||
"format": "prettier --write contracts/**/*.sol test/**/*.js",
|
||||
|
Loading…
x
Reference in New Issue
Block a user