Skip to content

Commit

Permalink
feat: partial rewards and withdraws (#880)
Browse files Browse the repository at this point in the history
* feat: partial rewards and withdraws

* test: missing reserve slot

* test: fix contracts
  • Loading branch information
AuHau authored Oct 10, 2024
1 parent 3699601 commit ffa203b
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 18 deletions.
5 changes: 5 additions & 0 deletions codex/purchasing/states/failed.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pkg/metrics
import ../statemachine
import ../../logutils
import ./error

declareCounter(codex_purchases_failed, "codex purchases failed")
Expand All @@ -12,5 +13,9 @@ method `$`*(state: PurchaseFailed): string =

method run*(state: PurchaseFailed, machine: Machine): Future[?State] {.async.} =
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")
return some State(PurchaseErrored(error: error))
4 changes: 3 additions & 1 deletion codex/purchasing/states/finished.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ method `$`*(state: PurchaseFinished): string =
method run*(state: PurchaseFinished, machine: Machine): Future[?State] {.async.} =
codex_purchases_finished.inc()
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()
4 changes: 3 additions & 1 deletion tests/codex/helpers/mockmarket.nim
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,9 @@ method freeSlot*(market: MockMarket, slotId: SlotId) {.async.} =
method withdrawFunds*(market: MockMarket,
requestId: RequestId) {.async.} =
market.withdrawn.add(requestId)
market.emitRequestCancelled(requestId)

if state =? market.requestState.?[requestId] and state == RequestState.Cancelled:
market.emitRequestCancelled(requestId)

proc setProofRequired*(mock: MockMarket, id: SlotId, required: bool) =
if required:
Expand Down
18 changes: 18 additions & 0 deletions tests/codex/testpurchasing.nim
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,21 @@ checksuite "Purchasing state machine":

let next = await future
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
10 changes: 7 additions & 3 deletions tests/contracts/testContracts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ ethersuite "Marketplace contracts":
var periodicity: Periodicity
var request: StorageRequest
var slotId: SlotId
var filledAt: UInt256

proc expectedPayout(endTimestamp: UInt256): UInt256 =
return (endTimestamp - filledAt) * request.ask.reward

proc switchAccount(account: Signer) =
marketplace = marketplace.connect(account)
Expand Down Expand Up @@ -46,6 +50,7 @@ ethersuite "Marketplace contracts":
switchAccount(host)
discard await token.approve(marketplace.address, request.ask.collateral)
discard await marketplace.reserveSlot(request.id, 0.u256)
filledAt = await ethProvider.currentTime()
discard await marketplace.fillSlot(request.id, 0.u256, proof)
slotId = request.slotId(0.u256)

Expand Down Expand Up @@ -87,8 +92,7 @@ ethersuite "Marketplace contracts":
let startBalance = await token.balanceOf(address)
discard await marketplace.freeSlot(slotId)
let endBalance = await token.balanceOf(address)

check endBalance == (startBalance + request.ask.duration * request.ask.reward + request.ask.collateral)
check endBalance == (startBalance + expectedPayout(requestEnd.u256) + request.ask.collateral)

test "can be paid out at the end, specifying reward and collateral recipient":
switchAccount(host)
Expand All @@ -105,7 +109,7 @@ ethersuite "Marketplace contracts":
let endBalanceCollateral = await token.balanceOf(collateralRecipient)

check endBalanceHost == startBalanceHost
check endBalanceReward == (startBalanceReward + request.ask.duration * request.ask.reward)
check endBalanceReward == (startBalanceReward + expectedPayout(requestEnd.u256))
check endBalanceCollateral == (startBalanceCollateral + request.ask.collateral)

test "cannot mark proofs missing for cancelled request":
Expand Down
28 changes: 19 additions & 9 deletions tests/contracts/testMarket.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ ethersuite "On-Chain Market":
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)
Expand All @@ -42,6 +46,7 @@ ethersuite "On-Chain Market":
request = StorageRequest.example
request.client = accounts[0]
host = ethProvider.getSigner(accounts[1])
otherHost = ethProvider.getSigner(accounts[3])

slotIndex = (request.ask.slots div 2).u256

Expand Down Expand Up @@ -447,22 +452,23 @@ ethersuite "On-Chain Market":

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 0..<request.ask.slots:
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)
check endBalance == (startBalance +
request.ask.duration * request.ask.reward +
request.ask.collateral)

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)
Expand All @@ -471,7 +477,11 @@ ethersuite "On-Chain Market":
await market.requestStorage(request)

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.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)

Expand All @@ -486,6 +496,6 @@ ethersuite "On-Chain Market":
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 +
request.ask.duration * request.ask.reward)
check endBalanceReward == (startBalanceReward + expectedPayout)
12 changes: 9 additions & 3 deletions tests/integration/testmarketplace.nim
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
check reservations.len == 5
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 data = await RandomChunker.example(blocks = 8)
let marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner())
Expand All @@ -55,7 +55,7 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
let nodes = 5'u

# 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

# client 1 requests storage
Expand All @@ -74,12 +74,18 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
let purchase = client1.getPurchase(id).get
check purchase.error == none string

let clientBalanceBeforeFinished = await token.balanceOf(account1)

# Proving mechanism uses blockchain clock to do proving/collect/cleanup round
# hence we must use `advanceTime` over `sleepAsync` as Hardhat does mine new blocks
# only with new transaction
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":

Expand Down
2 changes: 1 addition & 1 deletion vendor/codex-contracts-eth

0 comments on commit ffa203b

Please sign in to comment.