fix(tests): maximum gas deviation for reserveSlot (#250)

Co-authored-by: Adam Uhlíř <adam@uhlir.dev>
This commit is contained in:
markspanbroek 2025-08-13 16:36:57 +02:00 committed by GitHub
parent a86575b262
commit 92b796f537
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 185 additions and 4 deletions

181
test/GasUsage.test.js Normal file
View File

@ -0,0 +1,181 @@
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/codex-storage/nim-codex/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)
})
})
})

View File

@ -168,7 +168,7 @@ describe("Marketplace", function () {
patchOverloads(marketplace)
request = await exampleRequest()
request = exampleRequest()
request.client = client.address
slot = {
@ -426,7 +426,7 @@ describe("Marketplace", function () {
})
it("is rejected when request is unknown", async function () {
let unknown = await exampleRequest()
let unknown = exampleRequest()
await expect(
marketplace.fillSlot(requestId(unknown), 0, proof),
).to.be.revertedWithCustomError(marketplace, "Marketplace_UnknownRequest")

View File

@ -30,7 +30,7 @@ describe("SlotReservations", function () {
reservations = testSlotReservations
;[provider, address1, address2, address3] = await ethers.getSigners()
request = await exampleRequest()
request = exampleRequest()
reqId = requestId(request)
slotIndex = request.ask.slots / 2
slot = {

View File

@ -21,7 +21,7 @@ const exampleConfiguration = () => ({
requestDurationLimit: 60 * 60 * 24 * 30, // 30 days
})
const exampleRequest = async () => {
const exampleRequest = () => {
return {
client: hexlify(randomBytes(20)),
ask: {