2022-03-23 12:57:48 +00:00
|
|
|
import std/times
|
|
|
|
import pkg/asynctest
|
|
|
|
import pkg/chronos
|
2022-08-18 06:01:47 +00:00
|
|
|
import pkg/upraises
|
2022-03-23 12:57:48 +00:00
|
|
|
import pkg/stint
|
2022-05-19 19:56:03 +00:00
|
|
|
import pkg/codex/purchasing
|
2022-11-08 07:10:17 +00:00
|
|
|
import pkg/codex/purchasing/states/[finished, failed, error, started, submitted, unknown]
|
2022-03-23 12:57:48 +00:00
|
|
|
import ./helpers/mockmarket
|
2022-05-17 14:51:29 +00:00
|
|
|
import ./helpers/mockclock
|
2022-11-08 07:10:17 +00:00
|
|
|
import ./helpers/eventually
|
2022-03-23 12:57:48 +00:00
|
|
|
import ./examples
|
|
|
|
|
|
|
|
suite "Purchasing":
|
|
|
|
|
|
|
|
var purchasing: Purchasing
|
|
|
|
var market: MockMarket
|
2022-05-17 14:51:29 +00:00
|
|
|
var clock: MockClock
|
2022-03-24 09:00:40 +00:00
|
|
|
var request: StorageRequest
|
2022-03-23 12:57:48 +00:00
|
|
|
|
|
|
|
setup:
|
|
|
|
market = MockMarket.new()
|
2022-05-17 14:51:29 +00:00
|
|
|
clock = MockClock.new()
|
|
|
|
purchasing = Purchasing.new(market, clock)
|
2022-03-24 09:00:40 +00:00
|
|
|
request = StorageRequest(
|
2022-04-11 18:03:55 +00:00
|
|
|
ask: StorageAsk(
|
2022-08-02 12:21:12 +00:00
|
|
|
slots: uint8.example.uint64,
|
|
|
|
slotSize: uint32.example.u256,
|
2022-04-11 18:03:55 +00:00
|
|
|
duration: uint16.example.u256,
|
2022-08-02 12:21:12 +00:00
|
|
|
reward: uint8.example.u256
|
2022-04-11 18:03:55 +00:00
|
|
|
)
|
2022-03-24 09:00:40 +00:00
|
|
|
)
|
2022-03-23 12:57:48 +00:00
|
|
|
|
|
|
|
test "submits a storage request when asked":
|
2022-11-08 07:10:17 +00:00
|
|
|
discard await purchasing.purchase(request)
|
2022-03-24 12:48:18 +00:00
|
|
|
let submitted = market.requested[0]
|
2022-08-02 12:21:12 +00:00
|
|
|
check submitted.ask.slots == request.ask.slots
|
|
|
|
check submitted.ask.slotSize == request.ask.slotSize
|
2022-04-11 18:03:55 +00:00
|
|
|
check submitted.ask.duration == request.ask.duration
|
2022-07-20 12:11:00 +00:00
|
|
|
check submitted.ask.reward == request.ask.reward
|
2022-03-23 12:57:48 +00:00
|
|
|
|
2022-05-11 07:14:43 +00:00
|
|
|
test "remembers purchases":
|
2022-11-08 07:10:17 +00:00
|
|
|
let purchase1 = await purchasing.purchase(request)
|
|
|
|
let purchase2 = await purchasing.purchase(request)
|
2022-05-11 07:14:43 +00:00
|
|
|
check purchasing.getPurchase(purchase1.id) == some purchase1
|
|
|
|
check purchasing.getPurchase(purchase2.id) == some purchase2
|
|
|
|
|
2022-03-23 12:57:48 +00:00
|
|
|
test "has a default value for proof probability":
|
|
|
|
check purchasing.proofProbability != 0.u256
|
|
|
|
|
|
|
|
test "can change default value for proof probability":
|
|
|
|
purchasing.proofProbability = 42.u256
|
2022-11-08 07:10:17 +00:00
|
|
|
discard await purchasing.purchase(request)
|
2022-04-11 18:03:55 +00:00
|
|
|
check market.requested[0].ask.proofProbability == 42.u256
|
2022-03-23 12:57:48 +00:00
|
|
|
|
|
|
|
test "can override proof probability per request":
|
2022-04-11 18:03:55 +00:00
|
|
|
request.ask.proofProbability = 42.u256
|
2022-11-08 07:10:17 +00:00
|
|
|
discard await purchasing.purchase(request)
|
2022-04-11 18:03:55 +00:00
|
|
|
check market.requested[0].ask.proofProbability == 42.u256
|
2022-03-23 12:57:48 +00:00
|
|
|
|
|
|
|
test "has a default value for request expiration interval":
|
|
|
|
check purchasing.requestExpiryInterval != 0.u256
|
|
|
|
|
|
|
|
test "can change default value for request expiration interval":
|
|
|
|
purchasing.requestExpiryInterval = 42.u256
|
|
|
|
let start = getTime().toUnix()
|
2022-11-08 07:10:17 +00:00
|
|
|
discard await purchasing.purchase(request)
|
2022-03-24 12:48:18 +00:00
|
|
|
check market.requested[0].expiry == (start + 42).u256
|
2022-03-23 12:57:48 +00:00
|
|
|
|
|
|
|
test "can override expiry time per request":
|
|
|
|
let expiry = (getTime().toUnix() + 42).u256
|
2022-03-24 09:00:40 +00:00
|
|
|
request.expiry = expiry
|
2022-11-08 07:10:17 +00:00
|
|
|
discard await purchasing.purchase(request)
|
2022-03-24 12:48:18 +00:00
|
|
|
check market.requested[0].expiry == expiry
|
2022-03-23 12:57:48 +00:00
|
|
|
|
|
|
|
test "includes a random nonce in every storage request":
|
2022-11-08 07:10:17 +00:00
|
|
|
discard await purchasing.purchase(request)
|
|
|
|
discard await purchasing.purchase(request)
|
2022-03-24 12:48:18 +00:00
|
|
|
check market.requested[0].nonce != market.requested[1].nonce
|
2022-03-28 10:28:22 +00:00
|
|
|
|
2022-11-08 07:10:17 +00:00
|
|
|
test "sets client address in request":
|
|
|
|
discard await purchasing.purchase(request)
|
|
|
|
check market.requested[0].client == await market.getSigner()
|
|
|
|
|
|
|
|
test "succeeds when request is finished":
|
|
|
|
let purchase = await purchasing.purchase(request)
|
2022-03-28 10:28:22 +00:00
|
|
|
let request = market.requested[0]
|
2022-11-08 07:10:17 +00:00
|
|
|
let requestEnd = getTime().toUnix() + 42
|
|
|
|
market.requestEnds[request.id] = requestEnd
|
2022-07-21 12:48:35 +00:00
|
|
|
market.emitRequestFulfilled(request.id)
|
2022-11-08 07:10:17 +00:00
|
|
|
clock.set(requestEnd)
|
2022-03-28 10:28:22 +00:00
|
|
|
await purchase.wait()
|
2022-06-14 13:25:48 +00:00
|
|
|
check purchase.error.isNone
|
2022-03-28 12:40:41 +00:00
|
|
|
|
2022-06-14 13:25:48 +00:00
|
|
|
test "fails when request times out":
|
2022-11-08 07:10:17 +00:00
|
|
|
let purchase = await purchasing.purchase(request)
|
2022-03-28 12:40:41 +00:00
|
|
|
let request = market.requested[0]
|
2022-05-18 08:19:32 +00:00
|
|
|
clock.set(request.expiry.truncate(int64))
|
2022-06-14 13:25:48 +00:00
|
|
|
expect PurchaseTimeout:
|
|
|
|
await purchase.wait()
|
2022-08-18 06:01:47 +00:00
|
|
|
|
2022-09-07 11:02:00 +00:00
|
|
|
test "checks that funds were withdrawn when purchase times out":
|
2022-11-08 07:10:17 +00:00
|
|
|
let purchase = await purchasing.purchase(request)
|
2022-08-18 06:01:47 +00:00
|
|
|
let request = market.requested[0]
|
|
|
|
clock.set(request.expiry.truncate(int64))
|
2022-11-08 07:10:17 +00:00
|
|
|
expect PurchaseTimeout:
|
|
|
|
await purchase.wait()
|
|
|
|
check market.withdrawn == @[request.id]
|
2022-09-07 11:02:00 +00:00
|
|
|
|
2022-11-08 07:10:17 +00:00
|
|
|
suite "Purchasing state machine":
|
2022-09-07 11:02:00 +00:00
|
|
|
|
2022-11-08 07:10:17 +00:00
|
|
|
var purchasing: Purchasing
|
|
|
|
var market: MockMarket
|
|
|
|
var clock: MockClock
|
|
|
|
var request: StorageRequest
|
|
|
|
|
|
|
|
setup:
|
|
|
|
market = MockMarket.new()
|
|
|
|
clock = MockClock.new()
|
|
|
|
purchasing = Purchasing.new(market, clock)
|
|
|
|
request = StorageRequest(
|
|
|
|
ask: StorageAsk(
|
|
|
|
slots: uint8.example.uint64,
|
|
|
|
slotSize: uint32.example.u256,
|
|
|
|
duration: uint16.example.u256,
|
|
|
|
reward: uint8.example.u256
|
|
|
|
)
|
|
|
|
)
|
2022-09-07 11:02:00 +00:00
|
|
|
|
2022-11-08 07:10:17 +00:00
|
|
|
test "loads active purchases from market":
|
|
|
|
let me = await market.getSigner()
|
|
|
|
let request1, request2, request3 = StorageRequest.example
|
|
|
|
market.requested = @[request1, request2, request3]
|
|
|
|
market.activeRequests[me] = @[request1.id, request2.id]
|
|
|
|
await purchasing.load()
|
|
|
|
check isSome purchasing.getPurchase(PurchaseId(request1.id))
|
|
|
|
check isSome purchasing.getPurchase(PurchaseId(request2.id))
|
|
|
|
check isNone purchasing.getPurchase(PurchaseId(request3.id))
|
|
|
|
|
|
|
|
test "loads correct purchase.future state for purchases from market":
|
|
|
|
let me = await market.getSigner()
|
|
|
|
let request1, request2, request3, request4, request5 = StorageRequest.example
|
|
|
|
market.requested = @[request1, request2, request3, request4, request5]
|
|
|
|
market.activeRequests[me] = @[request1.id, request2.id, request3.id, request4.id, request5.id]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request1.id] = RequestState.New
|
|
|
|
market.requestState[request2.id] = RequestState.Started
|
|
|
|
market.requestState[request3.id] = RequestState.Cancelled
|
|
|
|
market.requestState[request4.id] = RequestState.Finished
|
|
|
|
market.requestState[request5.id] = RequestState.Failed
|
2022-11-08 07:10:17 +00:00
|
|
|
|
|
|
|
# ensure the started state doesn't error, giving a false positive test result
|
|
|
|
market.requestEnds[request2.id] = clock.now() - 1
|
|
|
|
|
|
|
|
await purchasing.load()
|
|
|
|
check purchasing.getPurchase(PurchaseId(request1.id)).?finished == false.some
|
|
|
|
check purchasing.getPurchase(PurchaseId(request2.id)).?finished == true.some
|
|
|
|
check purchasing.getPurchase(PurchaseId(request3.id)).?finished == true.some
|
|
|
|
check purchasing.getPurchase(PurchaseId(request4.id)).?finished == true.some
|
|
|
|
check purchasing.getPurchase(PurchaseId(request5.id)).?finished == true.some
|
|
|
|
check purchasing.getPurchase(PurchaseId(request5.id)).?error.isSome
|
|
|
|
|
|
|
|
test "moves to PurchaseSubmitted when request state is New":
|
|
|
|
let request = StorageRequest.example
|
|
|
|
let purchase = Purchase.new(request, market, clock)
|
|
|
|
market.requested = @[request]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request.id] = RequestState.New
|
2022-11-08 07:10:17 +00:00
|
|
|
purchase.switch(PurchaseUnknown())
|
|
|
|
check (purchase.state as PurchaseSubmitted).isSome
|
|
|
|
|
|
|
|
test "moves to PurchaseStarted when request state is Started":
|
|
|
|
let request = StorageRequest.example
|
|
|
|
let purchase = Purchase.new(request, market, clock)
|
|
|
|
market.requestEnds[request.id] = clock.now() + request.ask.duration.truncate(int64)
|
|
|
|
market.requested = @[request]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request.id] = RequestState.Started
|
2022-11-08 07:10:17 +00:00
|
|
|
purchase.switch(PurchaseUnknown())
|
|
|
|
check (purchase.state as PurchaseStarted).isSome
|
|
|
|
|
|
|
|
test "moves to PurchaseErrored when request state is Cancelled":
|
|
|
|
let request = StorageRequest.example
|
|
|
|
let purchase = Purchase.new(request, market, clock)
|
|
|
|
market.requested = @[request]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request.id] = RequestState.Cancelled
|
2022-11-08 07:10:17 +00:00
|
|
|
purchase.switch(PurchaseUnknown())
|
|
|
|
check (purchase.state as PurchaseErrored).isSome
|
|
|
|
check purchase.error.?msg == "Purchase cancelled due to timeout".some
|
|
|
|
|
|
|
|
test "moves to PurchaseFinished when request state is Finished":
|
|
|
|
let request = StorageRequest.example
|
|
|
|
let purchase = Purchase.new(request, market, clock)
|
|
|
|
market.requested = @[request]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request.id] = RequestState.Finished
|
2022-11-08 07:10:17 +00:00
|
|
|
purchase.switch(PurchaseUnknown())
|
|
|
|
check (purchase.state as PurchaseFinished).isSome
|
|
|
|
|
|
|
|
test "moves to PurchaseErrored when request state is Failed":
|
|
|
|
let request = StorageRequest.example
|
|
|
|
let purchase = Purchase.new(request, market, clock)
|
|
|
|
market.requested = @[request]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request.id] = RequestState.Failed
|
2022-11-08 07:10:17 +00:00
|
|
|
purchase.switch(PurchaseUnknown())
|
|
|
|
check (purchase.state as PurchaseErrored).isSome
|
|
|
|
check purchase.error.?msg == "Purchase failed".some
|
|
|
|
|
|
|
|
test "moves to PurchaseErrored state once RequestFailed emitted":
|
|
|
|
let me = await market.getSigner()
|
|
|
|
let request = StorageRequest.example
|
|
|
|
market.requested = @[request]
|
|
|
|
market.activeRequests[me] = @[request.id]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request.id] = RequestState.Started
|
2022-11-08 07:10:17 +00:00
|
|
|
market.requestEnds[request.id] = clock.now() + request.ask.duration.truncate(int64)
|
|
|
|
await purchasing.load()
|
|
|
|
|
|
|
|
# emit mock contract failure event
|
|
|
|
market.emitRequestFailed(request.id)
|
|
|
|
# must allow time for the callback to trigger the completion of the future
|
|
|
|
await sleepAsync(chronos.milliseconds(10))
|
|
|
|
|
|
|
|
# now check the result
|
|
|
|
let purchase = purchasing.getPurchase(PurchaseId(request.id))
|
|
|
|
let state = purchase.?state
|
|
|
|
check (state as PurchaseErrored).isSome
|
|
|
|
check (!purchase).error.?msg == "Purchase failed".some
|
|
|
|
|
|
|
|
test "moves to PurchaseFinished state once request finishes":
|
|
|
|
let me = await market.getSigner()
|
|
|
|
let request = StorageRequest.example
|
|
|
|
market.requested = @[request]
|
|
|
|
market.activeRequests[me] = @[request.id]
|
2023-02-07 06:26:34 +00:00
|
|
|
market.requestState[request.id] = RequestState.Started
|
2022-11-08 07:10:17 +00:00
|
|
|
market.requestEnds[request.id] = clock.now() + request.ask.duration.truncate(int64)
|
|
|
|
await purchasing.load()
|
|
|
|
|
|
|
|
# advance the clock to the end of the request
|
|
|
|
clock.advance(request.ask.duration.truncate(int64))
|
|
|
|
|
|
|
|
# now check the result
|
2023-01-23 04:55:49 +00:00
|
|
|
proc requestState: ?PurchaseState =
|
2022-11-08 07:10:17 +00:00
|
|
|
purchasing.getPurchase(PurchaseId(request.id)).?state as PurchaseState
|
|
|
|
|
2023-01-23 04:55:49 +00:00
|
|
|
check eventually (requestState() as PurchaseFinished).isSome
|