const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("EdutOfferEntitlement", function () { const PRICE = ethers.BigNumber.from("1000000000"); // 1000 USDC (6 decimals) const OFFER_WORKSPACE_CORE = "edut.workspace.core"; const OFFER_WORKSPACE_LANE24 = "edut.workspace.lane24"; async function expectRevertWithCustomError(promise, errorName) { try { await promise; expect.fail(`expected revert ${errorName}`); } catch (err) { expect(String(err)).to.contain(errorName); } } async function deployFixture() { const [deployer, treasury, ownerWallet, payer, other] = await ethers.getSigners(); const tokenFactory = await ethers.getContractFactory("MockERC20"); const usdc = await tokenFactory.connect(deployer).deploy("Mock USDC", "USDC", 6); await usdc.deployed(); const membershipFactory = await ethers.getContractFactory("MockMembershipStatus"); const membership = await membershipFactory.connect(deployer).deploy(); await membership.deployed(); const registryFactory = await ethers.getContractFactory("EdutOfferEntitlement"); const registry = await registryFactory .connect(deployer) .deploy(treasury.address, usdc.address, membership.address); await registry.deployed(); return { deployer, treasury, ownerWallet, payer, other, usdc, membership, registry }; } it("requires owner role to configure offers", async function () { const { registry, other } = await deployFixture(); await expectRevertWithCustomError( registry.connect(other).upsertOffer(OFFER_WORKSPACE_CORE, PRICE, true, true), "NotOwner", ); }); it("blocks purchase when membership-required offer has no active membership", async function () { const { registry, ownerWallet, payer, usdc } = await deployFixture(); await (await registry.upsertOffer(OFFER_WORKSPACE_CORE, PRICE, true, true)).wait(); await (await usdc.mint(payer.address, PRICE)).wait(); await (await usdc.connect(payer).approve(registry.address, PRICE)).wait(); await expectRevertWithCustomError( registry.connect(payer).purchaseEntitlement( OFFER_WORKSPACE_CORE, ownerWallet.address, ethers.utils.formatBytes32String("org_root_a"), ethers.utils.formatBytes32String("workspace_a"), ), "MembershipRequired", ); }); it("mints entitlement and settles USDC to treasury", async function () { const { registry, membership, ownerWallet, payer, treasury, usdc } = await deployFixture(); await (await registry.upsertOffer(OFFER_WORKSPACE_CORE, PRICE, true, true)).wait(); await (await membership.setMembership(ownerWallet.address, true)).wait(); await (await usdc.mint(payer.address, PRICE)).wait(); await (await usdc.connect(payer).approve(registry.address, PRICE)).wait(); const tx = await registry.connect(payer).purchaseEntitlement( OFFER_WORKSPACE_CORE, ownerWallet.address, ethers.utils.formatBytes32String("org_root_a"), ethers.utils.formatBytes32String("workspace_a"), ); await tx.wait(); expect((await usdc.balanceOf(treasury.address)).eq(PRICE)).to.equal(true); const entitlement = await registry.entitlementById(1); expect(entitlement.ownerWallet).to.equal(ownerWallet.address); expect(entitlement.payerWallet).to.equal(payer.address); expect(Number(entitlement.state)).to.equal(1); }); it("supports repeated lane purchases with deterministic incremental ids", async function () { const { registry, membership, ownerWallet, payer, usdc } = await deployFixture(); await (await registry.upsertOffer(OFFER_WORKSPACE_LANE24, PRICE, true, true)).wait(); await (await membership.setMembership(ownerWallet.address, true)).wait(); const total = PRICE.mul(2); await (await usdc.mint(payer.address, total)).wait(); await (await usdc.connect(payer).approve(registry.address, total)).wait(); await ( await registry.connect(payer).purchaseEntitlement( OFFER_WORKSPACE_LANE24, ownerWallet.address, ethers.utils.formatBytes32String("org_root_a"), ethers.utils.formatBytes32String("workspace_a"), ) ).wait(); await ( await registry.connect(payer).purchaseEntitlement( OFFER_WORKSPACE_LANE24, ownerWallet.address, ethers.utils.formatBytes32String("org_root_a"), ethers.utils.formatBytes32String("workspace_a"), ) ).wait(); const first = await registry.entitlementById(1); const second = await registry.entitlementById(2); expect(Number(first.state)).to.equal(1); expect(Number(second.state)).to.equal(1); expect((await registry.nextEntitlementId()).eq(3)).to.equal(true); }); it("allows owner to update entitlement state", async function () { const { registry, membership, ownerWallet, payer, usdc } = await deployFixture(); await (await registry.upsertOffer(OFFER_WORKSPACE_CORE, PRICE, true, true)).wait(); await (await membership.setMembership(ownerWallet.address, true)).wait(); await (await usdc.mint(payer.address, PRICE)).wait(); await (await usdc.connect(payer).approve(registry.address, PRICE)).wait(); await ( await registry.connect(payer).purchaseEntitlement( OFFER_WORKSPACE_CORE, ownerWallet.address, ethers.utils.formatBytes32String("org_root_a"), ethers.utils.formatBytes32String("workspace_a"), ) ).wait(); await (await registry.setEntitlementState(1, 2)).wait(); const entitlement = await registry.entitlementById(1); expect(Number(entitlement.state)).to.equal(2); }); });