feature: conditional payment as setting (#134)
* feat: add base contract for billing * feat: add withdraw function * feat: add billing requirement to mint * test: add foundry tests for minting with billing * refactor: remove transfer billing and add access point * test: add access point billing foundry tests * test: add test for billing value change * test: add hardhat test setup for billing * test: add hardhat tests for billing * feat: add withdrawn event and add public withdraw function * test: add tests for withdrawing founds and access control for billing * refactor: fix misspells and change variable names * feat: add initialize params for billing * feat: add gap to FleekBilling * fix: testname misspell
This commit is contained in:
parent
969cd12d92
commit
b8b8cb28ea
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
|
||||||
import "@openzeppelin/contracts/utils/Base64.sol";
|
import "@openzeppelin/contracts/utils/Base64.sol";
|
||||||
import "@openzeppelin/contracts/utils/Strings.sol";
|
import "@openzeppelin/contracts/utils/Strings.sol";
|
||||||
import "./FleekAccessControl.sol";
|
import "./FleekAccessControl.sol";
|
||||||
|
import "./FleekBilling.sol";
|
||||||
import "./util/FleekStrings.sol";
|
import "./util/FleekStrings.sol";
|
||||||
import "./FleekPausable.sol";
|
import "./FleekPausable.sol";
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ error ThereIsNoTokenMinted();
|
||||||
error InvalidTokenIdForAccessPoint();
|
error InvalidTokenIdForAccessPoint();
|
||||||
error AccessPointCreationStatusAlreadySet();
|
error AccessPointCreationStatusAlreadySet();
|
||||||
|
|
||||||
contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable {
|
contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable, FleekBilling {
|
||||||
using Strings for uint256;
|
using Strings for uint256;
|
||||||
using FleekStrings for FleekERC721.App;
|
using FleekStrings for FleekERC721.App;
|
||||||
using FleekStrings for FleekERC721.AccessPoint;
|
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.
|
* @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);
|
__ERC721_init(_name, _symbol);
|
||||||
__FleekAccessControl_init();
|
__FleekAccessControl_init();
|
||||||
_appIds = 0;
|
__FleekBilling_init(initialBillings);
|
||||||
__FleekPausable_init();
|
__FleekPausable_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +151,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
||||||
* Requirements:
|
* Requirements:
|
||||||
*
|
*
|
||||||
* - the caller must have ``collectionOwner``'s admin role.
|
* - the caller must have ``collectionOwner``'s admin role.
|
||||||
|
* - billing for the minting may be applied.
|
||||||
* - the contract must be not paused.
|
* - the contract must be not paused.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
@ -160,7 +166,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
||||||
string memory logo,
|
string memory logo,
|
||||||
uint24 color,
|
uint24 color,
|
||||||
bool accessPointAutoApproval
|
bool accessPointAutoApproval
|
||||||
) public payable requireCollectionRole(CollectionRoles.Owner) returns (uint256) {
|
) public payable requirePayment(Billing.Mint) requireCollectionRole(CollectionRoles.Owner) returns (uint256) {
|
||||||
uint256 tokenId = _appIds;
|
uint256 tokenId = _appIds;
|
||||||
_mint(to, tokenId);
|
_mint(to, tokenId);
|
||||||
|
|
||||||
|
|
@ -448,11 +454,14 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
|
||||||
* Requirements:
|
* Requirements:
|
||||||
*
|
*
|
||||||
* - the tokenId must be minted and valid.
|
* - the tokenId must be minted and valid.
|
||||||
|
* - billing for add acess point may be applied.
|
||||||
* - the contract must be not paused.
|
* - 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
|
// require(msg.value == 0.1 ether, "You need to pay at least 0.1 ETH"); // TODO: define a minimum price
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
if (_accessPoints[apName].owner != address(0)) revert AccessPointAlreadyExists();
|
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) {
|
function setPausable(bool pausable) public requireCollectionRole(CollectionRoles.Owner) {
|
||||||
_setPausable(pausable);
|
_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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const { getProxyAddress, proxyStore } = require('./utils/proxy-store');
|
||||||
const ARGUMENTS = [
|
const ARGUMENTS = [
|
||||||
'FleekNFAs', // Collection name
|
'FleekNFAs', // Collection name
|
||||||
'FLKNFA', // Collection symbol
|
'FLKNFA', // Collection symbol
|
||||||
|
[], // Billing values
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- Script Settings ---
|
// --- Script Settings ---
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
pragma solidity ^0.8.17;
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
import "./TestBase.sol";
|
import "./TestBase.sol";
|
||||||
|
import {FleekBilling} from "contracts/FleekBilling.sol";
|
||||||
import "contracts/FleekAccessControl.sol";
|
import "contracts/FleekAccessControl.sol";
|
||||||
|
|
||||||
contract Test_FleekERC721_AccessControlAssertions is Test {
|
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 {
|
contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC721_AccessControlAssertions {
|
||||||
uint256 internal tokenId;
|
uint256 internal tokenId;
|
||||||
address internal collectionOwner = address(1);
|
address internal collectionOwner = address(100);
|
||||||
address internal tokenOwner = address(3);
|
address internal tokenOwner = address(200);
|
||||||
address internal tokenController = address(4);
|
address internal tokenController = address(300);
|
||||||
address internal anyAddress = address(5);
|
address internal anyAddress = address(400);
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
baseSetUp();
|
baseSetUp();
|
||||||
|
|
@ -357,6 +358,56 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
||||||
CuT.burn(tokenId);
|
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 {
|
function test_pauseAndUnpause() public {
|
||||||
// ColletionOwner
|
// ColletionOwner
|
||||||
vm.startPrank(collectionOwner);
|
vm.startPrank(collectionOwner);
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,8 @@ library TestConstants {
|
||||||
|
|
||||||
uint24 public constant APP_COLOR = 0x123456;
|
uint24 public constant APP_COLOR = 0x123456;
|
||||||
|
|
||||||
|
bool public constant APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS = true;
|
||||||
|
|
||||||
string public constant LOGO_0 =
|
string public constant LOGO_0 =
|
||||||
"data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyMTgzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjQgMTQxLjUzMTk5OTk5OTk5OTk4Ij48cGF0aCBkPSJNMTAuMzgzIDEyNi44OTRMMCAwbDEyNCAuMjU1LTEwLjk3OSAxMjYuNjM5LTUwLjU1MyAxNC42Mzh6IiBmaWxsPSIjZTM0ZjI2Ii8+PHBhdGggZD0iTTYyLjQ2OCAxMjkuMjc3VjEyLjA4NWw1MS4wNjQuMTctOS4xMDYgMTA0Ljg1MXoiIGZpbGw9IiNlZjY1MmEiLz48cGF0aCBkPSJNOTkuNDkgNDEuMzYybDEuNDQ2LTE1LjQ5SDIyLjM4M2w0LjM0IDQ3LjQ5aDU0LjIxM0w3OC44MSA5My42MTdsLTE3LjM2MiA0LjY4LTE3LjYxNy01LjEwNi0uOTM2LTEyLjA4NUgyNy4zMTlsMi4xMjggMjQuNjgxIDMyIDguOTM2IDMyLjI1NS04LjkzNiA0LjM0LTQ4LjE3SDQxLjEwN0wzOS40OSA0MS4zNjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+";
|
"data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyMTgzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjQgMTQxLjUzMTk5OTk5OTk5OTk4Ij48cGF0aCBkPSJNMTAuMzgzIDEyNi44OTRMMCAwbDEyNCAuMjU1LTEwLjk3OSAxMjYuNjM5LTUwLjU1MyAxNC42Mzh6IiBmaWxsPSIjZTM0ZjI2Ii8+PHBhdGggZD0iTTYyLjQ2OCAxMjkuMjc3VjEyLjA4NWw1MS4wNjQuMTctOS4xMDYgMTA0Ljg1MXoiIGZpbGw9IiNlZjY1MmEiLz48cGF0aCBkPSJNOTkuNDkgNDEuMzYybDEuNDQ2LTE1LjQ5SDIyLjM4M2w0LjM0IDQ3LjQ5aDU0LjIxM0w3OC44MSA5My42MTdsLTE3LjM2MiA0LjY4LTE3LjYxNy01LjEwNi0uOTM2LTEyLjA4NUgyNy4zMTlsMi4xMjggMjQuNjgxIDMyIDguOTM2IDMyLjI1NS04LjkzNiA0LjM0LTQ4LjE3SDQxLjEwN0wzOS40OSA0MS4zNjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ contract Test_FleekERC721_Deploy is Test_FleekERC721_Base {
|
||||||
|
|
||||||
function testFuzz_nameAndSymbol(string memory _name, string memory _symbol) public {
|
function testFuzz_nameAndSymbol(string memory _name, string memory _symbol) public {
|
||||||
CuT = new FleekERC721();
|
CuT = new FleekERC721();
|
||||||
CuT.initialize(_name, _symbol);
|
CuT.initialize(_name, _symbol, new uint256[](0));
|
||||||
|
|
||||||
assertEq(CuT.name(), _name);
|
assertEq(CuT.name(), _name);
|
||||||
assertEq(CuT.symbol(), _symbol);
|
assertEq(CuT.symbol(), _symbol);
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions {
|
||||||
|
|
||||||
function baseSetUp() internal {
|
function baseSetUp() internal {
|
||||||
CuT = new FleekERC721();
|
CuT = new FleekERC721();
|
||||||
CuT.initialize("Test Contract", "FLKAPS");
|
CuT.initialize("Test Contract", "FLKAPS", new uint256[](0));
|
||||||
deployer = address(this);
|
deployer = address(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<ReturnType<typeof Fixtures.withMint>>;
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -11,6 +11,10 @@ export const TestConstants = Object.freeze({
|
||||||
REJECTED: 2,
|
REJECTED: 2,
|
||||||
REMOVED: 3,
|
REMOVED: 3,
|
||||||
},
|
},
|
||||||
|
Billing: {
|
||||||
|
Mint: 0,
|
||||||
|
AddAccessPoint: 1,
|
||||||
|
},
|
||||||
MintParams: {
|
MintParams: {
|
||||||
name: 'Fleek Test App',
|
name: 'Fleek Test App',
|
||||||
description: 'Fleek Test App Description',
|
description: 'Fleek Test App Description',
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,5 @@ export const Errors = Object.freeze({
|
||||||
ContractIsNotPausable: 'ContractIsNotPausable',
|
ContractIsNotPausable: 'ContractIsNotPausable',
|
||||||
PausableIsSetTo: 'PausableIsSetTo',
|
PausableIsSetTo: 'PausableIsSetTo',
|
||||||
ThereIsNoTokenMinted: 'ThereIsNoTokenMinted',
|
ThereIsNoTokenMinted: 'ThereIsNoTokenMinted',
|
||||||
|
RequiredPayment: 'RequiredPayment',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ export abstract class Fixtures {
|
||||||
const Contract = await ethers.getContractFactory('FleekERC721', {
|
const Contract = await ethers.getContractFactory('FleekERC721', {
|
||||||
libraries,
|
libraries,
|
||||||
});
|
});
|
||||||
|
|
||||||
const contract = await upgrades.deployProxy(
|
const contract = await upgrades.deployProxy(
|
||||||
Contract,
|
Contract,
|
||||||
[
|
[
|
||||||
TestConstants.CollectionParams.name,
|
TestConstants.CollectionParams.name,
|
||||||
TestConstants.CollectionParams.symbol,
|
TestConstants.CollectionParams.symbol,
|
||||||
|
[], // Initial Billings
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
unsafeAllow: ['external-library-linking'],
|
unsafeAllow: ['external-library-linking'],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue