feat: collateral per slot (#390)

Co-authored-by: Eric Mastro <github@egonat.me>
This commit is contained in:
Adam Uhlíř 2023-04-14 11:04:17 +02:00 committed by GitHub
parent 86a3f74448
commit 131d003a0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 130 additions and 214 deletions

View File

@ -32,33 +32,6 @@ let client = provider.getSigner(accounts[0])
let host = provider.getSigner(accounts[1]) let host = provider.getSigner(accounts[1])
``` ```
Collateral
----------
Hosts need to put up collateral before participating in storage contracts.
A host can learn about the amount of collateral that is required:
```nim
let config = await marketplace.config()
let collateral = config.collateral.initialAmount
```
After preparing the payment, the host can deposit collateral:
```nim
await storage
.connect(host)
.deposit(collateral)
```
When a host is not participating in storage offers or contracts, it can withdraw
its collateral:
```
await storage
.connect(host)
.withdraw()
```
Storage requests Storage requests
---------------- ----------------

View File

@ -9,10 +9,10 @@ type
collateral*: CollateralConfig collateral*: CollateralConfig
proofs*: ProofConfig proofs*: ProofConfig
CollateralConfig* = object CollateralConfig* = object
initialAmount*: UInt256 # amount of collateral necessary to fill a slot repairRewardPercentage*: uint8 # percentage of remaining collateral slot has after it has been freed
minimumAmount*: UInt256 # frees slot when collateral drops below this minimum maxNumberOfSlashes*: uint8 # frees slot when the number of slashes reaches this value
slashCriterion*: UInt256 # amount of proofs missed that lead to slashing slashCriterion*: uint16 # amount of proofs missed that lead to slashing
slashPercentage*: UInt256 # percentage of the collateral that is slashed slashPercentage*: uint8 # percentage of the collateral that is slashed
ProofConfig* = object ProofConfig* = object
period*: UInt256 # proofs requirements are calculated per period (in seconds) period*: UInt256 # proofs requirements are calculated per period (in seconds)
timeout*: UInt256 # mark proofs as missing before the timeout (in seconds) timeout*: UInt256 # mark proofs as missing before the timeout (in seconds)
@ -28,8 +28,8 @@ func fromTuple(_: type ProofConfig, tupl: tuple): ProofConfig =
func fromTuple(_: type CollateralConfig, tupl: tuple): CollateralConfig = func fromTuple(_: type CollateralConfig, tupl: tuple): CollateralConfig =
CollateralConfig( CollateralConfig(
initialAmount: tupl[0], repairRewardPercentage: tupl[0],
minimumAmount: tupl[1], maxNumberOfSlashes: tupl[1],
slashCriterion: tupl[2], slashCriterion: tupl[2],
slashPercentage: tupl[3] slashPercentage: tupl[3]
) )

View File

@ -1,4 +1,5 @@
import std/strutils import std/strutils
import pkg/chronicles
import pkg/ethers import pkg/ethers
import pkg/ethers/testing import pkg/ethers/testing
import pkg/upraises import pkg/upraises
@ -8,6 +9,9 @@ import ./marketplace
export market export market
logScope:
topics = "onchain market"
type type
OnChainMarket* = ref object of Market OnChainMarket* = ref object of Market
contract: Marketplace contract: Marketplace
@ -25,7 +29,8 @@ func new*(_: type OnChainMarket, contract: Marketplace): OnChainMarket =
signer: signer, signer: signer,
) )
method approveFunds*(market: OnChainMarket, amount: UInt256) {.async.} = proc approveFunds(market: OnChainMarket, amount: UInt256) {.async.} =
notice "approving tokens", amount
let tokenAddress = await market.contract.token() let tokenAddress = await market.contract.token()
let token = Erc20Token.new(tokenAddress, market.signer) let token = Erc20Token.new(tokenAddress, market.signer)
@ -41,6 +46,7 @@ method mySlots*(market: OnChainMarket): Future[seq[SlotId]] {.async.} =
return await market.contract.mySlots() return await market.contract.mySlots()
method requestStorage(market: OnChainMarket, request: StorageRequest){.async.} = method requestStorage(market: OnChainMarket, request: StorageRequest){.async.} =
await market.approveFunds(request.price())
await market.contract.requestStorage(request) await market.contract.requestStorage(request)
method getRequest(market: OnChainMarket, method getRequest(market: OnChainMarket,
@ -93,7 +99,9 @@ method getActiveSlot*(
method fillSlot(market: OnChainMarket, method fillSlot(market: OnChainMarket,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256, slotIndex: UInt256,
proof: seq[byte]) {.async.} = proof: seq[byte],
collateral: UInt256) {.async.} =
await market.approveFunds(collateral)
await market.contract.fillSlot(requestId, slotIndex, proof) await market.contract.fillSlot(requestId, slotIndex, proof)
method withdrawFunds(market: OnChainMarket, method withdrawFunds(market: OnChainMarket,

View File

@ -38,10 +38,6 @@ proc slashMisses*(marketplace: Marketplace): UInt256 {.contract, view.}
proc slashPercentage*(marketplace: Marketplace): UInt256 {.contract, view.} proc slashPercentage*(marketplace: Marketplace): UInt256 {.contract, view.}
proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view.} proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view.}
proc deposit*(marketplace: Marketplace, amount: UInt256) {.contract.}
proc withdraw*(marketplace: Marketplace) {.contract.}
proc balanceOf*(marketplace: Marketplace, account: Address): UInt256 {.contract, view.}
proc requestStorage*(marketplace: Marketplace, request: StorageRequest) {.contract.} proc requestStorage*(marketplace: Marketplace, request: StorageRequest) {.contract.}
proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: seq[byte]) {.contract.} proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: seq[byte]) {.contract.}
proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId) {.contract.} proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId) {.contract.}

View File

@ -19,6 +19,7 @@ type
duration*: UInt256 duration*: UInt256
proofProbability*: UInt256 proofProbability*: UInt256
reward*: UInt256 reward*: UInt256
collateral*: UInt256
maxSlotLoss*: uint64 maxSlotLoss*: uint64
StorageContent* = object StorageContent* = object
cid*: string cid*: string
@ -84,7 +85,8 @@ func fromTuple(_: type StorageAsk, tupl: tuple): StorageAsk =
duration: tupl[2], duration: tupl[2],
proofProbability: tupl[3], proofProbability: tupl[3],
reward: tupl[4], reward: tupl[4],
maxSlotLoss: tupl[5] collateral: tupl[5],
maxSlotLoss: tupl[6]
) )
func fromTuple(_: type StorageContent, tupl: tuple): StorageContent = func fromTuple(_: type StorageContent, tupl: tuple): StorageContent =

