Move to ethers 6 and use hardhat ignition for deployments

This commit is contained in:
Arnaud 2025-04-09 16:08:13 +02:00
parent 2dddc26015
commit de58989edf
No known key found for this signature in database
GPG Key ID: 20E40A5D3110766F
24 changed files with 7063 additions and 28937 deletions

View File

@ -10,7 +10,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }} group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
formatting: formatting:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -29,7 +28,7 @@ jobs:
# workaround for https://github.com/NomicFoundation/hardhat/issues/3877 # workaround for https://github.com/NomicFoundation/hardhat/issues/3877
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18.15 node-version: 22
- run: npm install - run: npm install
- run: npm test - run: npm test
- uses: actions/cache@v4 - uses: actions/cache@v4
@ -53,9 +52,9 @@ jobs:
- name: Install Java - name: Install Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: "zulu"
java-version: '11' java-version: "11"
java-package: 'jre' java-package: "jre"
- name: Install Certora CLI - name: Install Certora CLI
run: pip3 install certora-cli==7.10.2 run: pip3 install certora-cli==7.10.2
@ -88,4 +87,3 @@ jobs:
rule: rule:
- verify:marketplace - verify:marketplace
- verify:state_changes - verify:state_changes

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ artifacts
deployment-localhost.json deployment-localhost.json
crytic-export crytic-export
.certora_internal .certora_internal
coverage
coverage.json

View File

