mirror of
https://github.com/codex-storage/codex-contracts-eth.git
synced 2025-02-10 10:26:32 +00:00
feat(slot-reservations): Allow slots to be reserved (#177)
* feat(slot-reservations): Allow slots to be reserved Closes #175. Allows reservation of slots, without an implementation of the expanding window. - Add a function called `reserveSlot(address, SlotId)`, that allows three unique addresses per slot to be reserved, that returns bool if successful. - Use `mapping(SlotId => EnumerableSet.AddressSet)` - Return false if the address could not be added to the set (if `EnumerableSet.add` returns false) - Add `canReserveSlot(address, SlotId)` - Return `true` if set of reservations is less than 3 and the set doesn't already contain the address - Return `true` otherwise (for now, later add in logic for checking the address is inside the expanding window) - Call `canReserveSlot` from `reserveSlot` as a `require` or invariant - Add `SlotReservations` configuration struct to the network-level config, with `maxReservations`
This commit is contained in:
parent
3a074abd20
commit
33010bd20c
@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
struct MarketplaceConfig {
|
||||
CollateralConfig collateral;
|
||||
ProofConfig proofs;
|
||||
SlotReservationsConfig reservations;
|
||||
}
|
||||
|
||||
struct CollateralConfig {
|
||||
@ -29,3 +30,8 @@ struct ProofConfig {
|
||||
// blocks. Should be a prime number to ensure there are no cycles.
|
||||
uint8 downtimeProduct;
|
||||
}
|
||||
|
||||
struct SlotReservationsConfig {
|
||||
// Number of allowed reservations per slot
|
||||
uint8 maxReservations;
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ contract FuzzMarketplace is Marketplace {
|
||||
Marketplace(
|
||||
MarketplaceConfig(
|
||||
CollateralConfig(10, 5, 3, 10),
|
||||
ProofConfig(10, 5, 64, "", 67)
|
||||
ProofConfig(10, 5, 64, "", 67),
|
||||
SlotReservationsConfig(20)
|
||||
),
|
||||
new TestToken(),
|
||||
new TestVerifier()
|
||||
|
@ -7,11 +7,12 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
import "./Configuration.sol";
|
||||
import "./Requests.sol";
|
||||
import "./Proofs.sol";
|
||||
import "./SlotReservations.sol";
|
||||
import "./StateRetrieval.sol";
|
||||
import "./Endian.sol";
|
||||
import "./Groth16.sol";
|
||||
|
||||
contract Marketplace is Proofs, StateRetrieval, Endian {
|
||||
contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
using EnumerableSet for EnumerableSet.Bytes32Set;
|
||||
using Requests for Request;
|
||||
|
||||
@ -58,7 +59,10 @@ contract Marketplace is Proofs, StateRetrieval, Endian {
|
||||
MarketplaceConfig memory configuration,
|
||||
IERC20 token_,
|
||||
IGroth16Verifier verifier
|
||||
) Proofs(configuration.proofs, verifier) {
|
||||
)
|
||||
SlotReservations(configuration.reservations)
|
||||
Proofs(configuration.proofs, verifier)
|
||||
{
|
||||
_token = token_;
|
||||
|
||||
require(
|
||||
|
36
contracts/SlotReservations.sol
Normal file
36
contracts/SlotReservations.sol
Normal file
@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
import "./Requests.sol";
|
||||
import "./Configuration.sol";
|
||||
|
||||
contract SlotReservations {
|
||||
using EnumerableSet for EnumerableSet.AddressSet;
|
||||
|
||||
mapping(SlotId => EnumerableSet.AddressSet) internal _reservations;
|
||||
SlotReservationsConfig private _config;
|
||||
|
||||
constructor(SlotReservationsConfig memory config) {
|
||||
_config = config;
|
||||
}
|
||||
|
||||
function reserveSlot(RequestId requestId, uint256 slotIndex) public {
|
||||
require(canReserveSlot(requestId, slotIndex), "Reservation not allowed");
|
||||
|
||||
SlotId slotId = Requests.slotId(requestId, slotIndex);
|
||||
_reservations[slotId].add(msg.sender);
|
||||
}
|
||||
|
||||
function canReserveSlot(
|
||||
RequestId requestId,
|
||||
uint256 slotIndex
|
||||
) public view returns (bool) {
|
||||
address host = msg.sender;
|
||||
SlotId slotId = Requests.slotId(requestId, slotIndex);
|
||||
return
|
||||
// TODO: add in check for address inside of expanding window
|
||||
(_reservations[slotId].length() < _config.maxReservations) &&
|
||||
(!_reservations[slotId].contains(host));
|
||||
}
|
||||
}
|
19
contracts/TestSlotReservations.sol
Normal file
19
contracts/TestSlotReservations.sol
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.23;
|
||||
|
||||
import "./SlotReservations.sol";
|
||||
|
||||
contract TestSlotReservations is SlotReservations {
|
||||
using EnumerableSet for EnumerableSet.AddressSet;
|
||||
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
constructor(SlotReservationsConfig memory config) SlotReservations(config) {}
|
||||
|
||||
function contains(SlotId slotId, address host) public view returns (bool) {
|
||||
return _reservations[slotId].contains(host);
|
||||
}
|
||||
|
||||
function length(SlotId slotId) public view returns (uint256) {
|
||||
return _reservations[slotId].length();
|
||||
}
|
||||
}
|
@ -16,6 +16,9 @@ const CONFIGURATION = {
|
||||
downtime: 64,
|
||||
downtimeProduct: 67
|
||||
},
|
||||
reservations: {
|
||||
maxReservations: 3
|
||||
}
|
||||
}
|
||||
|
||||
async function mine256blocks({ network, ethers }) {
|
||||
|
101
test/SlotReservations.test.js
Normal file
101
test/SlotReservations.test.js
Normal file
@ -0,0 +1,101 @@
|
||||
const { expect } = require("chai")
|
||||
const { ethers } = require("hardhat")
|
||||
const { exampleRequest, exampleConfiguration } = require("./examples")
|
||||
const { requestId, slotId } = require("./ids")
|
||||
|
||||
describe("SlotReservations", function () {
|
||||
let reservations
|
||||
let provider, address1, address2, address3
|
||||
let request
|
||||
let reqId
|
||||
let slot
|
||||
let slotIndex
|
||||
let id // can't use slotId because it'll shadow the function slotId
|
||||
const config = exampleConfiguration()
|
||||
|
||||
beforeEach(async function () {
|
||||
let SlotReservations = await ethers.getContractFactory(
|
||||
"TestSlotReservations"
|
||||
)
|
||||
reservations = await SlotReservations.deploy(config.reservations)
|
||||
;[provider, address1, address2, address3] = await ethers.getSigners()
|
||||
|
||||
request = await exampleRequest()
|
||||
reqId = requestId(request)
|
||||
slotIndex = request.ask.slots / 2
|
||||
slot = {
|
||||
request: reqId,
|
||||
index: slotIndex,
|
||||
}
|
||||
id = slotId(slot)
|
||||
})
|
||||
|
||||
function switchAccount(account) {
|
||||
reservations = reservations.connect(account)
|
||||
}
|
||||
|
||||
it("allows a slot to be reserved", async function () {
|
||||
expect(reservations.reserveSlot(reqId, slotIndex)).to.not.be.reverted
|
||||
})
|
||||
|
||||
it("contains the correct addresses after reservation", async function () {
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
expect(await reservations.contains(id, provider.address)).to.be.true
|
||||
|
||||
switchAccount(address1)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
expect(await reservations.contains(id, address1.address)).to.be.true
|
||||
})
|
||||
|
||||
it("has the correct number of addresses after reservation", async function () {
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
expect(await reservations.length(id)).to.equal(1)
|
||||
|
||||
switchAccount(address1)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
expect(await reservations.length(id)).to.equal(2)
|
||||
})
|
||||
|
||||
it("reports a slot can be reserved", async function () {
|
||||
expect(await reservations.canReserveSlot(reqId, slotIndex)).to.be.true
|
||||
})
|
||||
|
||||
it("cannot reserve a slot more than once", async function () {
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
await expect(reservations.reserveSlot(reqId, slotIndex)).to.be.revertedWith(
|
||||
"Reservation not allowed"
|
||||
)
|
||||
expect(await reservations.length(id)).to.equal(1)
|
||||
})
|
||||
|
||||
it("reports a slot cannot be reserved if already reserved", async function () {
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
expect(await reservations.canReserveSlot(reqId, slotIndex)).to.be.false
|
||||
})
|
||||
|
||||
it("cannot reserve a slot if reservations are at capacity", async function () {
|
||||
switchAccount(address1)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
switchAccount(address2)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
switchAccount(address3)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
switchAccount(provider)
|
||||
await expect(reservations.reserveSlot(reqId, slotIndex)).to.be.revertedWith(
|
||||
"Reservation not allowed"
|
||||
)
|
||||
expect(await reservations.length(id)).to.equal(3)
|
||||
expect(await reservations.contains(id, provider.address)).to.be.false
|
||||
})
|
||||
|
||||
it("reports a slot cannot be reserved if reservations are at capacity", async function () {
|
||||
switchAccount(address1)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
switchAccount(address2)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
switchAccount(address3)
|
||||
await reservations.reserveSlot(reqId, slotIndex)
|
||||
switchAccount(provider)
|
||||
expect(await reservations.canReserveSlot(reqId, slotIndex)).to.be.false
|
||||
})
|
||||
})
|
@ -16,6 +16,9 @@ const exampleConfiguration = () => ({
|
||||
zkeyHash: "",
|
||||
downtimeProduct: 67,
|
||||
},
|
||||
reservations: {
|
||||
maxReservations: 3,
|
||||
},
|
||||
})
|
||||
|
||||
const exampleRequest = async () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user