const { ethers, ignition } = require("hardhat") const { exampleRequest, exampleProof, exampleConfiguration, } = require("./examples") const { maxPrice } = require("./price") const { collateralPerSlot } = require("./collateral") const { requestId, slotId } = require("./ids") const { revert, snapshot, ensureMinimumBlockHeight, advanceTimeTo, currentTime, } = require("./evm") const { expect } = require("chai") const { patchOverloads } = require("./marketplace") const { periodic } = require("./time") const { RequestState } = require("./requests") const MarketplaceModule = require("../ignition/modules/marketplace") // Ensures that the actual gas costs can never deviate from the gas estimate by // more than a certain percentage. // The percentages from these tests should be used to pad the gas estimates in // market.nim, for example when calling fillSlot: // https://github.com/logos-storage/logos-storage-nim/blob/6db6bf5f72a0038b77d02f48dcf128b4d77b469a/codex/contracts/market.nim#L278 describe("Marketplace gas estimates", function () { let marketplace let token let signer async function requestStorage() { const request = exampleRequest() request.client = signer.address await token.approve(await marketplace.getAddress(), maxPrice(request)) await marketplace.requestStorage(request) return request } async function startRequest(request) { const id = requestId(request) for (let i = 0; i < request.ask.slots; i++) { await marketplace.reserveSlot(id, i) await token.approve( await marketplace.getAddress(), collateralPerSlot(request), ) await marketplace.fillSlot(id, i, exampleProof()) } } beforeEach(async function () { await snapshot() await ensureMinimumBlockHeight(256) signer = (await ethers.getSigners())[0] const { testMarketplace, token: _token } = await ignition.deploy( MarketplaceModule, { parameters: { Marketplace: { configuration: exampleConfiguration(), }, }, }, ) marketplace = testMarketplace patchOverloads(marketplace) token = _token for (let account of await ethers.getSigners()) { await token.mint(account.address, 1_000_000_000_000_000n) } }) afterEach(async function () { await revert() }) describe("reserveSlot", function () { it("has at most 25% deviation in gas usage", async function () { const request = await requestStorage() const id = requestId(request) const gasUsage = [] for (let signer of await ethers.getSigners()) { marketplace = marketplace.connect(signer) for (let i = 0; i < request.ask.slots; i++) { try { const transaction = await marketplace.reserveSlot(id, i) const receipt = await transaction.wait() gasUsage.push(Number(receipt.gasUsed)) } catch (exception) { // ignore: reservations can be full } } } const deviation = Math.max(...gasUsage) / Math.min(...gasUsage) - 1.0 expect(deviation).to.be.gt(0) expect(deviation).to.be.lte(0.25) }) }) describe("fillSlot", function () { it("has at most 10% deviation in gas usage", async function () { const request = await requestStorage() const id = requestId(request) const gasUsage = [] for (let i = 0; i < request.ask.slots; i++) { await marketplace.reserveSlot(id, i) await token.approve( await marketplace.getAddress(), collateralPerSlot(request), ) const transaction = await marketplace.fillSlot(id, i, exampleProof()) const receipt = await transaction.wait() gasUsage.push(Number(receipt.gasUsed)) } const deviation = Math.max(...gasUsage) / Math.min(...gasUsage) - 1.0 expect(deviation).to.be.gt(0) expect(deviation).to.be.lte(0.1) }) }) describe("freeSlot", function () { it("has at most 200% deviation in gas usage", async function () { const request = await requestStorage() const id = requestId(request) await startRequest(request) const gasUsage = [] for (let i = 0; i < request.ask.slots; i++) { const slot = { request: id, index: i } const transaction = await marketplace.freeSlot(slotId(slot)) const receipt = await transaction.wait() gasUsage.push(Number(receipt.gasUsed)) } const deviation = Math.max(...gasUsage) / Math.min(...gasUsage) - 1.0 expect(deviation).to.be.gt(0) expect(deviation).to.be.lte(2.0) }) }) describe("markProofAsMissing", function () { let period, periodOf, periodEnd beforeEach(async function () { const configuration = await marketplace.configuration() period = Number(configuration.proofs.period) ;({ periodOf, periodEnd } = periodic(period)) }) it("has at most 50% deviation in gas usage", async function () { const request = await requestStorage() const id = requestId(request) await startRequest(request) const gasUsage = [] while ((await marketplace.requestState(id)) != RequestState.Failed) { const missingPeriod = periodOf(await currentTime()) await advanceTimeTo(periodEnd(missingPeriod) + 1) for (let i = 0; i < request.ask.slots; i++) { try { const slot = { request: id, index: i } const transaction = await marketplace.markProofAsMissing( slotId(slot), missingPeriod, ) const receipt = await transaction.wait() gasUsage.push(Number(receipt.gasUsed)) } catch (exception) { // ignore: proof might not be missing } } } const deviation = Math.max(...gasUsage) / Math.min(...gasUsage) - 1.0 expect(deviation).to.be.gt(0) expect(deviation).to.be.lte(0.5) }) }) })