nim-codex/tests/contracts/testMarket.nim

703 lines
28 KiB
Nim

import std/options
import std/importutils
import pkg/chronos
import pkg/ethers/erc20
import codex/contracts
import ../ethertest
import ./examples
import ./time
import ./deployment
import ./helpers/mockprovider
privateAccess(OnChainMarket) # enable access to private fields
# to see supportive information in the test output
# use `-d:"chronicles_enabled_topics:testMarket:DEBUG` option
# when compiling the test file
logScope:
topics = "testMarket"
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 mineNBlocks(provider: JsonRpcProvider, n: int) {.async.} =
for _ in 0..<n:
discard await provider.send("evm_mine")
proc waitUntilProofRequired(slotId: SlotId) {.async.} =
await advanceToNextPeriod()
while not (
(await marketplace.isProofRequired(slotId)) and
(await marketplace.getPointer(slotId)) < 250
):
await advanceToNextPeriod()
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..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()
test "subscribes only to fulfillment of a certain request":
var otherRequest = StorageRequest.example
otherRequest.client = accounts[0]
await market.requestStorage(request)
await market.requestStorage(otherRequest)
var receivedIds: seq[RequestId]
proc onFulfillment(id: RequestId) =
receivedIds.add(id)
let subscription = await market.subscribeFulfillment(request.id, onFulfillment)
for slotIndex in 0..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
for slotIndex in 0..<otherRequest.ask.slots:
await market.reserveSlot(otherRequest.id, slotIndex.u256)
await market.fillSlot(otherRequest.id, slotIndex.u256, proof, otherRequest.ask.collateral)
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()
test "support request cancelled subscriptions":
await market.requestStorage(request)
var receivedIds: seq[RequestId]
proc onRequestCancelled(id: RequestId) =
receivedIds.add(id)
let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled)
await advanceToCancelledRequest(request)
await market.withdrawFunds(request.id)
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()
test "support request failed subscriptions":
await market.requestStorage(request)
var receivedIds: seq[RequestId]
proc onRequestFailed(id: RequestId) =
receivedIds.add(id)
let subscription = await market.subscribeRequestFailed(request.id, onRequestFailed)
for slotIndex in 0..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
for slotIndex in 0..request.ask.maxSlotLoss:
let slotId = request.slotId(slotIndex.u256)
while true:
let slotState = await marketplace.slotState(slotId)
if slotState == SlotState.Free:
break
await waitUntilProofRequired(slotId)
let missingPeriod = periodicity.periodOf(await ethProvider.currentTime())
await advanceToNextPeriod()
discard await marketplace.markProofAsMissing(slotId, missingPeriod).confirm(1)
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()
test "subscribes only to a certain request cancellation":
var otherRequest = request
otherRequest.nonce = Nonce.example
await market.requestStorage(request)
await market.requestStorage(otherRequest)
var receivedIds: seq[RequestId]
proc onRequestCancelled(requestId: RequestId) =
receivedIds.add(requestId)
let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled)
await advanceToCancelledRequest(otherRequest) # shares expiry with otherRequest
await market.withdrawFunds(otherRequest.id)
await market.withdrawFunds(request.id)
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()
test "supports proof submission subscriptions":
await market.requestStorage(request)
await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
await advanceToNextPeriod()
var receivedIds: seq[SlotId]
proc onProofSubmission(id: SlotId) =
receivedIds.add(id)
let subscription = await market.subscribeProofSubmission(onProofSubmission)
await market.submitProof(slotId(request.id, slotIndex), proof)
check eventually receivedIds == @[slotId(request.id, slotIndex)]
await subscription.unsubscribe()
test "request is none when unknown":
check isNone await market.getRequest(request.id)
test "can retrieve active requests":
await market.requestStorage(request)
var request2 = StorageRequest.example
request2.client = accounts[0]
await market.requestStorage(request2)
check (await market.myRequests()) == @[request.id, request2.id]
test "retrieves correct request state when request is unknown":
check (await market.requestState(request.id)) == none RequestState
test "can retrieve request state":
await market.requestStorage(request)
for slotIndex in 0..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
check (await market.requestState(request.id)) == some RequestState.Started
test "can retrieve active slots":
await market.requestStorage(request)
await market.reserveSlot(request.id, slotIndex - 1)
await market.fillSlot(request.id, slotIndex - 1, proof, request.ask.collateral)
await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
let slotId1 = request.slotId(slotIndex - 1)
let slotId2 = request.slotId(slotIndex)
check (await market.mySlots()) == @[slotId1, slotId2]
test "returns none when slot is empty":
await market.requestStorage(request)
let slotId = request.slotId(slotIndex)
check (await market.getActiveSlot(slotId)) == none Slot
test "can retrieve request details from slot id":
await market.requestStorage(request)
await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
let slotId = request.slotId(slotIndex)
let expected = Slot(request: request, slotIndex: slotIndex)
check (await market.getActiveSlot(slotId)) == some expected
test "retrieves correct slot state when request is unknown":
let slotId = request.slotId(slotIndex)
check (await market.slotState(slotId)) == SlotState.Free
test "retrieves correct slot state once filled":
await market.requestStorage(request)
await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
let slotId = request.slotId(slotIndex)
check (await market.slotState(slotId)) == SlotState.Filled
test "can query past StorageRequested events":
var request1 = StorageRequest.example
var request2 = StorageRequest.example
request1.client = accounts[0]
request2.client = accounts[0]
await market.requestStorage(request)
await market.requestStorage(request1)
await market.requestStorage(request2)
# `market.requestStorage` executes an `approve` tx before the
# `requestStorage` tx, so that's two PoA blocks per `requestStorage` call (6
# blocks for 3 calls). We don't need to check the `approve` for the first
# `requestStorage` call, so we only need to check 5 "blocks ago". "blocks
# ago".
proc getsPastRequest(): Future[bool] {.async.} =
let reqs =
await market.queryPastStorageRequestedEvents(blocksAgo = 5)
reqs.mapIt(it.requestId) == @[request.id, request1.id, request2.id]
check eventually await getsPastRequest()
test "can query past SlotFilled events":
await market.requestStorage(request)
await market.reserveSlot(request.id, 0.u256)
await market.reserveSlot(request.id, 1.u256)
await market.reserveSlot(request.id, 2.u256)
await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral)
await market.fillSlot(request.id, 1.u256, proof, request.ask.collateral)
await market.fillSlot(request.id, 2.u256, proof, request.ask.collateral)
let slotId = request.slotId(slotIndex)
# `market.fill` executes an `approve` tx before the `fillSlot` tx, so that's
# two PoA blocks per `fillSlot` call (6 blocks for 3 calls). We don't need
# to check the `approve` for the first `fillSlot` call, so we only need to
# check 5 "blocks ago".
let events =
await market.queryPastSlotFilledEvents(blocksAgo = 5)
check events == @[
SlotFilled(requestId: request.id, slotIndex: 0.u256),
SlotFilled(requestId: request.id, slotIndex: 1.u256),
SlotFilled(requestId: request.id, slotIndex: 2.u256),
]
test "can query past SlotFilled events since given timestamp":
await market.requestStorage(request)
await market.reserveSlot(request.id, 0.u256)
await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral)
# The SlotFilled event will be included in the same block as
# the fillSlot transaction. If we want to ignore the SlotFilled event
# for this first slot, we need to jump to the next block and use the
# timestamp of that block as our "fromTime" parameter to the
# queryPastSlotFilledEvents function.
# await ethProvider.mineNBlocks(1)
await ethProvider.advanceTime(10.u256)
let (_, fromTime) =
await ethProvider.blockNumberAndTimestamp(BlockTag.latest)
await market.reserveSlot(request.id, 1.u256)
await market.reserveSlot(request.id, 2.u256)
await market.fillSlot(request.id, 1.u256, proof, request.ask.collateral)
await market.fillSlot(request.id, 2.u256, proof, request.ask.collateral)
let events = await market.queryPastSlotFilledEvents(
fromTime = fromTime.truncate(SecondsSince1970))
check events == @[
SlotFilled(requestId: request.id, slotIndex: 1.u256),
SlotFilled(requestId: request.id, slotIndex: 2.u256)
]
test "queryPastSlotFilledEvents returns empty sequence of events when " &
"no SlotFilled events have occurred since given timestamp":
await market.requestStorage(request)
await market.reserveSlot(request.id, 0.u256)
await market.reserveSlot(request.id, 1.u256)
await market.reserveSlot(request.id, 2.u256)
await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral)
await market.fillSlot(request.id, 1.u256, proof, request.ask.collateral)
await market.fillSlot(request.id, 2.u256, proof, request.ask.collateral)
await ethProvider.advanceTime(10.u256)
let (_, fromTime) =
await ethProvider.blockNumberAndTimestamp(BlockTag.latest)
let events = await market.queryPastSlotFilledEvents(
fromTime = fromTime.truncate(SecondsSince1970))
check events.len == 0
test "blockNumberForEpoch returns the earliest block when its timestamp " &
"is greater than the given epoch time and the earliest block is not " &
"block number 0 (genesis block)":
let mockProvider = newMockProvider(
numberOfBlocks = 10,
earliestBlockNumber = 1,
earliestBlockTimestamp = 10,
timeIntervalBetweenBlocks = 10
)
let (earliestBlockNumber, earliestTimestamp) =
await mockProvider.blockNumberAndTimestamp(BlockTag.earliest)
let epochTime = earliestTimestamp - 1
let actual = await mockProvider.blockNumberForEpoch(
epochTime.truncate(SecondsSince1970))
check actual == earliestBlockNumber
test "blockNumberForEpoch returns the earliest block when its timestamp " &
"is equal to the given epoch time":
let mockProvider = newMockProvider(
numberOfBlocks = 10,
earliestBlockNumber = 0,
earliestBlockTimestamp = 10,
timeIntervalBetweenBlocks = 10
)
let (earliestBlockNumber, earliestTimestamp) =
await mockProvider.blockNumberAndTimestamp(BlockTag.earliest)
let epochTime = earliestTimestamp
let actual = await mockProvider.blockNumberForEpoch(
epochTime.truncate(SecondsSince1970))
check earliestBlockNumber == 0.u256
check actual == earliestBlockNumber
test "blockNumberForEpoch returns the latest block when its timestamp " &
"is equal to the given epoch time":
let mockProvider = newMockProvider(
numberOfBlocks = 10,
earliestBlockNumber = 0,
earliestBlockTimestamp = 10,
timeIntervalBetweenBlocks = 10
)
let (latestBlockNumber, latestTimestamp) =
await mockProvider.blockNumberAndTimestamp(BlockTag.latest)
let epochTime = latestTimestamp
let actual = await mockProvider.blockNumberForEpoch(
epochTime.truncate(SecondsSince1970))
check actual == latestBlockNumber
test "blockNumberForEpoch finds closest blockNumber for given epoch time":
proc createBlockHistory(n: int, blockTime: int):
Future[seq[(UInt256, UInt256)]] {.async.} =
var blocks: seq[(UInt256, UInt256)] = @[]
for _ in 0..<n:
await ethProvider.advanceTime(blockTime.u256)
let (blockNumber, blockTimestamp) =
await ethProvider.blockNumberAndTimestamp(BlockTag.latest)
# collect blocknumbers and timestamps
blocks.add((blockNumber, blockTimestamp))
blocks
proc printBlockNumbersAndTimestamps(blocks: seq[(UInt256, UInt256)]) =
for (blockNumber, blockTimestamp) in blocks:
debug "Block", blockNumber = blockNumber, timestamp = blockTimestamp
type Expectations = tuple
epochTime: UInt256
expectedBlockNumber: UInt256
# We want to test that timestamps at the block boundaries, in the middle,
# and towards lower and upper part of the range are correctly mapped to
# the closest block number.
# For example: assume we have the following two blocks with
# the corresponding block numbers and timestamps:
# block1: (291, 1728436100)
# block2: (292, 1728436110)
# To test that binary search correctly finds the closest block number,
# we will test the following timestamps:
# 1728436100 => 291
# 1728436104 => 291
# 1728436105 => 292
# 1728436106 => 292
# 1728436110 => 292
proc generateExpectations(
blocks: seq[(UInt256, UInt256)]): seq[Expectations] =
var expectations: seq[Expectations] = @[]
for i in 0..<blocks.len - 1:
let (startNumber, startTimestamp) = blocks[i]
let (endNumber, endTimestamp) = blocks[i + 1]
let middleTimestamp = (startTimestamp + endTimestamp) div 2
let lowerExpectation = (middleTimestamp - 1, startNumber)
expectations.add((startTimestamp, startNumber))
expectations.add(lowerExpectation)
if middleTimestamp.truncate(int64) - startTimestamp.truncate(int64) <
endTimestamp.truncate(int64) - middleTimestamp.truncate(int64):
expectations.add((middleTimestamp, startNumber))
else:
expectations.add((middleTimestamp, endNumber))
let higherExpectation = (middleTimestamp + 1, endNumber)
expectations.add(higherExpectation)
if i == blocks.len - 2:
expectations.add((endTimestamp, endNumber))
expectations
proc printExpectations(expectations: seq[Expectations]) =
debug "Expectations", numberOfExpectations = expectations.len
for (epochTime, expectedBlockNumber) in expectations:
debug "Expectation", epochTime = epochTime,
expectedBlockNumber = expectedBlockNumber
# mark the beginning of the history for our test
await ethProvider.mineNBlocks(1)
# set average block time - 10s - we use larger block time
# then expected in Linea for more precise testing of the binary search
let averageBlockTime = 10
# create a history of N blocks
let N = 10
let blocks = await createBlockHistory(N, averageBlockTime)
printBlockNumbersAndTimestamps(blocks)
# generate expectations for block numbers
let expectations = generateExpectations(blocks)
printExpectations(expectations)
# validate expectations
for (epochTime, expectedBlockNumber) in expectations:
debug "Validating", epochTime = epochTime,
expectedBlockNumber = expectedBlockNumber
let actualBlockNumber = await ethProvider.blockNumberForEpoch(
epochTime.truncate(SecondsSince1970))
check actualBlockNumber == expectedBlockNumber
test "past event query can specify negative `blocksAgo` parameter":
await market.requestStorage(request)
check eventually (
(await market.queryPastStorageRequestedEvents(blocksAgo = -2)) ==
(await market.queryPastStorageRequestedEvents(blocksAgo = 2))
)
test "pays rewards and collateral to host":
await market.requestStorage(request)
let address = await host.getAddress()
switchAccount(host)
await market.reserveSlot(request.id, 0.u256)
await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral)
let filledAt = (await ethProvider.currentTime()) - 1.u256
for slotIndex in 1..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
let requestEnd = await market.getRequestEnd(request.id)
await ethProvider.advanceTimeTo(requestEnd.u256 + 1)
let startBalance = await token.balanceOf(address)
await market.freeSlot(request.slotId(0.u256))
let endBalance = await token.balanceOf(address)
let expectedPayout = request.expectedPayout(filledAt, requestEnd.u256)
check endBalance == (startBalance + expectedPayout + request.ask.collateral)
test "pays rewards to reward recipient, collateral to host":
market = OnChainMarket.new(marketplace, hostRewardRecipient.some)
let hostAddress = await host.getAddress()
await market.requestStorage(request)
switchAccount(host)
await market.reserveSlot(request.id, 0.u256)
await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral)
let filledAt = (await ethProvider.currentTime()) - 1.u256
for slotIndex in 1..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
let requestEnd = await market.getRequestEnd(request.id)
await ethProvider.advanceTimeTo(requestEnd.u256 + 1)
let startBalanceHost = await token.balanceOf(hostAddress)
let startBalanceReward = await token.balanceOf(hostRewardRecipient)
await market.freeSlot(request.slotId(0.u256))
let endBalanceHost = await token.balanceOf(hostAddress)
let endBalanceReward = await token.balanceOf(hostRewardRecipient)
let expectedPayout = request.expectedPayout(filledAt, requestEnd.u256)
check endBalanceHost == (startBalanceHost + request.ask.collateral)
check endBalanceReward == (startBalanceReward + expectedPayout)