View File

@ -19,10 +19,6 @@ type
OnRequestCancelled* = proc(requestId: RequestId) {.gcsafe, upraises:[].} OnRequestCancelled* = proc(requestId: RequestId) {.gcsafe, upraises:[].}
OnRequestFailed* = proc(requestId: RequestId) {.gcsafe, upraises:[].} OnRequestFailed* = proc(requestId: RequestId) {.gcsafe, upraises:[].}
method approveFunds*(market: Market, amount: UInt256) {.base, async.} =
## This is generally needed for On-Chain ERC20 functionality to approve funds transfer to marketplace contract
raiseAssert("not implemented")
method getSigner*(market: Market): Future[Address] {.base, async.} = method getSigner*(market: Market): Future[Address] {.base, async.} =
raiseAssert("not implemented") raiseAssert("not implemented")
@ -67,7 +63,8 @@ method getActiveSlot*(
method fillSlot*(market: Market, method fillSlot*(market: Market,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256, slotIndex: UInt256,
proof: seq[byte]) {.base, async.} = proof: seq[byte],
collateral: UInt256) {.base, async.} =
raiseAssert("not implemented") raiseAssert("not implemented")
method withdrawFunds*(market: Market, method withdrawFunds*(market: Market,

View File

@ -244,6 +244,7 @@ proc requestStorage*(self: CodexNodeRef,
nodes: uint, nodes: uint,
tolerance: uint, tolerance: uint,
reward: UInt256, reward: UInt256,
collateral: UInt256,
expiry = UInt256.none): Future[?!PurchaseId] {.async.} = expiry = UInt256.none): Future[?!PurchaseId] {.async.} =
## Initiate a request for storage sequence, this might ## Initiate a request for storage sequence, this might
## be a multistep procedure. ## be a multistep procedure.
@ -288,6 +289,7 @@ proc requestStorage*(self: CodexNodeRef,
duration: duration, duration: duration,
proofProbability: proofProbability, proofProbability: proofProbability,
reward: reward, reward: reward,
collateral: collateral,
maxSlotLoss: tolerance maxSlotLoss: tolerance
), ),
content: StorageContent( content: StorageContent(

View File

@ -10,7 +10,6 @@ method enterAsync(state: PurchasePending) {.async.} =
raiseAssert "invalid state" raiseAssert "invalid state"
try: try:
await purchase.market.approveFunds(request.price())
await purchase.market.requestStorage(request) await purchase.market.requestStorage(request)
except CatchableError as error: except CatchableError as error:
state.switch(PurchaseErrored(error: error)) state.switch(PurchaseErrored(error: error))

View File

@ -140,6 +140,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
## expiry - timestamp, in seconds, when the request expires if the Request does not find requested amount of nodes to host the data ## expiry - timestamp, in seconds, when the request expires if the Request does not find requested amount of nodes to host the data
## nodes - minimal number of nodes the content should be stored on ## nodes - minimal number of nodes the content should be stored on
## tolerance - allowed number of nodes that can be lost before pronouncing the content lost ## tolerance - allowed number of nodes that can be lost before pronouncing the content lost
## colateral - requested collateral from hosts when they fill slot
without cid =? cid.tryGet.catch, error: without cid =? cid.tryGet.catch, error:
return RestApiResponse.error(Http400, error.msg) return RestApiResponse.error(Http400, error.msg)
@ -159,6 +160,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
nodes, nodes,
tolerance, tolerance,
params.reward, params.reward,
params.collateral,
params.expiry), error: params.expiry), error:
return RestApiResponse.error(Http500, error.msg) return RestApiResponse.error(Http500, error.msg)
@ -272,6 +274,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
## size - size of available storage in bytes ## size - size of available storage in bytes
## duration - maximum time the storage should be sold for (in seconds) ## duration - maximum time the storage should be sold for (in seconds)
## minPrice - minimum price to be paid (in amount of tokens) ## minPrice - minimum price to be paid (in amount of tokens)
## maxCollateral - maximum collateral user is willing to pay per filled Slot (in amount of tokens)
without contracts =? node.contracts.host: without contracts =? node.contracts.host:
return RestApiResponse.error(Http503, "Sales unavailable") return RestApiResponse.error(Http503, "Sales unavailable")

View File

@ -10,6 +10,7 @@ type
duration*: UInt256 duration*: UInt256
proofProbability*: UInt256 proofProbability*: UInt256
reward*: UInt256 reward*: UInt256
collateral*: UInt256
expiry*: ?UInt256 expiry*: ?UInt256
nodes*: ?uint nodes*: ?uint
tolerance*: ?uint tolerance*: ?uint
@ -19,7 +20,8 @@ proc fromJson*(_: type Availability, bytes: seq[byte]): ?!Availability =
let size = ?catch UInt256.fromHex(json["size"].getStr) let size = ?catch UInt256.fromHex(json["size"].getStr)
let duration = ?catch UInt256.fromHex(json["duration"].getStr) let duration = ?catch UInt256.fromHex(json["duration"].getStr)
let minPrice = ?catch UInt256.fromHex(json["minPrice"].getStr) let minPrice = ?catch UInt256.fromHex(json["minPrice"].getStr)
success Availability.init(size, duration, minPrice) let maxCollateral = ?catch UInt256.fromHex(json["maxCollateral"].getStr)
success Availability.init(size, duration, minPrice, maxCollateral)
proc fromJson*(_: type StorageRequestParams, proc fromJson*(_: type StorageRequestParams,
bytes: seq[byte]): ?! StorageRequestParams = bytes: seq[byte]): ?! StorageRequestParams =
@ -27,6 +29,7 @@ proc fromJson*(_: type StorageRequestParams,
let duration = ?catch UInt256.fromHex(json["duration"].getStr) let duration = ?catch UInt256.fromHex(json["duration"].getStr)
let proofProbability = ?catch UInt256.fromHex(json["proofProbability"].getStr) let proofProbability = ?catch UInt256.fromHex(json["proofProbability"].getStr)
let reward = ?catch UInt256.fromHex(json["reward"].getStr) let reward = ?catch UInt256.fromHex(json["reward"].getStr)
let collateral = ?catch UInt256.fromHex(json["collateral"].getStr)
let expiry = UInt256.fromHex(json["expiry"].getStr).catch.option let expiry = UInt256.fromHex(json["expiry"].getStr).catch.option
let nodes = strutils.fromHex[uint](json["nodes"].getStr).catch.option let nodes = strutils.fromHex[uint](json["nodes"].getStr).catch.option
let tolerance = strutils.fromHex[uint](json["tolerance"].getStr).catch.option let tolerance = strutils.fromHex[uint](json["tolerance"].getStr).catch.option
@ -34,6 +37,7 @@ proc fromJson*(_: type StorageRequestParams,
duration: duration, duration: duration,
proofProbability: proofProbability, proofProbability: proofProbability,
reward: reward, reward: reward,
collateral: collateral,
expiry: expiry, expiry: expiry,
nodes: nodes, nodes: nodes,
tolerance: tolerance tolerance: tolerance

View File

@ -38,6 +38,7 @@ type
size*: UInt256 size*: UInt256
duration*: UInt256 duration*: UInt256
minPrice*: UInt256 minPrice*: UInt256
maxCollateral*: UInt256
used*: bool used*: bool
Reservations* = ref object Reservations* = ref object
repo: RepoStore repo: RepoStore
@ -67,11 +68,12 @@ proc init*(
_: type Availability, _: type Availability,
size: UInt256, size: UInt256,
duration: UInt256, duration: UInt256,
minPrice: UInt256): Availability = minPrice: UInt256,
maxCollateral: UInt256): Availability =
var id: array[32, byte] var id: array[32, byte]
doAssert randomBytes(id) == 32 doAssert randomBytes(id) == 32
Availability(id: AvailabilityId(id), size: size, duration: duration, minPrice: minPrice) Availability(id: AvailabilityId(id), size: size, duration: duration, minPrice: minPrice, maxCollateral: maxCollateral)
func toArray*(id: AvailabilityId): array[32, byte] = func toArray*(id: AvailabilityId): array[32, byte] =
array[32, byte](id) array[32, byte](id)
@ -81,6 +83,7 @@ proc `==`*(x, y: Availability): bool =
x.id == y.id and x.id == y.id and
x.size == y.size and x.size == y.size and
x.duration == y.duration and x.duration == y.duration and
x.maxCollateral == y.maxCollateral and
x.minPrice == y.minPrice x.minPrice == y.minPrice
proc `$`*(id: AvailabilityId): string = id.toArray.toHex proc `$`*(id: AvailabilityId): string = id.toArray.toHex
@ -318,7 +321,7 @@ proc unused*(r: Reservations): Future[?!seq[Availability]] {.async.} =
proc find*( proc find*(
self: Reservations, self: Reservations,
size, duration, minPrice: UInt256, size, duration, minPrice: UInt256, collateral: UInt256,
used: bool): Future[?Availability] {.async.} = used: bool): Future[?Availability] {.async.} =
@ -332,13 +335,15 @@ proc find*(
if used == availability.used and if used == availability.used and
size <= availability.size and size <= availability.size and
duration <= availability.duration and duration <= availability.duration and
collateral <= availability.maxCollateral and
minPrice >= availability.minPrice: minPrice >= availability.minPrice:
trace "availability matched", trace "availability matched",
used, availUsed = availability.used, used, availUsed = availability.used,
size, availsize = availability.size, size, availsize = availability.size,
duration, availDuration = availability.duration, duration, availDuration = availability.duration,
minPrice, availMinPrice = availability.minPrice minPrice, availMinPrice = availability.minPrice,
collateral, availMaxCollateral = availability.maxCollateral
return some availability return some availability
@ -346,4 +351,5 @@ proc find*(
used, availUsed = availability.used, used, availUsed = availability.used,
size, availsize = availability.size, size, availsize = availability.size,
duration, availDuration = availability.duration, duration, availDuration = availability.duration,
minPrice, availMinPrice = availability.minPrice minPrice, availMinPrice = availability.minPrice,
collateral, availMaxCollateral = availability.maxCollateral

View File

@ -50,6 +50,7 @@ method run*(state: SaleDownloading, machine: Machine): Future[?State] {.async.}
request.ask.slotSize, request.ask.slotSize,
request.ask.duration, request.ask.duration,
request.ask.pricePerSlot, request.ask.pricePerSlot,
request.ask.collateral,
used = false): used = false):
info "no availability found for request, ignoring", info "no availability found for request, ignoring",
slotSize = request.ask.slotSize, slotSize = request.ask.slotSize,

View File

@ -25,5 +25,7 @@ method onSlotFilled*(state: SaleFilling, requestId: RequestId,
method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} = method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} =
let data = SalesAgent(machine).data let data = SalesAgent(machine).data
let market = SalesAgent(machine).context.market let market = SalesAgent(machine).context.market
without (collateral =? data.request.?ask.?collateral):
raiseAssert "Request not set"
await market.fillSlot(data.requestId, data.slotIndex, state.proof) await market.fillSlot(data.requestId, data.slotIndex, state.proof, collateral)

View File

@ -112,6 +112,9 @@ components:
minPrice: minPrice:
type: string type: string
description: Minimum price to be paid (in amount of tokens) as hexadecimal string description: Minimum price to be paid (in amount of tokens) as hexadecimal string
maxCollateral:
type: string
description: Maximum collateral user is willing to pay per filled Slot (in amount of tokens)
StorageRequestCreation: StorageRequestCreation:
type: object type: object
@ -119,6 +122,7 @@ components:
- reward - reward
- duration - duration
- proofProbability - proofProbability
- collateral
properties: properties:
duration: duration:
$ref: "#/components/schemas/Duration" $ref: "#/components/schemas/Duration"
@ -134,6 +138,9 @@ components:
type: number type: number
description: Additional number of nodes on top of the `nodes` property that can be lost before pronouncing the content lost description: Additional number of nodes on top of the `nodes` property that can be lost before pronouncing the content lost
default: 0 default: 0
collateral:
type: string
description: Hexadecimal encoded number that represents how much collateral is asked from hosts that wants to fill a slots
StorageAsk: StorageAsk:
type: object type: object

View File

@ -57,5 +57,6 @@ proc example*(_: type Availability): Availability =
Availability.init( Availability.init(
size = uint16.example.u256, size = uint16.example.u256,
duration = uint16.example.u256, duration = uint16.example.u256,
minPrice = uint64.example.u256 minPrice = uint64.example.u256,
maxCollateral = uint16.example.u256
) )

View File

@ -67,10 +67,10 @@ proc hash*(requestId: RequestId): Hash =
proc new*(_: type MockMarket): MockMarket = proc new*(_: type MockMarket): MockMarket =
let config = MarketplaceConfig( let config = MarketplaceConfig(
collateral: CollateralConfig( collateral: CollateralConfig(
initialAmount: 100.u256, repairRewardPercentage: 10,
minimumAmount: 40.u256, maxNumberOfSlashes: 5,
slashCriterion: 3.u256, slashCriterion: 3,
slashPercentage: 10.u256 slashPercentage: 10
), ),
proofs: ProofConfig( proofs: ProofConfig(
period: 10.u256, period: 10.u256,
@ -80,9 +80,6 @@ proc new*(_: type MockMarket): MockMarket =
) )
MockMarket(signer: Address.example, config: config) MockMarket(signer: Address.example, config: config)
method approveFunds*(market: MockMarket, amount: UInt256) {.async.} =
discard
method getSigner*(market: MockMarket): Future[Address] {.async.} = method getSigner*(market: MockMarket): Future[Address] {.async.} =
return market.signer return market.signer
@ -182,7 +179,8 @@ proc fillSlot*(market: MockMarket,
method fillSlot*(market: MockMarket, method fillSlot*(market: MockMarket,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256, slotIndex: UInt256,
proof: seq[byte]) {.async.} = proof: seq[byte],
collateral: UInt256) {.async.} =
market.fillSlot(requestId, slotIndex, proof, market.signer) market.fillSlot(requestId, slotIndex, proof, market.signer)
method withdrawFunds*(market: MockMarket, method withdrawFunds*(market: MockMarket,

View File

@ -39,8 +39,8 @@ suite "Reservations module":
check (await reservations.allAvailabilities()).len == 0 check (await reservations.allAvailabilities()).len == 0
test "generates unique ids for storage availability": test "generates unique ids for storage availability":
let availability1 = Availability.init(1.u256, 2.u256, 3.u256) let availability1 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256)
let availability2 = Availability.init(1.u256, 2.u256, 3.u256) let availability2 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256)
check availability1.id != availability2.id check availability1.id != availability2.id
test "can reserve available storage": test "can reserve available storage":
@ -125,7 +125,7 @@ suite "Reservations module":
check isOk await reservations.markUsed(availability.id) check isOk await reservations.markUsed(availability.id)
without available =? await reservations.find(availability.size, without available =? await reservations.find(availability.size,
availability.duration, availability.minPrice, used = true): availability.duration, availability.minPrice, availability.maxCollateral, used = true):
fail() fail()
@ -133,13 +133,13 @@ suite "Reservations module":
check isOk await reservations.reserve(availability) check isOk await reservations.reserve(availability)
without available =? await reservations.find(availability.size, without available =? await reservations.find(availability.size,
availability.duration, availability.minPrice, used = false): availability.duration, availability.minPrice, availability.maxCollateral, used = false):
fail() fail()
test "non-existant availability cannot be found": test "non-existant availability cannot be found":
check isNone (await reservations.find(availability.size, check isNone (await reservations.find(availability.size,
availability.duration, availability.minPrice, used = false)) availability.duration, availability.minPrice, availability.maxCollateral, used = false))
test "non-existant availability cannot be retrieved": test "non-existant availability cannot be retrieved":
let r = await reservations.get(availability.id) let r = await reservations.get(availability.id)

View File

@ -38,7 +38,8 @@ suite "Sales":
availability = Availability.init( availability = Availability.init(
size=100.u256, size=100.u256,
duration=60.u256, duration=60.u256,
minPrice=600.u256 minPrice=600.u256,
maxCollateral=400.u256
) )
request = StorageRequest( request = StorageRequest(
ask: StorageAsk( ask: StorageAsk(
@ -46,6 +47,7 @@ suite "Sales":
slotSize: 100.u256, slotSize: 100.u256,
duration: 60.u256, duration: 60.u256,
reward: 10.u256, reward: 10.u256,
collateral: 200.u256,
), ),
content: StorageContent( content: StorageContent(
cid: "some cid" cid: "some cid"
@ -134,6 +136,13 @@ suite "Sales":
await market.requestStorage(request) await market.requestStorage(request)
check getAvailability().?used == success false check getAvailability().?used == success false
test "ignores request when asked collateral is too high":
var tooBigCollateral = request
tooBigCollateral.ask.collateral = availability.maxCollateral + 1
check isOk await reservations.reserve(availability)
await market.requestStorage(tooBigCollateral)
check await wasIgnored()
test "retrieves and stores data locally": test "retrieves and stores data locally":
var storingRequest: StorageRequest var storingRequest: StorageRequest
var storingSlot: UInt256 var storingSlot: UInt256

View File

@ -1,32 +0,0 @@
import pkg/chronos
import pkg/stint
import codex/contracts
import ./token
import ../ethertest
ethersuite "Collateral":
let collateral = 100.u256
var marketplace: Marketplace
var token: TestToken
setup:
let deployment = Deployment.init()
marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner())
token = TestToken.new(!deployment.address(TestToken), provider.getSigner())
await token.mint(accounts[0], 1000.u256)
test "increases collateral":
await token.approve(marketplace.address, collateral)
await marketplace.deposit(collateral)
let balance = await marketplace.balanceOf(accounts[0])
check balance == collateral
test "withdraws collateral":
await token.approve(marketplace.address, collateral)
await marketplace.deposit(collateral)
let balanceBefore = await token.balanceOf(accounts[0])
await marketplace.withdraw()
let balanceAfter = await token.balanceOf(accounts[0])
check (balanceAfter - balanceBefore) == collateral

View File

@ -1,20 +1,19 @@
import std/json import std/json
import pkg/chronos import pkg/chronos
import pkg/ethers/testing import pkg/ethers/testing
import pkg/ethers/erc20
import codex/contracts import codex/contracts
import codex/storageproofs import codex/storageproofs
import ../ethertest import ../ethertest
import ./examples import ./examples
import ./time import ./time
import ./token
ethersuite "Marketplace contracts": ethersuite "Marketplace contracts":
let proof = exampleProof() let proof = exampleProof()
var client, host: Signer var client, host: Signer
var marketplace: Marketplace var marketplace: Marketplace
var token: TestToken var token: Erc20Token
var collateral: UInt256
var periodicity: Periodicity var periodicity: Periodicity
var request: StorageRequest var request: StorageRequest
var slotId: SlotId var slotId: SlotId
@ -29,13 +28,11 @@ ethersuite "Marketplace contracts":
let deployment = Deployment.init() let deployment = Deployment.init()
marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner()) marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner())
token = TestToken.new(!deployment.address(TestToken), provider.getSigner())
await token.mint(await client.getAddress(), 1_000_000_000.u256) let tokenAddress = await marketplace.token()
await token.mint(await host.getAddress(), 1000_000_000.u256) token = Erc20Token.new(tokenAddress, provider.getSigner())
let config = await marketplace.config() let config = await marketplace.config()
collateral = config.collateral.initialAmount
periodicity = Periodicity(seconds: config.proofs.period) periodicity = Periodicity(seconds: config.proofs.period)
request = StorageRequest.example request = StorageRequest.example
@ -45,8 +42,7 @@ ethersuite "Marketplace contracts":
await token.approve(marketplace.address, request.price) await token.approve(marketplace.address, request.price)
await marketplace.requestStorage(request) await marketplace.requestStorage(request)
switchAccount(host) switchAccount(host)
await token.approve(marketplace.address, collateral) await token.approve(marketplace.address, request.ask.collateral)
await marketplace.deposit(collateral)
await marketplace.fillSlot(request.id, 0.u256, proof) await marketplace.fillSlot(request.id, 0.u256, proof)
slotId = request.slotId(0.u256) slotId = request.slotId(0.u256)
@ -61,6 +57,7 @@ ethersuite "Marketplace contracts":
proc startContract() {.async.} = proc startContract() {.async.} =
for slotIndex in 1..<request.ask.slots: for slotIndex in 1..<request.ask.slots:
await token.approve(marketplace.address, request.ask.collateral)
await marketplace.fillSlot(request.id, slotIndex.u256, proof) await marketplace.fillSlot(request.id, slotIndex.u256, proof)
test "accept marketplace proofs": test "accept marketplace proofs":
@ -85,7 +82,7 @@ ethersuite "Marketplace contracts":
let startBalance = await token.balanceOf(address) let startBalance = await token.balanceOf(address)
await marketplace.freeSlot(slotId) await marketplace.freeSlot(slotId)
let endBalance = await token.balanceOf(address) let endBalance = await token.balanceOf(address)
check endBalance == (startBalance + request.ask.duration * request.ask.reward) check endBalance == (startBalance + request.ask.duration * request.ask.reward + request.ask.collateral)
test "cannot mark proofs missing for cancelled request": test "cannot mark proofs missing for cancelled request":
await provider.advanceTimeTo(request.expiry + 1) await provider.advanceTimeTo(request.expiry + 1)

View File

@ -6,14 +6,12 @@ import codex/storageproofs
import ../ethertest import ../ethertest
import ./examples import ./examples
import ./time import ./time
import ./token
ethersuite "On-Chain Market": ethersuite "On-Chain Market":
let proof = exampleProof() let proof = exampleProof()
var market: OnChainMarket var market: OnChainMarket
var marketplace: Marketplace var marketplace: Marketplace
var token: TestToken
var request: StorageRequest var request: StorageRequest
var slotIndex: UInt256 var slotIndex: UInt256
var periodicity: Periodicity var periodicity: Periodicity
@ -21,13 +19,7 @@ ethersuite "On-Chain Market":
setup: setup:
let deployment = Deployment.init() let deployment = Deployment.init()
marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner()) marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner())
token = TestToken.new(!deployment.address(TestToken), provider.getSigner())
await token.mint(accounts[0], 1_000_000_000.u256)
let config = await marketplace.config() let config = await marketplace.config()
let collateral = config.collateral.initialAmount
await token.approve(marketplace.address, collateral)
await marketplace.deposit(collateral)
market = OnChainMarket.new(marketplace) market = OnChainMarket.new(marketplace)
periodicity = Periodicity(seconds: config.proofs.period) periodicity = Periodicity(seconds: config.proofs.period)
@ -55,18 +47,15 @@ ethersuite "On-Chain Market":
check (await market.getSigner()) == (await provider.getSigner().getAddress()) check (await market.getSigner()) == (await provider.getSigner().getAddress())
test "supports marketplace requests": test "supports marketplace requests":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
test "can retrieve previously submitted requests": test "can retrieve previously submitted requests":
check (await market.getRequest(request.id)) == none StorageRequest check (await market.getRequest(request.id)) == none StorageRequest
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
let r = await market.getRequest(request.id) let r = await market.getRequest(request.id)
check (r) == some request check (r) == some request
test "supports withdrawing of funds": test "supports withdrawing of funds":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
await provider.advanceTimeTo(request.expiry) await provider.advanceTimeTo(request.expiry)
await market.withdrawFunds(request.id) await market.withdrawFunds(request.id)
@ -78,26 +67,22 @@ ethersuite "On-Chain Market":
receivedIds.add(id) receivedIds.add(id)
receivedAsks.add(ask) receivedAsks.add(ask)
let subscription = await market.subscribeRequests(onRequest) let subscription = await market.subscribeRequests(onRequest)
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
check receivedIds == @[request.id] check receivedIds == @[request.id]
check receivedAsks == @[request.ask] check receivedAsks == @[request.ask]
await subscription.unsubscribe() await subscription.unsubscribe()
test "supports filling of slots": test "supports filling of slots":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
await market.fillSlot(request.id, slotIndex, proof) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
test "can retrieve host that filled slot": test "can retrieve host that filled slot":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
check (await market.getHost(request.id, slotIndex)) == none Address check (await market.getHost(request.id, slotIndex)) == none Address
await market.fillSlot(request.id, slotIndex, proof) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
check (await market.getHost(request.id, slotIndex)) == some accounts[0] check (await market.getHost(request.id, slotIndex)) == some accounts[0]
test "support slot filled subscriptions": test "support slot filled subscriptions":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
var receivedIds: seq[RequestId] var receivedIds: seq[RequestId]
var receivedSlotIndices: seq[UInt256] var receivedSlotIndices: seq[UInt256]
@ -105,34 +90,32 @@ ethersuite "On-Chain Market":
receivedIds.add(id) receivedIds.add(id)
receivedSlotIndices.add(slotIndex) receivedSlotIndices.add(slotIndex)
let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled) let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled)
await market.fillSlot(request.id, slotIndex, proof) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
check receivedIds == @[request.id] check receivedIds == @[request.id]
check receivedSlotIndices == @[slotIndex] check receivedSlotIndices == @[slotIndex]
await subscription.unsubscribe() await subscription.unsubscribe()
test "subscribes only to a certain slot": test "subscribes only to a certain slot":
var otherSlot = slotIndex - 1 var otherSlot = slotIndex - 1
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
var receivedSlotIndices: seq[UInt256] var receivedSlotIndices: seq[UInt256]
proc onSlotFilled(requestId: RequestId, slotIndex: UInt256) = proc onSlotFilled(requestId: RequestId, slotIndex: UInt256) =
receivedSlotIndices.add(slotIndex) receivedSlotIndices.add(slotIndex)
let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled) let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled)
await market.fillSlot(request.id, otherSlot, proof) await market.fillSlot(request.id, otherSlot, proof, request.ask.collateral)
check receivedSlotIndices.len == 0 check receivedSlotIndices.len == 0
await market.fillSlot(request.id, slotIndex, proof) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
check receivedSlotIndices == @[slotIndex] check receivedSlotIndices == @[slotIndex]
await subscription.unsubscribe() await subscription.unsubscribe()
test "support fulfillment subscriptions": test "support fulfillment subscriptions":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
var receivedIds: seq[RequestId] var receivedIds: seq[RequestId]
proc onFulfillment(id: RequestId) = proc onFulfillment(id: RequestId) =
receivedIds.add(id) receivedIds.add(id)
let subscription = await market.subscribeFulfillment(request.id, onFulfillment) let subscription = await market.subscribeFulfillment(request.id, onFulfillment)
for slotIndex in 0..<request.ask.slots: for slotIndex in 0..<request.ask.slots:
await market.fillSlot(request.id, slotIndex.u256, proof) await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
check receivedIds == @[request.id] check receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
@ -140,9 +123,7 @@ ethersuite "On-Chain Market":
var otherRequest = StorageRequest.example var otherRequest = StorageRequest.example
otherRequest.client = accounts[0] otherRequest.client = accounts[0]
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
await token.approve(marketplace.address, otherRequest.price)
await market.requestStorage(otherRequest) await market.requestStorage(otherRequest)
var receivedIds: seq[RequestId] var receivedIds: seq[RequestId]
@ -152,16 +133,15 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeFulfillment(request.id, onFulfillment) let subscription = await market.subscribeFulfillment(request.id, onFulfillment)
for slotIndex in 0..<request.ask.slots: for slotIndex in 0..<request.ask.slots:
await market.fillSlot(request.id, slotIndex.u256, proof) await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
for slotIndex in 0..<otherRequest.ask.slots: for slotIndex in 0..<otherRequest.ask.slots:
await market.fillSlot(otherRequest.id, slotIndex.u256, proof) await market.fillSlot(otherRequest.id, slotIndex.u256, proof, otherRequest.ask.collateral)
check receivedIds == @[request.id] check receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
test "support request cancelled subscriptions": test "support request cancelled subscriptions":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
var receivedIds: seq[RequestId] var receivedIds: seq[RequestId]
@ -175,7 +155,6 @@ ethersuite "On-Chain Market":
await subscription.unsubscribe() await subscription.unsubscribe()
test "support request failed subscriptions": test "support request failed subscriptions":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
var receivedIds: seq[RequestId] var receivedIds: seq[RequestId]
@ -184,7 +163,7 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeRequestFailed(request.id, onRequestFailed) let subscription = await market.subscribeRequestFailed(request.id, onRequestFailed)
for slotIndex in 0..<request.ask.slots: for slotIndex in 0..<request.ask.slots:
await market.fillSlot(request.id, slotIndex.u256, proof) await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
for slotIndex in 0..request.ask.maxSlotLoss: for slotIndex in 0..request.ask.maxSlotLoss:
let slotId = request.slotId(slotIndex.u256) let slotId = request.slotId(slotIndex.u256)
while true: while true:
@ -201,9 +180,7 @@ ethersuite "On-Chain Market":
test "subscribes only to a certain request cancellation": test "subscribes only to a certain request cancellation":
var otherRequest = request var otherRequest = request
otherRequest.nonce = Nonce.example otherRequest.nonce = Nonce.example
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
await token.approve(marketplace.address, otherRequest.price)
await market.requestStorage(otherRequest) await market.requestStorage(otherRequest)
var receivedIds: seq[RequestId] var receivedIds: seq[RequestId]
@ -222,11 +199,9 @@ ethersuite "On-Chain Market":
check isNone await market.getRequest(request.id) check isNone await market.getRequest(request.id)
test "can retrieve active requests": test "can retrieve active requests":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
var request2 = StorageRequest.example var request2 = StorageRequest.example
request2.client = accounts[0] request2.client = accounts[0]
await token.approve(marketplace.address, request2.price)
await market.requestStorage(request2) await market.requestStorage(request2)
check (await market.myRequests()) == @[request.id, request2.id] check (await market.myRequests()) == @[request.id, request2.id]
@ -234,31 +209,27 @@ ethersuite "On-Chain Market":
check (await market.requestState(request.id)) == none RequestState check (await market.requestState(request.id)) == none RequestState
test "can retrieve request state": test "can retrieve request state":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
for slotIndex in 0..<request.ask.slots: for slotIndex in 0..<request.ask.slots:
await market.fillSlot(request.id, slotIndex.u256, proof) await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
check (await market.requestState(request.id)) == some RequestState.Started check (await market.requestState(request.id)) == some RequestState.Started
test "can retrieve active slots": test "can retrieve active slots":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
await market.fillSlot(request.id, slotIndex - 1, proof) await market.fillSlot(request.id, slotIndex - 1, proof, request.ask.collateral)
await market.fillSlot(request.id, slotIndex, proof) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
let slotId1 = request.slotId(slotIndex - 1) let slotId1 = request.slotId(slotIndex - 1)
let slotId2 = request.slotId(slotIndex) let slotId2 = request.slotId(slotIndex)
check (await market.mySlots()) == @[slotId1, slotId2] check (await market.mySlots()) == @[slotId1, slotId2]
test "returns none when slot is empty": test "returns none when slot is empty":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
let slotId = request.slotId(slotIndex) let slotId = request.slotId(slotIndex)
check (await market.getActiveSlot(slotId)) == none Slot check (await market.getActiveSlot(slotId)) == none Slot
test "can retrieve request details from slot id": test "can retrieve request details from slot id":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
await market.fillSlot(request.id, slotIndex, proof) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
let slotId = request.slotId(slotIndex) let slotId = request.slotId(slotIndex)
let expected = Slot(request: request, slotIndex: slotIndex) let expected = Slot(request: request, slotIndex: slotIndex)
check (await market.getActiveSlot(slotId)) == some expected check (await market.getActiveSlot(slotId)) == some expected
@ -268,8 +239,7 @@ ethersuite "On-Chain Market":
check (await market.slotState(slotId)) == SlotState.Free check (await market.slotState(slotId)) == SlotState.Free
test "retrieves correct slot state once filled": test "retrieves correct slot state once filled":
await token.approve(marketplace.address, request.price)
await market.requestStorage(request) await market.requestStorage(request)
await market.fillSlot(request.id, slotIndex, proof) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
let slotId = request.slotId(slotIndex) let slotId = request.slotId(slotIndex)
check (await market.slotState(slotId)) == SlotState.Filled check (await market.slotState(slotId)) == SlotState.Filled

