diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index 65f9b26..738f2f4 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -260,25 +260,9 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { finished or cancelled requests to the host that has filled the slot. * @param slotId id of the slot to free * @dev The host that filled the slot must have initiated the transaction - (msg.sender). This overload allows `rewardRecipient` and - `collateralRecipient` to be optional. + (msg.sender). */ function freeSlot(SlotId slotId) public slotIsNotFree(slotId) { - return freeSlot(slotId, msg.sender, msg.sender); - } - - /** - * @notice Frees a slot, paying out rewards and returning collateral for - finished or cancelled requests. - * @param slotId id of the slot to free - * @param rewardRecipient address to send rewards to - * @param collateralRecipient address to refund collateral to - */ - function freeSlot( - SlotId slotId, - address rewardRecipient, - address collateralRecipient - ) public slotIsNotFree(slotId) { Slot storage slot = _slots[slotId]; if (slot.host != msg.sender) revert Marketplace_InvalidSlotHost(); @@ -286,14 +270,9 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { if (state == SlotState.Paid) revert Marketplace_AlreadyPaid(); if (state == SlotState.Finished) { - _payoutSlot(slot.requestId, slotId, rewardRecipient, collateralRecipient); + _payoutSlot(slot.requestId, slotId); } else if (state == SlotState.Cancelled) { - _payoutCancelledSlot( - slot.requestId, - slotId, - rewardRecipient, - collateralRecipient - ); + _payoutCancelledSlot(slot.requestId, slotId); } else if (state == SlotState.Failed) { _removeFromMySlots(msg.sender, slotId); } else if (state == SlotState.Filled) { @@ -399,9 +378,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { function _payoutSlot( RequestId requestId, - SlotId slotId, - address rewardRecipient, - address collateralRecipient + SlotId slotId ) private requestIsKnown(requestId) { RequestContext storage context = _requestContexts[requestId]; Request storage request = _requests[requestId]; @@ -415,24 +392,19 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { uint256 collateralAmount = slot.currentCollateral; _marketplaceTotals.sent += (payoutAmount + collateralAmount); slot.state = SlotState.Paid; - token().safeTransfer(rewardRecipient, payoutAmount); - token().safeTransfer(collateralRecipient, collateralAmount); + token().safeTransfer(slot.host, payoutAmount + collateralAmount); } /** * @notice Pays out a host for duration of time that the slot was filled, and returns the collateral. - * @dev The payouts are sent to the rewardRecipient, and collateral is returned - to the host address. * @param requestId RequestId of the request that contains the slot to be paid out. * @param slotId SlotId of the slot to be paid out. */ function _payoutCancelledSlot( RequestId requestId, - SlotId slotId, - address rewardRecipient, - address collateralRecipient + SlotId slotId ) private requestIsKnown(requestId) { Slot storage slot = _slots[slotId]; _removeFromMySlots(slot.host, slotId); @@ -445,8 +417,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { uint256 collateralAmount = slot.currentCollateral; _marketplaceTotals.sent += (payoutAmount + collateralAmount); slot.state = SlotState.Paid; - token().safeTransfer(rewardRecipient, payoutAmount); - token().safeTransfer(collateralRecipient, collateralAmount); + token().safeTransfer(slot.host, payoutAmount + collateralAmount); } /** @@ -456,21 +427,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { transaction must originate from the depositor address. * @param requestId the id of the request */ - function withdrawFunds(RequestId requestId) public { - withdrawFunds(requestId, msg.sender); - } - - /** - * @notice Withdraws storage request funds to the provided address. - * @dev Request must be expired, must be in RequestState.New, and the - transaction must originate from the depositer address. - * @param requestId the id of the request - * @param withdrawRecipient address to return the remaining funds to - */ - function withdrawFunds( - RequestId requestId, - address withdrawRecipient - ) public requestIsKnown(requestId) { + function withdrawFunds(RequestId requestId) public requestIsKnown(requestId) { Request storage request = _requests[requestId]; RequestContext storage context = _requestContexts[requestId]; @@ -513,7 +470,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian { uint256 amount = context.fundsToReturnToClient; _marketplaceTotals.sent += amount; - token().safeTransfer(withdrawRecipient, amount); + token().safeTransfer(request.client, amount); // We zero out the funds tracking in order to prevent double-spends context.fundsToReturnToClient = 0; diff --git a/contracts/TestMarketplace.sol b/contracts/TestMarketplace.sol index cb840a0..c13d7a1 100644 --- a/contracts/TestMarketplace.sol +++ b/contracts/TestMarketplace.sol @@ -9,9 +9,7 @@ contract TestMarketplace is Marketplace { MarketplaceConfig memory config, Vault vault, IGroth16Verifier verifier - ) - Marketplace(config, vault, verifier) - {} + ) Marketplace(config, vault, verifier) {} function forciblyFreeSlot(SlotId slotId) public { _forciblyFreeSlot(slotId); diff --git a/test/Marketplace.test.js b/test/Marketplace.test.js index 513dd9f..62c5e83 100644 --- a/test/Marketplace.test.js +++ b/test/Marketplace.test.js @@ -23,11 +23,7 @@ const { waitUntilSlotFailed, patchOverloads, } = require("./marketplace") -const { - maxPrice, - pricePerSlotPerSecond, - payoutForDuration, -} = require("./price") +const { maxPrice, pricePerSlotPerSecond } = require("./price") const { collateralPerSlot } = require("./collateral") const { snapshot, @@ -40,8 +36,6 @@ const { } = require("./evm") const { arrayify } = require("ethers/lib/utils") -const ACCOUNT_STARTING_BALANCE = 1_000_000_000_000_000 - describe("Marketplace constructor", function () { let Marketplace, token, vault, verifier, config @@ -103,15 +97,7 @@ describe("Marketplace", function () { let token let vault let verifier - let client, - clientWithdrawRecipient, - host, - host1, - host2, - host3, - hostRewardRecipient, - hostCollateralRecipient, - validatorRecipient + let client, host, host1, host2, host3, validator let request let slot @@ -120,31 +106,13 @@ describe("Marketplace", function () { beforeEach(async function () { await snapshot() await ensureMinimumBlockHeight(256) - ;[ - client, - clientWithdrawRecipient, - host1, - host2, - host3, - hostRewardRecipient, - hostCollateralRecipient, - validatorRecipient, - ] = await ethers.getSigners() + ;[client, host1, host2, host3, validator] = await ethers.getSigners() host = host1 const TestToken = await ethers.getContractFactory("TestToken") token = await TestToken.deploy() - for (let account of [ - client, - clientWithdrawRecipient, - host1, - host2, - host3, - hostRewardRecipient, - hostCollateralRecipient, - validatorRecipient, - ]) { - await token.mint(account.address, ACCOUNT_STARTING_BALANCE) + for (let account of [client, host1, host2, host3, validator]) { + await token.mint(account.address, 1_000_000_000_000_000) } const Vault = await ethers.getContractFactory("Vault") @@ -640,61 +608,6 @@ describe("Marketplace", function () { ) }) - it("returns collateral to host collateral address if specified", async function () { - await waitUntilStarted(marketplace, request, proof, token) - await waitUntilFinished(marketplace, requestId(request)) - - const startBalanceHost = await token.balanceOf(host.address) - const startBalanceCollateral = await token.balanceOf( - hostCollateralRecipient.address - ) - - const collateralToBeReturned = await marketplace.currentCollateral( - slotId(slot) - ) - - await marketplace.freeSlot( - slotId(slot), - hostRewardRecipient.address, - hostCollateralRecipient.address - ) - - const endBalanceCollateral = await token.balanceOf( - hostCollateralRecipient.address - ) - - const endBalanceHost = await token.balanceOf(host.address) - expect(endBalanceHost).to.equal(startBalanceHost) - expect(endBalanceCollateral - startBalanceCollateral).to.equal( - collateralPerSlot(request) - ) - expect(collateralToBeReturned).to.equal(collateralPerSlot(request)) - }) - - it("pays reward to host reward address if specified", async function () { - await waitUntilStarted(marketplace, request, proof, token) - await waitUntilFinished(marketplace, requestId(request)) - - const startBalanceHost = await token.balanceOf(host.address) - const startBalanceReward = await token.balanceOf( - hostRewardRecipient.address - ) - - await marketplace.freeSlot( - slotId(slot), - hostRewardRecipient.address, - hostCollateralRecipient.address - ) - - const endBalanceHost = await token.balanceOf(host.address) - const endBalanceReward = await token.balanceOf( - hostRewardRecipient.address - ) - - expect(endBalanceHost).to.equal(startBalanceHost) - expect(endBalanceReward - startBalanceReward).to.gt(0) - }) - it("pays the host when contract was cancelled", async function () { // Lets advance the time more into the expiry window const filledAt = (await currentTime()) + Math.floor(request.expiry / 3) @@ -702,93 +615,26 @@ describe("Marketplace", function () { await marketplace.requestExpiry(requestId(request)) ).toNumber() + const startBalance = await token.balanceOf(host.address) await marketplace.reserveSlot(slot.request, slot.index) await setNextBlockTimestamp(filledAt) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(marketplace, request) await marketplace.freeSlot(slotId(slot)) - - const expectedPartialPayout = - (expiresAt - filledAt) * pricePerSlotPerSecond(request) const endBalance = await token.balanceOf(host.address) - expect(endBalance - ACCOUNT_STARTING_BALANCE).to.be.equal( - expectedPartialPayout - ) - }) - - it("pays to host reward address when contract was cancelled, and returns collateral to host address", async function () { - // Lets advance the time more into the expiry window - const filledAt = (await currentTime()) + Math.floor(request.expiry / 3) - const expiresAt = ( - await marketplace.requestExpiry(requestId(request)) - ).toNumber() - - await marketplace.reserveSlot(slot.request, slot.index) - await setNextBlockTimestamp(filledAt) - await marketplace.fillSlot(slot.request, slot.index, proof) - await waitUntilCancelled(marketplace, request) - const startBalanceHost = await token.balanceOf(host.address) - const startBalanceReward = await token.balanceOf( - hostRewardRecipient.address - ) - const startBalanceCollateral = await token.balanceOf( - hostCollateralRecipient.address - ) - - const collateralToBeReturned = await marketplace.currentCollateral( - slotId(slot) - ) - - await marketplace.freeSlot( - slotId(slot), - hostRewardRecipient.address, - hostCollateralRecipient.address - ) const expectedPartialPayout = (expiresAt - filledAt) * pricePerSlotPerSecond(request) - - const endBalanceReward = await token.balanceOf( - hostRewardRecipient.address - ) - expect(endBalanceReward - startBalanceReward).to.be.equal( - expectedPartialPayout - ) - - const endBalanceHost = await token.balanceOf(host.address) - expect(endBalanceHost).to.be.equal(startBalanceHost) - - const endBalanceCollateral = await token.balanceOf( - hostCollateralRecipient.address - ) - expect(endBalanceCollateral - startBalanceCollateral).to.be.equal( - collateralPerSlot(request) - ) - - expect(collateralToBeReturned).to.be.equal(collateralPerSlot(request)) + expect(endBalance - startBalance).to.be.equal(expectedPartialPayout) }) it("does not pay when the contract hasn't ended", async function () { await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) - const startBalanceHost = await token.balanceOf(host.address) - const startBalanceReward = await token.balanceOf( - hostRewardRecipient.address - ) - const startBalanceCollateral = await token.balanceOf( - hostCollateralRecipient.address - ) + const startBalance = await token.balanceOf(host.address) await marketplace.freeSlot(slotId(slot)) - const endBalanceHost = await token.balanceOf(host.address) - const endBalanceReward = await token.balanceOf( - hostRewardRecipient.address - ) - const endBalanceCollateral = await token.balanceOf( - hostCollateralRecipient.address - ) - expect(endBalanceHost).to.equal(startBalanceHost) - expect(endBalanceReward).to.equal(startBalanceReward) - expect(endBalanceCollateral).to.equal(startBalanceCollateral) + const endBalance = await token.balanceOf(host.address) + expect(endBalance).to.equal(startBalance) }) it("can only be done once", async function () { @@ -881,16 +727,16 @@ describe("Marketplace", function () { it("rejects withdraw when request not yet timed out", async function () { switchAccount(client) - await expect( - marketplace.withdrawFunds(slot.request, clientWithdrawRecipient.address) - ).to.be.revertedWith("Marketplace_InvalidState") + await expect(marketplace.withdrawFunds(slot.request)).to.be.revertedWith( + "Marketplace_InvalidState" + ) }) it("rejects withdraw when wrong account used", async function () { await waitUntilCancelled(marketplace, request) - await expect( - marketplace.withdrawFunds(slot.request, clientWithdrawRecipient.address) - ).to.be.revertedWith("Marketplace_InvalidClientAddress") + await expect(marketplace.withdrawFunds(slot.request)).to.be.revertedWith( + "Marketplace_InvalidClientAddress" + ) }) it("rejects withdraw when in wrong state", async function () { @@ -906,9 +752,9 @@ describe("Marketplace", function () { } await waitUntilCancelled(marketplace, request) switchAccount(client) - await expect( - marketplace.withdrawFunds(slot.request, clientWithdrawRecipient.address) - ).to.be.revertedWith("Marketplace_InvalidState") + await expect(marketplace.withdrawFunds(slot.request)).to.be.revertedWith( + "Marketplace_InvalidState" + ) }) it("rejects withdraw when already withdrawn", async function () { @@ -916,99 +762,64 @@ describe("Marketplace", function () { await waitUntilFinished(marketplace, requestId(request)) switchAccount(client) - await marketplace.withdrawFunds( - slot.request, - clientWithdrawRecipient.address + await marketplace.withdrawFunds(slot.request) + await expect(marketplace.withdrawFunds(slot.request)).to.be.revertedWith( + "Marketplace_NothingToWithdraw" ) - await expect( - marketplace.withdrawFunds(slot.request, clientWithdrawRecipient.address) - ).to.be.revertedWith("Marketplace_NothingToWithdraw") }) it("emits event once request is cancelled", async function () { await waitUntilCancelled(marketplace, request) switchAccount(client) - await expect( - marketplace.withdrawFunds(slot.request, clientWithdrawRecipient.address) - ) + await expect(marketplace.withdrawFunds(slot.request)) .to.emit(marketplace, "RequestCancelled") .withArgs(requestId(request)) }) - it("withdraw rest of funds to the client payout address for finished requests", async function () { + it("withdraw rest of funds to the client for finished requests", async function () { await waitUntilStarted(marketplace, request, proof, token) await waitUntilFinished(marketplace, requestId(request)) switchAccount(client) - const startBalanceClient = await token.balanceOf(client.address) - const startBalancePayout = await token.balanceOf( - clientWithdrawRecipient.address - ) - await marketplace.withdrawFunds( - slot.request, - clientWithdrawRecipient.address - ) + const startBalance = await token.balanceOf(client.address) + await marketplace.withdrawFunds(slot.request) - const endBalanceClient = await token.balanceOf(client.address) - const endBalancePayout = await token.balanceOf( - clientWithdrawRecipient.address - ) + const endBalance = await token.balanceOf(client.address) - expect(endBalanceClient).to.equal(startBalanceClient) // As all the request's slots will get filled and request will start and successfully finishes, // then the upper bound to how much the client gets returned is the cumulative reward for all the // slots for expiry window. This limit is "inclusive" because it is possible that all slots are filled // at the time of expiry and hence the user would get the full "expiry window" reward back. - expect(endBalancePayout - startBalancePayout).to.be.gt(0) - expect(endBalancePayout - startBalancePayout).to.be.lte( + expect(endBalance - startBalance).to.be.gt(0) + expect(endBalance - startBalance).to.be.lte( request.expiry * pricePerSlotPerSecond(request) ) }) - it("withdraws to the client payout address when request is cancelled", async function () { + it("withdraws to the client when request is cancelled", async function () { await waitUntilCancelled(marketplace, request) switchAccount(client) - const startBalanceClient = await token.balanceOf(client.address) - const startBalancePayout = await token.balanceOf( - clientWithdrawRecipient.address - ) - await marketplace.withdrawFunds( - slot.request, - clientWithdrawRecipient.address - ) - const endBalanceClient = await token.balanceOf(client.address) - const endBalancePayout = await token.balanceOf( - clientWithdrawRecipient.address - ) - expect(endBalanceClient).to.equal(startBalanceClient) - expect(endBalancePayout - startBalancePayout).to.equal(maxPrice(request)) + const startBalance = await token.balanceOf(client.address) + await marketplace.withdrawFunds(slot.request) + const endBalance = await token.balanceOf(client.address) + expect(endBalance - startBalance).to.equal(maxPrice(request)) }) - it("withdraws full price for failed requests to the client payout address", async function () { + it("withdraws full price for failed requests to the client", async function () { await waitUntilStarted(marketplace, request, proof, token) await waitUntilFailed(marketplace, request) switchAccount(client) - const startBalanceClient = await token.balanceOf(client.address) - const startBalancePayout = await token.balanceOf( - clientWithdrawRecipient.address - ) - await marketplace.withdrawFunds( - slot.request, - clientWithdrawRecipient.address - ) + const startBalance = await token.balanceOf(client.address) + await marketplace.withdrawFunds(slot.request) - const endBalanceClient = await token.balanceOf(client.address) - const endBalancePayout = await token.balanceOf( - clientWithdrawRecipient.address - ) + const endBalance = await token.balanceOf(client.address) - expect(endBalanceClient).to.equal(startBalanceClient) - expect(endBalancePayout - startBalancePayout).to.equal(maxPrice(request)) + expect(endBalance - startBalance).to.equal(maxPrice(request)) }) - it("withdraws to the client payout address for cancelled requests lowered by hosts payout", async function () { + it("withdraws to the client for cancelled requests lowered by hosts payout", async function () { // Lets advance the time more into the expiry window const filledAt = (await currentTime()) + Math.floor(request.expiry / 3) const expiresAt = ( @@ -1019,21 +830,19 @@ describe("Marketplace", function () { await setNextBlockTimestamp(filledAt) await marketplace.fillSlot(slot.request, slot.index, proof) await waitUntilCancelled(marketplace, request) - const expectedPartialhostRewardRecipient = + const expectedPartialHostReward = (expiresAt - filledAt) * pricePerSlotPerSecond(request) switchAccount(client) - await marketplace.withdrawFunds( - slot.request, - clientWithdrawRecipient.address - ) - const endBalance = await token.balanceOf(clientWithdrawRecipient.address) - expect(endBalance - ACCOUNT_STARTING_BALANCE).to.equal( - maxPrice(request) - expectedPartialhostRewardRecipient + const startBalance = await token.balanceOf(client.address) + await marketplace.withdrawFunds(slot.request) + const endBalance = await token.balanceOf(client.address) + expect(endBalance - startBalance).to.equal( + maxPrice(request) - expectedPartialHostReward ) }) - it("when slot is freed and not repaired, client will get refunded the freed slot's funds", async function () { + it("refunds the client when slot is freed and not repaired", async function () { const payouts = await waitUntilStarted(marketplace, request, proof, token) await expect(marketplace.freeSlot(slotId(slot))).to.emit( @@ -1043,12 +852,10 @@ describe("Marketplace", function () { await waitUntilFinished(marketplace, requestId(request)) switchAccount(client) - await marketplace.withdrawFunds( - slot.request, - clientWithdrawRecipient.address - ) - const endBalance = await token.balanceOf(clientWithdrawRecipient.address) - expect(endBalance - ACCOUNT_STARTING_BALANCE).to.equal( + const startBalance = await token.balanceOf(client.address) + await marketplace.withdrawFunds(slot.request) + const endBalance = await token.balanceOf(client.address) + expect(endBalance - startBalance).to.equal( maxPrice(request) - payouts.reduce((a, b) => a + b, 0) + // This is the amount that user gets refunded for filling period in expiry window payouts[slot.index] // This is the refunded amount for the freed slot @@ -1080,10 +887,7 @@ describe("Marketplace", function () { it("remains 'Cancelled' when client withdraws funds", async function () { await waitUntilCancelled(marketplace, request) switchAccount(client) - await marketplace.withdrawFunds( - slot.request, - clientWithdrawRecipient.address - ) + await marketplace.withdrawFunds(slot.request) expect(await marketplace.requestState(slot.request)).to.equal(Cancelled) }) @@ -1389,16 +1193,16 @@ describe("Marketplace", function () { await marketplace.reserveSlot(slot.request, slot.index) await marketplace.fillSlot(slot.request, slot.index, proof) - switchAccount(validatorRecipient) + switchAccount(validator) - const startBalance = await token.balanceOf(validatorRecipient.address) + const startBalance = await token.balanceOf(validator.address) await waitUntilProofIsRequired(id) let missedPeriod = periodOf(await currentTime()) await advanceTime(period + 1) await marketplace.markProofAsMissing(id, missedPeriod) - const endBalance = await token.balanceOf(validatorRecipient.address) + const endBalance = await token.balanceOf(validator.address) const collateral = collateralPerSlot(request) const slashedAmount = (collateral * slashPercentage) / 100 @@ -1495,10 +1299,7 @@ describe("Marketplace", function () { it("removes request from list when funds are withdrawn", async function () { await marketplace.requestStorage(request) await waitUntilCancelled(marketplace, request) - await marketplace.withdrawFunds( - requestId(request), - clientWithdrawRecipient.address - ) + await marketplace.withdrawFunds(requestId(request)) expect(await marketplace.myRequests()).to.deep.equal([]) })