[contracts] Update to new marketplace design
This commit is contained in:
parent
48ebae9ae5
commit
efb4f5c375
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
|
@ -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])
|
|
||||||
|
|
|
@ -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/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)
|
||||||
|
|
|
@ -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