diff --git a/contracts/contracts/FleekBilling.sol b/contracts/contracts/FleekBilling.sol new file mode 100644 index 0000000..ed742a1 --- /dev/null +++ b/contracts/contracts/FleekBilling.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.7; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +error RequiredPayment(uint requiredValue); + +abstract contract FleekBilling is Initializable { + /** + * @dev Available billing values. + */ + enum Billing { + Mint, + AddAccessPoint + } + + /** + * @dev Emitted when the billing value is changed. + */ + event BillingChanged(Billing key, uint256 price); + + /** + * @dev Emitted when contract is withdrawn. + */ + event Withdrawn(uint256 value, address indexed byAddress); + + /** + * @dev Mapping of billing values. + */ + mapping(Billing => uint256) public _billings; + + /** + * @dev Initializes the contract by setting default billing values. + */ + function __FleekBilling_init(uint256[] memory initialBillings) internal onlyInitializing { + for (uint256 i = 0; i < initialBillings.length; i++) { + _setBilling(Billing(i), initialBillings[i]); + } + } + + /** + * @dev Returns the billing value for a given key. + */ + function getBilling(Billing key) public view returns (uint256) { + return _billings[key]; + } + + /** + * @dev Sets the billing value for a given key. + */ + function _setBilling(Billing key, uint256 price) internal { + _billings[key] = price; + emit BillingChanged(key, price); + } + + /** + * @dev Internal function to require a payment value. + */ + function _requirePayment(Billing key) internal { + uint256 requiredValue = _billings[key]; + if (msg.value != _billings[key]) revert RequiredPayment(requiredValue); + } + + /** + * @dev Internal function to withdraw the contract balance. + */ + function _withdraw() internal { + address by = msg.sender; + uint256 value = address(this).balance; + + payable(by).transfer(value); + emit Withdrawn(value, by); + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} diff --git a/contracts/contracts/FleekERC721.sol b/contracts/contracts/FleekERC721.sol index a73a4c4..f07367f 100644 --- a/contracts/contracts/FleekERC721.sol +++ b/contracts/contracts/FleekERC721.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts/utils/Base64.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "./FleekAccessControl.sol"; +import "./FleekBilling.sol"; import "./util/FleekStrings.sol"; import "./FleekPausable.sol"; @@ -18,7 +19,7 @@ error ThereIsNoTokenMinted(); error InvalidTokenIdForAccessPoint(); error AccessPointCreationStatusAlreadySet(); -contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable { +contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable, FleekBilling { using Strings for uint256; using FleekStrings for FleekERC721.App; using FleekStrings for FleekERC721.AccessPoint; @@ -123,10 +124,14 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl /** * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. */ - function initialize(string memory _name, string memory _symbol) public initializer { + function initialize( + string memory _name, + string memory _symbol, + uint256[] memory initialBillings + ) public initializer { __ERC721_init(_name, _symbol); __FleekAccessControl_init(); - _appIds = 0; + __FleekBilling_init(initialBillings); __FleekPausable_init(); } @@ -146,6 +151,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl * Requirements: * * - the caller must have ``collectionOwner``'s admin role. + * - billing for the minting may be applied. * - the contract must be not paused. * */ @@ -160,7 +166,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl string memory logo, uint24 color, bool accessPointAutoApproval - ) public payable requireCollectionRole(CollectionRoles.Owner) returns (uint256) { + ) public payable requirePayment(Billing.Mint) requireCollectionRole(CollectionRoles.Owner) returns (uint256) { uint256 tokenId = _appIds; _mint(to, tokenId); @@ -448,11 +454,14 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl * Requirements: * * - the tokenId must be minted and valid. + * - billing for add acess point may be applied. * - the contract must be not paused. * - * IMPORTANT: The payment is not set yet */ - function addAccessPoint(uint256 tokenId, string memory apName) public payable whenNotPaused { + function addAccessPoint( + uint256 tokenId, + string memory apName + ) public payable whenNotPaused requirePayment(Billing.AddAccessPoint) { // require(msg.value == 0.1 ether, "You need to pay at least 0.1 ETH"); // TODO: define a minimum price _requireMinted(tokenId); if (_accessPoints[apName].owner != address(0)) revert AccessPointAlreadyExists(); @@ -800,4 +809,44 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl function setPausable(bool pausable) public requireCollectionRole(CollectionRoles.Owner) { _setPausable(pausable); } + + /*////////////////////////////////////////////////////////////// + BILLING + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Modifier to require billing with a given key. + */ + modifier requirePayment(Billing key) { + _requirePayment(key); + _; + } + + /** + * @dev Sets the billing value for a given key. + * + * May emit a {BillingChanged} event. + * + * Requirements: + * + * - the sender must have the `collectionOwner` role. + * + */ + function setBilling(Billing key, uint256 value) public requireCollectionRole(CollectionRoles.Owner) { + _setBilling(key, value); + } + + /** + * @dev Withdraws all the funds from contract. + * + * May emmit a {Withdrawn} event. + * + * Requirements: + * + * - the sender must have the `collectionOwner` role. + * + */ + function withdraw() public requireCollectionRole(CollectionRoles.Owner) { + _withdraw(); + } } diff --git a/contracts/scripts/deploy.js b/contracts/scripts/deploy.js index d03c834..d98b395 100644 --- a/contracts/scripts/deploy.js +++ b/contracts/scripts/deploy.js @@ -8,6 +8,7 @@ const { getProxyAddress, proxyStore } = require('./utils/proxy-store'); const ARGUMENTS = [ 'FleekNFAs', // Collection name 'FLKNFA', // Collection symbol + [], // Billing values ]; // --- Script Settings --- diff --git a/contracts/test/foundry/FleekERC721/AccessControl.t.sol b/contracts/test/foundry/FleekERC721/AccessControl.t.sol index e7d493f..78b2eb5 100644 --- a/contracts/test/foundry/FleekERC721/AccessControl.t.sol +++ b/contracts/test/foundry/FleekERC721/AccessControl.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; import "./TestBase.sol"; +import {FleekBilling} from "contracts/FleekBilling.sol"; import "contracts/FleekAccessControl.sol"; contract Test_FleekERC721_AccessControlAssertions is Test { @@ -17,10 +18,10 @@ contract Test_FleekERC721_AccessControlAssertions is Test { contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC721_AccessControlAssertions { uint256 internal tokenId; - address internal collectionOwner = address(1); - address internal tokenOwner = address(3); - address internal tokenController = address(4); - address internal anyAddress = address(5); + address internal collectionOwner = address(100); + address internal tokenOwner = address(200); + address internal tokenController = address(300); + address internal anyAddress = address(400); function setUp() public { baseSetUp(); @@ -357,6 +358,56 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7 CuT.burn(tokenId); } + function test_setBilling() public { + // ColletionOwner + vm.prank(collectionOwner); + CuT.setBilling(FleekBilling.Billing.Mint, 1 ether); + + // TokenOwner + vm.prank(tokenOwner); + expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner); + CuT.setBilling(FleekBilling.Billing.Mint, 2 ether); + + // TokenController + vm.prank(tokenController); + expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner); + CuT.setBilling(FleekBilling.Billing.Mint, 2 ether); + + // AnyAddress + vm.prank(anyAddress); + expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner); + CuT.setBilling(FleekBilling.Billing.Mint, 2 ether); + } + + function test_withdraw() public { + // ColletionOwner + vm.deal(address(CuT), 1 ether); + vm.prank(collectionOwner); + CuT.withdraw(); + + // TokenOwner + vm.prank(tokenOwner); + expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner); + CuT.withdraw(); + + // TokenController + vm.prank(tokenController); + expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner); + CuT.withdraw(); + + // AnyAddress + vm.prank(anyAddress); + expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner); + CuT.withdraw(); + } + + /** + * @dev `receive` and `fallback` are required for test contract receive ETH + */ + receive() external payable {} + + fallback() external payable {} + function test_pauseAndUnpause() public { // ColletionOwner vm.startPrank(collectionOwner); diff --git a/contracts/test/foundry/FleekERC721/Billing.t.sol b/contracts/test/foundry/FleekERC721/Billing.t.sol new file mode 100644 index 0000000..dbde354 --- /dev/null +++ b/contracts/test/foundry/FleekERC721/Billing.t.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import "./TestBase.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {FleekAccessControl} from "contracts/FleekAccessControl.sol"; +import "contracts/FleekBilling.sol"; + +contract Test_FleekERC721_BillingAssertions is Test { + event BillingChanged(FleekBilling.Billing key, uint256 price); + event Withdrawn(uint256 value, address indexed byAddress); + + function expectRevertWithRequiredPayment(uint256 value) public { + vm.expectRevert(abi.encodeWithSelector(RequiredPayment.selector, value)); + } + + function expectEmitBillingChanged(FleekBilling.Billing key, uint256 price) public { + vm.expectEmit(true, true, true, true); + emit BillingChanged(key, price); + } + + function expectEmitWithdawn(uint256 value, address byAddress) public { + vm.expectEmit(true, true, true, true); + emit Withdrawn(value, byAddress); + } +} + +contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_BillingAssertions { + using Strings for address; + uint256 internal tokenId; + uint256 internal constant mintPrice = 1 ether; + uint256 internal constant addAPPrice = 1 ether; + + function setUp() public { + baseSetUp(); + tokenId = mintDefault(deployer); + CuT.setBilling(FleekBilling.Billing.Mint, mintPrice); + CuT.setBilling(FleekBilling.Billing.AddAccessPoint, addAPPrice); + } + + function test_setUp() public { + assertEq(CuT.getBilling(FleekBilling.Billing.Mint), mintPrice); + assertEq(CuT.getBilling(FleekBilling.Billing.AddAccessPoint), addAPPrice); + assertEq(address(CuT).balance, 0); + } + + function test_mint() public { + CuT.mint{value: mintPrice}( + deployer, + TestConstants.APP_NAME, + TestConstants.APP_DESCRIPTION, + TestConstants.APP_EXTERNAL_URL, + TestConstants.APP_ENS, + TestConstants.APP_COMMIT_HASH, + TestConstants.APP_GIT_REPOSITORY, + TestConstants.LOGO_0, + TestConstants.APP_COLOR, + TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS + ); + assertEq(CuT.ownerOf(tokenId), deployer); + assertEq(address(CuT).balance, mintPrice); + } + + function testFuzz_cannotMintWithWrongValue(uint256 value) public { + vm.assume(value != mintPrice); + vm.deal(deployer, value); + expectRevertWithRequiredPayment(mintPrice); + CuT.mint{value: value}( + deployer, + TestConstants.APP_NAME, + TestConstants.APP_DESCRIPTION, + TestConstants.APP_EXTERNAL_URL, + TestConstants.APP_ENS, + TestConstants.APP_COMMIT_HASH, + TestConstants.APP_GIT_REPOSITORY, + TestConstants.LOGO_0, + TestConstants.APP_COLOR, + TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS + ); + assertEq(address(CuT).balance, 0); + } + + function testFuzz_shouldChangeMintBillingValue(uint256 value) public { + expectEmitBillingChanged(FleekBilling.Billing.Mint, value); + CuT.setBilling(FleekBilling.Billing.Mint, value); + + assertEq(CuT.getBilling(FleekBilling.Billing.Mint), value); + + vm.deal(deployer, value); + CuT.mint{value: value}( + deployer, + TestConstants.APP_NAME, + TestConstants.APP_DESCRIPTION, + TestConstants.APP_EXTERNAL_URL, + TestConstants.APP_ENS, + TestConstants.APP_COMMIT_HASH, + TestConstants.APP_GIT_REPOSITORY, + TestConstants.LOGO_0, + TestConstants.APP_COLOR, + TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS + ); + assertEq(CuT.ownerOf(tokenId), deployer); + assertEq(address(CuT).balance, value); + } + + function test_addAccessPoint() public { + CuT.addAccessPoint{value: addAPPrice}(tokenId, "accesspoint.com"); + assertFalse(CuT.isAccessPointNameVerified("accesspoint.com")); + assertEq(address(CuT).balance, addAPPrice); + } + + function testFuzz_cannotAddAccessPointWithWrongValue(uint256 value) public { + vm.assume(value != addAPPrice); + vm.deal(deployer, value); + expectRevertWithRequiredPayment(addAPPrice); + CuT.addAccessPoint{value: value}(tokenId, "accesspoint.com"); + assertEq(address(CuT).balance, 0); + } + + function testFuzz_shouldChangeAddAPBillingValue(uint256 value) public { + expectEmitBillingChanged(FleekBilling.Billing.AddAccessPoint, value); + CuT.setBilling(FleekBilling.Billing.AddAccessPoint, value); + + assertEq(CuT.getBilling(FleekBilling.Billing.AddAccessPoint), value); + + vm.deal(deployer, value); + CuT.addAccessPoint{value: value}(tokenId, "accesspoint.com"); + assertFalse(CuT.isAccessPointNameVerified("accesspoint.com")); + assertEq(address(CuT).balance, value); + } + + function testFuzz_shouldWithdrawAnyContractFunds(uint128 value) public { + uint256 balanceBefore = address(this).balance; + vm.deal(address(CuT), value); + CuT.withdraw(); + assertEq(address(this).balance, value + balanceBefore); + } + + function testFuzz_shouldWithdrawAllContractFundsAfterPayableCall(uint8 iterations) public { + // this test is going to add access points up to 256 times and then withdraw all funds + uint256 balanceBefore = address(this).balance; + address randomAddress = address(1); + uint256 totalExpectedValue = iterations * addAPPrice; + + vm.deal(randomAddress, totalExpectedValue); + vm.startPrank(randomAddress); + for (uint256 i = 0; i < iterations; i++) { + CuT.addAccessPoint{value: addAPPrice}(tokenId, Strings.toString(i)); + } + vm.stopPrank(); + + expectEmitWithdawn(totalExpectedValue, deployer); + CuT.withdraw(); + assertEq(address(this).balance, totalExpectedValue + balanceBefore); + } + + /** + * @dev `receive` and `fallback` are required for test contract receive ETH + */ + receive() external payable {} + + fallback() external payable {} +} diff --git a/contracts/test/foundry/FleekERC721/Constants.sol b/contracts/test/foundry/FleekERC721/Constants.sol index 9f0c3af..8b206db 100644 --- a/contracts/test/foundry/FleekERC721/Constants.sol +++ b/contracts/test/foundry/FleekERC721/Constants.sol @@ -17,6 +17,8 @@ library TestConstants { uint24 public constant APP_COLOR = 0x123456; + bool public constant APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS = true; + string public constant LOGO_0 = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyMTgzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjQgMTQxLjUzMTk5OTk5OTk5OTk4Ij48cGF0aCBkPSJNMTAuMzgzIDEyNi44OTRMMCAwbDEyNCAuMjU1LTEwLjk3OSAxMjYuNjM5LTUwLjU1MyAxNC42Mzh6IiBmaWxsPSIjZTM0ZjI2Ii8+PHBhdGggZD0iTTYyLjQ2OCAxMjkuMjc3VjEyLjA4NWw1MS4wNjQuMTctOS4xMDYgMTA0Ljg1MXoiIGZpbGw9IiNlZjY1MmEiLz48cGF0aCBkPSJNOTkuNDkgNDEuMzYybDEuNDQ2LTE1LjQ5SDIyLjM4M2w0LjM0IDQ3LjQ5aDU0LjIxM0w3OC44MSA5My42MTdsLTE3LjM2MiA0LjY4LTE3LjYxNy01LjEwNi0uOTM2LTEyLjA4NUgyNy4zMTlsMi4xMjggMjQuNjgxIDMyIDguOTM2IDMyLjI1NS04LjkzNiA0LjM0LTQ4LjE3SDQxLjEwN0wzOS40OSA0MS4zNjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+"; diff --git a/contracts/test/foundry/FleekERC721/Deploy.t.sol b/contracts/test/foundry/FleekERC721/Deploy.t.sol index 323ba7d..e292f08 100644 --- a/contracts/test/foundry/FleekERC721/Deploy.t.sol +++ b/contracts/test/foundry/FleekERC721/Deploy.t.sol @@ -24,7 +24,7 @@ contract Test_FleekERC721_Deploy is Test_FleekERC721_Base { function testFuzz_nameAndSymbol(string memory _name, string memory _symbol) public { CuT = new FleekERC721(); - CuT.initialize(_name, _symbol); + CuT.initialize(_name, _symbol, new uint256[](0)); assertEq(CuT.name(), _name); assertEq(CuT.symbol(), _symbol); diff --git a/contracts/test/foundry/FleekERC721/TestBase.sol b/contracts/test/foundry/FleekERC721/TestBase.sol index b2cb14f..fe0a681 100644 --- a/contracts/test/foundry/FleekERC721/TestBase.sol +++ b/contracts/test/foundry/FleekERC721/TestBase.sol @@ -46,7 +46,7 @@ abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions { function baseSetUp() internal { CuT = new FleekERC721(); - CuT.initialize("Test Contract", "FLKAPS"); + CuT.initialize("Test Contract", "FLKAPS", new uint256[](0)); deployer = address(this); } diff --git a/contracts/test/hardhat/contracts/FleekERC721/billing.t.ts b/contracts/test/hardhat/contracts/FleekERC721/billing.t.ts new file mode 100644 index 0000000..733dd63 --- /dev/null +++ b/contracts/test/hardhat/contracts/FleekERC721/billing.t.ts @@ -0,0 +1,90 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { Fixtures, TestConstants, Errors } from './helpers'; + +const { Billing, MintParams } = TestConstants; + +describe('FleekERC721.Billing', () => { + let fixture: Awaited>; + const mintPrice = ethers.utils.parseEther('1'); + const addAPPrice = ethers.utils.parseEther('1'); + + const mint = (value?: any) => { + const { contract, owner } = fixture; + return contract.mint( + owner.address, + MintParams.name, + MintParams.description, + MintParams.externalUrl, + MintParams.ens, + MintParams.commitHash, + MintParams.gitRepository, + MintParams.logo, + MintParams.color, + MintParams.accessPointAutoApprovalSettings, + { value } + ); + }; + + const addAP = (value?: any) => { + const { contract } = fixture; + return contract.addAccessPoint(0, 'random.com', { value }); + }; + + beforeEach(async () => { + fixture = await loadFixture(Fixtures.withMint); + const { contract } = fixture; + await contract.setBilling(Billing.Mint, mintPrice); + await contract.setBilling(Billing.AddAccessPoint, addAPPrice); + }); + + it('should start with mint prices', async () => { + const { contract } = fixture; + expect(await contract.getBilling(Billing.Mint)).to.equal(mintPrice); + expect(await contract.getBilling(Billing.AddAccessPoint)).to.equal( + addAPPrice + ); + }); + + it('should allow mint with transfer', async () => { + const { contract, owner } = fixture; + await mint(mintPrice); + console.log('hit'); + expect(await contract.ownerOf(0)).to.equal(owner.address); + }); + + it('should not allow mint with empty value', async () => { + const { contract } = fixture; + await expect(mint()) + .to.be.revertedWithCustomError(contract, Errors.RequiredPayment) + .withArgs(mintPrice); + }); + + it('should not allow mint with different value', async () => { + const { contract } = fixture; + await expect(mint(ethers.utils.parseEther('2'))) + .to.be.revertedWithCustomError(contract, Errors.RequiredPayment) + .withArgs(mintPrice); + }); + + it('should allow add access point with transfer', async () => { + const { contract } = fixture; + await addAP(addAPPrice); + expect(await contract.getAccessPointJSON('random.com')).to.exist; + }); + + it('should not allow add access point with empty value', async () => { + const { contract } = fixture; + await expect(addAP()) + .to.be.revertedWithCustomError(contract, Errors.RequiredPayment) + .withArgs(addAPPrice); + }); + + it('should not allow add access point with different value', async () => { + const { contract } = fixture; + await expect(addAP(ethers.utils.parseEther('2'))) + .to.be.revertedWithCustomError(contract, Errors.RequiredPayment) + .withArgs(addAPPrice); + }); +}); diff --git a/contracts/test/hardhat/contracts/FleekERC721/helpers/constants.ts b/contracts/test/hardhat/contracts/FleekERC721/helpers/constants.ts index 4011ddd..0137a2b 100644 --- a/contracts/test/hardhat/contracts/FleekERC721/helpers/constants.ts +++ b/contracts/test/hardhat/contracts/FleekERC721/helpers/constants.ts @@ -11,6 +11,10 @@ export const TestConstants = Object.freeze({ REJECTED: 2, REMOVED: 3, }, + Billing: { + Mint: 0, + AddAccessPoint: 1, + }, MintParams: { name: 'Fleek Test App', description: 'Fleek Test App Description', diff --git a/contracts/test/hardhat/contracts/FleekERC721/helpers/errors.ts b/contracts/test/hardhat/contracts/FleekERC721/helpers/errors.ts index 3c3fff1..e8593b5 100644 --- a/contracts/test/hardhat/contracts/FleekERC721/helpers/errors.ts +++ b/contracts/test/hardhat/contracts/FleekERC721/helpers/errors.ts @@ -13,4 +13,5 @@ export const Errors = Object.freeze({ ContractIsNotPausable: 'ContractIsNotPausable', PausableIsSetTo: 'PausableIsSetTo', ThereIsNoTokenMinted: 'ThereIsNoTokenMinted', + RequiredPayment: 'RequiredPayment', }); diff --git a/contracts/test/hardhat/contracts/FleekERC721/helpers/fixture.ts b/contracts/test/hardhat/contracts/FleekERC721/helpers/fixture.ts index 6857703..99158c2 100644 --- a/contracts/test/hardhat/contracts/FleekERC721/helpers/fixture.ts +++ b/contracts/test/hardhat/contracts/FleekERC721/helpers/fixture.ts @@ -16,11 +16,13 @@ export abstract class Fixtures { const Contract = await ethers.getContractFactory('FleekERC721', { libraries, }); + const contract = await upgrades.deployProxy( Contract, [ TestConstants.CollectionParams.name, TestConstants.CollectionParams.symbol, + [], // Initial Billings ], { unsafeAllow: ['external-library-linking'],