feat: collateral per slot (#44)
This commit is contained in:
parent
fcc28b3931
commit
dfdbd16d5b
|
@ -0,0 +1,16 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
[{*.js, *.sol}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -1,77 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
abstract contract Collateral {
|
||||
IERC20 public immutable token;
|
||||
CollateralFunds private _funds;
|
||||
|
||||
mapping(address => uint256) private _balances;
|
||||
|
||||
constructor(IERC20 token_) collateralInvariant {
|
||||
token = token_;
|
||||
}
|
||||
|
||||
function balanceOf(address account) public view returns (uint256) {
|
||||
return _balances[account];
|
||||
}
|
||||
|
||||
function _add(address account, uint256 amount) private {
|
||||
_balances[account] += amount;
|
||||
_funds.balance += amount;
|
||||
}
|
||||
|
||||
function _subtract(address account, uint256 amount) private {
|
||||
_balances[account] -= amount;
|
||||
_funds.balance -= amount;
|
||||
}
|
||||
|
||||
function _transferFrom(address sender, uint256 amount) internal {
|
||||
address receiver = address(this);
|
||||
require(token.transferFrom(sender, receiver, amount), "Transfer failed");
|
||||
}
|
||||
|
||||
function deposit(uint256 amount) public collateralInvariant {
|
||||
_transferFrom(msg.sender, amount);
|
||||
_funds.deposited += amount;
|
||||
_add(msg.sender, amount);
|
||||
}
|
||||
|
||||
function _isWithdrawAllowed() internal virtual returns (bool);
|
||||
|
||||
function withdraw() public collateralInvariant {
|
||||
require(_isWithdrawAllowed(), "Account locked");
|
||||
uint256 amount = balanceOf(msg.sender);
|
||||
_funds.withdrawn += amount;
|
||||
_subtract(msg.sender, amount);
|
||||
assert(token.transfer(msg.sender, amount));
|
||||
}
|
||||
|
||||
function _slash(
|
||||
address account,
|
||||
uint256 percentage
|
||||
) internal collateralInvariant {
|
||||
uint256 amount = (balanceOf(account) * percentage) / 100;
|
||||
_funds.slashed += amount;
|
||||
_subtract(account, amount);
|
||||
}
|
||||
|
||||
modifier collateralInvariant() {
|
||||
CollateralFunds memory oldFunds = _funds;
|
||||
_;
|
||||
assert(_funds.deposited >= oldFunds.deposited);
|
||||
assert(_funds.withdrawn >= oldFunds.withdrawn);
|
||||
assert(_funds.slashed >= oldFunds.slashed);
|
||||
assert(
|
||||
_funds.deposited == _funds.balance + _funds.withdrawn + _funds.slashed
|
||||
);
|
||||
}
|
||||
|
||||
struct CollateralFunds {
|
||||
uint256 balance;
|
||||
uint256 deposited;
|
||||
uint256 withdrawn;
|
||||
uint256 slashed;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ struct MarketplaceConfig {
|
|||
}
|
||||
|
||||
struct CollateralConfig {
|
||||
uint256 initialAmount; // amount of collateral necessary to fill a slot
|
||||
uint256 minimumAmount; // frees slot when collateral drops below this minimum
|
||||
uint256 slashCriterion; // amount of proofs missed that lead to slashing
|
||||
uint256 slashPercentage; // percentage of the collateral that is slashed
|
||||
|
|
|
@ -6,20 +6,20 @@ import "@openzeppelin/contracts/utils/math/Math.sol";
|
|||
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
import "./Configuration.sol";
|
||||
import "./Requests.sol";
|
||||
import "./Collateral.sol";
|
||||
import "./Proofs.sol";
|
||||
import "./StateRetrieval.sol";
|
||||
|
||||
contract Marketplace is Collateral, Proofs, StateRetrieval {
|
||||
contract Marketplace is Proofs, StateRetrieval {
|
||||
using EnumerableSet for EnumerableSet.Bytes32Set;
|
||||
using Requests for Request;
|
||||
|
||||
IERC20 public immutable token;
|
||||
MarketplaceConfig public config;
|
||||
|
||||
MarketplaceFunds private _funds;
|
||||
mapping(RequestId => Request) private _requests;
|
||||
mapping(RequestId => RequestContext) private _requestContexts;
|
||||
mapping(SlotId => Slot) private _slots;
|
||||
mapping(SlotId => Slot) internal _slots;
|
||||
|
||||
struct RequestContext {
|
||||
RequestState state;
|
||||
|
@ -31,20 +31,22 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
struct Slot {
|
||||
SlotState state;
|
||||
RequestId requestId;
|
||||
|
||||
/// @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 of request.ask.collateral
|
||||
/// @dev When Host is slashed for missing a proof the slashed amount is reflected in this variable
|
||||
uint256 currentCollateral;
|
||||
address host;
|
||||
}
|
||||
|
||||
constructor(
|
||||
IERC20 token,
|
||||
IERC20 token_,
|
||||
MarketplaceConfig memory configuration
|
||||
) Collateral(token) Proofs(configuration.proofs) marketplaceInvariant {
|
||||
) Proofs(configuration.proofs) marketplaceInvariant {
|
||||
token = token_;
|
||||
config = configuration;
|
||||
}
|
||||
|
||||
function _isWithdrawAllowed() internal view override returns (bool) {
|
||||
return !_hasSlots(msg.sender);
|
||||
}
|
||||
|
||||
function requestStorage(
|
||||
Request calldata request
|
||||
) public marketplaceInvariant {
|
||||
|
@ -80,11 +82,6 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
|
||||
require(slotState(slotId) == SlotState.Free, "Slot is not free");
|
||||
|
||||
require(
|
||||
balanceOf(msg.sender) >= config.collateral.initialAmount,
|
||||
"Insufficient collateral"
|
||||
);
|
||||
|
||||
_startRequiringProofs(slotId, request.ask.proofProbability);
|
||||
submitProof(slotId, proof);
|
||||
|
||||
|
@ -93,6 +90,13 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
RequestContext storage context = _requestContexts[requestId];
|
||||
context.slotsFilled += 1;
|
||||
|
||||
// Collect collateral
|
||||
uint256 collateralAmount = request.ask.collateral;
|
||||
_transferFrom(msg.sender, collateralAmount);
|
||||
_funds.received += collateralAmount;
|
||||
_funds.balance += collateralAmount;
|
||||
slot.currentCollateral = collateralAmount;
|
||||
|
||||
_addToMySlots(slot.host, slotId);
|
||||
|
||||
emit SlotFilled(requestId, slotIndex, slotId);
|
||||
|
@ -108,6 +112,7 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
require(slot.host == msg.sender, "Slot filled by other host");
|
||||
SlotState state = slotState(slotId);
|
||||
require(state != SlotState.Paid, "Already paid");
|
||||
|
||||
if (state == SlotState.Finished) {
|
||||
_payoutSlot(slot.requestId, slotId);
|
||||
} else if (state == SlotState.Failed) {
|
||||
|
@ -120,11 +125,15 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
function markProofAsMissing(SlotId slotId, Period period) public {
|
||||
require(slotState(slotId) == SlotState.Filled, "Slot not accepting proofs");
|
||||
_markProofAsMissing(slotId, period);
|
||||
address host = getHost(slotId);
|
||||
if (missingProofs(slotId) % config.collateral.slashCriterion == 0) {
|
||||
_slash(host, config.collateral.slashPercentage);
|
||||
Slot storage slot = _slots[slotId];
|
||||
|
||||
if (balanceOf(host) < config.collateral.minimumAmount) {
|
||||
if (missingProofs(slotId) % config.collateral.slashCriterion == 0) {
|
||||
uint256 slashedAmount = (slot.currentCollateral * config.collateral.slashPercentage) / 100;
|
||||
slot.currentCollateral -= slashedAmount;
|
||||
_funds.slashed += slashedAmount;
|
||||
_funds.balance -= slashedAmount;
|
||||
|
||||
if (slot.currentCollateral < config.collateral.minimumAmount) {
|
||||
// When the collateral drops below the minimum threshold, the slot
|
||||
// needs to be freed so that there is enough remaining collateral to be
|
||||
// distributed for repairs and rewards (with any leftover to be burnt).
|
||||
|
@ -138,16 +147,9 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
RequestId requestId = slot.requestId;
|
||||
RequestContext storage context = _requestContexts[requestId];
|
||||
|
||||
// TODO: burn host's slot collateral except for repair costs + mark proof
|
||||
// missing reward
|
||||
// Slot collateral is not yet implemented as the design decision was
|
||||
// not finalised.
|
||||
|
||||
_removeFromMySlots(slot.host, slotId);
|
||||
|
||||
slot.state = SlotState.Free;
|
||||
slot.host = address(0);
|
||||
slot.requestId = RequestId.wrap(0);
|
||||
delete _slots[slotId];
|
||||
context.slotsFilled -= 1;
|
||||
emit SlotFreed(requestId, slotId);
|
||||
|
||||
|
@ -161,8 +163,6 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
context.endsAt = block.timestamp - 1;
|
||||
emit RequestFailed(requestId);
|
||||
|
||||
// TODO: burn all remaining slot collateral (note: slot collateral not
|
||||
// yet implemented)
|
||||
// TODO: send client remaining funds
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
|
||||
_removeFromMySlots(slot.host, slotId);
|
||||
|
||||
uint256 amount = _requests[requestId].pricePerSlot();
|
||||
uint256 amount = _requests[requestId].pricePerSlot() + slot.currentCollateral;
|
||||
_funds.sent += amount;
|
||||
_funds.balance -= amount;
|
||||
slot.state = SlotState.Paid;
|
||||
|
@ -212,10 +212,6 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
require(token.transfer(msg.sender, amount), "Withdraw failed");
|
||||
}
|
||||
|
||||
function getHost(SlotId slotId) public view returns (address) {
|
||||
return _slots[slotId].host;
|
||||
}
|
||||
|
||||
function getRequestFromSlotId(SlotId slotId)
|
||||
public
|
||||
view
|
||||
|
@ -252,6 +248,10 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
}
|
||||
}
|
||||
|
||||
function getHost(SlotId slotId) public view returns (address) {
|
||||
return _slots[slotId].host;
|
||||
}
|
||||
|
||||
function requestState(
|
||||
RequestId requestId
|
||||
) public view requestIsKnown(requestId) returns (RequestState) {
|
||||
|
@ -291,6 +291,11 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
return slot.state;
|
||||
}
|
||||
|
||||
function _transferFrom(address sender, uint256 amount) internal {
|
||||
address receiver = address(this);
|
||||
require(token.transferFrom(sender, receiver, amount), "Transfer failed");
|
||||
}
|
||||
|
||||
event StorageRequested(RequestId requestId, Ask ask);
|
||||
event RequestFulfilled(RequestId indexed requestId);
|
||||
event RequestFailed(RequestId indexed requestId);
|
||||
|
@ -307,12 +312,14 @@ contract Marketplace is Collateral, Proofs, StateRetrieval {
|
|||
_;
|
||||
assert(_funds.received >= oldFunds.received);
|
||||
assert(_funds.sent >= oldFunds.sent);
|
||||
assert(_funds.received == _funds.balance + _funds.sent);
|
||||
assert(_funds.slashed >= oldFunds.slashed);
|
||||
assert(_funds.received == _funds.balance + _funds.sent + _funds.slashed);
|
||||
}
|
||||
|
||||
struct MarketplaceFunds {
|
||||
uint256 balance;
|
||||
uint256 received;
|
||||
uint256 sent;
|
||||
uint256 slashed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ struct Request {
|
|||
address client;
|
||||
Ask ask;
|
||||
Content content;
|
||||
uint256 expiry; // time at which this request expires
|
||||
uint256 expiry; // time at which this request timeouts if all slots are not filled and is pronounced cancelled
|
||||
bytes32 nonce; // random nonce to differentiate between similar requests
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ struct Ask {
|
|||
uint256 duration; // how long content should be stored (in seconds)
|
||||
uint256 proofProbability; // how often storage proofs are required
|
||||
uint256 reward; // amount of tokens paid per second per slot to hosts
|
||||
uint256 collateral; // amount of tokens required to be deposited by the hosts in order to fill the slot
|
||||
uint64 maxSlotLoss; // Max slots that can be lost without data considered to be lost
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./Collateral.sol";
|
||||
|
||||
// exposes internal functions for testing
|
||||
contract TestCollateral is Collateral {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
constructor(IERC20 token) Collateral(token) {}
|
||||
|
||||
function slash(address account, uint256 percentage) public {
|
||||
_slash(account, percentage);
|
||||
}
|
||||
|
||||
function _isWithdrawAllowed() internal pure override returns (bool) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -15,4 +15,8 @@ contract TestMarketplace is Marketplace {
|
|||
function forciblyFreeSlot(SlotId slotId) public {
|
||||
_forciblyFreeSlot(slotId);
|
||||
}
|
||||
|
||||
function getSlotCollateral(SlotId slotId) public view returns (uint256) {
|
||||
return _slots[slotId].currentCollateral;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ async function deployMarketplace({ deployments, getNamedAccounts }) {
|
|||
const token = await deployments.get("TestToken")
|
||||
const configuration = {
|
||||
collateral: {
|
||||
initialAmount: 100,
|
||||
minimumAmount: 40,
|
||||
slashCriterion: 3,
|
||||
slashPercentage: 10,
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
"chai": "^4.3.7",
|
||||
"ethereum-waffle": "^3.4.4",
|
||||
"ethers": "^5.7.2",
|
||||
"hardhat": "^2.12.5",
|
||||
"hardhat-deploy": "^0.11.22",
|
||||
"hardhat": "^2.12.7",
|
||||
"hardhat-deploy": "^0.11.23",
|
||||
"hardhat-deploy-ethers": "^0.3.0-beta.13",
|
||||
"prettier": "^2.8.2",
|
||||
"prettier-plugin-solidity": "^1.1.1",
|
||||
|
@ -15670,8 +15670,6 @@
|
|||
},
|
||||
"node_modules/ganache-core/node_modules/keccak": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz",
|
||||
"integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"inBundle": true,
|
||||
|
@ -16245,8 +16243,6 @@
|
|||
},
|
||||
"node_modules/ganache-core/node_modules/node-addon-api": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
|
||||
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
|
@ -16261,8 +16257,6 @@
|
|||
},
|
||||
"node_modules/ganache-core/node_modules/node-gyp-build": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
|
||||
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
|
@ -19517,9 +19511,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hardhat": {
|
||||
"version": "2.12.5",
|
||||
"resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.5.tgz",
|
||||
"integrity": "sha512-f/t7+hLlhsnQZ6LDXyV+8rHGRZFZY1sgFvgrwr9fBjMdGp1Bu6hHq1KXS4/VFZfZcVdL1DAWWEkryinZhqce+A==",
|
||||
"version": "2.12.7",
|
||||
"resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.7.tgz",
|
||||
"integrity": "sha512-voWoN6zn5d8BOEaczSyK/1PyfdeOeI3SbGCFb36yCHTJUt6OIqLb+ZDX30VhA1UsYKzLqG7UnWl3fKJUuANc6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ethersproject/abi": "^5.1.2",
|
||||
|
@ -19569,7 +19563,7 @@
|
|||
"source-map-support": "^0.5.13",
|
||||
"stacktrace-parser": "^0.1.10",
|
||||
"tsort": "0.0.1",
|
||||
"undici": "^5.4.0",
|
||||
"undici": "^5.14.0",
|
||||
"uuid": "^8.3.2",
|
||||
"ws": "^7.4.6"
|
||||
},
|
||||
|
@ -19593,9 +19587,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hardhat-deploy": {
|
||||
"version": "0.11.22",
|
||||
"resolved": "https://registry.npmjs.org/hardhat-deploy/-/hardhat-deploy-0.11.22.tgz",
|
||||
"integrity": "sha512-ZhHVNB7Jo2l8Is+KIAk9F8Q3d7pptyiX+nsNbIFXztCz81kaP+6kxNODRBqRCy7SOD3It4+iKCL6tWsPAA/jVQ==",
|
||||
"version": "0.11.23",
|
||||
"resolved": "https://registry.npmjs.org/hardhat-deploy/-/hardhat-deploy-0.11.23.tgz",
|
||||
"integrity": "sha512-9F+sDRX79D/oV1cUEE0k2h5LiccrnzXEtrMofL5PTVDCJfUnRvhQqCRi4NhcYmxf2+MBkOIJv5KyzP0lz6ojTw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/qs": "^6.9.7",
|
||||
|
@ -33185,8 +33179,6 @@
|
|||
},
|
||||
"keccak": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz",
|
||||
"integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -33618,8 +33610,6 @@
|
|||
},
|
||||
"node-addon-api": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
|
||||
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
|
@ -33629,8 +33619,6 @@
|
|||
},
|
||||
"node-gyp-build": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
|
||||
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
|
@ -36069,9 +36057,9 @@
|
|||
}
|
||||
},
|
||||
"hardhat": {
|
||||
"version": "2.12.5",
|
||||
"resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.5.tgz",
|
||||
"integrity": "sha512-f/t7+hLlhsnQZ6LDXyV+8rHGRZFZY1sgFvgrwr9fBjMdGp1Bu6hHq1KXS4/VFZfZcVdL1DAWWEkryinZhqce+A==",
|
||||
"version": "2.12.7",
|
||||
"resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.7.tgz",
|
||||
"integrity": "sha512-voWoN6zn5d8BOEaczSyK/1PyfdeOeI3SbGCFb36yCHTJUt6OIqLb+ZDX30VhA1UsYKzLqG7UnWl3fKJUuANc6A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ethersproject/abi": "^5.1.2",
|
||||
|
@ -36121,7 +36109,7 @@
|
|||
"source-map-support": "^0.5.13",
|
||||
"stacktrace-parser": "^0.1.10",
|
||||
"tsort": "0.0.1",
|
||||
"undici": "^5.4.0",
|
||||
"undici": "^5.14.0",
|
||||
"uuid": "^8.3.2",
|
||||
"ws": "^7.4.6"
|
||||
},
|
||||
|
@ -36197,9 +36185,9 @@
|
|||
}
|
||||
},
|
||||
"hardhat-deploy": {
|
||||
"version": "0.11.22",
|
||||
"resolved": "https://registry.npmjs.org/hardhat-deploy/-/hardhat-deploy-0.11.22.tgz",
|
||||
"integrity": "sha512-ZhHVNB7Jo2l8Is+KIAk9F8Q3d7pptyiX+nsNbIFXztCz81kaP+6kxNODRBqRCy7SOD3It4+iKCL6tWsPAA/jVQ==",
|
||||
"version": "0.11.23",
|
||||
"resolved": "https://registry.npmjs.org/hardhat-deploy/-/hardhat-deploy-0.11.23.tgz",
|
||||
"integrity": "sha512-9F+sDRX79D/oV1cUEE0k2h5LiccrnzXEtrMofL5PTVDCJfUnRvhQqCRi4NhcYmxf2+MBkOIJv5KyzP0lz6ojTw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/qs": "^6.9.7",
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
"chai": "^4.3.7",
|
||||
"ethereum-waffle": "^3.4.4",
|
||||
"ethers": "^5.7.2",
|
||||
"hardhat": "^2.12.5",
|
||||
"hardhat-deploy": "^0.11.22",
|
||||
"hardhat": "^2.12.7",
|
||||
"hardhat-deploy": "^0.11.23",
|
||||
"hardhat-deploy-ethers": "^0.3.0-beta.13",
|
||||
"prettier": "^2.8.2",
|
||||
"prettier-plugin-solidity": "^1.1.1",
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
const { expect } = require("chai")
|
||||
|
||||
describe("Collateral", function () {
|
||||
let collateral, token
|
||||
let account0, account1
|
||||
|
||||
beforeEach(async function () {
|
||||
let Collateral = await ethers.getContractFactory("TestCollateral")
|
||||
let TestToken = await ethers.getContractFactory("TestToken")
|
||||
token = await TestToken.deploy()
|
||||
collateral = await Collateral.deploy(token.address)
|
||||
;[account0, account1] = await ethers.getSigners()
|
||||
await token.mint(account0.address, 1000)
|
||||
await token.mint(account1.address, 1000)
|
||||
})
|
||||
|
||||
it("assigns zero collateral by default", async function () {
|
||||
expect(await collateral.balanceOf(account0.address)).to.equal(0)
|
||||
expect(await collateral.balanceOf(account1.address)).to.equal(0)
|
||||
})
|
||||
|
||||
describe("depositing", function () {
|
||||
beforeEach(async function () {
|
||||
await token.connect(account0).approve(collateral.address, 100)
|
||||
await token.connect(account1).approve(collateral.address, 100)
|
||||
})
|
||||
|
||||
it("updates the amount of collateral", async function () {
|
||||
await collateral.connect(account0).deposit(40)
|
||||
await collateral.connect(account1).deposit(2)
|
||||
expect(await collateral.balanceOf(account0.address)).to.equal(40)
|
||||
expect(await collateral.balanceOf(account1.address)).to.equal(2)
|
||||
})
|
||||
|
||||
it("transfers tokens to the contract", async function () {
|
||||
let before = await token.balanceOf(collateral.address)
|
||||
await collateral.deposit(42)
|
||||
let after = await token.balanceOf(collateral.address)
|
||||
expect(after - before).to.equal(42)
|
||||
})
|
||||
|
||||
it("fails when token transfer fails", async function () {
|
||||
let allowed = await token.allowance(account0.address, collateral.address)
|
||||
let invalidAmount = allowed.toNumber() + 1
|
||||
await expect(collateral.deposit(invalidAmount)).to.be.revertedWith(
|
||||
"ERC20: insufficient allowance"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("withdrawing", function () {
|
||||
beforeEach(async function () {
|
||||
await token.connect(account0).approve(collateral.address, 100)
|
||||
await token.connect(account1).approve(collateral.address, 100)
|
||||
await collateral.connect(account0).deposit(40)
|
||||
await collateral.connect(account1).deposit(2)
|
||||
})
|
||||
|
||||
it("updates the amount of collateral", async function () {
|
||||
await collateral.connect(account0).withdraw()
|
||||
expect(await collateral.balanceOf(account0.address)).to.equal(0)
|
||||
expect(await collateral.balanceOf(account1.address)).to.equal(2)
|
||||
await collateral.connect(account1).withdraw()
|
||||
expect(await collateral.balanceOf(account0.address)).to.equal(0)
|
||||
expect(await collateral.balanceOf(account1.address)).to.equal(0)
|
||||
})
|
||||
|
||||
it("transfers balance to owner", async function () {
|
||||
let balance = await collateral.balanceOf(account0.address)
|
||||
let before = await token.balanceOf(account0.address)
|
||||
await collateral.withdraw()
|
||||
let after = await token.balanceOf(account0.address)
|
||||
expect(after - before).to.equal(balance)
|
||||
})
|
||||
})
|
||||
|
||||
describe("slashing", function () {
|
||||
beforeEach(async function () {
|
||||
await token.connect(account0).approve(collateral.address, 1000)
|
||||
await token.connect(account1).approve(collateral.address, 1000)
|
||||
await collateral.connect(account0).deposit(1000)
|
||||
await collateral.connect(account1).deposit(1000)
|
||||
})
|
||||
|
||||
it("reduces the amount of collateral by a percentage", async function () {
|
||||
await collateral.slash(account0.address, 10)
|
||||
await collateral.slash(account1.address, 5)
|
||||
expect(await collateral.balanceOf(account0.address)).to.equal(900)
|
||||
expect(await collateral.balanceOf(account1.address)).to.equal(950)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -118,14 +118,13 @@ describe("Marketplace", function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe("filling a slot", function () {
|
||||
describe("filling a slot with collateral", function () {
|
||||
beforeEach(async function () {
|
||||
switchAccount(client)
|
||||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("emits event when slot is filled", async function () {
|
||||
|
@ -160,16 +159,6 @@ describe("Marketplace", function () {
|
|||
).to.be.revertedWith("Invalid proof")
|
||||
})
|
||||
|
||||
it("is rejected when collateral is insufficient", async function () {
|
||||
let insufficient = config.collateral.initialAmount - 1
|
||||
await marketplace.withdraw()
|
||||
await token.approve(marketplace.address, insufficient)
|
||||
await marketplace.deposit(insufficient)
|
||||
await expect(
|
||||
marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
).to.be.revertedWith("Insufficient collateral")
|
||||
})
|
||||
|
||||
it("is rejected when slot already filled", async function () {
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
await expect(
|
||||
|
@ -196,15 +185,15 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("is rejected when request is finished", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, slot.request)
|
||||
await expect(
|
||||
marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
).to.be.revertedWith("Slot is not free")
|
||||
})
|
||||
|
||||
it("is rejected when request is failed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFailed(marketplace, request)
|
||||
await expect(
|
||||
marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
|
@ -220,6 +209,8 @@ describe("Marketplace", function () {
|
|||
|
||||
it("fails when all slots are already filled", async function () {
|
||||
const lastSlot = request.ask.slots - 1
|
||||
await token.approve(marketplace.address, request.ask.collateral * lastSlot)
|
||||
await token.approve(marketplace.address, price(request) * lastSlot)
|
||||
for (let i = 0; i <= lastSlot; i++) {
|
||||
await marketplace.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
|
@ -229,6 +220,31 @@ describe("Marketplace", function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe("filling slot without collateral", function () {
|
||||
beforeEach(async function () {
|
||||
switchAccount(client)
|
||||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
})
|
||||
|
||||
it("is rejected when approved collateral is insufficient", async function () {
|
||||
let insufficient = request.ask.collateral - 1
|
||||
await token.approve(marketplace.address, insufficient)
|
||||
await expect(
|
||||
marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
).to.be.revertedWith("ERC20: insufficient allowance")
|
||||
})
|
||||
|
||||
it("collects only requested collateral and not more", async function () {
|
||||
await token.approve(marketplace.address, request.ask.collateral*2)
|
||||
const startBalanace = await token.balanceOf(host.address)
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
const endBalance = await token.balanceOf(host.address)
|
||||
expect(startBalanace-endBalance).to.eq(request.ask.collateral)
|
||||
})
|
||||
})
|
||||
|
||||
describe("request end", function () {
|
||||
var requestTime
|
||||
beforeEach(async function () {
|
||||
|
@ -237,8 +253,7 @@ describe("Marketplace", function () {
|
|||
await marketplace.requestStorage(request)
|
||||
requestTime = await currentTime()
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("sets the request end time to now + duration", async function () {
|
||||
|
@ -249,7 +264,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("sets request end time to the past once failed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFailed(marketplace, request)
|
||||
let slot0 = { ...slot, index: request.ask.maxSlotLoss + 1 }
|
||||
const now = await currentTime()
|
||||
|
@ -268,7 +283,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("checks that request end time is in the past once finished", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
const now = await currentTime()
|
||||
// in the process of calling currentTime and requestEnd,
|
||||
|
@ -281,7 +296,8 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
describe("freeing a slot", function () {
|
||||
var id
|
||||
let id
|
||||
|
||||
beforeEach(async function () {
|
||||
slot.index = 0
|
||||
id = slotId(slot)
|
||||
|
@ -290,8 +306,7 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("fails to free slot when slot not filled", async function () {
|
||||
|
@ -303,7 +318,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("can only be freed by the host occupying the slot", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
switchAccount(client)
|
||||
await expect(marketplace.freeSlot(id)).to.be.revertedWith(
|
||||
"Slot filled by other host"
|
||||
|
@ -311,12 +326,12 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("successfully frees slot", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await expect(marketplace.freeSlot(id)).not.to.be.reverted
|
||||
})
|
||||
|
||||
it("emits event once slot is freed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await expect(await marketplace.freeSlot(id))
|
||||
.to.emit(marketplace, "SlotFreed")
|
||||
.withArgs(slot.request, id)
|
||||
|
@ -329,17 +344,16 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("pays the host when contract has finished", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
it("pays the host when contract has finished and returns collateral", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
const startBalance = await token.balanceOf(host.address)
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
const endBalance = await token.balanceOf(host.address)
|
||||
expect(endBalance - startBalance).to.equal(pricePerSlot(request))
|
||||
expect(endBalance - startBalance).to.equal(pricePerSlot(request) + request.ask.collateral)
|
||||
})
|
||||
|
||||
it("pays the host when contract was cancelled", async function () {
|
||||
|
@ -360,7 +374,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("can only be done once", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
await expect(marketplace.freeSlot(slotId(slot))).to.be.revertedWith(
|
||||
|
@ -369,7 +383,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("cannot be filled again", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
await expect(marketplace.fillSlot(slot.request, slot.index, proof)).to.be
|
||||
|
@ -383,22 +397,25 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("emits event when all slots are filled", async function () {
|
||||
const lastSlot = request.ask.slots - 1
|
||||
await token.approve(marketplace.address, request.ask.collateral * lastSlot)
|
||||
for (let i = 0; i < lastSlot; i++) {
|
||||
await marketplace.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
await expect(marketplace.fillSlot(slot.request, lastSlot, proof))
|
||||
.to.emit(marketplace, "RequestFulfilled")
|
||||
.withArgs(requestId(request))
|
||||
})
|
||||
it("sets state when all slots are filled", async function () {
|
||||
const lastSlot = request.ask.slots - 1
|
||||
for (let i = 0; i <= lastSlot; i++) {
|
||||
const slots = request.ask.slots
|
||||
await token.approve(marketplace.address, request.ask.collateral * slots)
|
||||
for (let i = 0; i < slots; i++) {
|
||||
await marketplace.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
await expect(await marketplace.requestState(slot.request)).to.equal(
|
||||
|
@ -407,6 +424,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
it("fails when all slots are already filled", async function () {
|
||||
const lastSlot = request.ask.slots - 1
|
||||
await token.approve(marketplace.address, request.ask.collateral * (lastSlot + 1))
|
||||
for (let i = 0; i <= lastSlot; i++) {
|
||||
await marketplace.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
|
@ -422,8 +440,7 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("rejects withdraw when request not yet timed out", async function () {
|
||||
|
@ -443,6 +460,7 @@ describe("Marketplace", function () {
|
|||
it("rejects withdraw when in wrong state", async function () {
|
||||
// fill all slots, should change state to RequestState.Started
|
||||
const lastSlot = request.ask.slots - 1
|
||||
await token.approve(marketplace.address, request.ask.collateral * (lastSlot + 1))
|
||||
for (let i = 0; i <= lastSlot; i++) {
|
||||
await marketplace.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
|
@ -471,34 +489,6 @@ describe("Marketplace", function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe("collateral locking", function () {
|
||||
beforeEach(async function () {
|
||||
switchAccount(client)
|
||||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
})
|
||||
|
||||
it("locks collateral of host when it fills a slot", async function () {
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
await expect(marketplace.withdraw()).to.be.revertedWith("Account locked")
|
||||
})
|
||||
|
||||
it("allows withdrawal when all slots are free", async function () {
|
||||
let slot1 = { ...slot, index: 0 }
|
||||
let slot2 = { ...slot, index: 1 }
|
||||
await marketplace.fillSlot(slot1.request, slot1.index, proof)
|
||||
await marketplace.fillSlot(slot2.request, slot2.index, proof)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.freeSlot(slotId(slot1))
|
||||
await expect(marketplace.withdraw()).to.be.revertedWith("Account locked")
|
||||
await marketplace.freeSlot(slotId(slot2))
|
||||
await expect(marketplace.withdraw()).not.to.be.reverted
|
||||
})
|
||||
})
|
||||
|
||||
describe("request state", function () {
|
||||
const { New, Cancelled, Started, Failed, Finished } = RequestState
|
||||
|
||||
|
@ -507,8 +497,7 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("is 'New' initially", async function () {
|
||||
|
@ -528,17 +517,18 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("changes to 'Started' once all slots are filled", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
expect(await marketplace.requestState(slot.request)).to.equal(Started)
|
||||
})
|
||||
|
||||
it("changes to 'Failed' once too many slots are freed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFailed(marketplace, request)
|
||||
expect(await marketplace.requestState(slot.request)).to.equal(Failed)
|
||||
})
|
||||
|
||||
it("does not change to 'Failed' before it is started", async function () {
|
||||
await token.approve(marketplace.address, request.ask.collateral * (request.ask.maxSlotLoss + 1))
|
||||
for (let i = 0; i <= request.ask.maxSlotLoss; i++) {
|
||||
await marketplace.fillSlot(slot.request, i, proof)
|
||||
}
|
||||
|
@ -551,13 +541,13 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("changes to 'Finished' when the request ends", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
expect(await marketplace.requestState(slot.request)).to.equal(Finished)
|
||||
})
|
||||
|
||||
it("remains 'Finished' once a slot is paid out", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
expect(await marketplace.requestState(slot.request)).to.equal(Finished)
|
||||
|
@ -576,8 +566,7 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
async function waitUntilProofIsRequired(id) {
|
||||
|
@ -602,7 +591,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("changes to 'Finished' when request finishes", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, slot.request)
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(Finished)
|
||||
})
|
||||
|
@ -620,7 +609,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("changes to 'Free' when too many proofs are missed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
while ((await marketplace.slotState(slotId(slot))) === Filled) {
|
||||
await waitUntilProofIsRequired(slotId(slot))
|
||||
const missedPeriod = periodOf(await currentTime())
|
||||
|
@ -631,13 +620,13 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("changes to 'Failed' when request fails", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilSlotFailed(marketplace, request, slot)
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(Failed)
|
||||
})
|
||||
|
||||
it("changes to 'Paid' when host has been paid", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, slot.request)
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(Paid)
|
||||
|
@ -655,8 +644,7 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
async function waitUntilProofWillBeRequired(id) {
|
||||
|
@ -735,8 +723,7 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
async function waitUntilProofIsRequired(id) {
|
||||
|
@ -763,8 +750,7 @@ describe("Marketplace", function () {
|
|||
describe("slashing when missing proofs", function () {
|
||||
it("reduces collateral when too many proofs are missing", async function () {
|
||||
const id = slotId(slot)
|
||||
const { slashCriterion, slashPercentage, initialAmount } =
|
||||
config.collateral
|
||||
const { slashCriterion, slashPercentage } = config.collateral
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
for (let i = 0; i < slashCriterion; i++) {
|
||||
await waitUntilProofIsRequired(id)
|
||||
|
@ -772,33 +758,31 @@ describe("Marketplace", function () {
|
|||
await advanceTime(period)
|
||||
await marketplace.markProofAsMissing(id, missedPeriod)
|
||||
}
|
||||
const expectedBalance = (initialAmount * (100 - slashPercentage)) / 100
|
||||
expect(await marketplace.balanceOf(host.address)).to.equal(
|
||||
expectedBalance
|
||||
)
|
||||
const expectedBalance = (request.ask.collateral * (100 - slashPercentage)) / 100
|
||||
|
||||
expect(BigNumber.from(expectedBalance).eq(await marketplace.getSlotCollateral(id)))
|
||||
})
|
||||
})
|
||||
|
||||
it("frees slot when collateral slashed below minimum threshold", async function () {
|
||||
const minimum = config.collateral.minimumAmount
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
while ((await marketplace.slotState(slotId(slot))) === SlotState.Filled) {
|
||||
expect(await marketplace.balanceOf(host.address)).to.be.gt(minimum)
|
||||
expect(await marketplace.getSlotCollateral(slotId(slot))).to.be.gt(minimum)
|
||||
await waitUntilProofIsRequired(slotId(slot))
|
||||
const missedPeriod = periodOf(await currentTime())
|
||||
await advanceTime(period)
|
||||
await marketplace.markProofAsMissing(slotId(slot), missedPeriod)
|
||||
}
|
||||
expect(await marketplace.slotState(slotId(slot))).to.equal(SlotState.Free)
|
||||
expect(await marketplace.balanceOf(host.address)).to.be.lte(minimum)
|
||||
expect(await marketplace.getSlotCollateral(slotId(slot))).to.be.lte(minimum)
|
||||
})
|
||||
})
|
||||
|
||||
describe("list of active requests", function () {
|
||||
beforeEach(async function () {
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
switchAccount(client)
|
||||
await token.approve(marketplace.address, price(request))
|
||||
})
|
||||
|
@ -824,7 +808,7 @@ describe("Marketplace", function () {
|
|||
it("keeps request in list when request fails", async function () {
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFailed(marketplace, request)
|
||||
switchAccount(client)
|
||||
expect(await marketplace.myRequests()).to.deep.equal([requestId(request)])
|
||||
|
@ -833,7 +817,7 @@ describe("Marketplace", function () {
|
|||
it("removes request from list when request finishes", async function () {
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
switchAccount(client)
|
||||
|
@ -847,13 +831,13 @@ describe("Marketplace", function () {
|
|||
await token.approve(marketplace.address, price(request))
|
||||
await marketplace.requestStorage(request)
|
||||
switchAccount(host)
|
||||
await token.approve(marketplace.address, config.collateral.initialAmount)
|
||||
await marketplace.deposit(config.collateral.initialAmount)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
})
|
||||
|
||||
it("adds slot to list when filling slot", async function () {
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
let slot1 = { ...slot, index: slot.index + 1 }
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
await marketplace.fillSlot(slot.request, slot1.index, proof)
|
||||
expect(await marketplace.mySlots()).to.have.members([
|
||||
slotId(slot),
|
||||
|
@ -864,7 +848,9 @@ describe("Marketplace", function () {
|
|||
it("removes slot from list when slot is freed", async function () {
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
let slot1 = { ...slot, index: slot.index + 1 }
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
await marketplace.fillSlot(slot.request, slot1.index, proof)
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
expect(await marketplace.mySlots()).to.have.members([slotId(slot1)])
|
||||
})
|
||||
|
@ -872,6 +858,8 @@ describe("Marketplace", function () {
|
|||
it("keeps slots when cancelled", async function () {
|
||||
await marketplace.fillSlot(slot.request, slot.index, proof)
|
||||
let slot1 = { ...slot, index: slot.index + 1 }
|
||||
|
||||
await token.approve(marketplace.address, request.ask.collateral)
|
||||
await marketplace.fillSlot(slot.request, slot1.index, proof)
|
||||
await waitUntilCancelled(request)
|
||||
expect(await marketplace.mySlots()).to.have.members([
|
||||
|
@ -881,7 +869,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("removes slot when finished slot is freed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilFinished(marketplace, requestId(request))
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
expect(await marketplace.mySlots()).to.not.contain(slotId(slot))
|
||||
|
@ -895,7 +883,7 @@ describe("Marketplace", function () {
|
|||
})
|
||||
|
||||
it("removes slot when failed slot is freed", async function () {
|
||||
await waitUntilStarted(marketplace, request, proof)
|
||||
await waitUntilStarted(marketplace, request, proof, token)
|
||||
await waitUntilSlotFailed(marketplace, request, slot)
|
||||
await marketplace.freeSlot(slotId(slot))
|
||||
expect(await marketplace.mySlots()).to.not.contain(slotId(slot))
|
||||
|
|
|
@ -5,7 +5,6 @@ const { hexlify, randomBytes } = ethers.utils
|
|||
|
||||
const exampleConfiguration = () => ({
|
||||
collateral: {
|
||||
initialAmount: 100,
|
||||
minimumAmount: 40,
|
||||
slashCriterion: 3,
|
||||
slashPercentage: 10,
|
||||
|
@ -28,6 +27,7 @@ const exampleRequest = async () => {
|
|||
proofProbability: 4, // require a proof roughly once every 4 periods
|
||||
reward: 84,
|
||||
maxSlotLoss: 2,
|
||||
collateral: 200,
|
||||
},
|
||||
content: {
|
||||
cid: "zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob",
|
||||
|
|
|
@ -2,7 +2,7 @@ const { ethers } = require("hardhat")
|
|||
const { keccak256, defaultAbiCoder } = ethers.utils
|
||||
|
||||
function requestId(request) {
|
||||
const Ask = "tuple(int64, uint256, uint256, uint256, uint256, int64)"
|
||||
const Ask = "tuple(int64, uint256, uint256, uint256, uint256, uint256, int64)"
|
||||
const Erasure = "tuple(uint64)"
|
||||
const PoR = "tuple(bytes, bytes, bytes)"
|
||||
const Content = "tuple(string, " + Erasure + ", " + PoR + ")"
|
||||
|
@ -18,6 +18,7 @@ function askToArray(ask) {
|
|||
ask.duration,
|
||||
ask.proofProbability,
|
||||
ask.reward,
|
||||
ask.collateral,
|
||||
ask.maxSlotLoss,
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
const { advanceTimeTo } = require("./evm")
|
||||
const { slotId, requestId } = require("./ids")
|
||||
const {price} = require("./price");
|
||||
|
||||
async function waitUntilCancelled(request) {
|
||||
await advanceTimeTo(request.expiry + 1)
|
||||
}
|
||||
|
||||
async function waitUntilStarted(contract, request, proof) {
|
||||
async function waitUntilStarted(contract, request, proof, token) {
|
||||
await token.approve(contract.address, price(request)*request.ask.slots)
|
||||
|
||||
for (let i = 0; i < request.ask.slots; i++) {
|
||||
await contract.fillSlot(requestId(request), i, proof)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue