feat: add `--payout-address` (#870)
* feat: add `--payout-address`
Allows SPs to be paid out to a separate address, keeping their profits secure.
Supports https://github.com/codex-storage/codex-contracts-eth/pull/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 8f9535cf35
.
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
This commit is contained in:
parent
1e2ad95659
commit
e8e9820d5b
|
@ -110,7 +110,7 @@ proc bootstrapInteractions(
|
||||||
quit QuitFailure
|
quit QuitFailure
|
||||||
|
|
||||||
let marketplace = Marketplace.new(marketplaceAddress, signer)
|
let marketplace = Marketplace.new(marketplaceAddress, signer)
|
||||||
let market = OnChainMarket.new(marketplace)
|
let market = OnChainMarket.new(marketplace, config.rewardRecipient)
|
||||||
let clock = OnChainClock.new(provider)
|
let clock = OnChainClock.new(provider)
|
||||||
|
|
||||||
var client: ?ClientInteractions
|
var client: ?ClientInteractions
|
||||||
|
|
|
@ -293,6 +293,11 @@ type
|
||||||
name: "validator-max-slots"
|
name: "validator-max-slots"
|
||||||
.}: int
|
.}: int
|
||||||
|
|
||||||
|
rewardRecipient* {.
|
||||||
|
desc: "Address to send payouts to (eg rewards and refunds)"
|
||||||
|
name: "reward-recipient"
|
||||||
|
.}: Option[EthAddress]
|
||||||
|
|
||||||
case persistenceCmd* {.
|
case persistenceCmd* {.
|
||||||
defaultValue: noCmd
|
defaultValue: noCmd
|
||||||
command }: PersistenceCmd
|
command }: PersistenceCmd
|
||||||
|
|
|
@ -19,17 +19,24 @@ type
|
||||||
OnChainMarket* = ref object of Market
|
OnChainMarket* = ref object of Market
|
||||||
contract: Marketplace
|
contract: Marketplace
|
||||||
signer: Signer
|
signer: Signer
|
||||||
|
rewardRecipient: ?Address
|
||||||
MarketSubscription = market.Subscription
|
MarketSubscription = market.Subscription
|
||||||
EventSubscription = ethers.Subscription
|
EventSubscription = ethers.Subscription
|
||||||
OnChainMarketSubscription = ref object of MarketSubscription
|
OnChainMarketSubscription = ref object of MarketSubscription
|
||||||
eventSubscription: EventSubscription
|
eventSubscription: EventSubscription
|
||||||
|
|
||||||
func new*(_: type OnChainMarket, contract: Marketplace): OnChainMarket =
|
func new*(
|
||||||
|
_: type OnChainMarket,
|
||||||
|
contract: Marketplace,
|
||||||
|
rewardRecipient = Address.none): OnChainMarket =
|
||||||
|
|
||||||
without signer =? contract.signer:
|
without signer =? contract.signer:
|
||||||
raiseAssert("Marketplace contract should have a signer")
|
raiseAssert("Marketplace contract should have a signer")
|
||||||
|
|
||||||
OnChainMarket(
|
OnChainMarket(
|
||||||
contract: contract,
|
contract: contract,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
|
rewardRecipient: rewardRecipient
|
||||||
)
|
)
|
||||||
|
|
||||||
proc raiseMarketError(message: string) {.raises: [MarketError].} =
|
proc raiseMarketError(message: string) {.raises: [MarketError].} =
|
||||||
|
@ -163,7 +170,23 @@ method fillSlot(market: OnChainMarket,
|
||||||
|
|
||||||
method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} =
|
method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} =
|
||||||
convertEthersError:
|
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,
|
method withdrawFunds(market: OnChainMarket,
|
||||||
requestId: RequestId) {.async.} =
|
requestId: RequestId) {.async.} =
|
||||||
|
|
|
@ -26,7 +26,9 @@ proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view
|
||||||
proc requestStorage*(marketplace: Marketplace, request: StorageRequest): ?TransactionResponse {.contract.}
|
proc requestStorage*(marketplace: Marketplace, request: StorageRequest): ?TransactionResponse {.contract.}
|
||||||
proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: Groth16Proof): ?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): ?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): ?TransactionResponse {.contract.}
|
||||||
|
proc freeSlot*(marketplace: Marketplace, id: SlotId, rewardRecipient: Address, collateralRecipient: Address): ?TransactionResponse {.contract.}
|
||||||
proc getRequest*(marketplace: Marketplace, id: RequestId): StorageRequest {.contract, view.}
|
proc getRequest*(marketplace: Marketplace, id: RequestId): StorageRequest {.contract, view.}
|
||||||
proc getHost*(marketplace: Marketplace, id: SlotId): Address {.contract, view.}
|
proc getHost*(marketplace: Marketplace, id: SlotId): Address {.contract, view.}
|
||||||
proc getActiveSlot*(marketplace: Marketplace, id: SlotId): Slot {.contract, view.}
|
proc getActiveSlot*(marketplace: Marketplace, id: SlotId): Slot {.contract, view.}
|
||||||
|
|
|
@ -11,6 +11,7 @@ ethersuite "Marketplace contracts":
|
||||||
let proof = Groth16Proof.example
|
let proof = Groth16Proof.example
|
||||||
|
|
||||||
var client, host: Signer
|
var client, host: Signer
|
||||||
|
var rewardRecipient, collateralRecipient: Address
|
||||||
var marketplace: Marketplace
|
var marketplace: Marketplace
|
||||||
var token: Erc20Token
|
var token: Erc20Token
|
||||||
var periodicity: Periodicity
|
var periodicity: Periodicity
|
||||||
|
@ -24,6 +25,8 @@ ethersuite "Marketplace contracts":
|
||||||
setup:
|
setup:
|
||||||
client = ethProvider.getSigner(accounts[0])
|
client = ethProvider.getSigner(accounts[0])
|
||||||
host = ethProvider.getSigner(accounts[1])
|
host = ethProvider.getSigner(accounts[1])
|
||||||
|
rewardRecipient = accounts[2]
|
||||||
|
collateralRecipient = accounts[3]
|
||||||
|
|
||||||
let address = Marketplace.address(dummyVerifier = true)
|
let address = Marketplace.address(dummyVerifier = true)
|
||||||
marketplace = Marketplace.new(address, ethProvider.getSigner())
|
marketplace = Marketplace.new(address, ethProvider.getSigner())
|
||||||
|
@ -82,8 +85,27 @@ ethersuite "Marketplace contracts":
|
||||||
let startBalance = await token.balanceOf(address)
|
let startBalance = await token.balanceOf(address)
|
||||||
discard await marketplace.freeSlot(slotId)
|
discard 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 + request.ask.collateral)
|
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":
|
test "cannot mark proofs missing for cancelled request":
|
||||||
let expiry = await marketplace.requestExpiry(request.id)
|
let expiry = await marketplace.requestExpiry(request.id)
|
||||||
await ethProvider.advanceTimeTo((expiry + 1).u256)
|
await ethProvider.advanceTimeTo((expiry + 1).u256)
|
||||||
|
|
|
@ -1,30 +1,47 @@
|
||||||
import std/options
|
import std/options
|
||||||
|
import std/importutils
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
|
import pkg/ethers/erc20
|
||||||
import codex/contracts
|
import codex/contracts
|
||||||
import ../ethertest
|
import ../ethertest
|
||||||
import ./examples
|
import ./examples
|
||||||
import ./time
|
import ./time
|
||||||
import ./deployment
|
import ./deployment
|
||||||
|
|
||||||
|
privateAccess(OnChainMarket) # enable access to private fields
|
||||||
|
|
||||||
ethersuite "On-Chain Market":
|
ethersuite "On-Chain Market":
|
||||||
let proof = Groth16Proof.example
|
let proof = Groth16Proof.example
|
||||||
|
|
||||||
var market: OnChainMarket
|
var market: OnChainMarket
|
||||||
var marketplace: Marketplace
|
var marketplace: Marketplace
|
||||||
|
var token: Erc20Token
|
||||||
var request: StorageRequest
|
var request: StorageRequest
|
||||||
var slotIndex: UInt256
|
var slotIndex: UInt256
|
||||||
var periodicity: Periodicity
|
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:
|
setup:
|
||||||
let address = Marketplace.address(dummyVerifier = true)
|
let address = Marketplace.address(dummyVerifier = true)
|
||||||
marketplace = Marketplace.new(address, ethProvider.getSigner())
|
marketplace = Marketplace.new(address, ethProvider.getSigner())
|
||||||
let config = await marketplace.config()
|
let config = await marketplace.config()
|
||||||
|
hostRewardRecipient = accounts[2]
|
||||||
|
|
||||||
market = OnChainMarket.new(marketplace)
|
market = OnChainMarket.new(marketplace)
|
||||||
|
let tokenAddress = await marketplace.token()
|
||||||
|
token = Erc20Token.new(tokenAddress, ethProvider.getSigner())
|
||||||
|
|
||||||
periodicity = Periodicity(seconds: config.proofs.period)
|
periodicity = Periodicity(seconds: config.proofs.period)
|
||||||
|
|
||||||
request = StorageRequest.example
|
request = StorageRequest.example
|
||||||
request.client = accounts[0]
|
request.client = accounts[0]
|
||||||
|
host = ethProvider.getSigner(accounts[1])
|
||||||
|
|
||||||
slotIndex = (request.ask.slots div 2).u256
|
slotIndex = (request.ask.slots div 2).u256
|
||||||
|
|
||||||
|
@ -72,11 +89,18 @@ ethersuite "On-Chain Market":
|
||||||
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 "withdraws funds to client":
|
||||||
|
let clientAddress = request.client
|
||||||
|
|
||||||
await market.requestStorage(request)
|
await market.requestStorage(request)
|
||||||
await advanceToCancelledRequest(request)
|
await advanceToCancelledRequest(request)
|
||||||
|
let startBalanceClient = await token.balanceOf(clientAddress)
|
||||||
await market.withdrawFunds(request.id)
|
await market.withdrawFunds(request.id)
|
||||||
|
|
||||||
|
let endBalanceClient = await token.balanceOf(clientAddress)
|
||||||
|
|
||||||
|
check endBalanceClient == (startBalanceClient + request.price)
|
||||||
|
|
||||||
test "supports request subscriptions":
|
test "supports request subscriptions":
|
||||||
var receivedIds: seq[RequestId]
|
var receivedIds: seq[RequestId]
|
||||||
var receivedAsks: seq[StorageAsk]
|
var receivedAsks: seq[StorageAsk]
|
||||||
|
@ -370,3 +394,49 @@ ethersuite "On-Chain Market":
|
||||||
(await market.queryPastEvents(StorageRequested, blocksAgo = -2)) ==
|
(await market.queryPastEvents(StorageRequested, blocksAgo = -2)) ==
|
||||||
(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)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit ed428767b3323048533b4d576888f36372bd9b27
|
Subproject commit 73a2ca0bd3ba90715ea7e818bafbd82208034a58
|
Loading…
Reference in New Issue