View File

@ -1,9 +0,0 @@
import pkg/chronos
import pkg/stint
import pkg/ethers
import pkg/ethers/erc20
type
TestToken* = ref object of Erc20Token
proc mint*(token: TestToken, holder: Address, amount: UInt256) {.contract.}

View File

@ -28,6 +28,7 @@ proc example*(_: type StorageRequest): StorageRequest =
slots: 4, slots: 4,
slotSize: (1 * 1024 * 1024 * 1024).u256, # 1 Gigabyte slotSize: (1 * 1024 * 1024 * 1024).u256, # 1 Gigabyte
duration: (10 * 60 * 60).u256, # 10 hours duration: (10 * 60 * 60).u256, # 10 hours
collateral: 200.u256,
proofProbability: 4.u256, # require a proof roughly once every 4 periods proofProbability: 4.u256, # require a proof roughly once every 4 periods
reward: 84.u256, reward: 84.u256,
maxSlotLoss: 2 # 2 slots can be freed without data considered to be lost maxSlotLoss: 2 # 2 slots can be freed without data considered to be lost

View File

@ -31,13 +31,15 @@ proc requestStorage*(client: CodexClient,
duration: uint64, duration: uint64,
reward: uint64, reward: uint64,
proofProbability: uint64, proofProbability: uint64,
expiry: UInt256): string = expiry: UInt256,
collateral: uint64): string =
let url = client.baseurl & "/storage/request/" & cid let url = client.baseurl & "/storage/request/" & cid
let json = %*{ let json = %*{
"duration": "0x" & duration.toHex, "duration": "0x" & duration.toHex,
"reward": "0x" & reward.toHex, "reward": "0x" & reward.toHex,
"proofProbability": "0x" & proofProbability.toHex, "proofProbability": "0x" & proofProbability.toHex,
"expiry": "0x" & expiry.toHex "expiry": "0x" & expiry.toHex,
"collateral": "0x" & collateral.toHex,
} }
let response = client.http.post(url, $json) let response = client.http.post(url, $json)
assert response.status == "200 OK" assert response.status == "200 OK"
@ -49,12 +51,13 @@ proc getPurchase*(client: CodexClient, purchase: string): JsonNode =
parseJson(body).catch |? nil parseJson(body).catch |? nil
proc postAvailability*(client: CodexClient, proc postAvailability*(client: CodexClient,
size, duration, minPrice: uint64): JsonNode = size, duration, minPrice: uint64, maxCollateral: uint64): JsonNode =
let url = client.baseurl & "/sales/availability" let url = client.baseurl & "/sales/availability"
let json = %*{ let json = %*{
"size": "0x" & size.toHex, "size": "0x" & size.toHex,
"duration": "0x" & duration.toHex, "duration": "0x" & duration.toHex,
"minPrice": "0x" & minPrice.toHex "minPrice": "0x" & minPrice.toHex,
"maxCollateral": "0x" & maxCollateral.toHex
} }
let response = client.http.post(url, $json) let response = client.http.post(url, $json)
assert response.status == "200 OK" assert response.status == "200 OK"

