Skip to content

Commit

Permalink
feat: add --payout-address (#870)
Browse files Browse the repository at this point in the history
* feat: add `--payout-address`

Allows SPs to be paid out to a separate address, keeping their profits secure.
Supports codex-storage/codex-contracts-eth#144 in the nim-codex client.

* Remove optional payoutAddress

Change --payout-address so that it is no longer optional. There is no longer an overload in `Marketplace.sol` for `fillSlot` accepting no `payoutAddress`.

* Update integration tests to include --payout-address

* move payoutAddress from fillSlot to freeSlot

* Update integration tests to use required payoutAddress

- to make payoutAddress required, the integration tests needed to avoid building the cli params until just before starting the node, otherwise if cli params were added ad-hoc, there would be an error after a non-required parameter was added before a required parameter.

* support client payout address

- withdrawFunds requires a withdrawAddress parameter, directs payouts for withdrawing of client funds (for a cancelled request) to go to that address.

* fix integration test

adds --payout-address to validators

* refactor: support withdrawFunds and freeSlot optional parameters

- withdrawFunds has an optional parameter for withdrawRecipient
- freeSlot has optional parameters for rewardRecipient and collateralRecipient
- change --payout-address to --reward-recipient to match contract signature naming

* Revert "Update integration tests to include --payout-address"

This reverts commit 8f9535c.
There are some valid improvements to the integration tests, but they can be handled in a separate PR.

* small fix

* bump contracts to fix marketplace spec

* bump codex-contracts-eth, now rebased on master

* bump codex-contracts-eth

now that feat/reward-address has been merged to master

* clean up, comments
  • Loading branch information
emizzle authored Sep 17, 2024
1 parent 1e2ad95 commit e8e9820
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 5 deletions.
2 changes: 1 addition & 1 deletion codex/codex.nim
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ proc bootstrapInteractions(
quit QuitFailure

let marketplace = Marketplace.new(marketplaceAddress, signer)
let market = OnChainMarket.new(marketplace)
let market = OnChainMarket.new(marketplace, config.rewardRecipient)
let clock = OnChainClock.new(provider)

var client: ?ClientInteractions
Expand Down
5 changes: 5 additions & 0 deletions codex/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,11 @@ type
name: "validator-max-slots"
.}: int

rewardRecipient* {.
desc: "Address to send payouts to (eg rewards and refunds)"
name: "reward-recipient"
.}: Option[EthAddress]

case persistenceCmd* {.
defaultValue: noCmd
command }: PersistenceCmd
Expand Down
27 changes: 25 additions & 2 deletions codex/contracts/market.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@ type
OnChainMarket* = ref object of Market
contract: Marketplace
signer: Signer
rewardRecipient: ?Address
MarketSubscription = market.Subscription
EventSubscription = ethers.Subscription
OnChainMarketSubscription = ref object of MarketSubscription
eventSubscription: EventSubscription

func new*(_: type OnChainMarket, contract: Marketplace): OnChainMarket =
func new*(
_: type OnChainMarket,
contract: Marketplace,
rewardRecipient = Address.none): OnChainMarket =

without signer =? contract.signer:
raiseAssert("Marketplace contract should have a signer")

OnChainMarket(
contract: contract,
signer: signer,
rewardRecipient: rewardRecipient
)

proc raiseMarketError(message: string) {.raises: [MarketError].} =
Expand Down Expand Up @@ -163,7 +170,23 @@ method fillSlot(market: OnChainMarket,

method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} =
convertEthersError:
discard await market.contract.freeSlot(slotId).confirm(0)
var freeSlot: Future[?TransactionResponse]
if rewardRecipient =? market.rewardRecipient:
# If --reward-recipient specified, use it as the reward recipient, and use
# the SP's address as the collateral recipient
let collateralRecipient = await market.getSigner()
freeSlot = market.contract.freeSlot(
slotId,
rewardRecipient, # --reward-recipient
collateralRecipient) # SP's address

else:
# Otherwise, use the SP's address as both the reward and collateral
# recipient (the contract will use msg.sender for both)
freeSlot = market.contract.freeSlot(slotId)

discard await freeSlot.confirm(0)


method withdrawFunds(market: OnChainMarket,
requestId: RequestId) {.async.} =
Expand Down
2 changes: 2 additions & 0 deletions codex/contracts/marketplace.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view
proc requestStorage*(marketplace: Marketplace, request: StorageRequest): ?TransactionResponse {.contract.}
proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: Groth16Proof): ?TransactionResponse {.contract.}
proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId): ?TransactionResponse {.contract.}
proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId, withdrawAddress: Address): ?TransactionResponse {.contract.}
proc freeSlot*(marketplace: Marketplace, id: SlotId): ?TransactionResponse {.contract.}
proc freeSlot*(marketplace: Marketplace, id: SlotId, rewardRecipient: Address, collateralRecipient: Address): ?TransactionResponse {.contract.}
proc getRequest*(marketplace: Marketplace, id: RequestId): StorageRequest {.contract, view.}
proc getHost*(marketplace: Marketplace, id: SlotId): Address {.contract, view.}
proc getActiveSlot*(marketplace: Marketplace, id: SlotId): Slot {.contract, view.}
Expand Down
22 changes: 22 additions & 0 deletions tests/contracts/testContracts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ethersuite "Marketplace contracts":
let proof = Groth16Proof.example

