[contracts] Update to new marketplace design

This commit is contained in:
Mark Spanbroek 2022-03-17 14:15:04 +01:00 committed by markspanbroek
parent 48ebae9ae5
commit efb4f5c375
6 changed files with 142 additions and 160 deletions

View File

@ -1,42 +1,30 @@
import pkg/stint import pkg/stint
import pkg/contractabi except Address import pkg/contractabi
import pkg/nimcrypto import pkg/nimcrypto
import pkg/chronos import pkg/chronos
export stint export stint
type type
StorageRequest* = object StorageRequest* = tuple
duration*: UInt256 client: Address
size*: UInt256 duration: UInt256
contentHash*: Hash size: UInt256
proofPeriod*: UInt256 contentHash: array[32, byte]
proofTimeout*: UInt256 proofProbability: UInt256
nonce*: array[32, byte] maxPrice: UInt256
StorageBid* = object expiry: UInt256
requestHash*: Hash nonce: array[32, byte]
bidExpiry*: UInt256 StorageOffer* = tuple
price*: UInt256 host: Address
Hash = array[32, byte] requestId: array[32, byte]
Signature = array[65, byte] price: UInt256
expiry: UInt256
func hashRequest*(request: StorageRequest): Hash = func id*(request: StorageRequest): array[32, byte] =
let encoding = AbiEncoder.encode: ( let encoding = AbiEncoder.encode(request)
"[dagger.request.v1]",
request.duration,
request.size,
request.contentHash,
request.proofPeriod,
request.proofTimeout,
request.nonce
)
keccak256.digest(encoding).data keccak256.digest(encoding).data
func hashBid*(bid: StorageBid): Hash = func id*(offer: StorageOffer): array[32, byte] =
let encoding = AbiEncoder.encode: ( let encoding = AbiEncoder.encode(offer)
"[dagger.bid.v1]",
bid.requestHash,
bid.bidExpiry,
bid.price
)
keccak256.digest(encoding).data keccak256.digest(encoding).data

View File

@ -12,59 +12,28 @@ type
Id = array[32, byte] Id = array[32, byte]
proc collateralAmount*(storage: Storage): UInt256 {.contract, view.} proc collateralAmount*(storage: Storage): UInt256 {.contract, view.}
proc slashMisses*(storage: Storage): UInt256 {.contract, view.}
proc slashPercentage*(storage: Storage): UInt256 {.contract, view.}
proc deposit*(storage: Storage, amount: UInt256) {.contract.} proc deposit*(storage: Storage, amount: UInt256) {.contract.}
proc withdraw*(storage: Storage) {.contract.} proc withdraw*(storage: Storage) {.contract.}
proc balanceOf*(storage: Storage, account: Address): UInt256 {.contract, view.} proc balanceOf*(storage: Storage, account: Address): UInt256 {.contract, view.}
proc duration*(storage: Storage, id: Id): UInt256 {.contract, view.}
proc size*(storage: Storage, id: Id): UInt256 {.contract, view.} proc requestStorage*(storage: Storage, request: StorageRequest) {.contract.}
proc contentHash*(storage: Storage, id: Id): array[32, byte] {.contract, view.} proc offerStorage*(storage: Storage, offer: StorageOffer) {.contract.}
proc proofPeriod*(storage: Storage, id: Id): UInt256 {.contract, view.} proc selectOffer*(storage: Storage, id: Id) {.contract.}
proc proofTimeout*(storage: Storage, id: Id): UInt256 {.contract, view.}
proc price*(storage: Storage, id: Id): UInt256 {.contract, view.}
proc host*(storage: Storage, id: Id): Address {.contract, view.}
proc startContract*(storage: Storage, id: Id) {.contract.} proc startContract*(storage: Storage, id: Id) {.contract.}
proc proofEnd*(storage: Storage, id: Id): UInt256 {.contract, view.}
proc isProofRequired*(storage: Storage,
id: Id,
blocknumber: UInt256): bool {.contract, view.}
proc submitProof*(storage: Storage,
id: Id,
blocknumber: UInt256,
proof: bool) {.contract.}
proc markProofAsMissing*(storage: Storage,
id: Id,
blocknumber: UInt256) {.contract.}
proc finishContract*(storage: Storage, id: Id) {.contract.} proc finishContract*(storage: Storage, id: Id) {.contract.}
proc newContract(storage: Storage, proc proofPeriod*(storage: Storage): UInt256 {.contract, view.}
duration: UInt256, proc proofTimeout*(storage: Storage): UInt256 {.contract, view.}
size: UInt256,
contentHash: array[32, byte],
proofPeriod: UInt256,
proofTimeout: UInt256,
nonce: array[32, byte],
price: UInt256,
host: Address,
bidExpiry: UInt256,
requestSignature: seq[byte],
bidSignature: seq[byte]) {.contract.}
proc newContract*(storage: Storage, proc proofEnd*(storage: Storage, id: Id): UInt256 {.contract, view.}
request: StorageRequest, proc missingProofs*(storage: Storage, id: Id): UInt256 {.contract, view.}
bid: StorageBid, proc isProofRequired*(storage: Storage, id: Id): bool {.contract, view.}
host: Address, proc getChallenge*(storage: Storage, id: Id): array[32, byte] {.contract, view.}
requestSignature: seq[byte], proc getPointer*(storage: Storage, id: Id): uint8 {.contract, view.}
bidSignature: seq[byte]) {.async.} =
await storage.newContract( proc submitProof*(storage: Storage, id: Id, proof: bool) {.contract.}
request.duration, proc markProofAsMissing*(storage: Storage, id: Id, period: UInt256) {.contract.}
request.size,
request.contentHash,
request.proofPeriod,
request.proofTimeout,
request.nonce,
bid.price,
host,
bid.bidExpiry,
requestSignature,
bidSignature
)