View File

@ -4,14 +4,14 @@ import pkg/stint
import ../contracts/time import ../contracts/time
import ../codex/helpers/eventually import ../codex/helpers/eventually
import ./twonodes import ./twonodes
import ./tokens
twonodessuite "Integration tests", debug1 = false, debug2 = false: twonodessuite "Integration tests", debug1 = false, debug2 = false:
setup: setup:
await provider.getSigner(accounts[0]).mint() # Our Hardhat configuration does use automine, which means that time tracked by `provider.currentTime()` is not
await provider.getSigner(accounts[1]).mint() # advanced until blocks are mined and that happens only when transaction is submitted.
await provider.getSigner(accounts[1]).deposit() # As we use in tests provider.currentTime() which uses block timestamp this can lead to synchronization issues.
await provider.advanceTime(1.u256)
test "nodes can print their peer information": test "nodes can print their peer information":
check client1.info() != client2.info() check client1.info() != client2.info()
@ -25,25 +25,25 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
check cid1 != cid2 check cid1 != cid2
test "node handles new storage availability": test "node handles new storage availability":
let availability1 = client1.postAvailability(size=1, duration=2, minPrice=3) let availability1 = client1.postAvailability(size=1, duration=2, minPrice=3, maxCollateral=4)
let availability2 = client1.postAvailability(size=4, duration=5, minPrice=6) let availability2 = client1.postAvailability(size=4, duration=5, minPrice=6, maxCollateral=7)
check availability1 != availability2 check availability1 != availability2
test "node lists storage that is for sale": test "node lists storage that is for sale":
let availability = client1.postAvailability(size=1, duration=2, minPrice=3) let availability = client1.postAvailability(size=1, duration=2, minPrice=3, maxCollateral=4)
check availability in client1.getAvailabilities() check availability in client1.getAvailabilities()
test "node handles storage request": test "node handles storage request":
let expiry = (await provider.currentTime()) + 30 let expiry = (await provider.currentTime()) + 30
let cid = client1.upload("some file contents") let cid = client1.upload("some file contents")
let id1 = client1.requestStorage(cid, duration=1, reward=2, proofProbability=3, expiry=expiry) let id1 = client1.requestStorage(cid, duration=1, reward=2, proofProbability=3, expiry=expiry, collateral=200)
let id2 = client1.requestStorage(cid, duration=4, reward=5, proofProbability=6, expiry=expiry) let id2 = client1.requestStorage(cid, duration=4, reward=5, proofProbability=6, expiry=expiry, collateral=201)
check id1 != id2 check id1 != id2
test "node retrieves purchase status": test "node retrieves purchase status":
let expiry = (await provider.currentTime()) + 30 let expiry = (await provider.currentTime()) + 30
let cid = client1.upload("some file contents") let cid = client1.upload("some file contents")
let id = client1.requestStorage(cid, duration=1, reward=2, proofProbability=3, expiry=expiry) let id = client1.requestStorage(cid, duration=1, reward=2, proofProbability=3, expiry=expiry, collateral=200)
let purchase = client1.getPurchase(id) let purchase = client1.getPurchase(id)
check purchase{"request"}{"ask"}{"duration"} == %"0x1" check purchase{"request"}{"ask"}{"duration"} == %"0x1"
check purchase{"request"}{"ask"}{"reward"} == %"0x2" check purchase{"request"}{"ask"}{"reward"} == %"0x2"
@ -52,7 +52,7 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
test "node remembers purchase status after restart": test "node remembers purchase status after restart":
let expiry = (await provider.currentTime()) + 30 let expiry = (await provider.currentTime()) + 30
let cid = client1.upload("some file contents") let cid = client1.upload("some file contents")
let id = client1.requestStorage(cid, duration=1, reward=2, proofProbability=3, expiry=expiry) let id = client1.requestStorage(cid, duration=1, reward=2, proofProbability=3, expiry=expiry, collateral=200)
check eventually client1.getPurchase(id){"state"}.getStr() == "submitted" check eventually client1.getPurchase(id){"state"}.getStr() == "submitted"
node1.restart() node1.restart()
@ -65,12 +65,12 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
test "nodes negotiate contracts on the marketplace": test "nodes negotiate contracts on the marketplace":
let size: uint64 = 0xFFFFF let size: uint64 = 0xFFFFF
# client 2 makes storage available # client 2 makes storage available
discard client2.postAvailability(size=size, duration=200, minPrice=300) discard client2.postAvailability(size=size, duration=200, minPrice=300, maxCollateral=300)
# client 1 requests storage # client 1 requests storage
let expiry = (await provider.currentTime()) + 30 let expiry = (await provider.currentTime()) + 30
let cid = client1.upload("some file contents") let cid = client1.upload("some file contents")
let purchase = client1.requestStorage(cid, duration=100, reward=400, proofProbability=3, expiry=expiry) let purchase = client1.requestStorage(cid, duration=100, reward=400, proofProbability=3, expiry=expiry, collateral=200)
check eventually client1.getPurchase(purchase){"state"} == %"started" check eventually client1.getPurchase(purchase){"state"} == %"started"
check client1.getPurchase(purchase){"error"} == newJNull() check client1.getPurchase(purchase){"error"} == newJNull()

