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

View File

@ -12,59 +12,28 @@ type
Id = array[32, byte]
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 withdraw*(storage: Storage) {.contract.}
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 contentHash*(storage: Storage, id: Id): array[32, byte] {.contract, view.}
proc proofPeriod*(storage: Storage, id: Id): UInt256 {.contract, view.}
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 requestStorage*(storage: Storage, request: StorageRequest) {.contract.}
proc offerStorage*(storage: Storage, offer: StorageOffer) {.contract.}
proc selectOffer*(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 newContract(storage: Storage,
duration: UInt256,
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 proofPeriod*(storage: Storage): UInt256 {.contract, view.}
proc proofTimeout*(storage: Storage): UInt256 {.contract, view.}
proc newContract*(storage: Storage,
request: StorageRequest,
bid: StorageBid,
host: Address,
requestSignature: seq[byte],
bidSignature: seq[byte]) {.async.} =
await storage.newContract(
request.duration,
request.size,
request.contentHash,
request.proofPeriod,
request.proofTimeout,
request.nonce,
bid.price,
host,
bid.bidExpiry,
requestSignature,
bidSignature
)
proc proofEnd*(storage: Storage, id: Id): UInt256 {.contract, view.}
proc missingProofs*(storage: Storage, id: Id): UInt256 {.contract, view.}
proc isProofRequired*(storage: Storage, id: Id): bool {.contract, view.}
proc getChallenge*(storage: Storage, id: Id): array[32, byte] {.contract, view.}
proc getPointer*(storage: Storage, id: Id): uint8 {.contract, view.}
proc submitProof*(storage: Storage, id: Id, proof: bool) {.contract.}
proc markProofAsMissing*(storage: Storage, id: Id, period: UInt256) {.contract.}

View File

@ -1,29 +1,31 @@
import std/times
import pkg/stint
import pkg/nimcrypto
import pkg/ethers
import dagger/contracts/marketplace
proc randomBytes(amount: static int): array[amount, byte] =
doAssert randomBytes(result) == amount
proc example*(_: type Address): Address =
Address(randomBytes(20))
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
contentHash: sha256.digest(0xdeadbeef'u32.toBytes).data,
proofPeriod: 8.u256, # 8 blocks ≈ 2 minutes
proofTimeout: 4.u256, # 4 blocks ≈ 1 minute
proofProbability: 4.u256, # require a proof roughly once every 4 periods
maxPrice: 84.u256,
expiry: (getTime() + initDuration(hours=1)).toUnix.u256,
nonce: randomBytes(32)
)
proc example*(_: type StorageBid): StorageBid =
StorageBid(
requestHash: hashRequest(StorageRequest.example),
bidExpiry: (getTime() + initDuration(hours=1)).toUnix.u256,
price: 42.u256
proc example*(_: type StorageOffer): StorageOffer =
(
host: Address.example,
requestId: StorageRequest.example.id,
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/nimcrypto
import dagger/contracts
import dagger/contracts/testtoken
import ./ethertest
import ./examples
import ./time
import ./periods
ethersuite "Storage contracts":
let (request, bid) = (StorageRequest, StorageBid).example
var client, host: Signer
var storage: Storage
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:
let deployment = deployment()
client = provider.getSigner(accounts[0])
host = provider.getSigner(accounts[1])
let deployment = deployment()
storage = Storage.new(!deployment.address(Storage), 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.connect(host).approve(Address(storage.address), stakeAmount)
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
await token.mint(await client.getAddress(), 1000.u256)
await token.mint(await host.getAddress(), 1000.u256)
proc mineUntilProofRequired(id: array[32, byte]): Future[UInt256] {.async.} =
var blocknumber: UInt256
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
collateralAmount = await storage.collateralAmount()
periodicity = Periodicity(seconds: await storage.proofPeriod())
proc mineUntilProofTimeout(id: array[32, byte]) {.async.} =
let timeout = await storage.proofTimeout(id)
for _ in 0..<timeout.truncate(int):
discard await provider.send("evm_mine")
request = StorageRequest.example
request.client = await client.getAddress()
proc mineUntilEnd(id: array[32, byte]) {.async.} =
let proofEnd = await storage.proofEnd(id)
while (await provider.getBlockNumber()) < proofEnd:
discard await provider.send("evm_mine")
offer = StorageOffer.example
offer.host = await host.getAddress()
offer.requestId = request.id
test "can be created":
let id = await newContract()
check (await storage.duration(id)) == request.duration
check (await storage.size(id)) == request.size
check (await storage.contentHash(id)) == request.contentHash
check (await storage.proofPeriod(id)) == request.proofPeriod
check (await storage.proofTimeout(id)) == request.proofTimeout
check (await storage.price(id)) == bid.price
check (await storage.host(id)) == (await host.getAddress())
switchAccount(client)
await token.approve(storage.address, request.maxPrice)
await storage.requestStorage(request)
switchAccount(host)
await token.approve(storage.address, collateralAmount)
await storage.deposit(collateralAmount)
await storage.offerStorage(offer)
switchAccount(client)
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":
let id = await newContract()
await storage.connect(host).startContract(id)
switchAccount(host)
await storage.startContract(id)
let proofEnd = await storage.proofEnd(id)
check proofEnd > 0
test "accept storage proofs":
let id = await newContract()
await storage.connect(host).startContract(id)
let blocknumber = await mineUntilProofRequired(id)
await storage.connect(host).submitProof(id, blocknumber, true)
switchAccount(host)
await storage.startContract(id)
await waitUntilProofRequired(id)
await storage.submitProof(id, true)
test "marks missing proofs":
let id = await newContract()
await storage.connect(host).startContract(id)
let blocknumber = await mineUntilProofRequired(id)
await mineUntilProofTimeout(id)
await storage.connect(client).markProofAsMissing(id, blocknumber)
test "can mark missing proofs":
switchAccount(host)
await storage.startContract(id)
await waitUntilProofRequired(id)
let missingPeriod = periodicity.periodOf(await provider.currentTime())
await provider.advanceTime(periodicity.seconds)
switchAccount(client)
await storage.markProofAsMissing(id, missingPeriod)
test "can be finished":
let id = await newContract()
await storage.connect(host).startContract(id)
await mineUntilEnd(id)
await storage.connect(host).finishContract(id)
switchAccount(host)
await storage.startContract(id)
await provider.advanceTimeTo(await storage.proofEnd(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")