[contracts] Update to new marketplace design
This commit is contained in:
parent
48ebae9ae5
commit
efb4f5c375
|
@ -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
|
||||
|
|
|
@ -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.}
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
Loading…
Reference in New Issue