View File

@ -3,7 +3,6 @@ import codex/contracts/deployment
import ../contracts/time import ../contracts/time
import ../codex/helpers/eventually import ../codex/helpers/eventually
import ./twonodes import ./twonodes
import ./tokens
twonodessuite "Proving integration test", debug1=false, debug2=false: twonodessuite "Proving integration test", debug1=false, debug2=false:
@ -14,15 +13,17 @@ twonodessuite "Proving integration test", debug1=false, debug2=false:
let deployment = Deployment.init() let deployment = Deployment.init()
marketplace = Marketplace.new(!deployment.address(Marketplace), provider) marketplace = Marketplace.new(!deployment.address(Marketplace), provider)
config = await marketplace.config() config = await marketplace.config()
await provider.getSigner(accounts[0]).mint()
await provider.getSigner(accounts[1]).mint() # Our Hardhat configuration does use automine, which means that time tracked by `provider.currentTime()` is not
await provider.getSigner(accounts[1]).deposit() # advanced until blocks are mined and that happens only when transaction is submitted.
# As we use in tests provider.currentTime() which uses block timestamp this can lead to synchronization issues.
await provider.advanceTime(1.u256)
proc waitUntilPurchaseIsStarted {.async.} = proc waitUntilPurchaseIsStarted {.async.} =
discard client2.postAvailability(size=0xFFFFF, duration=200, minPrice=300) discard client2.postAvailability(size=0xFFFFF, duration=200, minPrice=300, maxCollateral=200)
let expiry = (await provider.currentTime()) + 30 let expiry = (await provider.currentTime()) + 30
let cid = client1.upload("some file contents") let cid = client1.upload("some file contents")
let purchase = client1.requestStorage(cid, duration=100, reward=400, proofProbability=3, expiry=expiry) let purchase = client1.requestStorage(cid, duration=100, reward=400, proofProbability=3, expiry=expiry, collateral=100)
check eventually client1.getPurchase(purchase){"state"} == %"started" check eventually client1.getPurchase(purchase){"state"} == %"started"
test "hosts submit periodic proofs for slots they fill": test "hosts submit periodic proofs for slots they fill":