@ -1,6 +1,4 @@
require("@nomiclabs/hardhat-waffle") require("@nomicfoundation/hardhat-toolbox")
require("hardhat-deploy")
require("hardhat-deploy-ethers")
module.exports = { module.exports = {
solidity: { solidity: {

View File

@ -0,0 +1,15 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
module.exports = buildModule("Endian", (m) => {
const deployer = m.getAccount(0)
const endian = m.contract("Endian", [], {
from: deployer,
})
const testEndian = m.contract("TestEndian", [], {
from: deployer,
})
return { endian, testEndian }
})

View File

@ -0,0 +1,56 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
const { loadZkeyHash } = require("../../verifier/verifier.js")
const { loadConfiguration } = require("../../configuration/configuration.js")
const TokenModule = require("./token.js")
const VerifierModule = require("./verifier.js")
function getDefaultConfig() {
const zkeyHash = loadZkeyHash(hre.network.name)
const config = loadConfiguration(hre.network.name)
config.proofs.zkeyHash = zkeyHash
return config
}
function getDefaultVerifier(m) {
const { verifier } = m.useModule(VerifierModule)
return verifier
}
module.exports = buildModule("Marketplace", (m) => {
const deployer = m.getAccount(0)
const { token } = m.useModule(TokenModule)
const { verifier } = m.useModule(VerifierModule)
const configuration = m.getParameter("configuration", getDefaultConfig())
const marketplace = m.contract(
"Marketplace",
[configuration, token, verifier],
{
from: deployer,
}
)
let testMarketplace
if (hre.network.config.tags.includes("local")) {
const { testVerifier } = m.useModule(VerifierModule)
testMarketplace = m.contract(
"TestMarketplace",
[configuration, token, testVerifier],
{
from: deployer,
}
)
}
return {
marketplace,
testMarketplace,
token,
}
})
// // Tags and dependencies
// module.exports.tags = ["Marketplace"]

View File

@ -0,0 +1,12 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
module.exports = buildModule("Periods", (m) => {
const deployer = m.getAccount(0)
const secondsPerPeriod = m.getParameter("secondsPerPeriod", 0)
const periods = m.contract("Periods", [secondsPerPeriod], {
from: deployer,
})
return { periods }
})

View File

@ -0,0 +1,15 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
const VerifierModule = require("./verifier.js")
module.exports = buildModule("Proofs", (m) => {
const deployer = m.getAccount(0)
const { verifier } = m.useModule(VerifierModule)
const configuration = m.getParameter("configuration", null)
const testProofs = m.contract("TestProofs", [configuration, verifier], {
from: deployer,
})
return { testProofs }
})

View File

@ -0,0 +1,17 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
module.exports = buildModule("SlotReservations", (m) => {
const deployer = m.getAccount(0)
const configuration = m.getParameter("configuration", null)
const testSlotReservations = m.contract(
"TestSlotReservations",
[configuration],
{
from: deployer,
}
)
return { testSlotReservations }
})

22
ignition/modules/token.js Normal file
View File

@ -0,0 +1,22 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
const MAX_ACCOUNTS = 20
const MINTED_TOKENS = 1_000_000_000_000_000n
module.exports = buildModule("Token", (m) => {
const deployer = m.getAccount(0)
const token = m.contract("TestToken", [], {
from: deployer,
})
if (hre.network.config.tags.includes("local")) {
for (let i = 0; i < MAX_ACCOUNTS; i++) {
const account = m.getAccount(i)
const futureId = "SendingEth" + i
m.send(futureId, account, MINTED_TOKENS)
}
}
return { token }
})

View File

@ -0,0 +1,17 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
const { loadVerificationKey } = require("../../verifier/verifier.js")
module.exports = buildModule("Verifier", (m) => {
const deployer = m.getAccount(0)
const verificationKey = loadVerificationKey(hre.network.name)
const verifier = m.contract("Groth16Verifier", [verificationKey], {
from: deployer,
})
const testVerifier = m.contract("TestVerifier", [], {
from: deployer,
})
return { verifier, testVerifier }
})

35068
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,29 +2,28 @@
"name": "codex-contracts-eth", "name": "codex-contracts-eth",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "npm run lint && hardhat test", "test": "npm run lint && hardhat test --parallel",
"fuzz": "hardhat compile && fuzzing/fuzz.sh", "fuzz": "hardhat compile && fuzzing/fuzz.sh",
"start": "hardhat node --export deployment-localhost.json", "start": "hardhat node",
"compile": "hardhat compile", "compile": "hardhat compile",
"format": "prettier --write contracts/*.sol contracts/**/*.sol test/**/*.js", "format": "prettier --write contracts/*.sol contracts/**/*.sol test/**/*.js",
"format:check": "prettier --check contracts/*.sol contracts/**/*.sol test/**/*.js", "format:check": "prettier --check contracts/*.sol contracts/**/*.sol test/**/*.js",
"lint": "solhint contracts/**.sol", "lint": "solhint contracts/**.sol",
"deploy": "hardhat deploy", "deploy": "npx hardhat run scripts/deploy.js",
"verify": "npm run verify:marketplace && npm run verify:state_changes", "verify": "npm run verify:marketplace && npm run verify:state_changes",
"verify:marketplace": "certoraRun certora/confs/Marketplace.conf", "verify:marketplace": "certoraRun certora/confs/Marketplace.conf",
"verify:state_changes": "certoraRun certora/confs/StateChanges.conf" "verify:state_changes": "certoraRun certora/confs/StateChanges.conf",
"coverage": "npx hardhat coverage",
"gas:report": "REPORT_GAS=true npx hardhat test"
}, },
"devDependencies": { "devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.2.1",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@openzeppelin/contracts": "^5.3.0", "@openzeppelin/contracts": "^5.3.0",
"@stdlib/stats-binomial-test": "^0.0.7", "@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@stdlib/stats-binomial-test": "^0.2.2",
"chai": "^4.3.7", "chai": "^4.3.7",
"ethereum-waffle": "^3.4.4", "ethers": "6.14.3",
"ethers": "^5.7.2",
"hardhat": "^2.24.2", "hardhat": "^2.24.2",
"hardhat-deploy": "^0.11.34",
"hardhat-deploy-ethers": "^0.3.0-beta.13",
"prettier": "^2.8.2", "prettier": "^2.8.2",
"prettier-plugin-solidity": "^1.4.2", "prettier-plugin-solidity": "^1.4.2",
"solhint": "^5.0.5" "solhint": "^5.0.5"

24
scripts/deploy.js Normal file
View File

@ -0,0 +1,24 @@
const hre = require("hardhat")
const { mine } = require("@nomicfoundation/hardhat-network-helpers")
const MarketplaceModule = require("../ignition/modules/marketplace")
async function main() {
if (hre.network.config.tags.includes("local")) {
await mine(256)
}
const { marketplace, testMarketplace } = await hre.ignition.deploy(
MarketplaceModule
)
console.info("Deployed Marketplace with Groth16 Verifier at:")
console.log(await marketplace.getAddress())
console.log()
console.info("Deployed Marketplace with Test Verifier at:")
console.log(await testMarketplace.getAddress())
}
main().catch(console.error)

View File

@ -1,5 +1,5 @@
const { expect } = require("chai") const { expect } = require("chai")
const { ethers } = require("hardhat") const EndianModule = require("../ignition/modules/endian")
describe("Endian", function () { describe("Endian", function () {
const big = const big =
@ -10,8 +10,8 @@ describe("Endian", function () {
let endian let endian
beforeEach(async function () { beforeEach(async function () {
let Endian = await ethers.getContractFactory("TestEndian") const { testEndian } = await ignition.deploy(EndianModule)
endian = await Endian.deploy() endian = testEndian
}) })
it("converts from little endian to big endian", async function () { it("converts from little endian to big endian", async function () {

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,35 @@
const { expect } = require("chai") const { expect } = require("chai")
const { ethers } = require("hardhat") const PeriodsModule = require("../ignition/modules/periods")
describe("Periods", function () { describe("Periods", function () {
it("should revert when secondsPerPeriod is 0", async function () { it("should revert when secondsPerPeriod is 0", async function () {
const PeriodsContract = await ethers.getContractFactory("Periods") const promise = ignition.deploy(PeriodsModule, {
await expect(PeriodsContract.deploy(0)).to.be.revertedWith( parameters: {
"Periods_InvalidSecondsPerPeriod" Periods: {
) secondsPerPeriod: 0,
},
},
})
const expectedError = "Periods_InvalidSecondsPerPeriod"
const error = await expect(promise).to.be.rejected
expect(error)
.to.have.property("message")
.that.contains(
expectedError,
`Expected error ${expectedError}, but got ${error.message}`
)
}) })
it("should not revert when secondsPerPeriod more than 0", async function () { it("should not revert when secondsPerPeriod more than 0", async function () {
const PeriodsContract = await ethers.getContractFactory("Periods") const promise = ignition.deploy(PeriodsModule, {
await expect(PeriodsContract.deploy(10)).not.to.be.reverted parameters: {
Periods: {
secondsPerPeriod: 10,
},
},
})
await expect(promise).not.to.be.rejected
}) })
}) })

View File

@ -1,20 +1,21 @@
const { expect } = require("chai") const { expect } = require("chai")
const { ethers, deployments } = require("hardhat") const { ethers } = require("hardhat")
const { hexlify, randomBytes } = ethers.utils const { hexlify, randomBytes } = ethers
const { const {
snapshot, snapshot,
revert, revert,
mine,
ensureMinimumBlockHeight, ensureMinimumBlockHeight,
currentTime, currentTime,
advanceTime, advanceTime,
advanceTimeTo, advanceTimeTo,
mine,
} = require("./evm") } = require("./evm")
const { periodic } = require("./time") const { periodic } = require("./time")
const { loadProof, loadPublicInput } = require("../verifier/verifier") const { loadProof, loadPublicInput } = require("../verifier/verifier")
const { SlotState } = require("./requests") const { SlotState } = require("./requests")
const binomialTest = require("@stdlib/stats-binomial-test") const binomialTest = require("@stdlib/stats-binomial-test")
const { exampleProof } = require("./examples") const { exampleProof } = require("./examples")
const ProofsModule = require("../ignition/modules/proofs")
describe("Proofs", function () { describe("Proofs", function () {
const slotId = hexlify(randomBytes(32)) const slotId = hexlify(randomBytes(32))
@ -30,13 +31,22 @@ describe("Proofs", function () {
beforeEach(async function () { beforeEach(async function () {
await snapshot() await snapshot()
await ensureMinimumBlockHeight(256) await ensureMinimumBlockHeight(256)
const Proofs = await ethers.getContractFactory("TestProofs")
await deployments.fixture(["Verifier"]) const { testProofs } = await ignition.deploy(ProofsModule, {
const verifier = await deployments.get("Groth16Verifier") parameters: {
proofs = await Proofs.deploy( Proofs: {
{ period, timeout, downtime, zkeyHash: "", downtimeProduct }, configuration: {
verifier.address period,
) timeout,
downtime,
zkeyHash: "",
downtimeProduct,
},
},
},
})
proofs = testProofs
}) })
afterEach(async function () { afterEach(async function () {
@ -113,7 +123,7 @@ describe("Proofs", function () {
let previous = await proofs.getPointer(slotId) let previous = await proofs.getPointer(slotId)
await mine() await mine()
let current = await proofs.getPointer(slotId) let current = await proofs.getPointer(slotId)
expect(current).to.equal((previous + 1) % 256) expect(current).to.equal((previous + 1n) % 256n)
} }
}) })
}) })
@ -203,14 +213,14 @@ describe("Proofs", function () {
let invalid = exampleProof() let invalid = exampleProof()
await expect( await expect(
proofs.proofReceived(slotId, invalid, pubSignals) proofs.proofReceived(slotId, invalid, pubSignals)
).to.be.revertedWith("Proofs_InvalidProof") ).to.be.revertedWithCustomError(proofs, "Proofs_InvalidProof")
}) })
it("fails proof submission when public input is incorrect", async function () { it("fails proof submission when public input is incorrect", async function () {
let invalid = [1, 2, 3] let invalid = [1, 2, 3]
await expect( await expect(
proofs.proofReceived(slotId, proof, invalid) proofs.proofReceived(slotId, proof, invalid)
).to.be.revertedWith("Proofs_InvalidProof") ).to.be.revertedWithCustomError(proofs, "Proofs_InvalidProof")
}) })
it("emits an event when proof was submitted", async function () { it("emits an event when proof was submitted", async function () {
@ -223,7 +233,7 @@ describe("Proofs", function () {
await proofs.proofReceived(slotId, proof, pubSignals) await proofs.proofReceived(slotId, proof, pubSignals)
await expect( await expect(
proofs.proofReceived(slotId, proof, pubSignals) proofs.proofReceived(slotId, proof, pubSignals)
).to.be.revertedWith("Proofs_ProofAlreadySubmitted") ).to.be.revertedWithCustomError(proofs, "Proofs_ProofAlreadySubmitted")
}) })
it("marks a proof as missing", async function () { it("marks a proof as missing", async function () {
@ -240,7 +250,7 @@ describe("Proofs", function () {
let currentPeriod = periodOf(await currentTime()) let currentPeriod = periodOf(await currentTime())
await expect( await expect(
proofs.markProofAsMissing(slotId, currentPeriod) proofs.markProofAsMissing(slotId, currentPeriod)
).to.be.revertedWith("Proofs_PeriodNotEnded") ).to.be.revertedWithCustomError(proofs, "Proofs_PeriodNotEnded")
}) })
it("does not mark a proof as missing after timeout", async function () { it("does not mark a proof as missing after timeout", async function () {
@ -249,7 +259,7 @@ describe("Proofs", function () {
await advanceTimeTo(periodEnd(currentPeriod) + timeout + 1) await advanceTimeTo(periodEnd(currentPeriod) + timeout + 1)
await expect( await expect(
proofs.markProofAsMissing(slotId, currentPeriod) proofs.markProofAsMissing(slotId, currentPeriod)
).to.be.revertedWith("Proofs_ValidationTimedOut") ).to.be.revertedWithCustomError(proofs, "Proofs_ValidationTimedOut")
}) })
it("does not mark a received proof as missing", async function () { it("does not mark a received proof as missing", async function () {
@ -259,7 +269,7 @@ describe("Proofs", function () {
await advanceTimeTo(periodEnd(receivedPeriod) + 1) await advanceTimeTo(periodEnd(receivedPeriod) + 1)
await expect( await expect(
proofs.markProofAsMissing(slotId, receivedPeriod) proofs.markProofAsMissing(slotId, receivedPeriod)
).to.be.revertedWith("Proofs_ProofNotMissing") ).to.be.revertedWithCustomError(proofs, "Proofs_ProofNotMissing")
}) })
it("does not mark proof as missing when not required", async function () { it("does not mark proof as missing when not required", async function () {
@ -270,7 +280,7 @@ describe("Proofs", function () {
await advanceTimeTo(periodEnd(currentPeriod) + 1) await advanceTimeTo(periodEnd(currentPeriod) + 1)
await expect( await expect(
proofs.markProofAsMissing(slotId, currentPeriod) proofs.markProofAsMissing(slotId, currentPeriod)
).to.be.revertedWith("Proofs_ProofNotRequired") ).to.be.revertedWithCustomError(proofs, "Proofs_ProofNotRequired")
}) })
it("does not mark proof as missing twice", async function () { it("does not mark proof as missing twice", async function () {
@ -280,7 +290,10 @@ describe("Proofs", function () {
await proofs.markProofAsMissing(slotId, missedPeriod) await proofs.markProofAsMissing(slotId, missedPeriod)
await expect( await expect(
proofs.markProofAsMissing(slotId, missedPeriod) proofs.markProofAsMissing(slotId, missedPeriod)
).to.be.revertedWith("Proofs_ProofAlreadyMarkedMissing") ).to.be.revertedWithCustomError(
proofs,
"Proofs_ProofAlreadyMarkedMissing"
)
}) })
it("requires no proofs when slot is finished", async function () { it("requires no proofs when slot is finished", async function () {

View File

@ -3,6 +3,7 @@ const { ethers } = require("hardhat")
const { exampleRequest, exampleConfiguration } = require("./examples") const { exampleRequest, exampleConfiguration } = require("./examples")
const { requestId, slotId } = require("./ids") const { requestId, slotId } = require("./ids")
const { SlotState } = require("./requests") const { SlotState } = require("./requests")
const SlotReservationsModule = require("../ignition/modules/slot-reservations")
describe("SlotReservations", function () { describe("SlotReservations", function () {
let reservations let reservations
@ -15,10 +16,18 @@ describe("SlotReservations", function () {
const config = exampleConfiguration() const config = exampleConfiguration()
beforeEach(async function () { beforeEach(async function () {
let SlotReservations = await ethers.getContractFactory( const { testSlotReservations } = await ignition.deploy(
"TestSlotReservations" SlotReservationsModule,
{
parameters: {
SlotReservations: {
configuration: config.reservations,
},
},
}
) )
reservations = await SlotReservations.deploy(config.reservations)
reservations = testSlotReservations
;[provider, address1, address2, address3] = await ethers.getSigners() ;[provider, address1, address2, address3] = await ethers.getSigners()
request = await exampleRequest() request = await exampleRequest()
@ -76,7 +85,10 @@ describe("SlotReservations", function () {
it("cannot reserve a slot more than once", async function () { it("cannot reserve a slot more than once", async function () {
await reservations.reserveSlot(reqId, slotIndex) await reservations.reserveSlot(reqId, slotIndex)
await expect(reservations.reserveSlot(reqId, slotIndex)).to.be.revertedWith( await expect(
reservations.reserveSlot(reqId, slotIndex)
).to.be.revertedWithCustomError(
reservations,
"SlotReservations_ReservationNotAllowed" "SlotReservations_ReservationNotAllowed"
) )
expect(await reservations.length(id)).to.equal(1) expect(await reservations.length(id)).to.equal(1)
@ -95,7 +107,10 @@ describe("SlotReservations", function () {
switchAccount(address3) switchAccount(address3)
await reservations.reserveSlot(reqId, slotIndex) await reservations.reserveSlot(reqId, slotIndex)
switchAccount(provider) switchAccount(provider)
await expect(reservations.reserveSlot(reqId, slotIndex)).to.be.revertedWith( await expect(
reservations.reserveSlot(reqId, slotIndex)
).to.be.revertedWithCustomError(
reservations,
"SlotReservations_ReservationNotAllowed" "SlotReservations_ReservationNotAllowed"
) )
expect(await reservations.length(id)).to.equal(3) expect(await reservations.length(id)).to.equal(3)
@ -115,7 +130,10 @@ describe("SlotReservations", function () {
it("cannot reserve a slot if not free or not in repair", async function () { it("cannot reserve a slot if not free or not in repair", async function () {
await reservations.setSlotState(id, SlotState.Filled) await reservations.setSlotState(id, SlotState.Filled)
await expect(reservations.reserveSlot(reqId, slotIndex)).to.be.revertedWith( await expect(
reservations.reserveSlot(reqId, slotIndex)
).to.be.revertedWithCustomError(
reservations,
"SlotReservations_ReservationNotAllowed" "SlotReservations_ReservationNotAllowed"
) )
expect(await reservations.length(id)).to.equal(0) expect(await reservations.length(id)).to.equal(0)

View File

@ -1,63 +1,52 @@
const { ethers } = require("hardhat") const {
time,
mine,
takeSnapshot,
setNextBlockTimestamp,
} = require("@nomicfoundation/hardhat-network-helpers")
let snapshots = [] const snapshots = []
async function snapshot() { async function snapshot() {
const id = await ethers.provider.send("evm_snapshot") const snapshot = await takeSnapshot()
const time = await currentTime()
const automine = await ethers.provider.send("hardhat_getAutomine") const automine = await ethers.provider.send("hardhat_getAutomine")
snapshots.push({ id, time, automine }) const time = await currentTime()
snapshots.push({ snapshot, automine, time })
} }
async function revert() { async function revert() {
const { id, time, automine } = snapshots.pop() const { snapshot, time, automine } = snapshots.pop()
await ethers.provider.send("evm_revert", [id]) if (snapshot) {
setNextBlockTimestamp(time)
const current = await currentTime() await ethers.provider.send("evm_setAutomine", [automine])
const nextTime = Math.max(time + 1, current + 1) return snapshot.restore()
}
await ethers.provider.send("evm_setNextBlockTimestamp", [nextTime])
await ethers.provider.send("evm_setAutomine", [automine])
}
/**
* Enables or disables Hardhat's automine mode.
*
* When automine mode is disabled, transactions that revert are silently ignored!
*/
async function setAutomine(enabled) {
await ethers.provider.send("evm_setAutomine", [enabled])
}
async function mine() {
await ethers.provider.send("evm_mine")
} }
async function ensureMinimumBlockHeight(height) { async function ensureMinimumBlockHeight(height) {
while ((await ethers.provider.getBlockNumber()) < height) { while ((await time.latestBlock()) < height) {
await mine() await mine()
} }
} }
async function setNextBlockTimestamp(timestamp) {
return time.setNextBlockTimestamp(timestamp)
}
async function currentTime() { async function currentTime() {
let block = await ethers.provider.getBlock("latest") return time.latest()
return block.timestamp
} }
async function advanceTime(seconds) { async function advanceTime(seconds) {
await ethers.provider.send("evm_increaseTime", [seconds]) await time.increase(seconds)
await mine() await mine()
} }
async function advanceTimeTo(timestamp) { async function advanceTimeTo(timestamp) {
await setNextBlockTimestamp(timestamp) await time.setNextBlockTimestamp(timestamp)
await mine() await mine()
} }
async function setNextBlockTimestamp(timestamp) {
await ethers.provider.send("evm_setNextBlockTimestamp", [timestamp])
}
module.exports = { module.exports = {
snapshot, snapshot,
revert, revert,

View File

@ -1,6 +1,5 @@
const { ethers } = require("hardhat")
const { hours } = require("./time") const { hours } = require("./time")
const { hexlify, randomBytes } = ethers.utils const { hexlify, randomBytes } = require("hardhat").ethers
const exampleConfiguration = () => ({ const exampleConfiguration = () => ({
collateral: { collateral: {
@ -36,7 +35,7 @@ const exampleRequest = async () => {
}, },
content: { content: {
cid: Buffer.from("zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob"), cid: Buffer.from("zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob"),
merkleRoot: Array.from(randomBytes(32)), merkleRoot: randomBytes(32),
}, },
expiry: hours(1), expiry: hours(1),
nonce: hexlify(randomBytes(32)), nonce: hexlify(randomBytes(32)),

View File

@ -1,12 +1,11 @@
const { ethers } = require("hardhat") const { keccak256, AbiCoder } = require("hardhat").ethers
const { keccak256, defaultAbiCoder } = ethers.utils
function requestId(request) { function requestId(request) {
const Ask = "tuple(uint256, uint256, uint256, uint64, uint64, uint64, int64)" const Ask = "tuple(uint256, uint256, uint256, uint64, uint64, uint64, int64)"
const Content = "tuple(bytes, bytes32)" const Content = "tuple(bytes, bytes32)"
const Request = const Request =
"tuple(address, " + Ask + ", " + Content + ", uint64, bytes32)" "tuple(address, " + Ask + ", " + Content + ", uint64, bytes32)"
return keccak256(defaultAbiCoder.encode([Request], requestToArray(request))) return keccak256(new AbiCoder().encode([Request], requestToArray(request)))
} }
function askToArray(ask) { function askToArray(ask) {
@ -40,7 +39,7 @@ function requestToArray(request) {
function slotId(slot) { function slotId(slot) {
const types = "tuple(bytes32, uint256)" const types = "tuple(bytes32, uint256)"
const values = [slot.request, slot.index] const values = [slot.request, slot.index]
const encoding = defaultAbiCoder.encode([types], [values]) const encoding = new AbiCoder().encode([types], [values])
return keccak256(encoding) return keccak256(encoding)
} }

View File

@ -4,16 +4,16 @@ const { payoutForDuration } = require("./price")
const { collateralPerSlot } = require("./collateral") const { collateralPerSlot } = require("./collateral")
async function waitUntilCancelled(contract, request) { async function waitUntilCancelled(contract, request) {
const expiry = (await contract.requestExpiry(requestId(request))).toNumber() const expiry = await contract.requestExpiry(requestId(request))
// We do +1, because the expiry check in contract is done as `>` and not `>=`. // We do +1, because the expiry check in contract is done as `>` and not `>=`.
await advanceTimeTo(expiry + 1) return advanceTimeTo(expiry + 1n)
} }
async function waitUntilSlotsFilled(contract, request, proof, token, slots) { async function waitUntilSlotsFilled(contract, request, proof, token, slots) {
let collateral = collateralPerSlot(request) let collateral = collateralPerSlot(request)
await token.approve(contract.address, collateral * slots.length) await token.approve(await contract.getAddress(), collateral * slots.length)
let requestEnd = (await contract.requestEnd(requestId(request))).toNumber() let requestEnd = await contract.requestEnd(requestId(request))
const payouts = [] const payouts = []
for (let slotIndex of slots) { for (let slotIndex of slots) {
await contract.reserveSlot(requestId(request), slotIndex) await contract.reserveSlot(requestId(request), slotIndex)
@ -40,9 +40,9 @@ async function waitUntilStarted(contract, request, proof, token) {
} }
async function waitUntilFinished(contract, requestId) { async function waitUntilFinished(contract, requestId) {
const end = (await contract.requestEnd(requestId)).toNumber() const end = await contract.requestEnd(requestId)
// We do +1, because the end check in contract is done as `>` and not `>=`. // We do +1, because the end check in contract is done as `>` and not `>=`.
await advanceTimeTo(end + 1) await advanceTimeTo(end + 1n)
} }
async function waitUntilFailed(contract, request) { async function waitUntilFailed(contract, request) {
@ -96,6 +96,11 @@ function patchOverloads(contract) {
} }
} }
function littleEndianToBigInt(littleEndian) {
const buffer = Buffer.from(littleEndian)
return BigInt(`0x${buffer.toString("hex")}`)
}
module.exports = { module.exports = {
waitUntilCancelled, waitUntilCancelled,
waitUntilStarted, waitUntilStarted,
@ -104,4 +109,5 @@ module.exports = {
waitUntilFailed, waitUntilFailed,
waitUntilSlotFailed, waitUntilSlotFailed,
patchOverloads, patchOverloads,
littleEndianToBigInt,
} }

View File

@ -9,7 +9,21 @@ function maxPrice(request) {
} }
function payoutForDuration(request, start, end) { function payoutForDuration(request, start, end) {
return (end - start) * pricePerSlotPerSecond(request) return (Number(end) - Number(start)) * pricePerSlotPerSecond(request)
} }
module.exports = { maxPrice, pricePerSlotPerSecond, payoutForDuration } function calculatePartialPayout(request, expiresAt, filledAt) {
return (Number(expiresAt) - Number(filledAt)) * pricePerSlotPerSecond(request)
}
function calculateBalance(balance, reward) {
return BigInt(balance) + BigInt(reward)
}
module.exports = {
maxPrice,
pricePerSlotPerSecond,
payoutForDuration,
calculatePartialPayout,
calculateBalance,
}

View File

@ -1,5 +1,4 @@
const { Assertion } = require("chai") const { Assertion } = require("chai")
const { currentTime } = require("./evm")
const RequestState = { const RequestState = {
New: 0, New: 0,
@ -10,13 +9,13 @@ const RequestState = {
} }
const SlotState = { const SlotState = {
Free: 0, Free: 0n,
Filled: 1, Filled: 1n,
Finished: 2, Finished: 2n,
Failed: 3, Failed: 3n,
Paid: 4, Paid: 4n,
Cancelled: 5, Cancelled: 5n,
Repair: 6, Repair: 6n,
} }
function enableRequestAssertions() { function enableRequestAssertions() {