2021-11-01 11:30:35 +00:00
|
|
|
const { expect } = require("chai")
|
|
|
|
const { ethers } = require("hardhat")
|
2022-04-12 06:43:47 +00:00
|
|
|
const { hexlify, randomBytes } = ethers.utils
|
2022-03-08 14:58:08 +00:00
|
|
|
const {
|
|
|
|
snapshot,
|
|
|
|
revert,
|
2022-04-05 08:11:30 +00:00
|
|
|
mine,
|
2022-03-09 10:21:19 +00:00
|
|
|
ensureMinimumBlockHeight,
|
2022-03-08 14:58:08 +00:00
|
|
|
currentTime,
|
2023-10-16 09:14:02 +00:00
|
|
|
advanceTimeForNextBlock,
|
|
|
|
advanceTimeToForNextBlock,
|
2022-03-08 14:58:08 +00:00
|
|
|
} = require("./evm")
|
2023-01-09 13:21:23 +00:00
|
|
|
const { periodic } = require("./time")
|
2024-01-05 11:27:53 +00:00
|
|
|
const { loadProof } = require("./proof")
|
2023-01-16 15:31:04 +00:00
|
|
|
const { SlotState } = require("./requests")
|
2023-01-23 16:58:42 +00:00
|
|
|
const binomialTest = require("@stdlib/stats-binomial-test")
|
2021-11-01 11:30:35 +00:00
|
|
|
|
|
|
|
describe("Proofs", function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
const slotId = hexlify(randomBytes(32))
|
2022-04-05 08:11:30 +00:00
|
|
|
const period = 30 * 60
|
2021-11-01 11:30:35 +00:00
|
|
|
const timeout = 5
|
2022-03-10 09:19:21 +00:00
|
|
|
const downtime = 64
|
2022-03-10 09:12:03 +00:00
|
|
|
const probability = 4 // require a proof roughly once every 4 periods
|
2022-04-05 08:11:30 +00:00
|
|
|
const { periodOf, periodEnd } = periodic(period)
|
2021-11-01 11:30:35 +00:00
|
|
|
|
|
|
|
let proofs
|
|
|
|
|
|
|
|
beforeEach(async function () {
|
2022-03-08 14:58:08 +00:00
|
|
|
await snapshot()
|
2022-03-09 10:21:19 +00:00
|
|
|
await ensureMinimumBlockHeight(256)
|
2021-11-01 11:30:35 +00:00
|
|
|
const Proofs = await ethers.getContractFactory("TestProofs")
|
2024-01-05 11:27:53 +00:00
|
|
|
const Verifier = await ethers.getContractFactory("contracts/verifiers/testing/verifier.sol:Groth16Verifier")
|
|
|
|
const verifier = await Verifier.deploy()
|
|
|
|
proofs = await Proofs.deploy({ period, timeout, downtime }, verifier.address)
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
|
2022-03-08 14:58:08 +00:00
|
|
|
afterEach(async function () {
|
|
|
|
await revert()
|
|
|
|
})
|
|
|
|
|
2022-09-29 10:07:55 +00:00
|
|
|
describe("general", function () {
|
|
|
|
beforeEach(async function () {
|
2023-01-16 15:31:04 +00:00
|
|
|
await proofs.setSlotState(slotId, SlotState.Filled)
|
2022-09-29 10:07:55 +00:00
|
|
|
})
|
2021-11-01 14:28:22 +00:00
|
|
|
|
2022-09-29 10:07:55 +00:00
|
|
|
it("requires proofs with an agreed upon probability", async function () {
|
2023-01-23 16:58:42 +00:00
|
|
|
const samples = 256 // 256 samples avoids bias due to pointer downtime
|
|
|
|
|
2023-01-10 11:51:26 +00:00
|
|
|
await proofs.startRequiringProofs(slotId, probability)
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeForNextBlock(period)
|
|
|
|
await mine()
|
2022-09-29 10:07:55 +00:00
|
|
|
let amount = 0
|
2023-01-23 16:58:42 +00:00
|
|
|
for (let i = 0; i < samples; i++) {
|
2023-01-09 13:20:59 +00:00
|
|
|
if (await proofs.isProofRequired(slotId)) {
|
2022-09-29 10:07:55 +00:00
|
|
|
amount += 1
|
|
|
|
}
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeForNextBlock(period)
|
|
|
|
await mine()
|
2021-11-03 16:02:12 +00:00
|
|
|
}
|
2023-01-23 16:58:42 +00:00
|
|
|
|
|
|
|
const p = 1 / probability // expected probability
|
|
|
|
const alpha = 1 / 1000 // unit test can fail once every 1000 runs
|
|
|
|
|
|
|
|
// use binomial test to check that the measured amount is likely to occur
|
|
|
|
expect(binomialTest(amount, samples, { p, alpha }).rejected).to.be.false
|
2022-09-29 10:07:55 +00:00
|
|
|
})
|
2021-11-03 16:02:12 +00:00
|
|
|
|
2023-01-18 15:05:32 +00:00
|
|
|
it("supports probability 1 (proofs are always required)", async function () {
|
|
|
|
await proofs.startRequiringProofs(slotId, 1)
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeForNextBlock(period)
|
|
|
|
await mine()
|
2023-01-18 15:05:32 +00:00
|
|
|
while ((await proofs.getPointer(slotId)) < downtime) {
|
|
|
|
await mine()
|
|
|
|
}
|
|
|
|
expect(await proofs.isProofRequired(slotId)).to.be.true
|
|
|
|
})
|
|
|
|
|
2022-09-29 10:07:55 +00:00
|
|
|
it("requires no proofs in the start period", async function () {
|
|
|
|
const startPeriod = Math.floor((await currentTime()) / period)
|
|
|
|
const probability = 1
|
2023-01-10 11:51:26 +00:00
|
|
|
await proofs.startRequiringProofs(slotId, probability)
|
2022-09-29 10:07:55 +00:00
|
|
|
while (Math.floor((await currentTime()) / period) == startPeriod) {
|
2023-01-09 13:20:59 +00:00
|
|
|
expect(await proofs.isProofRequired(slotId)).to.be.false
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeForNextBlock(Math.floor(period / 10))
|
|
|
|
await mine()
|
2022-09-29 10:07:55 +00:00
|
|
|
}
|
|
|
|
})
|
2022-03-08 14:58:08 +00:00
|
|
|
|
2022-09-29 10:07:55 +00:00
|
|
|
it("requires proofs for different ids at different times", async function () {
|
|
|
|
let id1 = hexlify(randomBytes(32))
|
|
|
|
let id2 = hexlify(randomBytes(32))
|
|
|
|
let id3 = hexlify(randomBytes(32))
|
2023-01-09 13:20:59 +00:00
|
|
|
for (let slotId of [id1, id2, id3]) {
|
2023-01-16 15:31:04 +00:00
|
|
|
await proofs.setSlotState(slotId, SlotState.Filled)
|
2023-01-10 11:51:26 +00:00
|
|
|
await proofs.startRequiringProofs(slotId, probability)
|
2022-09-29 10:07:55 +00:00
|
|
|
}
|
|
|
|
let req1, req2, req3
|
|
|
|
while (req1 === req2 && req2 === req3) {
|
|
|
|
req1 = await proofs.isProofRequired(id1)
|
|
|
|
req2 = await proofs.isProofRequired(id2)
|
|
|
|
req3 = await proofs.isProofRequired(id3)
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeForNextBlock(period)
|
|
|
|
await mine()
|
2022-09-29 10:07:55 +00:00
|
|
|
}
|
|
|
|
})
|
2021-11-03 16:15:03 +00:00
|
|
|
|
2022-09-29 10:07:55 +00:00
|
|
|
it("moves pointer one block at a time", async function () {
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime())))
|
|
|
|
await mine()
|
2022-09-29 10:07:55 +00:00
|
|
|
for (let i = 0; i < 256; i++) {
|
2023-01-09 13:20:59 +00:00
|
|
|
let previous = await proofs.getPointer(slotId)
|
2022-09-29 10:07:55 +00:00
|
|
|
await mine()
|
2023-01-09 13:20:59 +00:00
|
|
|
let current = await proofs.getPointer(slotId)
|
2022-09-29 10:07:55 +00:00
|
|
|
expect(current).to.equal((previous + 1) % 256)
|
|
|
|
}
|
|
|
|
})
|
2022-04-05 08:11:30 +00:00
|
|
|
})
|
2022-04-05 09:27:02 +00:00
|
|
|
|
|
|
|
describe("when proof requirement is upcoming", function () {
|
|
|
|
async function waitUntilProofWillBeRequired() {
|
2023-01-09 13:20:59 +00:00
|
|
|
while (!(await proofs.willProofBeRequired(slotId))) {
|
2022-04-05 09:27:02 +00:00
|
|
|
await mine()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(async function () {
|
2023-01-16 15:31:04 +00:00
|
|
|
await proofs.setSlotState(slotId, SlotState.Filled)
|
2023-01-10 11:51:26 +00:00
|
|
|
await proofs.startRequiringProofs(slotId, probability)
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime())))
|
2022-04-05 09:27:02 +00:00
|
|
|
await waitUntilProofWillBeRequired()
|
|
|
|
})
|
|
|
|
|
|
|
|
it("means the pointer is in downtime", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
expect(await proofs.getPointer(slotId)).to.be.lt(downtime)
|
|
|
|
while ((await proofs.getPointer(slotId)) < downtime) {
|
|
|
|
expect(await proofs.willProofBeRequired(slotId)).to.be.true
|
2022-04-05 09:27:02 +00:00
|
|
|
await mine()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
it("means that a proof is required after downtime", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
while ((await proofs.getPointer(slotId)) < downtime) {
|
2022-04-05 09:27:02 +00:00
|
|
|
await mine()
|
|
|
|
}
|
2023-01-09 13:20:59 +00:00
|
|
|
expect(await proofs.willProofBeRequired(slotId)).to.be.false
|
|
|
|
expect(await proofs.isProofRequired(slotId)).to.be.true
|
2022-04-05 09:27:02 +00:00
|
|
|
})
|
2022-09-13 07:18:55 +00:00
|
|
|
|
2023-01-16 15:31:04 +00:00
|
|
|
it("will not require proofs when slot is finished", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
expect(await proofs.getPointer(slotId)).to.be.lt(downtime)
|
|
|
|
expect(await proofs.willProofBeRequired(slotId)).to.be.true
|
2023-01-16 15:31:04 +00:00
|
|
|
await proofs.setSlotState(slotId, SlotState.Finished)
|
2023-01-09 13:20:59 +00:00
|
|
|
expect(await proofs.willProofBeRequired(slotId)).to.be.false
|
2022-09-13 07:18:55 +00:00
|
|
|
})
|
2022-04-05 09:27:02 +00:00
|
|
|
})
|
2022-03-09 10:11:01 +00:00
|
|
|
|
2022-04-11 12:09:48 +00:00
|
|
|
describe("when proofs are required", function () {
|
2024-01-05 11:27:53 +00:00
|
|
|
const proof = loadProof('testing')
|
2022-04-12 06:43:47 +00:00
|
|
|
|
2021-11-01 11:30:35 +00:00
|
|
|
beforeEach(async function () {
|
2023-01-16 15:31:04 +00:00
|
|
|
await proofs.setSlotState(slotId, SlotState.Filled)
|
2023-01-10 11:51:26 +00:00
|
|
|
await proofs.startRequiringProofs(slotId, probability)
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
|
2023-01-09 13:20:59 +00:00
|
|
|
async function waitUntilProofIsRequired(slotId) {
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime())))
|
|
|
|
await mine()
|
|
|
|
|
2022-03-10 12:35:41 +00:00
|
|
|
while (
|
|
|
|
!(
|
2023-01-09 13:20:59 +00:00
|
|
|
(await proofs.isProofRequired(slotId)) &&
|
|
|
|
(await proofs.getPointer(slotId)) < 250
|
2022-03-10 12:35:41 +00:00
|
|
|
)
|
|
|
|
) {
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeForNextBlock(period)
|
|
|
|
await mine()
|
2021-11-01 11:30:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-10 12:04:46 +00:00
|
|
|
it("provides different challenges per period", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
await waitUntilProofIsRequired(slotId)
|
|
|
|
const challenge1 = await proofs.getChallenge(slotId)
|
|
|
|
await waitUntilProofIsRequired(slotId)
|
|
|
|
const challenge2 = await proofs.getChallenge(slotId)
|
2022-03-10 12:04:46 +00:00
|
|
|
expect(challenge2).not.to.equal(challenge1)
|
|
|
|
})
|
|
|
|
|
2023-01-09 13:20:59 +00:00
|
|
|
it("provides different challenges per slotId", async function () {
|
2022-04-12 06:43:47 +00:00
|
|
|
const id2 = hexlify(randomBytes(32))
|
|
|
|
const id3 = hexlify(randomBytes(32))
|
2023-01-09 13:20:59 +00:00
|
|
|
const challenge1 = await proofs.getChallenge(slotId)
|
2022-03-10 12:04:46 +00:00
|
|
|
const challenge2 = await proofs.getChallenge(id2)
|
|
|
|
const challenge3 = await proofs.getChallenge(id3)
|
|
|
|
expect(challenge1 === challenge2 && challenge2 === challenge3).to.be.false
|
|
|
|
})
|
|
|
|
|
2021-11-01 11:30:35 +00:00
|
|
|
it("submits a correct proof", async function () {
|
2024-01-05 11:27:53 +00:00
|
|
|
await proofs.submitProof(slotId, ...proof)
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it("fails proof submission when proof is incorrect", async function () {
|
2024-01-05 11:27:53 +00:00
|
|
|
await expect(proofs.submitProof(slotId, ["0x1bcdb9a3c52070f56e8d59b29239f0528817f99745157ce4d03faefddfff6acc", "0x2496ab7dd8f0596c21653105e4af7e48eb5395ea45e0c876d7db4dd31b4df23e"],[["0x002ef03c350ccfbf234bfde498378709edea3a506383d492b58c4c35ffecc508", "0x174d475745707d35989001e9216201bdb828130b0e78dbf772c4795fa845b5eb"],["0x1f04519f202fac14311c65d827f65f787dbe01985044278292723b9ee77ce5ee", "0x1c42f4d640e94c28401392031e74426ae68145f4f520cd576ca5e5b9af97c0bb"]],["0x1db1e61b32db677f3927ec117569e068f62747986e4ac7f54db8f2acd17e4abc", "0x20a59e1daca2ab80199c5bca2c5a7d6de6348bd795a0dd999752cc462d851128"],["0x00000000000000000000000000000000000000000000000000000001b9b78422","0x2389b3770d31a09a71cda2cb2114c203172eac63b61f76cb9f81db7adbe8fc9d","0x0000000000000000000000000000000000000000000000000000000000000003"]))
|
|
|
|
.to.be.revertedWith(
|
2022-03-08 14:58:08 +00:00
|
|
|
"Invalid proof"
|
|
|
|
)
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
|
2022-04-12 06:43:47 +00:00
|
|
|
it("emits an event when proof was submitted", async function () {
|
2024-01-05 11:27:53 +00:00
|
|
|
await expect(proofs.submitProof(slotId, ...proof))
|
2022-04-12 06:43:47 +00:00
|
|
|
.to.emit(proofs, "ProofSubmitted")
|
2024-01-05 11:27:53 +00:00
|
|
|
// .withArgs(slotId, proof) // TODO: Update when ProofSubmitted updated
|
2022-04-12 06:43:47 +00:00
|
|
|
})
|
|
|
|
|
2022-02-09 13:17:23 +00:00
|
|
|
it("fails proof submission when already submitted", async function () {
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime())))
|
2024-01-05 11:27:53 +00:00
|
|
|
await proofs.submitProof(slotId, ...proof)
|
|
|
|
await expect(proofs.submitProof(slotId, ...proof)).to.be.revertedWith(
|
2022-03-08 14:58:08 +00:00
|
|
|
"Proof already submitted"
|
|
|
|
)
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it("marks a proof as missing", async function () {
|
2023-01-09 13:41:28 +00:00
|
|
|
expect(await proofs.missingProofs(slotId)).to.equal(0)
|
2023-01-09 13:20:59 +00:00
|
|
|
await waitUntilProofIsRequired(slotId)
|
2022-03-08 14:58:08 +00:00
|
|
|
let missedPeriod = periodOf(await currentTime())
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(missedPeriod))
|
|
|
|
await mine()
|
2023-01-09 13:20:59 +00:00
|
|
|
await proofs.markProofAsMissing(slotId, missedPeriod)
|
2023-01-09 13:41:28 +00:00
|
|
|
expect(await proofs.missingProofs(slotId)).to.equal(1)
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
|
2022-03-08 14:58:08 +00:00
|
|
|
it("does not mark a proof as missing before period end", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
await waitUntilProofIsRequired(slotId)
|
2022-03-08 14:58:08 +00:00
|
|
|
let currentPeriod = periodOf(await currentTime())
|
|
|
|
await expect(
|
2023-01-09 13:20:59 +00:00
|
|
|
proofs.markProofAsMissing(slotId, currentPeriod)
|
2022-03-08 14:58:08 +00:00
|
|
|
).to.be.revertedWith("Period has not ended yet")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("does not mark a proof as missing after timeout", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
await waitUntilProofIsRequired(slotId)
|
2022-03-08 14:58:08 +00:00
|
|
|
let currentPeriod = periodOf(await currentTime())
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(currentPeriod) + timeout)
|
2021-11-01 11:30:35 +00:00
|
|
|
await expect(
|
2023-01-09 13:20:59 +00:00
|
|
|
proofs.markProofAsMissing(slotId, currentPeriod)
|
2022-03-08 14:58:08 +00:00
|
|
|
).to.be.revertedWith("Validation timed out")
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it("does not mark a submitted proof as missing", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
await waitUntilProofIsRequired(slotId)
|
2022-03-08 14:58:08 +00:00
|
|
|
let submittedPeriod = periodOf(await currentTime())
|
2024-01-05 11:27:53 +00:00
|
|
|
await proofs.submitProof(slotId, ...proof)
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(submittedPeriod))
|
|
|
|
await mine()
|
2021-11-01 11:30:35 +00:00
|
|
|
await expect(
|
2023-01-09 13:20:59 +00:00
|
|
|
proofs.markProofAsMissing(slotId, submittedPeriod)
|
2021-11-01 11:30:35 +00:00
|
|
|
).to.be.revertedWith("Proof was submitted, not missing")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("does not mark proof as missing when not required", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
while (await proofs.isProofRequired(slotId)) {
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeForNextBlock(period)
|
|
|
|
await mine()
|
2021-11-01 11:30:35 +00:00
|
|
|
}
|
2022-03-08 14:58:08 +00:00
|
|
|
let currentPeriod = periodOf(await currentTime())
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(currentPeriod))
|
|
|
|
await mine()
|
2021-11-01 11:30:35 +00:00
|
|
|
await expect(
|
2023-01-09 13:20:59 +00:00
|
|
|
proofs.markProofAsMissing(slotId, currentPeriod)
|
2021-11-01 11:30:35 +00:00
|
|
|
).to.be.revertedWith("Proof was not required")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("does not mark proof as missing twice", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
await waitUntilProofIsRequired(slotId)
|
2022-03-08 14:58:08 +00:00
|
|
|
let missedPeriod = periodOf(await currentTime())
|
2023-10-16 09:14:02 +00:00
|
|
|
await advanceTimeToForNextBlock(periodEnd(missedPeriod))
|
|
|
|
await mine()
|
2023-01-09 13:20:59 +00:00
|
|
|
await proofs.markProofAsMissing(slotId, missedPeriod)
|
2021-11-01 11:30:35 +00:00
|
|
|
await expect(
|
2023-01-09 13:20:59 +00:00
|
|
|
proofs.markProofAsMissing(slotId, missedPeriod)
|
2021-11-01 11:30:35 +00:00
|
|
|
).to.be.revertedWith("Proof already marked as missing")
|
|
|
|
})
|
2022-09-13 07:18:55 +00:00
|
|
|
|
2023-01-16 15:31:04 +00:00
|
|
|
it("requires no proofs when slot is finished", async function () {
|
2023-01-09 13:20:59 +00:00
|
|
|
await waitUntilProofIsRequired(slotId)
|
2023-01-16 15:31:04 +00:00
|
|
|
await proofs.setSlotState(slotId, SlotState.Finished)
|
|
|
|
expect(await proofs.isProofRequired(slotId)).to.be.false
|
2022-09-13 07:18:55 +00:00
|
|
|
})
|
2021-11-01 11:30:35 +00:00
|
|
|
})
|
|
|
|
})
|