Format using prettier

This commit is contained in:
Mark Spanbroek 2022-02-09 14:17:23 +01:00 committed by markspanbroek
parent 41fd33ac7a
commit 78755ecaa2
19 changed files with 462 additions and 358 deletions

16
.prettierrc Normal file
View File

@ -0,0 +1,16 @@
{
"overrides": [
{
"files": "*.js",
"options": {
"semi": false
}
},
{
"files": "*.sol",
"options": {
"tabWidth": 2
}
}
]
}

View File

@ -4,21 +4,20 @@ pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract Contracts { contract Contracts {
mapping(bytes32 => bool) private ids; // contract id, equal to hash of bid mapping(bytes32 => bool) private ids; // contract id, equal to hash of bid
mapping(bytes32=>uint) private durations; // contract duration in blocks mapping(bytes32 => uint256) private durations; // contract duration in blocks
mapping(bytes32=>uint) private sizes; // storage size in bytes mapping(bytes32 => uint256) private sizes; // storage size in bytes
mapping(bytes32 => bytes32) private contentHashes; // hash of data to be stored mapping(bytes32 => bytes32) private contentHashes; // hash of data to be stored
mapping(bytes32=>uint) private proofPeriods; // period between proofs mapping(bytes32 => uint256) private proofPeriods; // period between proofs
mapping(bytes32=>uint) private proofTimeouts; // timeout for proof submission mapping(bytes32 => uint256) private proofTimeouts; // timeout for proof submission
mapping(bytes32=>uint) private prices; // price in coins mapping(bytes32 => uint256) private prices; // price in coins
mapping(bytes32 => address) private hosts; // host that provides storage mapping(bytes32 => address) private hosts; // host that provides storage
function _duration(bytes32 id) internal view returns (uint) { function _duration(bytes32 id) internal view returns (uint256) {
return durations[id]; return durations[id];
} }
function _size(bytes32 id) internal view returns (uint) { function _size(bytes32 id) internal view returns (uint256) {
return sizes[id]; return sizes[id];
} }
@ -26,15 +25,15 @@ contract Contracts {
return contentHashes[id]; return contentHashes[id];
} }
function _proofPeriod(bytes32 id) internal view returns (uint) { function _proofPeriod(bytes32 id) internal view returns (uint256) {
return proofPeriods[id]; return proofPeriods[id];
} }
function _proofTimeout(bytes32 id) internal view returns (uint) { function _proofTimeout(bytes32 id) internal view returns (uint256) {
return proofTimeouts[id]; return proofTimeouts[id];
} }
function _price(bytes32 id) internal view returns (uint) { function _price(bytes32 id) internal view returns (uint256) {
return prices[id]; return prices[id];
} }
@ -43,21 +42,18 @@ contract Contracts {
} }
function _newContract( function _newContract(
uint duration, uint256 duration,
uint size, uint256 size,
bytes32 contentHash, bytes32 contentHash,
uint proofPeriod, uint256 proofPeriod,
uint proofTimeout, uint256 proofTimeout,
bytes32 nonce, bytes32 nonce,
uint price, uint256 price,
address host, address host,
uint bidExpiry, uint256 bidExpiry,
bytes memory requestSignature, bytes memory requestSignature,
bytes memory bidSignature bytes memory bidSignature
) ) internal returns (bytes32 id) {
internal
returns (bytes32 id)
{
bytes32 requestHash = _hashRequest( bytes32 requestHash = _hashRequest(
duration, duration,
size, size,
@ -84,17 +80,16 @@ contract Contracts {
// Creates hash for a storage request that can be used to check its signature. // Creates hash for a storage request that can be used to check its signature.
function _hashRequest( function _hashRequest(
uint duration, uint256 duration,
uint size, uint256 size,
bytes32 hash, bytes32 hash,
uint proofPeriod, uint256 proofPeriod,
uint proofTimeout, uint256 proofTimeout,
bytes32 nonce bytes32 nonce
) ) private pure returns (bytes32) {
private pure return
returns (bytes32) keccak256(
{ abi.encode(
return keccak256(abi.encode(
"[dagger.request.v1]", "[dagger.request.v1]",
duration, duration,
size, size,
@ -102,39 +97,35 @@ contract Contracts {
proofPeriod, proofPeriod,
proofTimeout, proofTimeout,
nonce nonce
)); )
);
} }
// Creates hash for a storage bid that can be used to check its signature. // Creates hash for a storage bid that can be used to check its signature.
function _hashBid(bytes32 requestHash, uint expiry, uint price) function _hashBid(
private pure bytes32 requestHash,
returns (bytes32) uint256 expiry,
{ uint256 price
return keccak256(abi.encode( ) private pure returns (bytes32) {
"[dagger.bid.v1]", return keccak256(abi.encode("[dagger.bid.v1]", requestHash, expiry, price));
requestHash,
expiry,
price
));
} }
// Checks a signature for a storage request or bid, given its hash. // Checks a signature for a storage request or bid, given its hash.
function _checkSignature(bytes memory signature, bytes32 hash, address signer) function _checkSignature(
private pure bytes memory signature,
{ bytes32 hash,
address signer
) private pure {
bytes32 messageHash = ECDSA.toEthSignedMessageHash(hash); bytes32 messageHash = ECDSA.toEthSignedMessageHash(hash);
address recovered = ECDSA.recover(messageHash, signature); address recovered = ECDSA.recover(messageHash, signature);
require(recovered == signer, "Invalid signature"); require(recovered == signer, "Invalid signature");
} }
function _checkBidExpiry(uint expiry) private view { function _checkBidExpiry(uint256 expiry) private view {
require(expiry > block.timestamp, "Bid expired"); require(expiry > block.timestamp, "Bid expired");
} }
function _checkId(bytes32 id) private view { function _checkId(bytes32 id) private view {
require( require(!ids[id], "A contract with this id already exists");
!ids[id],
"A contract with this id already exists"
);
} }
} }

View File

@ -2,45 +2,44 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
contract Proofs { contract Proofs {
mapping(bytes32 => bool) private ids; mapping(bytes32 => bool) private ids;
mapping(bytes32=>uint) private periods; mapping(bytes32 => uint256) private periods;
mapping(bytes32=>uint) private timeouts; mapping(bytes32 => uint256) private timeouts;
mapping(bytes32=>uint) private starts; mapping(bytes32 => uint256) private starts;
mapping(bytes32=>uint) private ends; mapping(bytes32 => uint256) private ends;
mapping(bytes32=>uint) private markers; mapping(bytes32 => uint256) private markers;
mapping(bytes32=>uint) private missed; mapping(bytes32 => uint256) private missed;
mapping(bytes32=>mapping(uint=>bool)) private received; mapping(bytes32 => mapping(uint256 => bool)) private received;
mapping(bytes32=>mapping(uint=>bool)) private missing; mapping(bytes32 => mapping(uint256 => bool)) private missing;
function _period(bytes32 id) internal view returns (uint) { function _period(bytes32 id) internal view returns (uint256) {
return periods[id]; return periods[id];
} }
function _timeout(bytes32 id) internal view returns (uint) { function _timeout(bytes32 id) internal view returns (uint256) {
return timeouts[id]; return timeouts[id];
} }
function _end(bytes32 id) internal view returns (uint) { function _end(bytes32 id) internal view returns (uint256) {
return ends[id]; return ends[id];
} }
function _missed(bytes32 id) internal view returns (uint) { function _missed(bytes32 id) internal view returns (uint256) {
return missed[id]; return missed[id];
} }
// Checks that proof timeout is <= 128. Only the latest 256 blocks can be // Checks that proof timeout is <= 128. Only the latest 256 blocks can be
// checked in a smart contract, so that leaves a period of at least 128 blocks // checked in a smart contract, so that leaves a period of at least 128 blocks
// after timeout for a validator to signal the absence of a proof. // after timeout for a validator to signal the absence of a proof.
function _checkTimeout(uint timeout) private pure { function _checkTimeout(uint256 timeout) private pure {
require(timeout <= 128, "Invalid proof timeout, needs to be <= 128"); require(timeout <= 128, "Invalid proof timeout, needs to be <= 128");
} }
function _expectProofs( function _expectProofs(
bytes32 id, bytes32 id,
uint period, uint256 period,
uint timeout, uint256 timeout,
uint duration uint256 duration
) internal { ) internal {
require(!ids[id], "Proof id already in use"); require(!ids[id], "Proof id already in use");
_checkTimeout(timeout); _checkTimeout(timeout);
@ -49,32 +48,28 @@ contract Proofs {
timeouts[id] = timeout; timeouts[id] = timeout;
starts[id] = block.number; starts[id] = block.number;
ends[id] = block.number + duration + 2 * timeout; ends[id] = block.number + duration + 2 * timeout;
markers[id] = uint(blockhash(block.number - 1)) % period; markers[id] = uint256(blockhash(block.number - 1)) % period;
} }
// Check whether a proof is required at the time of the block with the // Check whether a proof is required at the time of the block with the
// specified block number. A proof has to be submitted within the proof // specified block number. A proof has to be submitted within the proof
// timeout for it to be valid. Whether a proof is required is determined // timeout for it to be valid. Whether a proof is required is determined
// randomly, but on average it is once every proof period. // randomly, but on average it is once every proof period.
function _isProofRequired( function _isProofRequired(bytes32 id, uint256 blocknumber)
bytes32 id, internal
uint blocknumber view
)
internal view
returns (bool) returns (bool)
{ {
if (blocknumber < starts[id] || blocknumber >= ends[id]) { if (blocknumber < starts[id] || blocknumber >= ends[id]) {
return false; return false;
} }
bytes32 hash = blockhash(blocknumber - 1); bytes32 hash = blockhash(blocknumber - 1);
return hash != 0 && uint(hash) % periods[id] == markers[id]; return hash != 0 && uint256(hash) % periods[id] == markers[id];
} }
function _isProofTimedOut( function _isProofTimedOut(bytes32 id, uint256 blocknumber)
bytes32 id, internal
uint blocknumber view
)
internal view
returns (bool) returns (bool)
{ {
return block.number >= blocknumber + timeouts[id]; return block.number >= blocknumber + timeouts[id];
@ -82,11 +77,9 @@ contract Proofs {
function _submitProof( function _submitProof(
bytes32 id, bytes32 id,
uint blocknumber, uint256 blocknumber,
bool proof bool proof
) ) internal {
internal
{
require(proof, "Invalid proof"); // TODO: replace bool by actual proof require(proof, "Invalid proof"); // TODO: replace bool by actual proof
require( require(
_isProofRequired(id, blocknumber), _isProofRequired(id, blocknumber),
@ -100,19 +93,10 @@ contract Proofs {
received[id][blocknumber] = true; received[id][blocknumber] = true;
} }
function _markProofAsMissing(bytes32 id, uint blocknumber) internal { function _markProofAsMissing(bytes32 id, uint256 blocknumber) internal {
require( require(_isProofTimedOut(id, blocknumber), "Proof has not timed out yet");
_isProofTimedOut(id, blocknumber), require(!received[id][blocknumber], "Proof was submitted, not missing");
"Proof has not timed out yet" require(_isProofRequired(id, blocknumber), "Proof was not required");
);
require(
!received[id][blocknumber],
"Proof was submitted, not missing"
);
require(
_isProofRequired(id, blocknumber),
"Proof was not required"
);
require(!missing[id][blocknumber], "Proof already marked as missing"); require(!missing[id][blocknumber], "Proof already marked as missing");
missing[id][blocknumber] = true; missing[id][blocknumber] = true;
missed[id] += 1; missed[id] += 1;

View File

@ -4,10 +4,9 @@ pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Stakes { contract Stakes {
IERC20 private token; IERC20 private token;
mapping(address=>uint) private stakes; mapping(address => uint256) private stakes;
mapping(address=>uint) private locks; mapping(address => uint256) private locks;
constructor(IERC20 __token) { constructor(IERC20 __token) {
token = __token; token = __token;
@ -17,11 +16,11 @@ contract Stakes {
return token; return token;
} }
function _stake(address account) internal view returns (uint) { function _stake(address account) internal view returns (uint256) {
return stakes[account]; return stakes[account];
} }
function _increaseStake(uint amount) internal { function _increaseStake(uint256 amount) internal {
token.transferFrom(msg.sender, address(this), amount); token.transferFrom(msg.sender, address(this), amount);
stakes[msg.sender] += amount; stakes[msg.sender] += amount;
} }
@ -40,7 +39,7 @@ contract Stakes {
locks[account] -= 1; locks[account] -= 1;
} }
function _slash(address account, uint percentage) internal { function _slash(address account, uint256 percentage) internal {
stakes[account] = stakes[account] * (100 - percentage) / 100; stakes[account] = (stakes[account] * (100 - percentage)) / 100;
} }
} }

View File

@ -6,41 +6,36 @@ import "./Proofs.sol";
import "./Stakes.sol"; import "./Stakes.sol";
contract Storage is Contracts, Proofs, Stakes { contract Storage is Contracts, Proofs, Stakes {
uint256 public stakeAmount;
uint public stakeAmount; uint256 public slashMisses;
uint public slashMisses; uint256 public slashPercentage;
uint public slashPercentage;
mapping(bytes32 => bool) private finished; mapping(bytes32 => bool) private finished;
constructor( constructor(
IERC20 token, IERC20 token,
uint _stakeAmount, uint256 _stakeAmount,
uint _slashMisses, uint256 _slashMisses,
uint _slashPercentage uint256 _slashPercentage
) ) Stakes(token) {
Stakes(token)
{
stakeAmount = _stakeAmount; stakeAmount = _stakeAmount;
slashMisses = _slashMisses; slashMisses = _slashMisses;
slashPercentage = _slashPercentage; slashPercentage = _slashPercentage;
} }
function newContract( function newContract(
uint _duration, uint256 _duration,
uint _size, uint256 _size,
bytes32 _contentHash, bytes32 _contentHash,
uint _proofPeriod, uint256 _proofPeriod,
uint _proofTimeout, uint256 _proofTimeout,
bytes32 _nonce, bytes32 _nonce,
uint _price, uint256 _price,
address _host, address _host,
uint _bidExpiry, uint256 _bidExpiry,
bytes memory requestSignature, bytes memory requestSignature,
bytes memory bidSignature bytes memory bidSignature
) ) public {
public
{
require(_stake(_host) >= stakeAmount, "Insufficient stake"); require(_stake(_host) >= stakeAmount, "Insufficient stake");
_lockStake(_host); _lockStake(_host);
_token().transferFrom(msg.sender, address(this), _price); _token().transferFrom(msg.sender, address(this), _price);
@ -76,11 +71,11 @@ contract Storage is Contracts, Proofs, Stakes {
finished[id] = true; finished[id] = true;
} }
function duration(bytes32 contractId) public view returns (uint) { function duration(bytes32 contractId) public view returns (uint256) {
return _duration(contractId); return _duration(contractId);
} }
function size(bytes32 contractId) public view returns (uint) { function size(bytes32 contractId) public view returns (uint256) {
return _size(contractId); return _size(contractId);
} }
@ -88,7 +83,7 @@ contract Storage is Contracts, Proofs, Stakes {
return _contentHash(contractId); return _contentHash(contractId);
} }
function price(bytes32 contractId) public view returns (uint) { function price(bytes32 contractId) public view returns (uint256) {
return _price(contractId); return _price(contractId);
} }
@ -96,41 +91,37 @@ contract Storage is Contracts, Proofs, Stakes {
return _host(contractId); return _host(contractId);
} }
function proofPeriod(bytes32 contractId) public view returns (uint) { function proofPeriod(bytes32 contractId) public view returns (uint256) {
return _proofPeriod(contractId); return _proofPeriod(contractId);
} }
function proofTimeout(bytes32 contractId) public view returns (uint) { function proofTimeout(bytes32 contractId) public view returns (uint256) {
return _proofTimeout(contractId); return _proofTimeout(contractId);
} }
function proofEnd(bytes32 contractId) public view returns (uint) { function proofEnd(bytes32 contractId) public view returns (uint256) {
return _end(contractId); return _end(contractId);
} }
function missingProofs(bytes32 contractId) public view returns (uint) { function missingProofs(bytes32 contractId) public view returns (uint256) {
return _missed(contractId); return _missed(contractId);
} }
function stake(address account) public view returns (uint) { function stake(address account) public view returns (uint256) {
return _stake(account); return _stake(account);
} }
function isProofRequired( function isProofRequired(bytes32 contractId, uint256 blocknumber)
bytes32 contractId, public
uint blocknumber view
)
public view
returns (bool) returns (bool)
{ {
return _isProofRequired(contractId, blocknumber); return _isProofRequired(contractId, blocknumber);
} }
function isProofTimedOut( function isProofTimedOut(bytes32 contractId, uint256 blocknumber)
bytes32 contractId, public
uint blocknumber view
)
public view
returns (bool) returns (bool)
{ {
return _isProofTimedOut(contractId, blocknumber); return _isProofTimedOut(contractId, blocknumber);
@ -138,22 +129,20 @@ contract Storage is Contracts, Proofs, Stakes {
function submitProof( function submitProof(
bytes32 contractId, bytes32 contractId,
uint blocknumber, uint256 blocknumber,
bool proof bool proof
) ) public {
public
{
_submitProof(contractId, blocknumber, proof); _submitProof(contractId, blocknumber, proof);
} }
function markProofAsMissing(bytes32 contractId, uint blocknumber) public { function markProofAsMissing(bytes32 contractId, uint256 blocknumber) public {
_markProofAsMissing(contractId, blocknumber); _markProofAsMissing(contractId, blocknumber);
if (_missed(contractId) % slashMisses == 0) { if (_missed(contractId) % slashMisses == 0) {
_slash(host(contractId), slashPercentage); _slash(host(contractId), slashPercentage);
} }
} }
function increaseStake(uint amount) public { function increaseStake(uint256 amount) public {
_increaseStake(amount); _increaseStake(amount);
} }

View File

@ -5,22 +5,19 @@ import "./Contracts.sol";
// exposes internal functions of Contracts for testing // exposes internal functions of Contracts for testing
contract TestContracts is Contracts { contract TestContracts is Contracts {
function newContract( function newContract(
uint _duration, uint256 _duration,
uint _size, uint256 _size,
bytes32 _contentHash, bytes32 _contentHash,
uint _proofPeriod, uint256 _proofPeriod,
uint _proofTimeout, uint256 _proofTimeout,
bytes32 _nonce, bytes32 _nonce,
uint _price, uint256 _price,
address _host, address _host,
uint _bidExpiry, uint256 _bidExpiry,
bytes memory requestSignature, bytes memory requestSignature,
bytes memory bidSignature bytes memory bidSignature
) ) public {
public
{
_newContract( _newContract(
_duration, _duration,
_size, _size,
@ -32,14 +29,15 @@ contract TestContracts is Contracts {
_host, _host,
_bidExpiry, _bidExpiry,
requestSignature, requestSignature,
bidSignature); bidSignature
);
} }
function duration(bytes32 id) public view returns (uint) { function duration(bytes32 id) public view returns (uint256) {
return _duration(id); return _duration(id);
} }
function size(bytes32 id) public view returns (uint) { function size(bytes32 id) public view returns (uint256) {
return _size(id); return _size(id);
} }
@ -47,7 +45,7 @@ contract TestContracts is Contracts {
return _contentHash(id); return _contentHash(id);
} }
function price(bytes32 id) public view returns (uint) { function price(bytes32 id) public view returns (uint256) {
return _price(id); return _price(id);
} }

View File

@ -5,37 +5,34 @@ import "./Proofs.sol";
// exposes internal functions of Proofs for testing // exposes internal functions of Proofs for testing
contract TestProofs is Proofs { contract TestProofs is Proofs {
function period(bytes32 id) public view returns (uint256) {
function period(bytes32 id) public view returns (uint) {
return _period(id); return _period(id);
} }
function timeout(bytes32 id) public view returns (uint) { function timeout(bytes32 id) public view returns (uint256) {
return _timeout(id); return _timeout(id);
} }
function end(bytes32 id) public view returns (uint) { function end(bytes32 id) public view returns (uint256) {
return _end(id); return _end(id);
} }
function missed(bytes32 id) public view returns (uint) { function missed(bytes32 id) public view returns (uint256) {
return _missed(id); return _missed(id);
} }
function expectProofs( function expectProofs(
bytes32 id, bytes32 id,
uint _period, uint256 _period,
uint _timeout, uint256 _timeout,
uint _duration uint256 _duration
) public { ) public {
_expectProofs(id, _period, _timeout, _duration); _expectProofs(id, _period, _timeout, _duration);
} }
function isProofRequired( function isProofRequired(bytes32 id, uint256 blocknumber)
bytes32 id, public
uint blocknumber view
)
public view
returns (bool) returns (bool)
{ {
return _isProofRequired(id, blocknumber); return _isProofRequired(id, blocknumber);
@ -43,15 +40,13 @@ contract TestProofs is Proofs {
function submitProof( function submitProof(
bytes32 id, bytes32 id,
uint blocknumber, uint256 blocknumber,
bool proof bool proof
) ) public {
public
{
_submitProof(id, blocknumber, proof); _submitProof(id, blocknumber, proof);
} }
function markProofAsMissing(bytes32 id, uint blocknumber) public { function markProofAsMissing(bytes32 id, uint256 blocknumber) public {
_markProofAsMissing(id, blocknumber); _markProofAsMissing(id, blocknumber);
} }
} }

View File

@ -5,14 +5,13 @@ import "./Stakes.sol";
// exposes internal functions of Stakes for testing // exposes internal functions of Stakes for testing
contract TestStakes is Stakes { contract TestStakes is Stakes {
constructor(IERC20 token) Stakes(token) {} constructor(IERC20 token) Stakes(token) {}
function stake(address account) public view returns (uint) { function stake(address account) public view returns (uint256) {
return _stake(account); return _stake(account);
} }
function increaseStake(uint amount) public { function increaseStake(uint256 amount) public {
_increaseStake(amount); _increaseStake(amount);
} }
@ -28,7 +27,7 @@ contract TestStakes is Stakes {
_unlockStake(account); _unlockStake(account);
} }
function slash(address account, uint percentage) public { function slash(address account, uint256 percentage) public {
_slash(account, percentage); _slash(account, percentage);
} }
} }

View File

@ -6,7 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TestToken is ERC20 { contract TestToken is ERC20 {
constructor() ERC20("TestToken", "TST") {} constructor() ERC20("TestToken", "TST") {}
function mint(address holder, uint amount) public { function mint(address holder, uint256 amount) public {
_mint(holder, amount); _mint(holder, amount);
} }
} }

View File

@ -1,12 +1,12 @@
module.exports = async ({ deployments, getNamedAccounts }) => { module.exports = async ({ deployments, getNamedAccounts }) => {
const token = await deployments.get('TestToken') const token = await deployments.get("TestToken")
const stakeAmount = 100 const stakeAmount = 100
const slashMisses = 3 const slashMisses = 3
const slashPercentage = 10 const slashPercentage = 10
const args = [token.address, stakeAmount, slashMisses, slashPercentage] const args = [token.address, stakeAmount, slashMisses, slashPercentage]
const { deployer } = await getNamedAccounts() const { deployer } = await getNamedAccounts()
await deployments.deploy('Storage', { args, from: deployer }) await deployments.deploy("Storage", { args, from: deployer })
} }
module.exports.tags = ['Storage'] module.exports.tags = ["Storage"]
module.exports.dependencies = ['TestToken'] module.exports.dependencies = ["TestToken"]

View File

@ -1,6 +1,6 @@
module.exports = async ({ deployments, getNamedAccounts }) => { module.exports = async ({ deployments, getNamedAccounts }) => {
const { deployer } = await getNamedAccounts() const { deployer } = await getNamedAccounts()
await deployments.deploy('TestToken', { from: deployer }) await deployments.deploy("TestToken", { from: deployer })
} }
module.exports.tags = ['TestToken'] module.exports.tags = ["TestToken"]

119
package-lock.json generated
View File

@ -1338,6 +1338,12 @@
"color-convert": "^1.9.0" "color-convert": "^1.9.0"
} }
}, },
"antlr4ts": {
"version": "0.5.0-alpha.4",
"resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz",
"integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==",
"dev": true
},
"anymatch": { "anymatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
@ -12982,11 +12988,112 @@
"dev": true "dev": true
}, },
"prettier": { "prettier": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
"dev": true "dev": true
}, },
"prettier-plugin-solidity": {
"version": "1.0.0-beta.19",
"resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.19.tgz",
"integrity": "sha512-xxRQ5ZiiZyUoMFLE9h7HnUDXI/daf1tnmL1msEdcKmyh7ZGQ4YklkYLC71bfBpYU2WruTb5/SFLUaEb3RApg5g==",
"dev": true,
"requires": {
"@solidity-parser/parser": "^0.14.0",
"emoji-regex": "^10.0.0",
"escape-string-regexp": "^4.0.0",
"semver": "^7.3.5",
"solidity-comments-extractor": "^0.0.7",
"string-width": "^4.2.3"
},
"dependencies": {
"@solidity-parser/parser": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.0.tgz",
"integrity": "sha512-cX0JJRcmPtNUJpzD2K7FdA7qQsTOk1UZnFx2k7qAg9ZRvuaH5NBe5IEdBMXGlmf2+FmjhqbygJ26H8l2SV7aKQ==",
"dev": true,
"requires": {
"antlr4ts": "^0.5.0-alpha.4"
}
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"emoji-regex": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.0.0.tgz",
"integrity": "sha512-KmJa8l6uHi1HrBI34udwlzZY1jOEuID/ft4d8BSSEdRyap7PwBEt910453PJa5MuGvxkLqlt4Uvhu7tttFHViw==",
"dev": true
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"dependencies": {
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
}
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"printj": { "printj": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
@ -13360,6 +13467,12 @@
} }
} }
}, },
"solidity-comments-extractor": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz",
"integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==",
"dev": true
},
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

View File

@ -3,7 +3,8 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "hardhat test", "test": "hardhat test",
"start": "hardhat node --export 'deployment-localhost.json'" "start": "hardhat node --export 'deployment-localhost.json'",
"format": "prettier --write contracts/**/*.sol test/**/*.js"
}, },
"devDependencies": { "devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
@ -14,6 +15,8 @@
"ethers": "^5.4.7", "ethers": "^5.4.7",
"hardhat": "^2.6.5", "hardhat": "^2.6.5",
"hardhat-deploy": "^0.9.8", "hardhat-deploy": "^0.9.8",
"hardhat-deploy-ethers": "^0.3.0-beta.11" "hardhat-deploy-ethers": "^0.3.0-beta.11",
"prettier": "^2.5.1",
"prettier-plugin-solidity": "^1.0.0-beta.19"
} }
} }

View File

@ -4,7 +4,6 @@ const { hashRequest, hashBid, sign } = require("./marketplace")
const { exampleRequest, exampleBid } = require("./examples") const { exampleRequest, exampleBid } = require("./examples")
describe("Contracts", function () { describe("Contracts", function () {
const request = exampleRequest() const request = exampleRequest()
const bid = exampleBid() const bid = exampleBid()
@ -14,7 +13,7 @@ describe("Contracts", function () {
let id let id
beforeEach(async function () { beforeEach(async function () {
[client, host] = await ethers.getSigners() ;[client, host] = await ethers.getSigners()
let Contracts = await ethers.getContractFactory("TestContracts") let Contracts = await ethers.getContractFactory("TestContracts")
contracts = await Contracts.deploy() contracts = await Contracts.deploy()
requestHash = hashRequest(request) requestHash = hashRequest(request)
@ -57,7 +56,8 @@ describe("Contracts", function () {
await sign(client, requestHash), await sign(client, requestHash),
await sign(host, bidHash) await sign(host, bidHash)
) )
await expect(contracts.newContract( await expect(
contracts.newContract(
request.duration, request.duration,
request.size, request.size,
request.contentHash, request.contentHash,
@ -69,13 +69,18 @@ describe("Contracts", function () {
bid.bidExpiry, bid.bidExpiry,
await sign(client, requestHash), await sign(client, requestHash),
await sign(host, bidHash) await sign(host, bidHash)
)).to.be.revertedWith("A contract with this id already exists") )
).to.be.revertedWith("A contract with this id already exists")
}) })
it("cannot be created when client signature is invalid", async function () { it("cannot be created when client signature is invalid", async function () {
let invalidHash = hashRequest({...request, duration: request.duration + 1}) let invalidHash = hashRequest({
...request,
duration: request.duration + 1,
})
let invalidSignature = await sign(client, invalidHash) let invalidSignature = await sign(client, invalidHash)
await expect(contracts.newContract( await expect(
contracts.newContract(
request.duration, request.duration,
request.size, request.size,
request.contentHash, request.contentHash,
@ -87,13 +92,15 @@ describe("Contracts", function () {
bid.bidExpiry, bid.bidExpiry,
invalidSignature, invalidSignature,
await sign(host, bidHash) await sign(host, bidHash)
)).to.be.revertedWith("Invalid signature") )
).to.be.revertedWith("Invalid signature")
}) })
it("cannot be created when host signature is invalid", async function () { it("cannot be created when host signature is invalid", async function () {
let invalidBid = hashBid({ ...bid, requestHash, price: bid.price - 1 }) let invalidBid = hashBid({ ...bid, requestHash, price: bid.price - 1 })
let invalidSignature = await sign(host, invalidBid) let invalidSignature = await sign(host, invalidBid)
await expect(contracts.newContract( await expect(
contracts.newContract(
request.duration, request.duration,
request.size, request.size,
request.contentHash, request.contentHash,
@ -105,13 +112,15 @@ describe("Contracts", function () {
bid.bidExpiry, bid.bidExpiry,
await sign(client, requestHash), await sign(client, requestHash),
invalidSignature invalidSignature
)).to.be.revertedWith("Invalid signature") )
).to.be.revertedWith("Invalid signature")
}) })
it("cannot be created when bid has expired", async function () { it("cannot be created when bid has expired", async function () {
let expired = Math.round(Date.now() / 1000) - 60 // 1 minute ago let expired = Math.round(Date.now() / 1000) - 60 // 1 minute ago
let bidHash = hashBid({ ...bid, requestHash, bidExpiry: expired }) let bidHash = hashBid({ ...bid, requestHash, bidExpiry: expired })
await expect(contracts.newContract( await expect(
contracts.newContract(
request.duration, request.duration,
request.size, request.size,
request.contentHash, request.contentHash,
@ -122,7 +131,8 @@ describe("Contracts", function () {
await host.getAddress(), await host.getAddress(),
expired, expired,
await sign(client, requestHash), await sign(client, requestHash),
await sign(host, bidHash), await sign(host, bidHash)
)).to.be.revertedWith("Bid expired") )
).to.be.revertedWith("Bid expired")
}) })
}) })

View File

@ -3,7 +3,6 @@ const { ethers } = require("hardhat")
const { mineBlock, minedBlockNumber } = require("./mining") const { mineBlock, minedBlockNumber } = require("./mining")
describe("Proofs", function () { describe("Proofs", function () {
const id = ethers.utils.randomBytes(32) const id = ethers.utils.randomBytes(32)
const period = 10 const period = 10
const timeout = 5 const timeout = 5
@ -69,13 +68,12 @@ describe("Proofs", function () {
}) })
describe("when proofs are required", async function () { describe("when proofs are required", async function () {
beforeEach(async function () { beforeEach(async function () {
await proofs.expectProofs(id, period, timeout, duration) await proofs.expectProofs(id, period, timeout, duration)
}) })
async function mineUntilProofIsRequired(id) { async function mineUntilProofIsRequired(id) {
while (!await proofs.isProofRequired(id, await minedBlockNumber())) { while (!(await proofs.isProofRequired(id, await minedBlockNumber()))) {
mineBlock() mineBlock()
} }
} }
@ -88,7 +86,7 @@ describe("Proofs", function () {
async function mineUntilEnd() { async function mineUntilEnd() {
const end = await proofs.end(id) const end = await proofs.end(id)
while (await minedBlockNumber() < end) { while ((await minedBlockNumber()) < end) {
mineBlock() mineBlock()
} }
} }
@ -96,7 +94,8 @@ describe("Proofs", function () {
it("requires no proof for blocks that are unavailable", async function () { it("requires no proof for blocks that are unavailable", async function () {
await mineUntilProofIsRequired(id) await mineUntilProofIsRequired(id)
let blocknumber = await minedBlockNumber() let blocknumber = await minedBlockNumber()
for (let i=0; i<256; i++) { // only last 256 blocks available in solidity for (let i = 0; i < 256; i++) {
// only last 256 blocks available in solidity
mineBlock() mineBlock()
} }
expect(await proofs.isProofRequired(id, blocknumber)).to.be.false expect(await proofs.isProofRequired(id, blocknumber)).to.be.false

View File

@ -2,13 +2,12 @@ const { expect } = require("chai")
const { ethers } = require("hardhat") const { ethers } = require("hardhat")
describe("Stakes", function () { describe("Stakes", function () {
var stakes var stakes
var token var token
var host var host
beforeEach(async function () { beforeEach(async function () {
[host] = await ethers.getSigners() ;[host] = await ethers.getSigners()
const Stakes = await ethers.getContractFactory("TestStakes") const Stakes = await ethers.getContractFactory("TestStakes")
const TestToken = await ethers.getContractFactory("TestToken") const TestToken = await ethers.getContractFactory("TestToken")
token = await TestToken.deploy() token = await TestToken.deploy()
@ -30,9 +29,9 @@ describe("Stakes", function () {
}) })
it("does not increase stake when token transfer fails", async function () { it("does not increase stake when token transfer fails", async function () {
await expect( await expect(stakes.increaseStake(20)).to.be.revertedWith(
stakes.increaseStake(20) "ERC20: transfer amount exceeds allowance"
).to.be.revertedWith("ERC20: transfer amount exceeds allowance") )
}) })
it("allows withdrawal of stake", async function () { it("allows withdrawal of stake", async function () {
@ -54,9 +53,9 @@ describe("Stakes", function () {
}) })
it("fails to unlock when already unlocked", async function () { it("fails to unlock when already unlocked", async function () {
await expect( await expect(stakes.unlockStake(host.address)).to.be.revertedWith(
stakes.unlockStake(host.address) "Stake already unlocked"
).to.be.revertedWith("Stake already unlocked") )
}) })
it("requires an equal amount of locks and unlocks", async function () { it("requires an equal amount of locks and unlocks", async function () {

View File

@ -5,7 +5,6 @@ const { exampleRequest, exampleBid } = require("./examples")
const { mineBlock, minedBlockNumber } = require("./mining") const { mineBlock, minedBlockNumber } = require("./mining")
describe("Storage", function () { describe("Storage", function () {
const request = exampleRequest() const request = exampleRequest()
const bid = exampleBid() const bid = exampleBid()
@ -15,10 +14,10 @@ describe("Storage", function () {
let stakeAmount, slashMisses, slashPercentage let stakeAmount, slashMisses, slashPercentage
beforeEach(async function () { beforeEach(async function () {
[client, host] = await ethers.getSigners() ;[client, host] = await ethers.getSigners()
await deployments.fixture(['TestToken', 'Storage']) await deployments.fixture(["TestToken", "Storage"])
token = await ethers.getContract('TestToken') token = await ethers.getContract("TestToken")
storage = await ethers.getContract('Storage') storage = await ethers.getContract("Storage")
await token.mint(client.address, 1000) await token.mint(client.address, 1000)
await token.mint(host.address, 1000) await token.mint(host.address, 1000)
stakeAmount = await storage.stakeAmount() stakeAmount = await storage.stakeAmount()
@ -27,7 +26,6 @@ describe("Storage", function () {
}) })
describe("creating a new storage contract", function () { describe("creating a new storage contract", function () {
let id let id
beforeEach(async function () { beforeEach(async function () {
@ -63,9 +61,9 @@ describe("Storage", function () {
}) })
it("locks up host stake", async function () { it("locks up host stake", async function () {
await expect( await expect(storage.connect(host).withdrawStake()).to.be.revertedWith(
storage.connect(host).withdrawStake() "Stake locked"
).to.be.revertedWith("Stake locked") )
}) })
describe("starting the contract", function () { describe("starting the contract", function () {
@ -87,14 +85,13 @@ describe("Storage", function () {
}) })
describe("finishing the contract", function () { describe("finishing the contract", function () {
beforeEach(async function () { beforeEach(async function () {
await storage.connect(host).startContract(id) await storage.connect(host).startContract(id)
}) })
async function mineUntilEnd() { async function mineUntilEnd() {
const end = await storage.proofEnd(id) const end = await storage.proofEnd(id)
while (await minedBlockNumber() < end) { while ((await minedBlockNumber()) < end) {
await mineBlock() await mineBlock()
} }
} }
@ -114,24 +111,23 @@ describe("Storage", function () {
}) })
it("is only allowed when end time has passed", async function () { it("is only allowed when end time has passed", async function () {
await expect( await expect(storage.finishContract(id)).to.be.revertedWith(
storage.finishContract(id) "Contract has not ended yet"
).to.be.revertedWith("Contract has not ended yet") )
}) })
it("can only be done once", async function () { it("can only be done once", async function () {
await mineUntilEnd() await mineUntilEnd()
await storage.finishContract(id) await storage.finishContract(id)
await expect( await expect(storage.finishContract(id)).to.be.revertedWith(
storage.finishContract(id) "Contract already finished"
).to.be.revertedWith("Contract already finished") )
}) })
}) })
describe("slashing when missing proofs", function () { describe("slashing when missing proofs", function () {
async function ensureProofIsMissing() { async function ensureProofIsMissing() {
while (!await storage.isProofRequired(id, await minedBlockNumber())) { while (!(await storage.isProofRequired(id, await minedBlockNumber()))) {
mineBlock() mineBlock()
} }
const blocknumber = await minedBlockNumber() const blocknumber = await minedBlockNumber()
@ -146,7 +142,7 @@ describe("Storage", function () {
for (let i = 0; i < slashMisses; i++) { for (let i = 0; i < slashMisses; i++) {
await ensureProofIsMissing() await ensureProofIsMissing()
} }
const expectedStake = stakeAmount * (100 - slashPercentage) / 100 const expectedStake = (stakeAmount * (100 - slashPercentage)) / 100
expect(await storage.stake(host.address)).to.equal(expectedStake) expect(await storage.stake(host.address)).to.equal(expectedStake)
}) })
}) })
@ -158,7 +154,8 @@ describe("Storage", function () {
await storage.connect(host).increaseStake(stakeAmount - 1) await storage.connect(host).increaseStake(stakeAmount - 1)
let requestHash = hashRequest(request) let requestHash = hashRequest(request)
let bidHash = hashBid({ ...bid, requestHash }) let bidHash = hashBid({ ...bid, requestHash })
await expect(storage.newContract( await expect(
storage.newContract(
request.duration, request.duration,
request.size, request.size,
request.contentHash, request.contentHash,
@ -170,7 +167,8 @@ describe("Storage", function () {
bid.bidExpiry, bid.bidExpiry,
await sign(client, requestHash), await sign(client, requestHash),
await sign(host, bidHash) await sign(host, bidHash)
)).to.be.revertedWith("Insufficient stake") )
).to.be.revertedWith("Insufficient stake")
}) })
it("doesn't create contract without payment of price", async function () { it("doesn't create contract without payment of price", async function () {
@ -179,7 +177,8 @@ describe("Storage", function () {
await storage.connect(host).increaseStake(stakeAmount) await storage.connect(host).increaseStake(stakeAmount)
let requestHash = hashRequest(request) let requestHash = hashRequest(request)
let bidHash = hashBid({ ...bid, requestHash }) let bidHash = hashBid({ ...bid, requestHash })
await expect(storage.newContract( await expect(
storage.newContract(
request.duration, request.duration,
request.size, request.size,
request.contentHash, request.contentHash,
@ -191,7 +190,8 @@ describe("Storage", function () {
bid.bidExpiry, bid.bidExpiry,
await sign(client, requestHash), await sign(client, requestHash),
await sign(host, bidHash) await sign(host, bidHash)
)).to.be.revertedWith("ERC20: transfer amount exceeds allowance") )
).to.be.revertedWith("ERC20: transfer amount exceeds allowance")
}) })
}) })

View File

@ -6,12 +6,12 @@ const exampleRequest = () => ({
contentHash: ethers.utils.sha256("0xdeadbeef"), contentHash: ethers.utils.sha256("0xdeadbeef"),
proofPeriod: 8, // 8 blocks ≈ 2 minutes proofPeriod: 8, // 8 blocks ≈ 2 minutes
proofTimeout: 4, // 4 blocks ≈ 1 minute proofTimeout: 4, // 4 blocks ≈ 1 minute
nonce: ethers.utils.randomBytes(32) nonce: ethers.utils.randomBytes(32),
}) })
const exampleBid = () => ({ const exampleBid = () => ({
price: 42, price: 42,
bidExpiry: Math.round(Date.now() / 1000) + 60 * 60 // 1 hour from now bidExpiry: Math.round(Date.now() / 1000) + 60 * 60, // 1 hour from now
}) })
module.exports = { exampleRequest, exampleBid } module.exports = { exampleRequest, exampleBid }

View File

@ -1,21 +1,30 @@
const { ethers } = require("hardhat") const { ethers } = require("hardhat")
function hashRequest({ function hashRequest({
duration, size, contentHash, proofPeriod, proofTimeout, nonce duration,
size,
contentHash,
proofPeriod,
proofTimeout,
nonce,
}) { }) {
const type = "[dagger.request.v1]" const type = "[dagger.request.v1]"
return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( return ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(
["string", "uint", "uint", "bytes32", "uint", "uint", "bytes32"], ["string", "uint", "uint", "bytes32", "uint", "uint", "bytes32"],
[type, duration, size, contentHash, proofPeriod, proofTimeout, nonce] [type, duration, size, contentHash, proofPeriod, proofTimeout, nonce]
)) )
)
} }
function hashBid({ requestHash, bidExpiry, price }) { function hashBid({ requestHash, bidExpiry, price }) {
const type = "[dagger.bid.v1]" const type = "[dagger.bid.v1]"
return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( return ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(
["string", "bytes32", "uint", "uint"], ["string", "bytes32", "uint", "uint"],
[type, requestHash, bidExpiry, price] [type, requestHash, bidExpiry, price]
)) )
)
} }
async function sign(signer, hash) { async function sign(signer, hash) {