From efb4f5c375d2820b8e77276065f864197e9001e7 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 17 Mar 2022 14:15:04 +0100 Subject: [PATCH] [contracts] Update to new marketplace design --- dagger/contracts/marketplace.nim | 50 +++++------- dagger/contracts/storage.nim | 67 +++++----------- tests/contracts/examples.nim | 30 +++---- tests/contracts/periods.nim | 16 ++++ tests/contracts/testContracts.nim | 126 ++++++++++++++---------------- tests/contracts/time.nim | 13 +++ 6 files changed, 142 insertions(+), 160 deletions(-) create mode 100644 tests/contracts/periods.nim create mode 100644 tests/contracts/time.nim diff --git a/dagger/contracts/marketplace.nim b/dagger/contracts/marketplace.nim index 9f377144..df6c2716 100644 --- a/dagger/contracts/marketplace.nim +++ b/dagger/contracts/marketplace.nim @@ -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 diff --git a/dagger/contracts/storage.nim b/dagger/contracts/storage.nim index 131428b0..90aef23d 100644 --- a/dagger/contracts/storage.nim +++ b/dagger/contracts/storage.nim @@ -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.} diff --git a/tests/contracts/examples.nim b/tests/contracts/examples.nim index 9c418d54..ff3b70c4 100644 --- a/tests/contracts/examples.nim +++ b/tests/contracts/examples.nim @@ -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]) diff --git a/tests/contracts/periods.nim b/tests/contracts/periods.nim new file mode 100644 index 00000000..f0b789e1 --- /dev/null +++ b/tests/contracts/periods.nim @@ -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) diff --git a/tests/contracts/testContracts.nim b/tests/contracts/testContracts.nim index b238c52b..bd2e8562 100644 --- a/tests/contracts/testContracts.nim +++ b/tests/contracts/testContracts.nim @@ -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.. 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) diff --git a/tests/contracts/time.nim b/tests/contracts/time.nim new file mode 100644 index 00000000..418e3fd9 --- /dev/null +++ b/tests/contracts/time.nim @@ -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")