import std/options import std/importutils import pkg/chronos import pkg/ethers/erc20 import codex/contracts import ../ethertest import ./examples import ./time import ./deployment privateAccess(OnChainMarket) # enable access to private fields ethersuite "On-Chain Market": let proof = Groth16Proof.example var market: OnChainMarket var marketplace: Marketplace var token: Erc20Token var request: StorageRequest var slotIndex: UInt256 var periodicity: Periodicity var host: Signer var otherHost: Signer var hostRewardRecipient: Address proc expectedPayout(r: StorageRequest, startTimestamp: UInt256, endTimestamp: UInt256): UInt256 = return (endTimestamp - startTimestamp) * r.ask.reward proc switchAccount(account: Signer) = marketplace = marketplace.connect(account) token = token.connect(account) market = OnChainMarket.new(marketplace, market.rewardRecipient) setup: let address = Marketplace.address(dummyVerifier = true) marketplace = Marketplace.new(address, ethProvider.getSigner()) let config = await marketplace.configuration() hostRewardRecipient = accounts[2] market = OnChainMarket.new(marketplace) let tokenAddress = await marketplace.token() token = Erc20Token.new(tokenAddress, ethProvider.getSigner()) periodicity = Periodicity(seconds: config.proofs.period) request = StorageRequest.example request.client = accounts[0] host = ethProvider.getSigner(accounts[1]) otherHost = ethProvider.getSigner(accounts[3]) slotIndex = (request.ask.slots div 2).u256 proc advanceToNextPeriod() {.async.} = let currentPeriod = periodicity.periodOf(await ethProvider.currentTime()) await ethProvider.advanceTimeTo(periodicity.periodEnd(currentPeriod) + 1) proc advanceToCancelledRequest(request: StorageRequest) {.async.} = let expiry = (await market.requestExpiresAt(request.id)) + 1 await ethProvider.advanceTimeTo(expiry.u256) proc waitUntilProofRequired(slotId: SlotId) {.async.} = await advanceToNextPeriod() while not ( (await marketplace.isProofRequired(slotId)) and (await marketplace.getPointer(slotId)) < 250 ): await advanceToNextPeriod() test "caches marketplace configuration": check isNone market.configuration discard await market.periodicity() check isSome market.configuration test "fails to instantiate when contract does not have a signer": let storageWithoutSigner = marketplace.connect(ethProvider) expect AssertionDefect: discard OnChainMarket.new(storageWithoutSigner) test "knows signer address": check (await market.getSigner()) == (await ethProvider.getSigner().getAddress()) test "can retrieve proof periodicity": let periodicity = await market.periodicity() let config = await marketplace.configuration() let periodLength = config.proofs.period check periodicity.seconds == periodLength test "can retrieve proof timeout": let proofTimeout = await market.proofTimeout() let config = await marketplace.configuration() check proofTimeout == config.proofs.timeout test "supports marketplace requests": await market.requestStorage(request) test "can retrieve previously submitted requests": check (await market.getRequest(request.id)) == none StorageRequest await market.requestStorage(request) let r = await market.getRequest(request.id) check (r) == some request test "withdraws funds to client": let clientAddress = request.client await market.requestStorage(request) await advanceToCancelledRequest(request) let startBalanceClient = await token.balanceOf(clientAddress) await market.withdrawFunds(request.id) let endBalanceClient = await token.balanceOf(clientAddress) check endBalanceClient == (startBalanceClient + request.price) test "supports request subscriptions": var receivedIds: seq[RequestId] var receivedAsks: seq[StorageAsk] proc onRequest(id: RequestId, ask: StorageAsk, expiry: UInt256) = receivedIds.add(id) receivedAsks.add(ask) let subscription = await market.subscribeRequests(onRequest) await market.requestStorage(request) check eventually receivedIds == @[request.id] and receivedAsks == @[request.ask] await subscription.unsubscribe() test "supports filling of slots": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) test "can retrieve host that filled slot": await market.requestStorage(request) check (await market.getHost(request.id, slotIndex)) == none Address await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) check (await market.getHost(request.id, slotIndex)) == some accounts[0] test "supports freeing a slot": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) await market.freeSlot(slotId(request.id, slotIndex)) check (await market.getHost(request.id, slotIndex)) == none Address test "supports checking whether proof is required now": check (await market.isProofRequired(slotId(request.id, slotIndex))) == false test "supports checking whether proof is required soon": check (await market.willProofBeRequired(slotId(request.id, slotIndex))) == false test "submits proofs": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) await advanceToNextPeriod() await market.submitProof(slotId(request.id, slotIndex), proof) test "marks a proof as missing": let slotId = slotId(request, slotIndex) await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) await waitUntilProofRequired(slotId) let missingPeriod = periodicity.periodOf(await ethProvider.currentTime()) await advanceToNextPeriod() await market.markProofAsMissing(slotId, missingPeriod) check (await marketplace.missingProofs(slotId)) == 1 test "can check whether a proof can be marked as missing": let slotId = slotId(request, slotIndex) await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) await waitUntilProofRequired(slotId) let missingPeriod = periodicity.periodOf(await ethProvider.currentTime()) await advanceToNextPeriod() check (await market.canProofBeMarkedAsMissing(slotId, missingPeriod)) == true test "supports slot filled subscriptions": await market.requestStorage(request) var receivedIds: seq[RequestId] var receivedSlotIndices: seq[UInt256] proc onSlotFilled(id: RequestId, slotIndex: UInt256) = receivedIds.add(id) receivedSlotIndices.add(slotIndex) let subscription = await market.subscribeSlotFilled(onSlotFilled) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) check eventually receivedIds == @[request.id] and receivedSlotIndices == @[slotIndex] await subscription.unsubscribe() test "subscribes only to a certain slot": var otherSlot = slotIndex - 1 await market.requestStorage(request) var receivedSlotIndices: seq[UInt256] proc onSlotFilled(requestId: RequestId, slotIndex: UInt256) = receivedSlotIndices.add(slotIndex) let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled) await market.reserveSlot(request.id, otherSlot) await market.fillSlot(request.id, otherSlot, proof, request.ask.collateral) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) check eventually receivedSlotIndices == @[slotIndex] await subscription.unsubscribe() test "supports slot freed subscriptions": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) var receivedRequestIds: seq[RequestId] = @[] var receivedIdxs: seq[UInt256] = @[] proc onSlotFreed(requestId: RequestId, idx: UInt256) = receivedRequestIds.add(requestId) receivedIdxs.add(idx) let subscription = await market.subscribeSlotFreed(onSlotFreed) await market.freeSlot(slotId(request.id, slotIndex)) check eventually receivedRequestIds == @[request.id] and receivedIdxs == @[slotIndex] await subscription.unsubscribe() test "supports slot reservations full subscriptions": let account2 = ethProvider.getSigner(accounts[2]) let account3 = ethProvider.getSigner(accounts[3]) await market.requestStorage(request) var receivedRequestIds: seq[RequestId] = @[] var receivedIdxs: seq[UInt256] = @[] proc onSlotReservationsFull(requestId: RequestId, idx: UInt256) = receivedRequestIds.add(requestId) receivedIdxs.add(idx) let subscription = await market.subscribeSlotReservationsFull(onSlotReservationsFull) await market.reserveSlot(request.id, slotIndex) switchAccount(account2) await market.reserveSlot(request.id, slotIndex) switchAccount(account3) await market.reserveSlot(request.id, slotIndex) check eventually receivedRequestIds == @[request.id] and receivedIdxs == @[slotIndex] await subscription.unsubscribe() test "support fulfillment subscriptions": await market.requestStorage(request) var receivedIds: seq[RequestId] proc onFulfillment(id: RequestId) = receivedIds.add(id) let subscription = await market.subscribeFulfillment(request.id, onFulfillment) for slotIndex in 0..