View File

@ -1,22 +0,0 @@
import pkg/ethers/erc20
import codex/contracts
import ../contracts/token
proc mint*(signer: Signer, amount = 1_000_000.u256) {.async.} =
## Mints a considerable amount of tokens and approves them for transfer to
## the Marketplace contract.
let deployment = Deployment.init()
let token = TestToken.new(!deployment.address(TestToken), signer)
let marketplace = Marketplace.new(!deployment.address(Marketplace), signer)
await token.mint(await signer.getAddress(), amount)
proc deposit*(signer: Signer) {.async.} =
## Deposits sufficient collateral into the Marketplace contract.
let deployment = Deployment.init()
let marketplace = Marketplace.new(!deployment.address(Marketplace), signer)
let config = await marketplace.config()
let tokenAddress = await marketplace.token()
let token = Erc20Token.new(tokenAddress, signer)
await token.approve(marketplace.address, config.collateral.initialAmount)
await marketplace.deposit(config.collateral.initialAmount)

View File

@ -1,4 +1,3 @@
import ./contracts/testCollateral
import ./contracts/testContracts import ./contracts/testContracts
import ./contracts/testMarket import ./contracts/testMarket
import ./contracts/testProofs import ./contracts/testProofs

@ -1 +1 @@
Subproject commit fb76f7d0b2f94914b00f2a0f4136ebfb27df6abc Subproject commit 6e66abbfcd9be6cbd0434dc5a80f1419c66a914e

2
vendor/questionable vendored

@ -1 +1 @@
Subproject commit 6cbbda7e4d009e02d0583b325b31dc68dff27854 Subproject commit 30e4184a99c8c1ba329925912d2c5d4b09acf8cc