import { time } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";
import {
approveEventPlace,
cancelEvent,
closeEvent,
createAndCancelEvent,
createAndCloseEvent,
createAndUpdateEventPlace,
createEvent,
createEventForCategories,
createEventPlace,
declineEventPlace,
deployContract,
itExpectsOnlyLocalProvider,
itExpectsOnlyMaster,
itExpectsOnlyVerifiedShaman,
loadFixture,
submitEventPlaceRequest,
submitEventRequest,
timestamp,
} from "./helpers";
import {
approveEventPlaceArgsToArray,
baseEventArgsToArray,
declineEventPlaceArgsToArray,
submitEventPlaceRequestArgsToArray,
updateEventPlaceArgsToArray,
} from "./types";
import {
defaultCreateEventPlaceRequest,
defaultEventDepositSize,
defaultSubmitEventRequest,
defaultUpdateEventPlaceRequest,
} from "./data";
const limitedCategory = {
name: "Seed",
discountPercentage: 0,
quota: defaultCreateEventPlaceRequest.minTickets,
hasQuota: true,
};
import {
createEventPlaceCornerCases,
getSubmitEventCases,
getSubmitEventDateRangeOverlapCornerCases,
} from "./corner-cases";
describe("CyberValleyEventManager", () => {
let testStartBlock: number;
let provider: typeof ethers.provider;
beforeEach(async () => {
provider = ethers.provider;
testStartBlock = await provider.getBlockNumber();
});
afterEach(async function () {
const currentBlock = await provider.getBlockNumber();
const txHashes: string[] = [];
console.log(`\n${"=".repeat(80)}`);
console.log(`TEST: ${this.currentTest?.title || "unknown"}`);
console.log(`Blocks: ${testStartBlock + 1} to ${currentBlock}`);
console.log(`${"=".repeat(80)}`);
for (let i = testStartBlock + 1; i <= currentBlock; i++) {
const block = await provider.getBlock(i, true);
if (block && block.transactions.length > 0) {
console.log(
`\nBlock ${i} (${block.transactions.length} transactions):`,
);
for (const txHash of block.transactions) {
const tx = await provider.getTransaction(txHash as string);
if (tx) {
const receipt = await provider.getTransactionReceipt(tx.hash);
const eventCount = receipt?.logs.length || 0;
console.log(` โข TX: ${tx.hash} (${eventCount} events)`);
txHashes.push(tx.hash);
}
}
}
}
console.log(`\nTotal transactions in test: ${txHashes.length}`);
console.log(`${"=".repeat(80)}\n`);
});
async function createSubmittedEvent() {
const {
eventManager,
ERC20,
master,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const VERIFIED_SHAMAN_ROLE = await eventManager.VERIFIED_SHAMAN_ROLE();
await eventManager
.connect(master)
.grantRole(VERIFIED_SHAMAN_ROLE, await creator.getAddress());
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{},
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
});
const eventId = await getEventId();
return {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
eventId,
};
}
describe("setRevenueSplitter", () => {
itExpectsOnlyMaster("setRevenueSplitter", [ethers.ZeroAddress]);
it("sets revenue splitter address", async () => {
const { eventManager, master, splitter } =
await loadFixture(deployContract);
const newSplitter = await splitter.getAddress();
await eventManager.connect(master).setRevenueSplitter(newSplitter);
expect(await eventManager.revenueSplitter()).to.equal(newSplitter);
});
it("reverts when splitter is zero address", async () => {
const { eventManager, master } = await loadFixture(deployContract);
await expect(
eventManager.connect(master).setRevenueSplitter(ethers.ZeroAddress),
).to.be.revertedWith("Splitter address cannot be zero");
});
});
describe("submitEventPlaceRequest", () => {
itExpectsOnlyVerifiedShaman(
"submitEventPlaceRequest",
submitEventPlaceRequestArgsToArray(defaultCreateEventPlaceRequest),
);
it("should emit NewEventPlaceRequest", async () => {
const { eventManager, verifiedShaman } =
await loadFixture(deployContract);
const { tx } = await submitEventPlaceRequest(
eventManager,
verifiedShaman,
);
const args = submitEventPlaceRequestArgsToArray(
defaultCreateEventPlaceRequest,
);
await expect(tx)
.to.emit(eventManager, "NewEventPlaceRequest")
.withArgs(0, await verifiedShaman.getAddress(), ...args.slice(0, 9));
});
it("auto-approves when requester is a local provider", async () => {
const { eventManager, master, localProvider } =
await loadFixture(deployContract);
const VERIFIED_SHAMAN_ROLE = await eventManager.VERIFIED_SHAMAN_ROLE();
await eventManager
.connect(master)
.grantRole(VERIFIED_SHAMAN_ROLE, await localProvider.getAddress());
const tx = await eventManager
.connect(localProvider)
.submitEventPlaceRequest(
...submitEventPlaceRequestArgsToArray(defaultCreateEventPlaceRequest),
);
const args = submitEventPlaceRequestArgsToArray(
defaultCreateEventPlaceRequest,
);
await expect(tx)
.to.emit(eventManager, "EventPlaceUpdated")
.withArgs(
await localProvider.getAddress(),
0,
args[0],
args[1],
args[2],
args[3],
args[4],
args[5],
1,
args[6],
args[7],
args[8],
args[9],
);
const eventPlace = await eventManager.eventPlaces(0);
expect(eventPlace.status).to.equal(1);
expect(eventPlace.provider).to.equal(await localProvider.getAddress());
});
createEventPlaceCornerCases.forEach(({ patch, revertedWith }, idx) =>
it(`should validate invariants. Case ${idx + 1}: ${JSON.stringify(patch)}`, async () => {
const { eventManager, verifiedShaman } =
await loadFixture(deployContract);
await expect(
submitEventPlaceRequest(eventManager, verifiedShaman, {
...defaultCreateEventPlaceRequest,
...patch,
}),
).to.be.revertedWith(revertedWith);
}),
);
});
describe("approveEventPlace", () => {
itExpectsOnlyLocalProvider(
"approveEventPlace",
approveEventPlaceArgsToArray({ eventPlaceId: 0, eventDepositSize: 100 }),
);
it("emits EventPlaceUpdated", async () => {
const { eventManager, verifiedShaman, localProvider } =
await loadFixture(deployContract);
const { eventPlaceId } = await submitEventPlaceRequest(
eventManager,
verifiedShaman,
);
await expect(
approveEventPlace(eventManager, localProvider, {
eventPlaceId,
eventDepositSize: 100,
}),
).to.emit(eventManager, "EventPlaceUpdated");
});
it("reverts on non-existing event place", async () => {
const { eventManager, localProvider } = await loadFixture(deployContract);
await expect(
approveEventPlace(eventManager, localProvider, {
eventPlaceId: 999,
eventDepositSize: 100,
}),
).to.be.revertedWith("EventPlace does not exist");
});
it("reverts on already approved event place", async () => {
const { eventManager, verifiedShaman, localProvider } =
await loadFixture(deployContract);
const { eventPlaceId } = await submitEventPlaceRequest(
eventManager,
verifiedShaman,
);
await approveEventPlace(eventManager, localProvider, {
eventPlaceId,
eventDepositSize: 100,
});
await expect(
approveEventPlace(eventManager, localProvider, {
eventPlaceId,
eventDepositSize: 100,
}),
).to.be.revertedWith("EventPlace status differs from submitted");
});
it("reverts when deposit is zero", async () => {
const { eventManager, verifiedShaman, localProvider } =
await loadFixture(deployContract);
const { eventPlaceId } = await submitEventPlaceRequest(
eventManager,
verifiedShaman,
);
await expect(
approveEventPlace(eventManager, localProvider, {
eventPlaceId,
eventDepositSize: 0,
}),
).to.be.revertedWith("Deposit must be greater than zero");
});
});
describe("declineEventPlace", () => {
itExpectsOnlyLocalProvider(
"declineEventPlace",
declineEventPlaceArgsToArray({ eventPlaceId: 0 }),
);
it("reverts on non-existing event place", async () => {
const { eventManager, localProvider } = await loadFixture(deployContract);
await expect(
declineEventPlace(eventManager, localProvider, { eventPlaceId: 999 }),
).to.be.revertedWith("EventPlace does not exist");
});
});
describe("updateEventPlace", () => {
itExpectsOnlyLocalProvider(
"updateEventPlace",
updateEventPlaceArgsToArray(defaultUpdateEventPlaceRequest),
);
it("should emit EventPlaceUpdated", async () => {
const { eventManager, verifiedShaman, localProvider } =
await loadFixture(deployContract);
const tx = await createAndUpdateEventPlace(
eventManager,
verifiedShaman,
localProvider,
{},
);
const args = updateEventPlaceArgsToArray(defaultUpdateEventPlaceRequest);
await expect(tx)
.to.emit(eventManager, "EventPlaceUpdated")
.withArgs(
await localProvider.getAddress(),
args[0], // eventPlaceId
args[1], // maxTickets
args[2], // minTickets
args[3], // minPrice
args[4], // daysBeforeCancel
args[5], // minDays
args[6], // available
1, // status (Approved)
args[7], // digest
args[8], // hashFunction
args[9], // size
100, // eventDepositSize
);
});
createEventPlaceCornerCases.forEach(({ patch, revertedWith }, idx) =>
it(`should validate invariants. Case ${idx + 1}: ${JSON.stringify(patch)}`, async () => {
const { eventManager, verifiedShaman, localProvider } =
await loadFixture(deployContract);
await expect(
createAndUpdateEventPlace(
eventManager,
verifiedShaman,
localProvider,
{
...defaultUpdateEventPlaceRequest,
...patch,
},
),
).to.be.revertedWith(revertedWith);
}),
);
});
describe("submitEventRequest", () => {
it("emits NewEventRequest", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
const { request, tx, getEventId } = await submitEventRequest(
eventManager,
creator,
{
eventPlaceId,
},
);
await expect(tx)
.to.emit(eventManager, "NewEventRequest")
.withArgs(
await getEventId(),
await creator.getAddress(),
...baseEventArgsToArray(request),
);
});
it("transfers ERC20 token", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await expect(
await ERC20.balanceOf(await eventManager.getAddress()),
).to.equal(0);
const { eventPlaceId, tx: createEventPlaceTx } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { tx } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
});
await expect(tx).to.changeTokenBalances(
ERC20,
[await eventManager.getAddress(), await creator.getAddress()],
[defaultEventDepositSize, -defaultEventDepositSize],
);
});
it("reverts on insufficient funds", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{
...defaultCreateEventPlaceRequest,
},
);
const { tx } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
});
await expect(tx).to.be.revertedWith("Not enough tokens");
});
for (const idx of [0, 1, 2, 3, 4]) {
it(`validation case ${idx + 1}`, async () => {
const cases = await getSubmitEventCases(timestamp);
const testCase = cases[idx];
if (!testCase) return;
const { eventPlacePatch, eventRequestPatch, revertsWith } = testCase;
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
eventPlacePatch,
);
const { tx } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
...eventRequestPatch,
});
if (revertsWith == null) {
await expect(tx).to.not.be.reverted;
} else {
await expect(tx).to.be.revertedWith(revertsWith);
}
});
}
for (const idx of [0, 1, 2]) {
it(`reverts on overlap: Case ${idx}`, async () => {
const cases =
await getSubmitEventDateRangeOverlapCornerCases(timestamp);
const { approvedEventPatch, submittedEventPatch } = cases[idx];
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx: createEventTx } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
approvedEventPatch,
{},
splitter,
);
await createEventTx;
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { tx } = await submitEventRequest(
eventManager,
creator,
submittedEventPatch,
);
await expect(tx).to.be.revertedWith(
"Requested event overlaps with existing",
);
});
}
it("allows submit in a one-day gap between approved events", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { request: firstRequest } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{ startDate: await timestamp(11), daysAmount: 3 },
{},
splitter,
);
const eventPlaceId = firstRequest.eventPlaceId;
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { tx: secondSubmitTx, getEventId: getSecondEventId } =
await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(15),
daysAmount: 3,
});
await secondSubmitTx;
const secondEventId = await getSecondEventId();
await expect(
eventManager.connect(localProvider).approveEvent(secondEventId, 1n),
).to.not.be.reverted;
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { tx: gapEventTx } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(14),
daysAmount: 1,
});
await expect(gapEventTx).to.not.be.reverted;
});
});
describe("approveEvent", () => {
itExpectsOnlyLocalProvider("approveEvent", [BigInt(0), BigInt(1)]);
it("emits EventStatusChanged", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { request, tx, eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
await expect(tx)
.to.emit(eventManager, "EventStatusChanged")
.withArgs(eventId, 1);
});
it("reverts on unexisting event request", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{ eventId: BigInt(1000 + Math.floor(Math.random() * 1000)) },
splitter,
);
await expect(tx).to.be.revertedWith("Event with given id does not exist");
});
it("reverts when event has no categories", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{},
);
const tx = eventManager
.connect(creator)
.submitEventRequest(
eventPlaceId,
defaultSubmitEventRequest.ticketPrice,
await timestamp(10),
defaultSubmitEventRequest.daysAmount,
defaultSubmitEventRequest.digest,
defaultSubmitEventRequest.hashFunction,
defaultSubmitEventRequest.size,
[],
);
await expect(tx).to.be.revertedWith("At least one category is required");
});
});
describe("declineEvent", () => {
itExpectsOnlyLocalProvider("declineEvent", [BigInt(0)]);
it("emits EventStatusChanged", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
await createEventPlace(eventManager, verifiedShaman, localProvider);
const { request, tx, getEventId } = await submitEventRequest(
eventManager,
creator,
{},
);
const eventId = await getEventId();
await expect(
await eventManager.connect(localProvider).declineEvent(eventId),
)
.to.emit(eventManager, "EventStatusChanged")
.withArgs(eventId, 2);
});
it("reverts on unexisting event request", async () => {
const { eventManager, localProvider } = await loadFixture(deployContract);
await expect(
eventManager
.connect(localProvider)
.declineEvent(Math.floor(Math.random() * 1000)),
).to.be.revertedWith("Event with given id does not exist");
});
it("refunds tokens to creator", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
await createEventPlace(eventManager, verifiedShaman, localProvider);
const { request, tx, getEventId } = await submitEventRequest(
eventManager,
creator,
{},
);
await tx;
await expect(
await eventManager
.connect(localProvider)
.declineEvent(await getEventId()),
).to.changeTokenBalances(
ERC20,
[await eventManager.getAddress(), await creator.getAddress()],
[-defaultEventDepositSize, defaultEventDepositSize],
);
});
});
describe("updateEvent", () => {
itExpectsOnlyLocalProvider("updateEvent", [
BigInt(0),
...baseEventArgsToArray(defaultSubmitEventRequest),
]);
it("emits EventUpdated", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{ startDate: await timestamp(5), daysAmount: 3 },
{},
splitter,
);
const updatedRequest = {
...defaultSubmitEventRequest,
startDate: await timestamp(10),
daysAmount: 2,
ticketPrice: 50,
};
const tx = await eventManager
.connect(localProvider)
.updateEvent(eventId, ...baseEventArgsToArray(updatedRequest));
await expect(tx)
.to.emit(eventManager, "EventUpdated")
.withArgs(eventId, ...baseEventArgsToArray(updatedRequest));
});
it("reverts on unexisting event", async () => {
const { eventManager, localProvider } = await loadFixture(deployContract);
const nonExistentEventId = BigInt(9999);
await expect(
eventManager
.connect(localProvider)
.updateEvent(
nonExistentEventId,
...baseEventArgsToArray(defaultSubmitEventRequest),
),
).to.be.revertedWith("Event with given id does not exist");
});
it("checks date ranges overlap", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { eventId: firstEventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{ startDate: await timestamp(5), daysAmount: 4 },
{},
splitter,
);
const { eventId: secondEventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{ startDate: await timestamp(15), daysAmount: 4 },
{},
splitter,
);
const updatedRequest = {
...defaultSubmitEventRequest,
startDate: await timestamp(5),
daysAmount: 4,
};
await expect(
eventManager
.connect(localProvider)
.updateEvent(secondEventId, ...baseEventArgsToArray(updatedRequest)),
).to.be.revertedWith("Requested event overlaps with existing");
});
it("allows moving event into gap without self-overlap false positive", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { request: firstRequest } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{ startDate: await timestamp(11), daysAmount: 3 },
{},
splitter,
);
const eventPlaceId = firstRequest.eventPlaceId;
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { tx: secondSubmitTx, getEventId: getSecondEventId } =
await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(15),
daysAmount: 3,
});
await secondSubmitTx;
const secondEventId = await getSecondEventId();
await expect(
eventManager.connect(localProvider).approveEvent(secondEventId, 1n),
).to.not.be.reverted;
const updatedRequest = {
...defaultSubmitEventRequest,
eventPlaceId,
startDate: await timestamp(14),
daysAmount: 3,
};
await expect(
eventManager
.connect(localProvider)
.updateEvent(secondEventId, ...baseEventArgsToArray(updatedRequest)),
).to.not.be.reverted;
});
});
describe("mintTicket", () => {
it("emits TicketMinted", async () => {
const {
eventManager,
eventTicket,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
splitter,
} = await loadFixture(deployContract);
const { eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
const ticketPrice = 20;
const multihash = {
digest:
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
hashFunction: 18,
size: 32,
};
await ERC20.connect(owner).mint(ticketPrice);
await ERC20.connect(owner).approve(
await eventManager.getAddress(),
ticketPrice,
);
// Use category 0 (the default category created by createEvent)
const tx = await eventManager.connect(owner).mintTickets(
eventId,
0, // categoryId
1, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
);
await expect(tx).to.emit(eventTicket, "TicketMinted");
});
it("emits referralData as 0x-hex string when referrer is provided", async () => {
const {
eventManager,
eventTicket,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
staff,
splitter,
} = await loadFixture(deployContract);
const { eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
const ticketPrice = 20;
const multihash = {
digest:
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
hashFunction: 18,
size: 32,
};
await ERC20.connect(owner).mint(ticketPrice);
await ERC20.connect(owner).approve(
await eventManager.getAddress(),
ticketPrice,
);
const referrer = await staff.getAddress();
const tx = eventManager.connect(owner).mintTickets(
eventId,
0, // categoryId
1, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
referrer,
);
await expect(tx)
.to.emit(eventTicket, "TicketMinted")
.withArgs(
eventId,
1,
0,
await owner.getAddress(),
multihash.digest,
multihash.hashFunction,
multihash.size,
referrer,
ticketPrice,
);
});
it("reverts on sold out", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
} = await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 2, minTickets: 1 },
{},
);
// Create a category with quota of 1
await eventManager
.connect(creator)
.createCategory(eventId, "Limited", 0, 1, true);
// Approve the event
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
const ticketPrice = 20;
const multihash = {
digest:
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
hashFunction: 18,
size: 32,
};
await ERC20.connect(owner).mint(ticketPrice * 2);
await ERC20.connect(owner).approve(
await eventManager.getAddress(),
ticketPrice * 2,
);
// Mint first ticket with category 1 (the limited category)
await eventManager.connect(owner).mintTickets(
eventId,
1, // categoryId for "Limited"
1, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
);
// Second mint should revert due to category quota
await expect(
eventManager.connect(owner).mintTickets(
eventId,
1, // categoryId for "Limited"
1, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
),
).to.be.revertedWith("Category quota exceeded");
});
it("transfers required amount of tokens", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
splitter,
} = await loadFixture(deployContract);
const { eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
const ticketPrice = 20;
const multihash = {
digest:
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
hashFunction: 18,
size: 32,
};
await ERC20.connect(owner).mint(ticketPrice);
await ERC20.connect(owner).approve(
await eventManager.getAddress(),
ticketPrice,
);
// Use category 0 (the default category created by createEvent)
const tx = await eventManager.connect(owner).mintTickets(
eventId,
0, // categoryId
1, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
);
await expect(tx).to.changeTokenBalances(
ERC20,
[await eventManager.getAddress(), await owner.getAddress()],
[ticketPrice, -ticketPrice],
);
});
it("mints NFT with proper metadata", async () => {
const {
eventManager,
eventTicket,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
splitter,
} = await loadFixture(deployContract);
const { eventId, tx: createEventTx } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
await createEventTx;
const ticketPrice = 20;
const multihash = {
digest:
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
hashFunction: 18,
size: 32,
};
await ERC20.connect(owner).mint(ticketPrice);
await ERC20.connect(owner).approve(
await eventManager.getAddress(),
ticketPrice,
);
// Use category 0 (the default category created by createEvent)
const tx = eventManager.connect(owner).mintTickets(
eventId,
0, // categoryId
1, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
);
await expect(tx)
.to.emit(eventTicket, "TicketMinted")
.withArgs(
eventId,
1,
0, // categoryId (default category)
await owner.getAddress(),
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
ticketPrice,
);
});
});
describe("ticket categories", () => {
itExpectsOnlyVerifiedShaman("createCategory", [
BigInt(0),
"Families",
1000,
10,
true,
]);
it("reverts creating category after event is approved", async () => {
const { eventManager, creator, localProvider, eventId } =
await createSubmittedEvent();
// Event is already created with default category, approve it
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
// Try to create another category after approval
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "Families", 1000, 10, true),
).to.be.revertedWith("Event must be in submitted state");
});
it("allows one unlimited category per event", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize * 2n);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize * 2n,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{},
);
const { getEventId: getFirstEventId } = await submitEventRequest(
eventManager,
creator,
{
eventPlaceId,
startDate: await timestamp(10),
categories: [limitedCategory],
},
);
const { getEventId: getSecondEventId } = await submitEventRequest(
eventManager,
creator,
{
eventPlaceId,
startDate: await timestamp(20),
categories: [limitedCategory],
},
);
const firstEventId = await getFirstEventId();
const secondEventId = await getSecondEventId();
await eventManager
.connect(creator)
.createCategory(firstEventId, "Regular", 0, 0, false);
await expect(
eventManager
.connect(creator)
.createCategory(secondEventId, "Regular", 0, 0, false),
).to.not.be.reverted;
});
it("applies discount for unlimited categories", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
} = await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{},
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
ticketPrice: 100,
categories: [limitedCategory],
});
const eventId = await getEventId();
await eventManager
.connect(creator)
.createCategory(eventId, "Regular", 1000, 0, false);
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
await ERC20.connect(owner).mint(90);
await ERC20.connect(owner).approve(await eventManager.getAddress(), 90);
await expect(
eventManager.connect(owner).mintTickets(
eventId,
1, // Regular category (with 10% discount)
1, // amount
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
18,
32,
"0x0000000000000000000000000000000000000000",
),
).to.changeTokenBalances(
ERC20,
[await eventManager.getAddress(), await owner.getAddress()],
[90, -90],
);
});
it("enforces quota for limited categories", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
} = await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{},
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
ticketPrice: 100,
categories: defaultSubmitEventRequest.categories,
});
const eventId = await getEventId();
await eventManager
.connect(creator)
.createCategory(eventId, "Families", 0, 1, true);
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
await ERC20.connect(owner).mint(200);
await ERC20.connect(owner).approve(await eventManager.getAddress(), 200);
await eventManager.connect(owner).mintTickets(
eventId,
1, // Families category (created after default)
1, // amount
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
18,
32,
"0x0000000000000000000000000000000000000000",
);
await expect(
eventManager.connect(owner).mintTickets(
eventId,
1, // Families category (created after default)
1, // amount
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
18,
32,
"0x0000000000000000000000000000000000000000",
),
).to.be.revertedWith("Category quota exceeded");
});
it("should revert when creating category with quota exceeding maxTickets", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 100 },
{},
);
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "BigCategory", 1000, 150, true),
).to.be.revertedWith("Quota exceeds event capacity");
});
it("should revert when total category quotas exceed maxTickets", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 100 },
{},
);
// Create first category with quota = 60
await eventManager
.connect(creator)
.createCategory(eventId, "Category1", 1000, 60, true);
// Try to create second category with quota = 50 (total would be 110 > 100)
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "Category2", 1000, 50, true),
).to.be.revertedWith("Total category quotas exceed event capacity");
});
it("should revert when creating category with quota = 0", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
);
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "ZeroQuota", 1000, 0, true),
).to.be.revertedWith("Quota must be greater than 0");
});
it("should allow creating categories until quotas sum to maxTickets", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 100 },
{},
);
// Create category1 with quota = 50
await eventManager
.connect(creator)
.createCategory(eventId, "Category1", 1000, 50, true);
// Create category2 with quota = 50 (total = 100)
await eventManager
.connect(creator)
.createCategory(eventId, "Category2", 1000, 50, true);
// Try to create category3 with quota = 1 (should fail, total would be 101)
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "Category3", 1000, 1, true),
).to.be.revertedWith("Total category quotas exceed event capacity");
});
it("should allow creating unlimited category when quotas fill maxTickets", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 100 },
{ categories: [limitedCategory] },
);
// Create category1 with quota = 50 (fills all remaining capacity)
await eventManager
.connect(creator)
.createCategory(eventId, "FullCapacity", 1000, 50, true);
// Create unlimited category (should succeed)
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "Unlimited", 500, 0, false),
).to.not.be.reverted;
});
it("should revert when updating category quota below sold amount", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
} = await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
);
// Create category with quota = 50
await eventManager
.connect(creator)
.createCategory(eventId, "Category", 0, 50, true);
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
// Buy 10 tickets from category
await ERC20.connect(owner).mint(1000);
await ERC20.connect(owner).approve(await eventManager.getAddress(), 1000);
await eventManager.connect(owner).mintTickets(
eventId,
0,
10, // amount
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
18,
32,
"0x0000000000000000000000000000000000000000",
);
// Try to update quota to 5 (less than 10 sold) - should fail because event is approved
await expect(
eventManager
.connect(localProvider)
.updateCategory(0, "Category", 0, 5, true),
).to.be.revertedWith("Event must be in submitted state");
});
it("should revert when updating increases total quotas beyond maxTickets", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 100 },
{ categories: defaultSubmitEventRequest.categories },
);
// Create category1 with quota = 60
await eventManager
.connect(creator)
.createCategory(eventId, "Category1", 1000, 60, true);
// Create category2 with quota = 30
await eventManager
.connect(creator)
.createCategory(eventId, "Category2", 1000, 30, true);
// Try to update category2 (categoryId 2) quota to 50 (total would be 110 > 100)
await expect(
eventManager
.connect(localProvider)
.updateCategory(2, "Category2", 1000, 50, true),
).to.be.revertedWith("Total category quotas exceed event capacity");
});
it("should allow updating category within quota constraints", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 100 },
{},
);
// Create category1 with quota = 50
await eventManager
.connect(creator)
.createCategory(eventId, "Category1", 1000, 50, true);
// Create category2 with quota = 30
await eventManager
.connect(creator)
.createCategory(eventId, "Category2", 1000, 30, true);
// Update category2 quota to 40 (total = 90, should succeed)
await expect(
eventManager
.connect(localProvider)
.updateCategory(2, "Category2", 1000, 40, true),
).to.not.be.reverted;
});
it("should track totalCategoryQuota correctly after multiple operations", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
// Create event with empty categories for full control
const { eventId } = await createEventForCategories(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{ maxTickets: 100 },
{ categories: defaultSubmitEventRequest.categories },
);
// Create category1 with quota = 30 (totalCategoryQuota = 30)
await eventManager
.connect(creator)
.createCategory(eventId, "Category1", 1000, 30, true);
// Create category2 with quota = 40 (totalCategoryQuota = 70)
await eventManager
.connect(creator)
.createCategory(eventId, "Category2", 1000, 40, true);
// Verify we can only add 30 more (100 - 70 = 30)
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "Category3", 1000, 31, true),
).to.be.revertedWith("Total category quotas exceed event capacity");
// Update category1 (categoryId 1) quota to 50 (totalCategoryQuota = 90)
await eventManager
.connect(localProvider)
.updateCategory(1, "Category1", 1000, 50, true);
// Now we can only add 10 more (100 - 90 = 10)
await expect(
eventManager
.connect(creator)
.createCategory(eventId, "Category3", 1000, 11, true),
).to.be.revertedWith("Total category quotas exceed event capacity");
// But 10 should work
await eventManager
.connect(creator)
.createCategory(eventId, "Category3", 1000, 10, true);
});
});
describe("closeEvent", () => {
itExpectsOnlyLocalProvider("closeEvent", [BigInt(0)]);
it("allows master to close any provider event", async () => {
const {
eventManager,
ERC20,
master,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx: createEventTx, eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
await createEventTx;
await time.increase(100_000_000);
const tx = await eventManager.connect(master).closeEvent(eventId);
await expect(tx).to.emit(eventManager, "EventStatusChanged").withArgs(eventId, 4);
});
it("emits EventStatusChanged", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx, request } = await createAndCloseEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
splitter,
);
await expect(tx)
.to.emit(eventManager, "EventStatusChanged")
.withArgs(request.eventId, 4);
});
it("reverts on unexisting event", async () => {
const { eventManager, localProvider } = await loadFixture(deployContract);
const { tx } = await closeEvent(eventManager, localProvider, {
eventId: Math.floor(Math.random() * 1000),
});
await expect(tx).to.be.revertedWith("Event with given id does not exist");
});
it("reverts to close cancelled event", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx, request } = await createAndCancelEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
splitter,
);
await tx;
const { tx: closeEventTx } = await closeEvent(
eventManager,
localProvider,
request,
);
await expect(closeEventTx).to.be.revertedWith(
"Only event in approved state can be closed",
);
});
it("reverts to close closed event", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx, request } = await createAndCloseEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
splitter,
);
await tx;
const { tx: closeEventTx } = await closeEvent(
eventManager,
localProvider,
request,
);
await expect(closeEventTx).to.be.revertedWith(
"Only event in approved state can be closed",
);
});
it("reverts to close submitted event", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
const { request, tx, getEventId } = await submitEventRequest(
eventManager,
creator,
{
eventPlaceId,
},
);
await tx;
const { tx: closeEventTx } = await closeEvent(
eventManager,
localProvider,
{
eventId: await getEventId(),
},
);
await expect(closeEventTx).to.be.revertedWith(
"Only event in approved state can be closed",
);
});
it("reverts to close declined event", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
await createEventPlace(eventManager, verifiedShaman, localProvider);
const { request, tx, getEventId } = await submitEventRequest(
eventManager,
creator,
{},
);
const eventId = await getEventId();
const { tx: closeEventTx } = await closeEvent(
eventManager,
localProvider,
{
eventId,
},
);
await expect(closeEventTx).to.be.revertedWith(
"Only event in approved state can be closed",
);
});
it("reverts if event was not finished", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx: createEventTx, eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
await createEventTx;
const { tx } = await closeEvent(eventManager, localProvider, { eventId });
await expect(tx).to.be.revertedWith("Event has not been finished yet");
});
it("proportionally spreads funds", async () => {
const {
eventManager,
ERC20,
master,
verifiedShaman,
localProvider,
creator,
staff,
owner,
} = await loadFixture(deployContract);
// Create event with ticket sales
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
const { tx: submitTx, getEventId } = await submitEventRequest(
eventManager,
creator,
{ eventPlaceId, ticketPrice: 20 },
);
await submitTx;
const eventId = await getEventId();
// Categories are created automatically during event submission
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
// Buy 5 tickets @ 20 USDT = 100 USDT revenue
await ERC20.connect(owner).mint(100);
await ERC20.connect(owner).approve(await eventManager.getAddress(), 100);
await eventManager.connect(owner).mintTickets(
eventId,
0,
5, // amount
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
18,
32,
"0x0000000000000000000000000000000000000000",
);
// Move time to allow closing
await time.increase(100_000_000);
const { tx } = await closeEvent(eventManager, localProvider, { eventId });
// Deposit (100) returned to creator
// Ticket revenue (100) distributed:
// 10% (10) to master (cyberiaDAO), 5% (5) to staff (cvePtPma)
// 5% (5) to localProvider (profile manager bps)
// 80% (80) to profile recipient (creator)
await expect(tx).to.changeTokenBalances(
ERC20,
[
await creator.getAddress(),
await master.getAddress(),
await staff.getAddress(),
await localProvider.getAddress(),
await eventManager.getAddress(),
],
[180, 10, 5, 5, -200],
);
});
});
describe("cancelEvent", () => {
itExpectsOnlyLocalProvider("cancelEvent", [BigInt(0)]);
it("allows master to cancel any provider event", async () => {
const {
eventManager,
ERC20,
master,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx: createEventTx, eventId } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
await createEventTx;
await time.increase(100_000_000);
const tx = await eventManager.connect(master).cancelEvent(eventId);
await expect(tx).to.emit(eventManager, "EventStatusChanged").withArgs(eventId, 3);
});
it("emits EventStatusChanged", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx, request } = await createAndCancelEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
splitter,
);
await expect(tx)
.to.emit(eventManager, "EventStatusChanged")
.withArgs(request.eventId, 3);
});
it("refunds discounted ticket price", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
owner,
} = await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
ticketPrice: 100,
startDate: await timestamp(10),
});
const eventId = await getEventId();
await eventManager
.connect(creator)
.createCategory(eventId, "Families", 1000, 10, true);
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
const discountedPrice = 90n;
await ERC20.connect(owner).mint(discountedPrice);
await ERC20.connect(owner).approve(
await eventManager.getAddress(),
discountedPrice,
);
// Category 0 = "General" (created by default), Category 1 = "Families" (with discount)
await eventManager.connect(owner).mintTickets(
eventId,
1, // Use Families category (with discount)
1, // amount
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
18,
32,
"0x0000000000000000000000000000000000000000",
);
const tx = await eventManager.connect(localProvider).cancelEvent(eventId);
// On cancel:
// - Deposit goes to creator (shaman)
// - Ticket revenue goes to localProvider
await expect(tx).to.changeTokenBalances(
ERC20,
[
await eventManager.getAddress(),
await owner.getAddress(),
await localProvider.getAddress(),
await creator.getAddress(),
],
[
-(defaultEventDepositSize + discountedPrice),
0,
discountedPrice,
defaultEventDepositSize,
],
);
});
it("reverts to cancel cancelled event", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx, request } = await createAndCancelEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
splitter,
);
await tx;
const { tx: cancelEventTx } = await cancelEvent(
eventManager,
localProvider,
request,
);
await expect(cancelEventTx).to.be.revertedWith(
"Only event in approved state can be cancelled",
);
});
it("reverts to cancel closed event", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
const { tx, request } = await createAndCancelEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
splitter,
);
await tx;
const { tx: cancelEventTx } = await cancelEvent(
eventManager,
localProvider,
request,
);
await expect(cancelEventTx).to.be.revertedWith(
"Only event in approved state can be cancelled",
);
});
it("reverts to cancel submitted event", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
const { request, tx, getEventId } = await submitEventRequest(
eventManager,
creator,
{
eventPlaceId,
},
);
await tx;
const { tx: cancelEventTx } = await cancelEvent(
eventManager,
localProvider,
{
eventId: await getEventId(),
},
);
await expect(cancelEventTx).to.be.revertedWith(
"Only event in approved state can be cancelled",
);
});
it("reverts to cancel declined event", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
await createEventPlace(eventManager, verifiedShaman, localProvider);
const { request, tx, getEventId } = await submitEventRequest(
eventManager,
creator,
{},
);
const eventId = await getEventId();
const { tx: cancelEventTx } = await cancelEvent(
eventManager,
localProvider,
{
eventId,
},
);
await expect(cancelEventTx).to.be.revertedWith(
"Only event in approved state can be cancelled",
);
});
it("refunds tokens to owners and local provider", async () => {
const {
eventManager,
ERC20,
master,
verifiedShaman,
localProvider,
creator,
staff,
} = await loadFixture(deployContract);
const allSigners = await ethers.getSigners();
const customer1 = allSigners[5];
const customer2 = allSigners[6];
const customer3 = allSigners[7];
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{
maxTickets: 100,
minTickets: 1,
minPrice: 10,
},
);
const { tx: submitEventRequestTx, getEventId } = await submitEventRequest(
eventManager,
creator,
{
eventPlaceId,
ticketPrice: 10,
},
);
await submitEventRequestTx;
const eventId = await getEventId();
// Categories are created automatically during event submission
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
const customers = [customer1, customer2, customer3];
const ticketsPerCustomer = [2, 3, 1];
const totalTickets = ticketsPerCustomer.reduce((a, b) => a + b, 0);
const multihash = {
digest:
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
hashFunction: 18,
size: 32,
};
for (let i = 0; i < customers.length; i++) {
const customer = customers[i];
const ticketCount = ticketsPerCustomer[i];
const totalCost = ticketCount * 10;
await ERC20.connect(customer).mint(totalCost);
await ERC20.connect(customer).approve(
await eventManager.getAddress(),
totalCost,
);
await eventManager.connect(customer).mintTickets(
eventId,
0, // categoryId
ticketCount, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
);
}
const { tx: cancelTx } = await cancelEvent(eventManager, localProvider, {
eventId,
});
const eventDepositSize = defaultEventDepositSize;
const totalRevenue = BigInt(totalTickets) * 10n;
// After cancelEvent:
// - Deposit goes to creator (shaman)
// - Ticket revenue goes to localProvider
await expect(cancelTx).to.changeTokenBalances(
ERC20,
[
await master.getAddress(),
await localProvider.getAddress(),
await creator.getAddress(),
await customer1.getAddress(),
await customer2.getAddress(),
await customer3.getAddress(),
await eventManager.getAddress(),
],
[
0,
totalRevenue,
eventDepositSize,
0,
0,
0,
-(eventDepositSize + totalRevenue),
],
);
});
});
describe("Fund Distribution", () => {
it("distributes funds through DynamicRevenueSplitter on closeEvent", async () => {
const {
ERC20,
eventManager,
splitter,
master,
staff,
verifiedShaman,
localProvider,
creator,
owner,
} = await loadFixture(deployContract);
// Setup event and tickets
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{ maxTickets: 100, minTickets: 1, minPrice: 10 },
);
const { tx: submitTx, getEventId } = await submitEventRequest(
eventManager,
creator,
{ eventPlaceId, ticketPrice: 10 },
);
await submitTx;
const eventId = await getEventId();
// Categories are created automatically during event submission
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
// Buy 10 tickets @ 10 USDT = 100 USDT
const ticketPrice = 10;
const ticketCount = 10;
const totalTicketCost = ticketPrice * ticketCount;
const multihash = {
digest:
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
hashFunction: 18,
size: 32,
};
await ERC20.connect(owner).mint(totalTicketCost);
await ERC20.connect(owner).approve(
await eventManager.getAddress(),
totalTicketCost,
);
await eventManager.connect(owner).mintTickets(
eventId,
0, // categoryId
ticketCount, // amount
multihash.digest,
multihash.hashFunction,
multihash.size,
"0x0000000000000000000000000000000000000000",
);
// Total ticket revenue = 100 (distributed via splitter)
// Deposit = 100 (returned to creator)
const totalAmount = 100;
// Move time to allow closing
await time.increase(100_000_000);
// Close event and check distribution
// Deposit: 100 returned to creator
// Ticket revenue: 10% (10) to master, 5% (5) to staff,
// 5% (5) to localProvider (profile manager bps), 80% (80) to profile recipient (creator)
const tx = await eventManager.connect(localProvider).closeEvent(eventId);
await expect(tx).to.changeTokenBalances(
ERC20,
[
await creator.getAddress(),
await master.getAddress(),
await staff.getAddress(),
await localProvider.getAddress(),
await eventManager.getAddress(),
],
[180, 10, 5, 5, -200],
);
await expect(tx)
.to.emit(splitter, "RevenueDistributed")
.withArgs(totalAmount, eventId);
});
it("uses event-specific profile if set", async () => {
const {
ERC20,
eventManager,
splitter,
master,
staff,
verifiedShaman,
localProvider,
creator,
owner,
} = await loadFixture(deployContract);
const customer = owner;
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
const { tx: submitTx, getEventId } = await submitEventRequest(
eventManager,
creator,
{ eventPlaceId, ticketPrice: 20 },
);
await submitTx;
const eventId = await getEventId();
// Categories are created automatically during event submission
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
// Create and set event-specific profile (admin creates for themselves)
await splitter
.connect(master)
.createDistributionProfile([await customer.getAddress()], [8500]);
await splitter.connect(master).setEventProfile(eventId, 2);
await time.increase(100_000_000);
// No tickets sold, so no revenue to distribute
// Deposit (100) is returned to creator
const tx = await eventManager.connect(localProvider).closeEvent(eventId);
// Creator gets deposit back (100), no ticket revenue to distribute
await expect(tx).to.changeTokenBalance(ERC20, creator, 100);
});
});
describe("Local Provider Management", () => {
describe("grantLocalProvider", () => {
itExpectsOnlyMaster("grantLocalProvider", [ethers.ZeroAddress, 100]);
it("grants LOCAL_PROVIDER_ROLE and sets bps in splitter", async () => {
const { eventManager, splitter, master, owner } =
await loadFixture(deployContract);
const LOCAL_PROVIDER_ROLE = await eventManager.LOCAL_PROVIDER_ROLE();
await expect(
eventManager
.connect(master)
.grantLocalProvider(await owner.getAddress(), 1000),
)
.to.emit(eventManager, "RoleGranted")
.withArgs(
LOCAL_PROVIDER_ROLE,
await owner.getAddress(),
await master.getAddress(),
);
expect(
await eventManager.hasRole(
LOCAL_PROVIDER_ROLE,
await owner.getAddress(),
),
).to.be.true;
expect(
await splitter.profileManagerBps(await owner.getAddress()),
).to.equal(1000);
});
it("reverts when revenue splitter is not set", async () => {
const { eventManager, master, owner } =
await loadFixture(deployContract);
// Create a new event manager without splitter
const CyberValleyEventManagerFactory = await ethers.getContractFactory(
"CyberValleyEventManager",
);
const newEventManager = await CyberValleyEventManagerFactory.deploy(
await eventManager.usdtTokenContract(),
await eventManager.eventTicketContract(),
master,
await timestamp(0),
);
await expect(
newEventManager
.connect(master)
.grantLocalProvider(await owner.getAddress(), 1000),
).to.be.revertedWith("Revenue splitter not set");
});
it("reverts for zero address", async () => {
const { eventManager, master } = await loadFixture(deployContract);
await expect(
eventManager
.connect(master)
.grantLocalProvider(ethers.ZeroAddress, 1000),
).to.be.revertedWith("Local provider cannot be zero address");
});
});
describe("revokeLocalProvider", () => {
itExpectsOnlyMaster("revokeLocalProvider", [ethers.ZeroAddress]);
it("transfers EventPlaces to Master and profiles to Master", async () => {
const {
eventManager,
splitter,
master,
localProvider,
verifiedShaman,
creator,
} = await loadFixture(deployContract);
// Create an EventPlace - shamans submit, localProvider approves
// This creates the place with localProvider as provider
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{
...defaultCreateEventPlaceRequest,
eventDepositSize: 200,
},
);
// Verify localProvider is the provider
let eventPlace = await eventManager.eventPlaces(eventPlaceId);
expect(eventPlace.provider).to.equal(await localProvider.getAddress());
// Create a profile for localProvider (using creator as recipient)
await splitter
.connect(localProvider)
.createDistributionProfile([await creator.getAddress()], [8000]);
// Verify localProvider owns the profile (profileId 1)
expect(
await splitter.isProfileOwner(1, await localProvider.getAddress()),
).to.be.true;
// Revoke local provider
await expect(
eventManager
.connect(master)
.revokeLocalProvider(await localProvider.getAddress()),
)
.to.emit(eventManager, "EventPlaceUpdated")
.withArgs(
await master.getAddress(),
eventPlaceId,
eventPlace.maxTickets,
eventPlace.minTickets,
eventPlace.minPrice,
eventPlace.daysBeforeCancel,
eventPlace.minDays,
eventPlace.available,
eventPlace.status,
eventPlace.meta.digest,
eventPlace.meta.hashFunction,
eventPlace.meta.size,
eventPlace.eventDepositSize,
);
// Verify EventPlace provider is now Master
eventPlace = await eventManager.eventPlaces(eventPlaceId);
expect(eventPlace.provider).to.equal(await master.getAddress());
// Verify LOCAL_PROVIDER_ROLE is revoked
const LOCAL_PROVIDER_ROLE = await eventManager.LOCAL_PROVIDER_ROLE();
expect(
await eventManager.hasRole(
LOCAL_PROVIDER_ROLE,
await localProvider.getAddress(),
),
).to.be.false;
// Verify profiles were transferred to Master
expect(
await splitter.isProfileOwner(1, await localProvider.getAddress()),
).to.be.false;
expect(await splitter.isProfileOwner(1, await master.getAddress())).to
.be.true;
});
it("handles provider with no EventPlaces", async () => {
const { eventManager, splitter, master } =
await loadFixture(deployContract);
// Create a completely new provider address
const newProvider = ethers.Wallet.createRandom().connect(
ethers.provider,
);
// Send some ETH for gas
await master.sendTransaction({
to: await newProvider.getAddress(),
value: ethers.parseEther("1"),
});
// Grant local provider role
await eventManager
.connect(master)
.grantLocalProvider(await newProvider.getAddress(), 500);
// Create a profile for the new provider (so transferAllProfiles has something to transfer)
await splitter
.connect(newProvider)
.createDistributionProfile([await master.getAddress()], [8000]);
// Revoke should succeed even with no EventPlaces
await expect(
eventManager
.connect(master)
.revokeLocalProvider(await newProvider.getAddress()),
).to.not.be.reverted;
// Verify role is revoked
const LOCAL_PROVIDER_ROLE = await eventManager.LOCAL_PROVIDER_ROLE();
expect(
await eventManager.hasRole(
LOCAL_PROVIDER_ROLE,
await newProvider.getAddress(),
),
).to.be.false;
});
});
describe("revokeVerifiedShaman", () => {
itExpectsOnlyLocalProvider("revokeVerifiedShaman", [ethers.ZeroAddress]);
it("revokes VERIFIED_SHAMAN_ROLE", async () => {
const { eventManager, master, localProvider, creator } =
await loadFixture(deployContract);
const VERIFIED_SHAMAN_ROLE = await eventManager.VERIFIED_SHAMAN_ROLE();
// Ensure creator has the role
expect(
await eventManager.hasRole(
VERIFIED_SHAMAN_ROLE,
await creator.getAddress(),
),
).to.be.true;
await expect(
eventManager
.connect(localProvider)
.revokeVerifiedShaman(await creator.getAddress()),
)
.to.emit(eventManager, "RoleRevoked")
.withArgs(
VERIFIED_SHAMAN_ROLE,
await creator.getAddress(),
await localProvider.getAddress(),
);
expect(
await eventManager.hasRole(
VERIFIED_SHAMAN_ROLE,
await creator.getAddress(),
),
).to.be.false;
});
});
});
describe("Category Management", () => {
describe("updateCategory", () => {
itExpectsOnlyLocalProvider("updateCategory", [
BigInt(0),
"Updated Name",
500,
10,
true,
]);
it("updates category name and discount", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
categories: [limitedCategory],
});
const eventId = await getEventId();
// Category 0 is the default category created during event submission
await expect(
eventManager
.connect(localProvider)
.updateCategory(0, "Updated Name", 500, 15, true),
)
.to.emit(eventManager, "TicketCategoryUpdated")
.withArgs(0, eventId, "Updated Name", 500, 15, true);
const category = await eventManager.getCategory(0);
expect(category.name).to.equal("Updated Name");
expect(category.discountPercentage).to.equal(500);
expect(category.quota).to.equal(15);
});
it("reverts when event is not in submitted state", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
});
const eventId = await getEventId();
// Approve the event - now it's no longer in submitted state
await eventManager.connect(localProvider).approveEvent(eventId, 1n);
// Try to update category after approval - should fail
await expect(
eventManager
.connect(localProvider)
.updateCategory(0, "Updated Name", 500, 10, true),
).to.be.revertedWith("Event must be in submitted state");
});
it("reverts when caller is not the event's local provider", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
master,
splitter,
} = await loadFixture(deployContract);
// Create a second local provider who has role but doesn't own the event place
const [, , , , , otherProvider] = await ethers.getSigners();
await eventManager
.connect(master)
.grantLocalProvider(await otherProvider.getAddress(), 300);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{ minTickets: 5 },
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
categories: [
{
name: "Limited",
discountPercentage: 0,
quota: 5,
hasQuota: true,
},
],
});
const eventId = await getEventId();
// Try to update category as otherProvider (has LOCAL_PROVIDER_ROLE but doesn't own this event)
await expect(
eventManager
.connect(otherProvider)
.updateCategory(0, "Limited", 0, 3, true),
).to.be.revertedWith(
"Only the local provider of the event can update categories",
);
});
it("reverts when increasing total quotas beyond maxTickets", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{ maxTickets: 10, minTickets: 1 },
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
categories: [
{ name: "Cat1", discountPercentage: 0, quota: 5, hasQuota: true },
{ name: "Cat2", discountPercentage: 0, quota: 5, hasQuota: true },
],
});
const eventId = await getEventId();
// Try to update Cat1 quota to exceed maxTickets (total would be 6+5=11 > 10)
await expect(
eventManager
.connect(localProvider)
.updateCategory(0, "Cat1", 0, 6, true),
).to.be.revertedWith("Total category quotas exceed event capacity");
});
it("reverts when changing from limited to unlimited quota", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
// Create event place with lower minTickets
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{ minTickets: 5 },
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
// Create a limited category first (quota 5 meets minTickets=5)
categories: [
{
name: "Limited",
discountPercentage: 0,
quota: 5,
hasQuota: true,
},
],
});
const eventId = await getEventId();
// Try to change from limited to unlimited
await expect(
eventManager
.connect(localProvider)
.updateCategory(0, "Unlimited", 0, 0, false),
).to.be.revertedWith("Cannot change from limited to unlimited quota");
});
it("reverts for non-existent category", async () => {
const { eventManager, localProvider } =
await loadFixture(deployContract);
await expect(
eventManager
.connect(localProvider)
.updateCategory(999, "Name", 0, 10, true),
).to.be.revertedWith("Category does not exist");
});
});
describe("getCategory", () => {
it("returns correct category data", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
categories: [
{
name: "TestCat",
discountPercentage: 1000,
quota: 50,
hasQuota: true,
},
],
});
const eventId = await getEventId();
const category = await eventManager.getCategory(0);
expect(category.name).to.equal("TestCat");
expect(category.eventId).to.equal(eventId);
expect(category.discountPercentage).to.equal(1000);
expect(category.quota).to.equal(50);
expect(category.hasQuota).to.be.true;
expect(category.sold).to.equal(0);
});
it("reverts for non-existent category", async () => {
const { eventManager } = await loadFixture(deployContract);
await expect(eventManager.getCategory(999)).to.be.revertedWith(
"Category does not exist",
);
});
});
describe("getCategoriesForEvent", () => {
it("returns all category IDs for an event", async () => {
const { eventManager, ERC20, verifiedShaman, localProvider, creator } =
await loadFixture(deployContract);
const { eventPlaceId } = await createEventPlace(
eventManager,
verifiedShaman,
localProvider,
{ minTickets: 10 }, // Lower minTickets so 25+25=50 >= 10 passes
);
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { getEventId } = await submitEventRequest(eventManager, creator, {
eventPlaceId,
startDate: await timestamp(10),
categories: [
{ name: "Cat1", discountPercentage: 0, quota: 25, hasQuota: true },
{
name: "Cat2",
discountPercentage: 500,
quota: 25,
hasQuota: true,
},
],
});
const eventId = await getEventId();
const categoryIds = await eventManager.getCategoriesForEvent(eventId);
expect(categoryIds.length).to.equal(2);
expect(categoryIds[0]).to.equal(0);
expect(categoryIds[1]).to.equal(1);
});
it("returns empty array for event with no categories", async () => {
const { eventManager } = await loadFixture(deployContract);
const categoryIds = await eventManager.getCategoriesForEvent(999);
expect(categoryIds).to.deep.equal([]);
});
});
});
describe("View Functions", () => {
describe("getEventsCount", () => {
it("returns correct event count", async () => {
const {
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
splitter,
} = await loadFixture(deployContract);
expect(await eventManager.getEventsCount()).to.equal(0);
// Create first event
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventId: eventId1 } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{},
{},
splitter,
);
expect(await eventManager.getEventsCount()).to.equal(1);
// Create second event
await ERC20.connect(creator).mint(defaultEventDepositSize);
await ERC20.connect(creator).approve(
await eventManager.getAddress(),
defaultEventDepositSize,
);
const { eventId: eventId2 } = await createEvent(
eventManager,
ERC20,
verifiedShaman,
localProvider,
creator,
{},
{ startDate: await timestamp(30) },
{},
splitter,
);
expect(await eventManager.getEventsCount()).to.equal(2);
});
});
});
});