var client, host: Signer
var rewardRecipient, collateralRecipient: Address
var marketplace: Marketplace
var token: Erc20Token
var periodicity: Periodicity
Expand All @@ -24,6 +25,8 @@ ethersuite "Marketplace contracts":
setup:
client = ethProvider.getSigner(accounts[0])
host = ethProvider.getSigner(accounts[1])
rewardRecipient = accounts[2]
collateralRecipient = accounts[3]

let address = Marketplace.address(dummyVerifier = true)
marketplace = Marketplace.new(address, ethProvider.getSigner())
Expand Down Expand Up @@ -82,8 +85,27 @@ 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)

test "can be paid out at the end, specifying reward and collateral recipient":
switchAccount(host)
let hostAddress = await host.getAddress()
await startContract()
let requestEnd = await marketplace.requestEnd(request.id)
await ethProvider.advanceTimeTo(requestEnd.u256 + 1)
let startBalanceHost = await token.balanceOf(hostAddress)
let startBalanceReward = await token.balanceOf(rewardRecipient)
let startBalanceCollateral = await token.balanceOf(collateralRecipient)
discard await marketplace.freeSlot(slotId, rewardRecipient, collateralRecipient)
let endBalanceHost = await token.balanceOf(hostAddress)
let endBalanceReward = await token.balanceOf(rewardRecipient)
let endBalanceCollateral = await token.balanceOf(collateralRecipient)

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

test "cannot mark proofs missing for cancelled request":
let expiry = await marketplace.requestExpiry(request.id)
await ethProvider.advanceTimeTo((expiry + 1).u256)
Expand Down
72 changes: 71 additions & 1 deletion tests/contracts/testMarket.nim
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
import std/options
import std/importutils
import pkg/chronos
import pkg/ethers/erc20
import codex/contracts
import ../ethertest
import ./examples
import ./time
import ./deployment

privateAccess(OnChainMarket) # enable access to private fields

ethersuite "On-Chain Market":
let proof = Groth16Proof.example

var market: OnChainMarket
var marketplace: Marketplace
var token: Erc20Token
var request: StorageRequest
var slotIndex: UInt256
var periodicity: Periodicity
var host: Signer
var hostRewardRecipient: Address

proc switchAccount(account: Signer) =
marketplace = marketplace.connect(account)
token = token.connect(account)
market = OnChainMarket.new(marketplace, market.rewardRecipient)

setup:
let address = Marketplace.address(dummyVerifier = true)
marketplace = Marketplace.new(address, ethProvider.getSigner())
let config = await marketplace.config()
hostRewardRecipient = accounts[2]

market = OnChainMarket.new(marketplace)
let tokenAddress = await marketplace.token()
token = Erc20Token.new(tokenAddress, ethProvider.getSigner())

periodicity = Periodicity(seconds: config.proofs.period)

request = StorageRequest.example
request.client = accounts[0]
host = ethProvider.getSigner(accounts[1])

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

Expand Down Expand Up @@ -72,11 +89,18 @@ ethersuite "On-Chain Market":
let r = await market.getRequest(request.id)
check (r) == some request

test "supports withdrawing of funds":
test "withdraws funds to client":
let clientAddress = request.client

await market.requestStorage(request)
await advanceToCancelledRequest(request)
let startBalanceClient = await token.balanceOf(clientAddress)
await market.withdrawFunds(request.id)

let endBalanceClient = await token.balanceOf(clientAddress)

check endBalanceClient == (startBalanceClient + request.price)

test "supports request subscriptions":
var receivedIds: seq[RequestId]
var receivedAsks: seq[StorageAsk]
Expand Down Expand Up @@ -370,3 +394,49 @@ ethersuite "On-Chain Market":
(await market.queryPastEvents(StorageRequested, blocksAgo = -2)) ==
(await market.queryPastEvents(StorageRequested, blocksAgo = 2))
)

test "pays rewards and collateral to host":
await market.requestStorage(request)

let address = await host.getAddress()
switchAccount(host)

for slotIndex in 0..<request.ask.slots:
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)

test "pays rewards to reward recipient, collateral to host":
market = OnChainMarket.new(marketplace, hostRewardRecipient.some)
let hostAddress = await host.getAddress()

await market.requestStorage(request)

switchAccount(host)
for slotIndex in 0..<request.ask.slots:
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 startBalanceHost = await token.balanceOf(hostAddress)
let startBalanceReward = await token.balanceOf(hostRewardRecipient)

await market.freeSlot(request.slotId(0.u256))

let endBalanceHost = await token.balanceOf(hostAddress)
let endBalanceReward = await token.balanceOf(hostRewardRecipient)

check endBalanceHost == (startBalanceHost + request.ask.collateral)
check endBalanceReward == (startBalanceReward +
request.ask.duration * request.ask.reward)
2 changes: 1 addition & 1 deletion vendor/codex-contracts-eth

0 comments on commit e8e9820

Please sign in to comment.