View File

@ -1,29 +1,31 @@
import std/times import std/times
import pkg/stint import pkg/stint
import pkg/nimcrypto import pkg/nimcrypto
import pkg/ethers
import dagger/contracts/marketplace import dagger/contracts/marketplace
proc randomBytes(amount: static int): array[amount, byte] = proc randomBytes(amount: static int): array[amount, byte] =
doAssert randomBytes(result) == amount doAssert randomBytes(result) == amount
proc example*(_: type Address): Address =
Address(randomBytes(20))
proc example*(_: type StorageRequest): StorageRequest = proc example*(_: type StorageRequest): StorageRequest =
StorageRequest( (
duration: 150.u256, # 150 blocks ≈ half an hour client: Address.example,
duration: (10 * 60 * 60).u256, # 10 hours
size: (1 * 1024 * 1024 * 1024).u256, # 1 Gigabyte size: (1 * 1024 * 1024 * 1024).u256, # 1 Gigabyte
contentHash: sha256.digest(0xdeadbeef'u32.toBytes).data, contentHash: sha256.digest(0xdeadbeef'u32.toBytes).data,
proofPeriod: 8.u256, # 8 blocks ≈ 2 minutes proofProbability: 4.u256, # require a proof roughly once every 4 periods
proofTimeout: 4.u256, # 4 blocks ≈ 1 minute maxPrice: 84.u256,
expiry: (getTime() + initDuration(hours=1)).toUnix.u256,
nonce: randomBytes(32) nonce: randomBytes(32)
) )
proc example*(_: type StorageBid): StorageBid = proc example*(_: type StorageOffer): StorageOffer =
StorageBid( (
requestHash: hashRequest(StorageRequest.example), host: Address.example,
bidExpiry: (getTime() + initDuration(hours=1)).toUnix.u256, requestId: StorageRequest.example.id,
price: 42.u256 price: 42.u256,
expiry: (getTime() + initDuration(hours=1)).toUnix.u256,
) )
proc example*(_: type (StorageRequest, StorageBid)): (StorageRequest, StorageBid) =
result[0] = StorageRequest.example
result[1] = StorageBid.example
result[1].requestHash = hashRequest(result[0])

View File

@ -0,0 +1,16 @@
import pkg/stint
type
Periodicity* = object
seconds*: UInt256
Period* = UInt256
Timestamp* = UInt256
func periodOf*(periodicity: Periodicity, timestamp: Timestamp): Period =
timestamp div periodicity.seconds
func periodStart*(periodicity: Periodicity, period: Period): Timestamp =
period * periodicity.seconds
func periodEnd*(periodicity: Periodicity, period: Period): Timestamp =
periodicity.periodStart(period + 1)

View File

