From 7f59e545b2b11af23a12df5687561078d1d3b253 Mon Sep 17 00:00:00 2001 From: Eric Mastro Date: Wed, 23 Nov 2022 00:14:39 +1100 Subject: [PATCH] Add more tests - Marketplace tests for requestsForHost, and additional tests for myRequests and mySlots - Added Utils lib with tests - Added additional Bytes32AddressSetMap.keys expectations --- contracts/Marketplace.sol | 29 ++++++++++++++-- contracts/libs/SetMap.sol | 3 +- contracts/libs/TestUtils.sol | 17 ++++++++++ contracts/libs/Utils.sol | 23 +++++++++++++ test/Marketplace.test.js | 64 ++++++++++++++++++++++++++++++++++++ test/SetMap.test.js | 9 +++++ test/Utils.test.js | 45 +++++++++++++++++++++++++ 7 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 contracts/libs/TestUtils.sol create mode 100644 contracts/libs/Utils.sol create mode 100644 test/Utils.test.js diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index c85d3cb..26b7bc7 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -7,6 +7,7 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "./Collateral.sol"; import "./Proofs.sol"; import "./libs/SetMap.sol"; +import "./libs/Utils.sol"; contract Marketplace is Collateral, Proofs { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -43,7 +44,16 @@ contract Marketplace is Collateral, Proofs { function myRequests() public view returns (RequestId[] memory) { SetMap.AddressBytes32SetMapKey key = _toAddressSetMapKey(msg.sender); - return _toRequestIds(activeRequestsForClients.values(key)); + RequestId[] memory requestIds = _toRequestIds(activeRequestsForClients.values(key)); + RequestId[] memory result = new RequestId[](requestIds.length); + uint8 counter = 0; + for (uint8 i = 0; i < requestIds.length; i++) { + if (!_isCancelled(requestIds[i])) { + result[counter] = requestIds[i]; + counter++; + } + } + return _toRequestIds(Utils._resize(_toBytes32s(result), counter)); } function requestsForHost(address host) public view returns(RequestId[] memory) { @@ -55,12 +65,14 @@ contract Marketplace is Collateral, Proofs { for (uint8 i = 0; i < keyLength; i++) { RequestId requestId = RequestId.wrap(keys.at(i)); SetMap.Bytes32AddressSetMapKey key = _toBytes32AddressSetMapKey(requestId); - if (activeRequestsForHosts.contains(key, host)) { + if (activeRequestsForHosts.contains(key, host) && + !_isCancelled(requestId)) + { result[counter] = requestId; counter++; } } - return result; + return _toRequestIds(Utils._resize(_toBytes32s(result), counter)); } function mySlots(RequestId requestId) @@ -442,6 +454,17 @@ contract Marketplace is Collateral, Proofs { } } + function _toBytes32s(RequestId[] memory array) + private + pure + returns (bytes32[] memory result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + result := array + } + } + function _toSlotId(RequestId requestId, uint256 slotIndex) internal pure diff --git a/contracts/libs/SetMap.sol b/contracts/libs/SetMap.sol index 8be47fb..82038f8 100644 --- a/contracts/libs/SetMap.sol +++ b/contracts/libs/SetMap.sol @@ -315,5 +315,6 @@ library SetMap { internal { map._index[key]++; + map._keys.remove(Bytes32AddressSetMapKey.unwrap(key)); } -} \ No newline at end of file +} diff --git a/contracts/libs/TestUtils.sol b/contracts/libs/TestUtils.sol new file mode 100644 index 0000000..6ae06e1 --- /dev/null +++ b/contracts/libs/TestUtils.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Utils.sol"; + +// exposes public functions for testing +contract TestUtils { + + function resize(bytes32[] memory array, + uint8 newSize) + public + pure + returns (bytes32[] memory) + { + return Utils._resize(array, newSize); + } +} diff --git a/contracts/libs/Utils.sol b/contracts/libs/Utils.sol new file mode 100644 index 0000000..b15fd13 --- /dev/null +++ b/contracts/libs/Utils.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.8; + +library Utils { + function _resize(bytes32[] memory array, uint8 newSize) + internal + pure + returns (bytes32[] memory) + { + require(newSize <= array.length, "size out of bounds"); + + if (newSize == 0) { + bytes32[] memory empty; + return empty; + } else { + bytes32[] memory sized = new bytes32[](newSize); + for (uint8 i = 0; i < newSize; i++) { + sized[i] = array[i]; + } + return sized; + } + } +} diff --git a/test/Marketplace.test.js b/test/Marketplace.test.js index 41d8522..2f719fe 100644 --- a/test/Marketplace.test.js +++ b/test/Marketplace.test.js @@ -705,6 +705,12 @@ describe("Marketplace", function () { expect(await marketplace.myRequests()).to.deep.equal([requestId(request)]) }) + it("removes request from list when cancelled", async function () { + await marketplace.requestStorage(request) + await waitUntilCancelled(request) + expect(await marketplace.myRequests()).to.deep.equal([]) + }) + it("removes request from list when funds are withdrawn", async function () { await marketplace.requestStorage(request) await waitUntilCancelled(request) @@ -732,6 +738,56 @@ describe("Marketplace", function () { }) }) + describe("list of active requests for host", function () { + beforeEach(async function () { + switchAccount(client) + await token.approve(marketplace.address, price(request)) + await marketplace.requestStorage(request) + switchAccount(host) + await token.approve(marketplace.address, collateral) + await marketplace.deposit(collateral) + }) + + it("is empty when no slot is filled", async function () { + expect(await marketplace.requestsForHost(host.address)).to.deep.equal([]) + }) + + it("adds request to list when filling slot", async function () { + await marketplace.fillSlot(slot.request, slot.index, proof) + expect(await marketplace.requestsForHost(host.address)).to.deep.equal([ + slot.request, + ]) + }) + + it("removes request from list when cancelled", async function () { + await marketplace.fillSlot(slot.request, slot.index, proof) + await waitUntilCancelled(request) + expect(await marketplace.requestsForHost(host.address)).to.deep.equal([]) + }) + + it("removes request from list when funds are withdrawn", async function () { + await marketplace.fillSlot(slot.request, slot.index, proof) + await waitUntilCancelled(request) + switchAccount(client) + await marketplace.withdrawFunds(slot.request) + expect(await marketplace.requestsForHost(host.address)).to.deep.equal([]) + }) + + it("removes request from list when request fails", async function () { + await waitUntilStarted(marketplace, request, proof) + await waitUntilFailed(marketplace, request, slot) + expect(await marketplace.requestsForHost(host.address)).to.deep.equal([]) + }) + + it("removes request from list when request finishes", async function () { + switchAccount(host) + await waitUntilStarted(marketplace, request, proof) + await waitUntilFinished(marketplace, requestId(request)) + await marketplace.payoutSlot(slot.request, slot.index) + expect(await marketplace.requestsForHost(host.address)).to.deep.equal([]) + }) + }) + describe("list of active slots", function () { beforeEach(async function () { switchAccount(client) @@ -762,6 +818,14 @@ describe("Marketplace", function () { ]) }) + it("returns no slots when cancelled", async function () { + await marketplace.fillSlot(slot.request, slot.index, proof) + let slot1 = { ...slot, index: slot.index + 1 } + await marketplace.fillSlot(slot.request, slot1.index, proof) + await waitUntilCancelled(request) + expect(await marketplace.mySlots(slot.request)).to.deep.equal([]) + }) + it("removes active slots for all hosts in a request when it fails", async function () { let halfOfSlots = request.ask.slots / 2 diff --git a/test/SetMap.test.js b/test/SetMap.test.js index d32469b..bd737ec 100644 --- a/test/SetMap.test.js +++ b/test/SetMap.test.js @@ -151,6 +151,15 @@ describe("SetMap", function () { hexlify(key), hexlify(key1), ]) + await contract.remove(key1, value) + await expect(await contract.keys()).to.deep.equal([ + hexlify(key), + hexlify(key1), + ]) + await contract.remove(key1, value1) + await expect(await contract.keys()).to.deep.equal([hexlify(key)]) + await contract.clear(key) + await expect(await contract.keys()).to.deep.equal([]) }) it("contains a key/value pair", async function () { diff --git a/test/Utils.test.js b/test/Utils.test.js new file mode 100644 index 0000000..7d9799b --- /dev/null +++ b/test/Utils.test.js @@ -0,0 +1,45 @@ +const { ethers } = require("hardhat") +const { expect } = require("chai") +const { hexlify, randomBytes } = ethers.utils +const { exampleAddress } = require("./examples") +const { hexZeroPad } = require("ethers/lib/utils") + +describe("Utils", function () { + let contract + let value1 + let value2 + let value3 + let value4 + let value5 + let array + + describe("resize", function () { + beforeEach(async function () { + let Utils = await ethers.getContractFactory("TestUtils") + contract = await Utils.deploy() + value1 = hexlify(randomBytes(32)) + value2 = hexlify(randomBytes(32)) + value3 = hexlify(randomBytes(32)) + value4 = hexZeroPad(0, 32) + value5 = hexZeroPad(0, 32) + array = [value1, value2, value3, value4, value5] + }) + + it("resizes to zero length if new size is 0", async function () { + await expect(await contract.resize(array, 0)).to.deep.equal([]) + }) + + it("resizes to specified length", async function () { + await expect(await contract.resize(array, 3)).to.deep.equal([ + value1, + value2, + value3, + ]) + }) + + it("fails to resize to out of bounds length", async function () { + await expect(contract.resize(array, 6)) + .to.be.revertedWith("size out of bounds") + }) + }) +})