nim-dagger/codex/contracts
markspanbroek 4175689745
Load purchase state from chain (#283)
* [purchasing] Simplify test

* [utils] Move StorageRequest.example up one level

* [purchasing] Load purchases from market

* [purchasing] load purchase states

* Implement myRequest() and getState() methods for OnChainMarket

* [proofs] Fix intermittently failing tests

Ensures that examples of proofs in tests are never of length 0;
these are considered invalid proofs by the smart contract logic.

* [contracts] Fix failing test

With the new solidity contracts update, a contract can only
be paid out after it started.

* [market] Add method to get request end time

* [purchasing] wait until purchase is finished

Purchase.wait() would previously wait until purchase
was started, now we wait until it is finished.

* [purchasing] Handle 'finished' and 'failed' states

* [marketplace] move to failed state once request fails

- Add support for subscribing to request failure events.
- Add supporting contract tests for subscribing to request failure events.
- Allow the PurchaseStarted state to move to PurchaseFailure once a request failure event is emitted
- Add supporting tests for moving from PurchaseStarted to PurchaseFailure
- Add state transition tests for PurchaseUnknown.

* [marketplace] Fix test with longer sleepAsync

* [integration] Add function to restart a codex node

* [purchasing] Set client address before requesting storage

To prevent the purchase id (which equals the request id)
from changing once it's been submitted.

* [contracts] Fix: OnChainMarket.getState()

Had the wrong method signature before

* [purchasing] Load purchases on node start

* [purchasing] Rename state 'PurchaseError' to 'PurchaseErrored'

Allows for an exception type called 'PurchaseError'

* [purchasing] Load purchases in background

No longer calls market.getRequest() for every purchase
on node start.

* [contracts] Add `$` for RequestId, SlotId and Nonce

To aid with debugging

* [purchasing] Add Purchasing.stop()

To ensure that all contract interactions have both a
start() and a stop() for

* [tests] Remove sleepAsync where possible

Use `eventually` loop instead, to make sure that we're
not waiting unnecessarily.

* [integration] Fix: handle non-json response in test

* [purchasing] Add purchase state to json

* [integration] Ensure that purchase is submitted before restart

Fixes test failure on slower CI

* [purchasing] re-implement `description` as method

Allows description to be set in the same module where the
state type is defined.

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>

* [contracts] fix typo

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>

* [market] Use more generic error type

Should we decide to change the provider type later

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>
2022-11-08 08:10:17 +01:00
..
Readme.md Change every dagger to codex (#102) 2022-05-19 13:56:03 -06:00
clock.nim [contracts] Use Duration instead of float for clock offset 2022-10-03 15:17:03 +02:00
deployment.nim Change every dagger to codex (#102) 2022-05-19 13:56:03 -06:00
interactions.nim Load purchase state from chain (#283) 2022-11-08 08:10:17 +01:00
market.nim Load purchase state from chain (#283) 2022-11-08 08:10:17 +01:00
proofs.nim Update to new reverts API, add tests 2022-10-25 15:10:35 +11:00
requests.nim Load purchase state from chain (#283) 2022-11-08 08:10:17 +01:00
storage.nim Load purchase state from chain (#283) 2022-11-08 08:10:17 +01:00
testtoken.nim Change every dagger to codex (#102) 2022-05-19 13:56:03 -06:00

Readme.md

Codex Contracts in Nim

Nim API for the Codex smart contracts.

Usage

For a global overview of the steps involved in starting and fulfilling a storage contract, see Codex Contracts.

Smart contract

Connecting to the smart contract on an Ethereum node:

import codex/contracts
import ethers

let address = # fill in address where the contract was deployed
let provider = JsonRpcProvider.new("ws://localhost:8545")
let storage = Storage.new(address, provider)

Setup client and host so that they can sign transactions; here we use the first two accounts on the Ethereum node:

let accounts = await provider.listAccounts()
let client = provider.getSigner(accounts[0])
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:

let collateralAmount = await storage.collateralAmount()

The host then needs to prepare a payment to the smart contract by calling the approve method on the ERC20 token. Note that interaction with ERC20 contracts is not part of this library.

After preparing the payment, the host can deposit collateral:

await storage
  .connect(host)
  .deposit(collateralAmount)

When a host is not participating in storage offers or contracts, it can withdraw its collateral:

await storage
  .connect(host)
  .withdraw()

Storage requests

Creating a request for storage:

let request : StorageRequest = (
  client:           # address of the client requesting storage
  duration:         # duration of the contract in seconds
  size:             # size in bytes
  contentHash:      # SHA256 hash of the content that's going to be stored
  proofProbability: # require a storage proof roughly once every N periods
  maxPrice:         # maximum price the client is willing to pay
  expiry:           # expiration time of the request (in unix time)
  nonce:            # random nonce to differentiate between similar requests
)

When a client wants to submit this request to the network, it needs to pay the maximum price to the smart contract in advance. The difference between the maximum price and the offered price will be reimbursed later. To prepare, the client needs to call the approve method on the ERC20 token. Note that interaction with ERC20 contracts is not part of this library.

Once the payment has been prepared, the client can submit the request to the network:

await storage
  .connect(client)
  .requestStorage(request)

Storage offers

Creating a storage offer:

let offer: StorageOffer = (
  host:       # address of the host that is offering storage
  requestId:  request.id,
  price:      # offered price (in number of tokens)
  expiry:     # expiration time of the offer (in unix time)
)

Hosts submits an offer:

await storage
  .connect(host)
  .offerStorage(offer)

Client selects an offer:

await storage
  .connect(client)
  .selectOffer(offer.id)

Starting and finishing a storage contract

The host whose offer got selected can start the storage contract once it received the data that needs to be stored:

await storage
  .connect(host)
  .startContract(offer.id)

Once the storage contract is finished, the host can release payment:

await storage
  .connect(host)
  .finishContract(id)

Storage proofs

Time is divided into periods, and each period a storage proof may be required from the host. The odds of requiring a storage proof are negotiated through the storage request. For more details about the timing of storage proofs, please refer to the design document.

At the start of each period of time, the host can check whether a storage proof is required:

let isProofRequired = await storage.isProofRequired(offer.id)

If a proof is required, the host can submit it before the end of the period:

await storage
  .connect(host)
  .submitProof(id, proof)

If a proof is not submitted, then a validator can mark a proof as missing:

await storage
  .connect(validator)
  .markProofAsMissing(id, period)