@ -1,98 +1,92 @@
import std/json
import pkg/chronos import pkg/chronos
import pkg/nimcrypto import pkg/nimcrypto
import dagger/contracts import dagger/contracts
import dagger/contracts/testtoken import dagger/contracts/testtoken
import ./ethertest import ./ethertest
import ./examples import ./examples
import ./time
import ./periods
ethersuite "Storage contracts": ethersuite "Storage contracts":
let (request, bid) = (StorageRequest, StorageBid).example
var client, host: Signer var client, host: Signer
var storage: Storage var storage: Storage
var token: TestToken var token: TestToken
var stakeAmount: UInt256 var collateralAmount: UInt256
var periodicity: Periodicity
var request: StorageRequest
var offer: StorageOffer
var id: array[32, byte]
proc switchAccount(account: Signer) =
storage = storage.connect(account)
token = token.connect(account)
setup: setup:
let deployment = deployment()
client = provider.getSigner(accounts[0]) client = provider.getSigner(accounts[0])
host = provider.getSigner(accounts[1]) host = provider.getSigner(accounts[1])
let deployment = deployment()
storage = Storage.new(!deployment.address(Storage), provider.getSigner()) storage = Storage.new(!deployment.address(Storage), provider.getSigner())
token = TestToken.new(!deployment.address(TestToken), provider.getSigner()) token = TestToken.new(!deployment.address(TestToken), provider.getSigner())
await token.connect(client).mint(await client.getAddress(), 1000.u256)
await token.connect(host).mint(await host.getAddress(), 1000.u256)
stakeAmount = await storage.stakeAmount()
proc newContract(): Future[array[32, byte]] {.async.} = await token.mint(await client.getAddress(), 1000.u256)
await token.connect(host).approve(Address(storage.address), stakeAmount) await token.mint(await host.getAddress(), 1000.u256)
await storage.connect(host).increaseStake(stakeAmount)
await token.connect(client).approve(Address(storage.address), bid.price)
let requestHash = hashRequest(request)
let bidHash = hashBid(bid)
let requestSignature = await client.signMessage(@requestHash)
let bidSignature = await host.signMessage(@bidHash)
await storage.connect(client).newContract(
request,
bid,
await host.getAddress(),
requestSignature,
bidSignature
)
let id = bidHash
return id
proc mineUntilProofRequired(id: array[32, byte]): Future[UInt256] {.async.} = collateralAmount = await storage.collateralAmount()
var blocknumber: UInt256 periodicity = Periodicity(seconds: await storage.proofPeriod())
var done = false
while not done:
blocknumber = await provider.getBlockNumber()
done = await storage.isProofRequired(id, blocknumber)
if not done:
discard await provider.send("evm_mine")
return blocknumber
proc mineUntilProofTimeout(id: array[32, byte]) {.async.} = request = StorageRequest.example
let timeout = await storage.proofTimeout(id) request.client = await client.getAddress()
for _ in 0..<timeout.truncate(int):
discard await provider.send("evm_mine")
proc mineUntilEnd(id: array[32, byte]) {.async.} = offer = StorageOffer.example
let proofEnd = await storage.proofEnd(id) offer.host = await host.getAddress()
while (await provider.getBlockNumber()) < proofEnd: offer.requestId = request.id
discard await provider.send("evm_mine")
test "can be created": switchAccount(client)
let id = await newContract() await token.approve(storage.address, request.maxPrice)
check (await storage.duration(id)) == request.duration await storage.requestStorage(request)
check (await storage.size(id)) == request.size switchAccount(host)
check (await storage.contentHash(id)) == request.contentHash await token.approve(storage.address, collateralAmount)
check (await storage.proofPeriod(id)) == request.proofPeriod await storage.deposit(collateralAmount)
check (await storage.proofTimeout(id)) == request.proofTimeout await storage.offerStorage(offer)
check (await storage.price(id)) == bid.price switchAccount(client)
check (await storage.host(id)) == (await host.getAddress()) await storage.selectOffer(offer.id)
id = offer.id
proc waitUntilProofRequired(id: array[32, byte]) {.async.} =
let currentPeriod = periodicity.periodOf(await provider.currentTime())
await provider.advanceTimeTo(periodicity.periodEnd(currentPeriod))
while not (
(await storage.isProofRequired(id)) and
(await storage.getPointer(id)) < 250
):
await provider.advanceTime(periodicity.seconds)
test "can be started by the host": test "can be started by the host":
let id = await newContract() switchAccount(host)
await storage.connect(host).startContract(id) await storage.startContract(id)
let proofEnd = await storage.proofEnd(id) let proofEnd = await storage.proofEnd(id)
check proofEnd > 0 check proofEnd > 0
test "accept storage proofs": test "accept storage proofs":
let id = await newContract() switchAccount(host)
await storage.connect(host).startContract(id) await storage.startContract(id)
let blocknumber = await mineUntilProofRequired(id) await waitUntilProofRequired(id)
await storage.connect(host).submitProof(id, blocknumber, true) await storage.submitProof(id, true)
test "marks missing proofs": test "can mark missing proofs":
let id = await newContract() switchAccount(host)
await storage.connect(host).startContract(id) await storage.startContract(id)
let blocknumber = await mineUntilProofRequired(id) await waitUntilProofRequired(id)
await mineUntilProofTimeout(id) let missingPeriod = periodicity.periodOf(await provider.currentTime())
await storage.connect(client).markProofAsMissing(id, blocknumber) await provider.advanceTime(periodicity.seconds)
switchAccount(client)
await storage.markProofAsMissing(id, missingPeriod)
test "can be finished": test "can be finished":
let id = await newContract() switchAccount(host)
await storage.connect(host).startContract(id) await storage.startContract(id)
await mineUntilEnd(id) await provider.advanceTimeTo(await storage.proofEnd(id))
await storage.connect(host).finishContract(id) await storage.finishContract(id)

13
tests/contracts/time.nim Normal file
View File

@ -0,0 +1,13 @@
import pkg/ethers
proc currentTime*(provider: Provider): Future[UInt256] {.async.} =
return (!await provider.getBlock(BlockTag.latest)).timestamp
proc advanceTime*(provider: JsonRpcProvider, seconds: UInt256) {.async.} =
discard await provider.send("evm_increaseTime", @[%seconds])
discard await provider.send("evm_mine")
proc advanceTimeTo*(provider: JsonRpcProvider, timestamp: UInt256) {.async.} =
if (await provider.currentTime()) != timestamp:
discard await provider.send("evm_setNextBlockTimestamp", @[%timestamp])
discard await provider.send("evm_mine")