diff --git a/contracts/AccountLocks.sol b/contracts/AccountLocks.sol index 65fecab..5c9ed72 100644 --- a/contracts/AccountLocks.sol +++ b/contracts/AccountLocks.sol @@ -64,6 +64,25 @@ contract AccountLocks { require(accountLocks.length == 0, "Account locked"); } + /// Removes an account lock + function _removeAccountLock(address account, bytes32 lockId) internal { + require(accounts[account].locks.length > 0, "Account lock doesn't exist"); + bytes32[] storage accountLocks = accounts[account].locks; + uint256 index = 0; + while (true) { + if (index >= accountLocks.length) { + return; + } + if (accountLocks[index] == lockId) { + accountLocks[index] = accountLocks[accountLocks.length - 1]; + accountLocks.pop(); + } else { + index++; + } + } + + } + function removeInactiveLocks(bytes32[] storage lockIds) private { uint256 index = 0; while (true) { diff --git a/contracts/Collateral.sol b/contracts/Collateral.sol index efca0da..015af31 100644 --- a/contracts/Collateral.sol +++ b/contracts/Collateral.sol @@ -51,6 +51,12 @@ contract Collateral is AccountLocks { internal collateralInvariant { + // TODO: perhaps we need to add a minCollateral parameter so that + // a host's collateral can't drop below a certain amount, possibly + // preventing malicious behaviour when collateral drops too low for it + // to matter that it will be lost. Also, we need collateral to be high + // enough to cover repair costs in case of repair as well as marked + // proofs as missing fees. uint256 amount = (balanceOf(account) * percentage) / 100; funds.slashed += amount; subtract(account, amount); diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index 4cc1454..a911383 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -47,13 +47,36 @@ contract Marketplace is Collateral, Proofs { emit StorageRequested(id, request.ask); } + function _freeSlot( + bytes32 slotId + ) internal marketplaceInvariant { + bytes32 requestId = _getRequestIdForSlot(slotId); + RequestContext storage context = requestContexts[requestId]; + require(context.state == RequestState.Started, "Invalid state"); + require(!_isCancelled(requestId), "Request cancelled"); + + Slot storage slot = _slot(slotId); + require(slot.host != address(0), "Slot not filled"); + + _removeAccountLock(slot.host, requestId); + // TODO: burn host's collateral except for repair costs + mark proof + // missing reward + + _unexpectProofs(slotId); + + slot.host = address(0); + slot.requestId = 0; + context.slotsFilled -= 1; + emit SlotFreed(requestId, slotId); + } + function fillSlot( bytes32 requestId, uint256 slotIndex, bytes calldata proof ) public marketplaceInvariant { - Request storage request = requests[requestId]; - require(request.client != address(0), "Unknown request"); + Request storage request = _request(requestId); + // TODO: change below to check !_isCancelled(requestId) instead? require(request.expiry > block.timestamp, "Request expired"); require(slotIndex < request.ask.slots, "Invalid slot"); RequestContext storage context = requestContexts[requestId]; @@ -160,11 +183,13 @@ contract Marketplace is Collateral, Proofs { } function _request(bytes32 id) internal view returns (Request storage) { + Request storage request = requests[id]; + require(request.client != address(0), "Unknown request"); return requests[id]; } - function _slot(bytes32 slotId) internal view returns (Slot memory) { - Slot memory slot = slots[slotId]; + function _slot(bytes32 slotId) internal view returns (Slot storage) { + Slot storage slot = slots[slotId]; require(slot.host != address(0), "Slot empty"); return slot; } @@ -292,8 +317,9 @@ contract Marketplace is Collateral, Proofs { event SlotFilled( bytes32 indexed requestId, uint256 indexed slotIndex, - bytes32 indexed slotId + bytes32 slotId ); + event SlotFreed(bytes32 indexed requestId, bytes32 slotId); event RequestCancelled(bytes32 indexed requestId); modifier marketplaceInvariant() { diff --git a/contracts/Proofs.sol b/contracts/Proofs.sol index 9aa1f70..822c1d2 100644 --- a/contracts/Proofs.sol +++ b/contracts/Proofs.sol @@ -63,6 +63,13 @@ contract Proofs { markers[id] = uint256(blockhash(block.number - 1)) % period; } + function _unexpectProofs( + bytes32 id + ) internal { + require(ids[id], "Proof id not in use"); + ids[id] = false; + } + function _getPointer(bytes32 id, uint256 proofPeriod) internal view @@ -111,7 +118,8 @@ contract Proofs { pointer = _getPointer(id, proofPeriod); bytes32 challenge = _getChallenge(pointer); uint256 probability = (probabilities[id] * (256 - downtime)) / 256; - isRequired = uint256(challenge) % probability == 0; + // TODO: add test for below change + isRequired = ids[id] && uint256(challenge) % probability == 0; } function _isProofRequired(bytes32 id, uint256 proofPeriod) diff --git a/contracts/Storage.sol b/contracts/Storage.sol index 2c0ea79..7070c61 100644 --- a/contracts/Storage.sol +++ b/contracts/Storage.sol @@ -9,6 +9,7 @@ contract Storage is Collateral, Marketplace { uint256 public collateralAmount; uint256 public slashMisses; uint256 public slashPercentage; + uint256 public missThreshold; constructor( IERC20 token, @@ -17,7 +18,8 @@ contract Storage is Collateral, Marketplace { uint8 _proofDowntime, uint256 _collateralAmount, uint256 _slashMisses, - uint256 _slashPercentage + uint256 _slashPercentage, + uint256 _missThreshold ) Marketplace( token, @@ -30,6 +32,7 @@ contract Storage is Collateral, Marketplace { collateralAmount = _collateralAmount; slashMisses = _slashMisses; slashPercentage = _slashPercentage; + missThreshold = _missThreshold; } function getRequest(bytes32 requestId) public view returns (Request memory) { @@ -82,8 +85,12 @@ contract Storage is Collateral, Marketplace { slotMustAcceptProofs(slotId) { _markProofAsMissing(slotId, period); - if (_missed(slotId) % slashMisses == 0) { + uint256 missed = _missed(slotId); + if (missed % slashMisses == 0) { _slash(_host(slotId), slashPercentage); } + if (missed > missThreshold) { + _freeSlot(slotId); + } } } diff --git a/deploy/storage.js b/deploy/storage.js index 0fd684c..6fe6fbd 100644 --- a/deploy/storage.js +++ b/deploy/storage.js @@ -6,6 +6,7 @@ async function deployStorage({ deployments, getNamedAccounts }) { const collateralAmount = 100 const slashMisses = 3 const slashPercentage = 10 + const missThreshold = 20 const args = [ token.address, proofPeriod, @@ -14,6 +15,7 @@ async function deployStorage({ deployments, getNamedAccounts }) { collateralAmount, slashMisses, slashPercentage, + missThreshold, ] const { deployer } = await getNamedAccounts() await deployments.deploy("Storage", { args, from: deployer })