import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { BigNumber, constants } from 'ethers'; import { ethers } from 'hardhat'; import { ForwarderDeployer } from '../scripts/deployers/ForwarderDeployer'; import { VenueRegistarDeployer } from '../scripts/deployers/VenueRegistarDeployer'; import { toETH, toUSDC } from './utils'; import { USDCDeployer } from '../scripts/deployers/USDCDeployer'; import { ERC721EFactoryDeployer } from '../scripts/deployers/ERC721EFactoryDeployer'; import { ERC721EFactory, ERC721EW, ERC721EWFactory, ReservForwarder, USDC, VenueFactory, VenueRegistar, VenueSBT, } from '../types'; import { VenueFactoryDeployer } from '../scripts/deployers/VenueFactoryDeployer'; import { getLatestTimestamp, timeIncreaseTo } from '../scripts/time'; import { getProof, merkleTree } from '../helpers/merkletree'; import MerkleTree from 'merkletreejs'; import { ERC721EWFactoryDeployer } from '../scripts/deployers/ERC721EWFactoryDeployer'; chai.use(chaiAsPromised); const { expect } = chai; let signer: SignerWithAddress; let otherSigner: SignerWithAddress; let buyer: SignerWithAddress; let tokenCreator: SignerWithAddress; let otherBuyer: SignerWithAddress; let venueRegistar: VenueRegistar; let usdc: USDC; let forwarder: ReservForwarder; let myVenue: VenueSBT; let erc721EFactory: ERC721EFactory; let erc721EWFactory: ERC721EWFactory; let myEvent: ERC721EW; let merkleRoot: MerkleTree; let merkleSigners: Array; describe('NFT_ERC721EW', function () { let saleEnd: number; let timestamp: number; before(async () => { [signer, tokenCreator, otherSigner, buyer, otherBuyer] = await ethers.getSigners(); merkleSigners = [otherBuyer]; merkleRoot = merkleTree(merkleSigners); }); beforeEach(async () => { const USDC = new USDCDeployer(signer); usdc = await USDC.deploy({ name: 'USDC', symbol: 'USDC', }); const venueRegistarDeployer = new VenueRegistarDeployer(signer); venueRegistar = await venueRegistarDeployer.deployAndInitialize(toETH(1), tokenCreator.address); const VenueFactory = new VenueFactoryDeployer(signer); const venueFactory = (await VenueFactory.deploy(venueRegistar.address)) as VenueFactory; await venueRegistar.functions.setVenueFactory(venueFactory.address); await venueRegistar.functions.setVenueSBTVersion('1.0.0-beta.0+fob.rsv.iVenueSBT'); const ReservForwarder = new ForwarderDeployer(signer); forwarder = await ReservForwarder.deployAndInitialize({ venueRegistar: venueRegistar.address, }); await venueRegistar.functions.setForwarder(forwarder.address); await venueRegistar.functions.whitelistPaymentToken(usdc.address, true); const erc721EFactoryDeployer = new ERC721EFactoryDeployer(signer); erc721EFactory = await erc721EFactoryDeployer.deploy({ forwarder: forwarder.address, }); await forwarder.functions.addfactory(erc721EFactory.address); expect(await forwarder.getFactoryAt(1)).to.be.equal(erc721EFactory.address); const erc721EWFactoryDeployer = new ERC721EWFactoryDeployer(signer); erc721EWFactory = await erc721EWFactoryDeployer.deploy({ forwarder: forwarder.address, }); await forwarder.functions.addfactory(erc721EWFactory.address); expect(await forwarder.getFactoryAt(2)).to.be.equal(erc721EWFactory.address); await venueRegistar.functions.ownerDeployVenue('Santiagos', 'SDV', 'sdv.com/', otherSigner.address); const venues = await venueRegistar.getVenues(); myVenue = (await ethers.getContractAt('VenueSBT', venues[0], otherSigner)) as VenueSBT; const factoryType = await forwarder.getFactoryPosition(erc721EWFactory.address); timestamp = (await getLatestTimestamp()).toNumber(); const abi = ethers.utils.defaultAbiCoder; const extraData = abi.encode(['uint256'], [timestamp + 100]); const tx = await ( await myVenue.functions.mint(factoryType, 'MyEvent', 'MEV', 'www.sdv.mev.com/', usdc.address, extraData) ).wait(); const event = tx.events?.find((e) => e.event === 'NewEvent')?.args?._event; myEvent = (await ethers.getContractAt('ERC721EW', event)) as ERC721EW; saleEnd = timestamp + 60; }); describe('Change FactoryWide forwarder', () => { it('Changes trusted forwarder', async () => { const ReservForwarder = new ForwarderDeployer(signer); const otherForwarder = await ReservForwarder.deployAndInitialize({ venueRegistar: venueRegistar.address, }); await erc721EFactory.functions.setTrustedForwarer(otherForwarder.address); expect(await erc721EFactory.isTrustedForwarder(otherForwarder.address)).to.be.true; }); it('Fails with wrong forwarder interface', async () => { const ReservForwarder = await ethers.getContractFactory('ForwarderWrongInterface'); const wrongForwarder = await ReservForwarder.deploy(); await expect(erc721EFactory.functions.setTrustedForwarer(wrongForwarder.address)).to.be.revertedWith( 'ERC721EFactoryWideInvalidForwarderInterface', ); }); }); describe('Create Entry', () => { it('Creates entry', async () => { await expect( myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero), ) .to.emit(myEvent, 'NewEntry') .withArgs(1, 'VIP', toUSDC(100), 10, saleEnd); }); it('Creates entry with 0 price', async () => { await expect(myEvent.connect(otherSigner).functions.createEntry('FREE', 0, 10, 1, saleEnd, constants.HashZero)) .to.emit(myEvent, 'NewEntry') .withArgs(1, 'FREE', 0, 10, saleEnd); }); it('Fails with wrong sale end', async () => { await expect( myEvent .connect(otherSigner) .functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd - 100, constants.HashZero), ).to.be.revertedWith('ERC721WrongSaleEnd()'); }); it('Fails with wrong max supply', async () => { await expect( myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 0, 1, saleEnd, constants.HashZero), ).to.be.revertedWith('ERC721WrongMaxSupply'); }); it('Fails no minter role', async () => { await expect( myEvent.connect(signer).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero), ).to.be.reverted; }); }); describe('Edit Entry', () => { it('Edits entry name', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero) ).wait(); const entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as number; await myEvent.connect(otherSigner).functions.editEntryName(entryPos, 'VIPs'); const entry = await myEvent.getEntry(entryPos); expect(entry.name).to.be.equal('VIPs'); }); it('Edits entry sale end', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero) ).wait(); const entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as number; await myEvent.connect(otherSigner).functions.editEntrySaleEnd(entryPos, saleEnd + 100); const entry = await myEvent.getEntry(entryPos); expect(entry.saleEnd).to.be.equal(saleEnd + 100); }); it('Edits entry price', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero) ).wait(); const entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as number; await myEvent.connect(otherSigner).functions.editEntryPrice(entryPos, toUSDC(150)); const entry = await myEvent.getEntry(entryPos); expect(entry.price).to.be.equal(toUSDC(150)); }); it('Edits entry max supply', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero) ).wait(); const entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as number; await myEvent.connect(otherSigner).functions.editEntryMaxSupply(entryPos, 15); const entry = await myEvent.getEntry(entryPos); expect(entry.maxSupply).to.be.equal(15); }); it('Edits entry max buy', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero) ).wait(); const entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as number; await myEvent.connect(otherSigner).functions.editEntryMaxBuy(entryPos, 0); const entry = await myEvent.getEntry(entryPos); expect(entry.maxBuy).to.be.equal(0); }); it('Edits merkleRoot', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero) ).wait(); const entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as number; await myEvent.connect(otherSigner).functions.editMerkleRoot(entryPos, merkleRoot.getHexRoot()); const entry = await myEvent.getEntry(entryPos); expect(entry.merkleRoot).to.be.equal(merkleRoot.getHexRoot()); }); it('Fails with wrong entryPos', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 1, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; entryPos = BigNumber.from('10000'); await expect(myEvent.connect(otherSigner).functions.editEntryName(entryPos, 'Vips')).to.be.revertedWith( 'ERC721WrongEntryPos', ); await expect(myEvent.connect(otherSigner).functions.editEntryPrice(entryPos, toUSDC(150))).to.be.revertedWith( 'ERC721WrongEntryPos', ); await expect(myEvent.connect(otherSigner).functions.editEntryMaxSupply(entryPos, 20)).to.be.revertedWith( 'ERC721WrongEntryPos', ); await expect(myEvent.connect(otherSigner).functions.editEntryMaxBuy(entryPos, 5)).to.be.revertedWith( 'ERC721WrongEntryPos', ); }); it('Fails if max supply is lesser than sold', async () => { const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('VIP', toUSDC(100), 10, 10, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; await usdc.functions.mint(buyer.address, toUSDC(1000)); await usdc.connect(buyer).functions.approve(myEvent.address, toUSDC(1000)); const prices = new Array(10).fill(toUSDC(150)); await myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 10, prices, []); await expect(myEvent.connect(otherSigner).functions.editEntryMaxSupply(entryPos, 9)).to.be.revertedWith( 'ERC721WrongMaxSupply', ); }); }); describe('Buy Tickets', () => { it('Buys tickets', async () => { const ticketPrice = toUSDC(100); const primarySalesFee = BigNumber.from(360); const feePerTicket = ticketPrice.mul(primarySalesFee).div(BigNumber.from(10000)); const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('VIP', ticketPrice, 10, 10, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; await usdc.functions.mint(buyer.address, ticketPrice.mul(10)); await usdc.connect(buyer).functions.approve(myEvent.address, ticketPrice.mul(10)); const prices = new Array(10).fill(ticketPrice.mul(2)); await expect(myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 10, prices, [])) .to.emit(myEvent, 'MintTickets') .withArgs(buyer.address, 123, entryPos, 10); expect(await usdc.balanceOf(myVenue.address)).to.be.equal(ticketPrice.mul(10).sub(feePerTicket.mul(10))); expect(await usdc.balanceOf(signer.address)).to.be.equal(feePerTicket.mul(10)); expect(await myEvent.ownerOf(0)).to.be.equal(buyer.address); const ticketInfo = await myEvent.getTicketInfo(0); expect(ticketInfo.maxBuy).to.been.equal(10); expect(ticketInfo.maxSupply).to.been.equal(10); expect(ticketInfo.sold).to.been.equal(10); expect(ticketInfo.price).to.been.equal(toUSDC(100)); expect(ticketInfo.name).to.been.equal('VIP'); }); it('Buys free tickets', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('Free', 0, 10, 10, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; const prices = new Array(10).fill(toUSDC(10)); await expect(myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 10, prices, [])) .to.emit(myEvent, 'MintTickets') .withArgs(buyer.address, 123, entryPos, 10); expect(await myEvent.ownerOf(0)).to.be.equal(buyer.address); const ticketInfo = await myEvent.getTicketInfo(0); expect(ticketInfo.maxBuy).to.been.equal(10); expect(ticketInfo.maxSupply).to.been.equal(10); expect(ticketInfo.sold).to.been.equal(10); expect(ticketInfo.price).to.been.equal(0); expect(ticketInfo.name).to.been.equal('Free'); }); it('Buys if sender is tokenCreator', async () => { const ticketPrice = toUSDC(100); const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('VIP', ticketPrice, 10, 10, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; const prices = new Array(10).fill(ticketPrice.mul(2)); await expect(myEvent.connect(tokenCreator).buyTickets(buyer.address, 123, entryPos, 10, prices, [])) .to.emit(myEvent, 'MintTickets') .withArgs(buyer.address, 123, entryPos, 10); expect(await usdc.balanceOf(myVenue.address)).to.be.equal(0); expect(await myEvent.ownerOf(0)).to.be.equal(buyer.address); }); it('Fails with wrong batch length', async () => { const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('VIP', toUSDC(100), 10, 10, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; await usdc.functions.mint(buyer.address, toUSDC(1000)); await usdc.connect(buyer).functions.approve(myEvent.address, toUSDC(1000)); const prices = new Array(9).fill(toUSDC(150)); await expect(myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 10, prices, [])).to.be.revertedWith( 'ERC721WrongBatchLengths', ); }); it('Fails if sale ended', async () => { const ticketPrice = toUSDC(100); const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('VIP', toUSDC(100), 10, 10, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; await timeIncreaseTo(timestamp + 400); const prices = new Array(10).fill(ticketPrice.mul(2)); await expect(myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 10, prices, [])).to.be.revertedWith( 'ERC721SaleEnded', ); }); it('Fails if max supply reached', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 5, 10, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; await usdc.functions.mint(buyer.address, toUSDC(1000)); await usdc.connect(buyer).functions.approve(myEvent.address, toUSDC(1000)); const prices = new Array(5).fill(toUSDC(150)); await myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 5, prices, []); await expect( myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 1, [toUSDC(150)], []), ).to.be.revertedWith('ERC721ReachedMaxSupply'); }); it('Fails if max buy reached', async () => { const tx = await ( await myEvent.connect(otherSigner).functions.createEntry('VIP', toUSDC(100), 10, 5, saleEnd, constants.HashZero) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; await usdc.functions.mint(buyer.address, toUSDC(1000)); await usdc.connect(buyer).functions.approve(myEvent.address, toUSDC(1000)); const prices = new Array(5).fill(toUSDC(150)); await myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 5, prices, []); await expect( myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 1, [toUSDC(150)], []), ).to.be.revertedWith('ERC721TooManyTickets'); }); }); describe('Whitelist', () => { it('Buys ticket', async () => { const ticketPrice = toUSDC(100); const primarySalesFee = BigNumber.from(360); const feePerTicket = ticketPrice.mul(primarySalesFee).div(BigNumber.from(10000)); const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('Whitelist VIP', toUSDC(100), 10, 10, saleEnd, merkleRoot.getHexRoot()) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; await usdc.functions.mint(otherBuyer.address, ticketPrice.mul(10)); await usdc.connect(otherBuyer).functions.approve(myEvent.address, ticketPrice.mul(10)); const prices = new Array(10).fill(ticketPrice.mul(2)); const merkleProof = getProof(merkleRoot, otherBuyer.address); await expect(myEvent.connect(otherBuyer).buyTickets(otherBuyer.address, 123, entryPos, 10, prices, merkleProof)) .to.emit(myEvent, 'MintTickets') .withArgs(otherBuyer.address, 123, entryPos, 10); expect(await usdc.balanceOf(myVenue.address)).to.be.equal(ticketPrice.mul(10).sub(feePerTicket.mul(10))); expect(await usdc.balanceOf(signer.address)).to.be.equal(feePerTicket.mul(10)); expect(await myEvent.ownerOf(0)).to.be.equal(otherBuyer.address); const ticketInfo = await myEvent.getTicketInfo(0); expect(ticketInfo.maxBuy).to.been.equal(10); expect(ticketInfo.maxSupply).to.been.equal(10); expect(ticketInfo.sold).to.been.equal(10); expect(ticketInfo.price).to.been.equal(toUSDC(100)); expect(ticketInfo.name).to.been.equal('Whitelist VIP'); }); it('Buys free tickets', async () => { const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('Whitelist Free', 0, 10, 10, saleEnd, merkleRoot.getHexRoot()) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; const prices = new Array(10).fill(toUSDC(10)); const merkleProof = getProof(merkleRoot, otherBuyer.address); await expect(myEvent.connect(otherBuyer).buyTickets(otherBuyer.address, 123, entryPos, 10, prices, merkleProof)) .to.emit(myEvent, 'MintTickets') .withArgs(otherBuyer.address, 123, entryPos, 10); expect(await myEvent.ownerOf(0)).to.be.equal(otherBuyer.address); const ticketInfo = await myEvent.getTicketInfo(0); expect(ticketInfo.maxBuy).to.been.equal(10); expect(ticketInfo.maxSupply).to.been.equal(10); expect(ticketInfo.sold).to.been.equal(10); expect(ticketInfo.price).to.been.equal(0); expect(ticketInfo.name).to.been.equal('Whitelist Free'); }); it('Fails if wrong merkleProof', async () => { const tx = await ( await myEvent .connect(otherSigner) .functions.createEntry('Whitelist Free', 0, 10, 10, saleEnd, merkleRoot.getHexRoot()) ).wait(); let entryPos = tx.events?.find((e) => e.event === 'NewEntry')?.args?.pos as BigNumber; const prices = new Array(10).fill(toUSDC(10)); const merkleProof = getProof(merkleRoot, buyer.address); await expect( myEvent.connect(buyer).buyTickets(buyer.address, 123, entryPos, 10, prices, merkleProof), ).to.be.revertedWith('ERC721EWUnableToVerifyMerkleProof'); }); }); });