feat: partial rewards and withdraws (#880)
* feat: partial rewards and withdraws * test: missing reserve slot * test: fix contracts
This commit is contained in:
parent
3699601393
commit
ffa203b04f
|
@ -1,5 +1,6 @@
|
||||||
import pkg/metrics
|
import pkg/metrics
|
||||||
import ../statemachine
|
import ../statemachine
|
||||||
|
import ../../logutils
|
||||||
import ./error
|
import ./error
|
||||||
|
|
||||||
declareCounter(codex_purchases_failed, "codex purchases failed")
|
declareCounter(codex_purchases_failed, "codex purchases failed")
|
||||||
|
@ -12,5 +13,9 @@ method `$`*(state: PurchaseFailed): string =
|
||||||
|
|
||||||
method run*(state: PurchaseFailed, machine: Machine): Future[?State] {.async.} =
|
method run*(state: PurchaseFailed, machine: Machine): Future[?State] {.async.} =
|
||||||
codex_purchases_failed.inc()
|
codex_purchases_failed.inc()
|
||||||
|
let purchase = Purchase(machine)
|
||||||
|
warn "Request failed, withdrawing remaining funds", requestId = purchase.requestId
|
||||||
|
await purchase.market.withdrawFunds(purchase.requestId)
|
||||||
|
|
||||||
let error = newException(PurchaseError, "Purchase failed")
|
let error = newException(PurchaseError, "Purchase failed")
|
||||||
return some State(PurchaseErrored(error: error))
|
return some State(PurchaseErrored(error: error))
|
||||||
|
|
|
@ -16,5 +16,7 @@ method `$`*(state: PurchaseFinished): string =
|
||||||
method run*(state: PurchaseFinished, machine: Machine): Future[?State] {.async.} =
|
method run*(state: PurchaseFinished, machine: Machine): Future[?State] {.async.} =
|
||||||
codex_purchases_finished.inc()
|
codex_purchases_finished.inc()
|
||||||
let purchase = Purchase(machine)
|
let purchase = Purchase(machine)
|
||||||
info "Purchase finished", requestId = purchase.requestId
|
info "Purchase finished, withdrawing remaining funds", requestId = purchase.requestId
|
||||||
|
await purchase.market.withdrawFunds(purchase.requestId)
|
||||||
|
|
||||||
purchase.future.complete()
|
purchase.future.complete()
|
||||||
|
|
|
@ -269,6 +269,8 @@ method freeSlot*(market: MockMarket, slotId: SlotId) {.async.} =
|
||||||
method withdrawFunds*(market: MockMarket,
|
method withdrawFunds*(market: MockMarket,
|
||||||
requestId: RequestId) {.async.} =
|
requestId: RequestId) {.async.} =
|
||||||
market.withdrawn.add(requestId)
|
market.withdrawn.add(requestId)
|
||||||
|
|
||||||
|
if state =? market.requestState.?[requestId] and state == RequestState.Cancelled:
|
||||||
market.emitRequestCancelled(requestId)
|
market.emitRequestCancelled(requestId)
|
||||||
|
|
||||||
proc setProofRequired*(mock: MockMarket, id: SlotId, required: bool) =
|
proc setProofRequired*(mock: MockMarket, id: SlotId, required: bool) =
|
||||||
|
|
|
@ -230,3 +230,21 @@ checksuite "Purchasing state machine":
|
||||||
|
|
||||||
let next = await future
|
let next = await future
|
||||||
check !next of PurchaseFinished
|
check !next of PurchaseFinished
|
||||||
|
|
||||||
|
test "withdraw funds in PurchaseFinished":
|
||||||
|
let request = StorageRequest.example
|
||||||
|
let purchase = Purchase.new(request, market, clock)
|
||||||
|
discard await PurchaseFinished().run(purchase)
|
||||||
|
check request.id in market.withdrawn
|
||||||
|
|
||||||
|
test "withdraw funds in PurchaseFailed":
|
||||||
|
let request = StorageRequest.example
|
||||||
|
let purchase = Purchase.new(request, market, clock)
|
||||||
|
discard await PurchaseFailed().run(purchase)
|
||||||
|
check request.id in market.withdrawn
|
||||||
|
|
||||||
|
test "withdraw funds in PurchaseCancelled":
|
||||||
|
let request = StorageRequest.example
|
||||||
|
let purchase = Purchase.new(request, market, clock)
|
||||||
|
discard await PurchaseCancelled().run(purchase)
|
||||||
|
check request.id in market.withdrawn
|
||||||
|
|
|
@ -17,6 +17,10 @@ ethersuite "Marketplace contracts":
|
||||||
var periodicity: Periodicity
|
var periodicity: Periodicity
|
||||||
var request: StorageRequest
|
var request: StorageRequest
|
||||||
var slotId: SlotId
|
var slotId: SlotId
|
||||||
|
var filledAt: UInt256
|
||||||
|
|
||||||
|
proc expectedPayout(endTimestamp: UInt256): UInt256 =
|
||||||
|
return (endTimestamp - filledAt) * request.ask.reward
|
||||||
|
|
||||||
proc switchAccount(account: Signer) =
|
proc switchAccount(account: Signer) =
|
||||||
marketplace = marketplace.connect(account)
|
marketplace = marketplace.connect(account)
|
||||||
|
@ -46,6 +50,7 @@ ethersuite "Marketplace contracts":
|
||||||
switchAccount(host)
|
switchAccount(host)
|
||||||
discard await token.approve(marketplace.address, request.ask.collateral)
|
discard await token.approve(marketplace.address, request.ask.collateral)
|
||||||
discard await marketplace.reserveSlot(request.id, 0.u256)
|
discard await marketplace.reserveSlot(request.id, 0.u256)
|
||||||
|
filledAt = await ethProvider.currentTime()
|
||||||
discard await marketplace.fillSlot(request.id, 0.u256, proof)
|
discard await marketplace.fillSlot(request.id, 0.u256, proof)
|
||||||
slotId = request.slotId(0.u256)
|
slotId = request.slotId(0.u256)
|
||||||
|
|
||||||
|
@ -87,8 +92,7 @@ ethersuite "Marketplace contracts":
|
||||||
let startBalance = await token.balanceOf(address)
|
let startBalance = await token.balanceOf(address)
|
||||||
discard await marketplace.freeSlot(slotId)
|
discard await marketplace.freeSlot(slotId)
|
||||||
let endBalance = await token.balanceOf(address)
|
let endBalance = await token.balanceOf(address)
|
||||||
|
check endBalance == (startBalance + expectedPayout(requestEnd.u256) + request.ask.collateral)
|
||||||
check endBalance == (startBalance + request.ask.duration * request.ask.reward + request.ask.collateral)
|
|
||||||
|
|
||||||
test "can be paid out at the end, specifying reward and collateral recipient":
|
test "can be paid out at the end, specifying reward and collateral recipient":
|
||||||
switchAccount(host)
|
switchAccount(host)
|
||||||
|
@ -105,7 +109,7 @@ ethersuite "Marketplace contracts":
|
||||||
let endBalanceCollateral = await token.balanceOf(collateralRecipient)
|
let endBalanceCollateral = await token.balanceOf(collateralRecipient)
|
||||||
|
|
||||||
check endBalanceHost == startBalanceHost
|
check endBalanceHost == startBalanceHost
|
||||||
check endBalanceReward == (startBalanceReward + request.ask.duration * request.ask.reward)
|
check endBalanceReward == (startBalanceReward + expectedPayout(requestEnd.u256))
|
||||||
check endBalanceCollateral == (startBalanceCollateral + request.ask.collateral)
|
check endBalanceCollateral == (startBalanceCollateral + request.ask.collateral)
|
||||||
|
|
||||||
test "cannot mark proofs missing for cancelled request":
|
test "cannot mark proofs missing for cancelled request":
|
||||||
|
|
|
@ -20,8 +20,12 @@ ethersuite "On-Chain Market":
|
||||||
var slotIndex: UInt256
|
var slotIndex: UInt256
|
||||||
var periodicity: Periodicity
|
var periodicity: Periodicity
|
||||||
var host: Signer
|
var host: Signer
|
||||||
|
var otherHost: Signer
|
||||||
var hostRewardRecipient: Address
|
var hostRewardRecipient: Address
|
||||||
|
|
||||||
|
proc expectedPayout(r: StorageRequest, startTimestamp: UInt256, endTimestamp: UInt256): UInt256 =
|
||||||
|
return (endTimestamp - startTimestamp) * r.ask.reward
|
||||||
|
|
||||||
proc switchAccount(account: Signer) =
|
proc switchAccount(account: Signer) =
|
||||||
marketplace = marketplace.connect(account)
|
marketplace = marketplace.connect(account)
|
||||||
token = token.connect(account)
|
token = token.connect(account)
|
||||||
|
@ -42,6 +46,7 @@ ethersuite "On-Chain Market":
|
||||||
request = StorageRequest.example
|
request = StorageRequest.example
|
||||||
request.client = accounts[0]
|
request.client = accounts[0]
|
||||||
host = ethProvider.getSigner(accounts[1])
|
host = ethProvider.getSigner(accounts[1])
|
||||||
|
otherHost = ethProvider.getSigner(accounts[3])
|
||||||
|
|
||||||
slotIndex = (request.ask.slots div 2).u256
|
slotIndex = (request.ask.slots div 2).u256
|
||||||
|
|
||||||
|
@ -447,8 +452,11 @@ ethersuite "On-Chain Market":
|
||||||
|
|
||||||
let address = await host.getAddress()
|
let address = await host.getAddress()
|
||||||
switchAccount(host)
|
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 0..<request.ask.slots:
|
for slotIndex in 1..<request.ask.slots:
|
||||||
await market.reserveSlot(request.id, slotIndex.u256)
|
await market.reserveSlot(request.id, slotIndex.u256)
|
||||||
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
|
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
|
||||||
|
|
||||||
|
@ -456,13 +464,11 @@ ethersuite "On-Chain Market":
|
||||||
await ethProvider.advanceTimeTo(requestEnd.u256 + 1)
|
await ethProvider.advanceTimeTo(requestEnd.u256 + 1)
|
||||||
|
|
||||||
let startBalance = await token.balanceOf(address)
|
let startBalance = await token.balanceOf(address)
|
||||||
|
|
||||||
await market.freeSlot(request.slotId(0.u256))
|
await market.freeSlot(request.slotId(0.u256))
|
||||||
|
|
||||||
let endBalance = await token.balanceOf(address)
|
let endBalance = await token.balanceOf(address)
|
||||||
check endBalance == (startBalance +
|
|
||||||
request.ask.duration * request.ask.reward +
|
let expectedPayout = request.expectedPayout(filledAt, requestEnd.u256)
|
||||||
request.ask.collateral)
|
check endBalance == (startBalance + expectedPayout + request.ask.collateral)
|
||||||
|
|
||||||
test "pays rewards to reward recipient, collateral to host":
|
test "pays rewards to reward recipient, collateral to host":
|
||||||
market = OnChainMarket.new(marketplace, hostRewardRecipient.some)
|
market = OnChainMarket.new(marketplace, hostRewardRecipient.some)
|
||||||
|
@ -471,7 +477,11 @@ ethersuite "On-Chain Market":
|
||||||
await market.requestStorage(request)
|
await market.requestStorage(request)
|
||||||
|
|
||||||
switchAccount(host)
|
switchAccount(host)
|
||||||
for slotIndex in 0..<request.ask.slots:
|
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.reserveSlot(request.id, slotIndex.u256)
|
||||||
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
|
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
|
||||||
|
|
||||||
|
@ -486,6 +496,6 @@ ethersuite "On-Chain Market":
|
||||||
let endBalanceHost = await token.balanceOf(hostAddress)
|
let endBalanceHost = await token.balanceOf(hostAddress)
|
||||||
let endBalanceReward = await token.balanceOf(hostRewardRecipient)
|
let endBalanceReward = await token.balanceOf(hostRewardRecipient)
|
||||||
|
|
||||||
|
let expectedPayout = request.expectedPayout(filledAt, requestEnd.u256)
|
||||||
check endBalanceHost == (startBalanceHost + request.ask.collateral)
|
check endBalanceHost == (startBalanceHost + request.ask.collateral)
|
||||||
check endBalanceReward == (startBalanceReward +
|
check endBalanceReward == (startBalanceReward + expectedPayout)
|
||||||
request.ask.duration * request.ask.reward)
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
|
||||||
check reservations.len == 5
|
check reservations.len == 5
|
||||||
check reservations[0].requestId == purchase.requestId
|
check reservations[0].requestId == purchase.requestId
|
||||||
|
|
||||||
test "node slots gets paid out":
|
test "node slots gets paid out and rest of tokens are returned to client":
|
||||||
let size = 0xFFFFFF.u256
|
let size = 0xFFFFFF.u256
|
||||||
let data = await RandomChunker.example(blocks = 8)
|
let data = await RandomChunker.example(blocks = 8)
|
||||||
let marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner())
|
let marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner())
|
||||||
|
@ -55,7 +55,7 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
|
||||||
let nodes = 5'u
|
let nodes = 5'u
|
||||||
|
|
||||||
# client 2 makes storage available
|
# client 2 makes storage available
|
||||||
let startBalance = await token.balanceOf(account2)
|
let startBalanceHost = await token.balanceOf(account2)
|
||||||
discard client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
discard client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
||||||
|
|
||||||
# client 1 requests storage
|
# client 1 requests storage
|
||||||
|
@ -74,12 +74,18 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
|
||||||
let purchase = client1.getPurchase(id).get
|
let purchase = client1.getPurchase(id).get
|
||||||
check purchase.error == none string
|
check purchase.error == none string
|
||||||
|
|
||||||
|
let clientBalanceBeforeFinished = await token.balanceOf(account1)
|
||||||
|
|
||||||
# Proving mechanism uses blockchain clock to do proving/collect/cleanup round
|
# Proving mechanism uses blockchain clock to do proving/collect/cleanup round
|
||||||
# hence we must use `advanceTime` over `sleepAsync` as Hardhat does mine new blocks
|
# hence we must use `advanceTime` over `sleepAsync` as Hardhat does mine new blocks
|
||||||
# only with new transaction
|
# only with new transaction
|
||||||
await ethProvider.advanceTime(duration)
|
await ethProvider.advanceTime(duration)
|
||||||
|
|
||||||
check eventually (await token.balanceOf(account2)) - startBalance == duration*reward*nodes.u256
|
# Checking that the hosting node received reward for at least the time between <expiry;end>
|
||||||
|
check eventually (await token.balanceOf(account2)) - startBalanceHost >= (duration-5*60)*reward*nodes.u256
|
||||||
|
|
||||||
|
# Checking that client node receives some funds back that were not used for the host nodes
|
||||||
|
check eventually (await token.balanceOf(account1)) - clientBalanceBeforeFinished > 0
|
||||||
|
|
||||||
marketplacesuite "Marketplace payouts":
|
marketplacesuite "Marketplace payouts":
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f5a54c7ed4e9d562d984b02169a7e6ab2be973ba
|
Subproject commit 997696a20e0976011cdbc2f0ff3a844672056ba2
|
Loading…
Reference in New Issue