diff --git a/contracts/token/TokenERC1155.sol b/contracts/token/TokenERC1155.sol index 300c36cb6..f7c1f0183 100644 --- a/contracts/token/TokenERC1155.sol +++ b/contracts/token/TokenERC1155.sol @@ -65,7 +65,13 @@ contract TokenERC1155 is using StringsUpgradeable for uint256; bytes32 private constant MODULE_TYPE = bytes32("TokenERC1155"); - uint256 private constant VERSION = 1; + uint256 private constant VERSION = 2; + + /// @dev Fee type variants: percentage fee and flat fee + enum PlatformFeeType { + Bps, + Flat + } // Token name string public name; @@ -107,6 +113,12 @@ contract TokenERC1155 is /// @dev The % of primary sales collected by the contract as fees. uint128 private platformFeeBps; + /// @dev The flat amount collected by the contract as fees on primary sales. + uint256 private flatPlatformFee; + + /// @dev Fee type variants: percentage fee and flat fee + PlatformFeeType private platformFeeType; + /// @dev Contract level metadata. string public contractURI; @@ -124,6 +136,12 @@ contract TokenERC1155 is /// @dev Token ID => royalty recipient and bps for token mapping(uint256 => RoyaltyInfo) private royaltyInfoForToken; + /// @dev Emitted when flat fee on primary sales is updated. + event FlatPlatformFeeUpdated(address platformFeeRecipient, uint256 flatFee); + + /// @dev Emitted when platform fee type is updated. + event PlatformFeeTypeUpdated(PlatformFeeType feeType); + constructor() initializer {} /// @dev Initiliazes the contract, like a constructor. @@ -158,6 +176,9 @@ contract TokenERC1155 is require(_platformFeeBps <= MAX_BPS, "exceeds MAX_BPS"); platformFeeBps = _platformFeeBps; + // Fee type Bps by default + platformFeeType = PlatformFeeType.Bps; + _owner = _defaultAdmin; _setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin); _setupRole(MINTER_ROLE, _defaultAdmin); @@ -304,6 +325,24 @@ contract TokenERC1155 is emit PlatformFeeInfoUpdated(_platformFeeRecipient, _platformFeeBps); } + /// @dev Lets a module admin set a flat fee on primary sales. + function setFlatPlatformFeeInfo(address _platformFeeRecipient, uint256 _flatFee) + external + onlyRole(DEFAULT_ADMIN_ROLE) + { + flatPlatformFee = _flatFee; + platformFeeRecipient = _platformFeeRecipient; + + emit FlatPlatformFeeUpdated(_platformFeeRecipient, _flatFee); + } + + /// @dev Lets a module admin set a flat fee on primary sales. + function setPlatformFeeType(PlatformFeeType _feeType) external onlyRole(DEFAULT_ADMIN_ROLE) { + platformFeeType = _feeType; + + emit PlatformFeeTypeUpdated(_feeType); + } + /// @dev Lets a module admin set a new owner for the contract. The new owner must be a module admin. function setOwner(address _newOwner) external onlyRole(DEFAULT_ADMIN_ROLE) { require(hasRole(DEFAULT_ADMIN_ROLE, _newOwner), "new owner not module admin."); @@ -325,7 +364,17 @@ contract TokenERC1155 is return (platformFeeRecipient, uint16(platformFeeBps)); } - /// @dev Returns the platform fee bps and recipient. + /// @dev Returns the flat platform fee and recipient. + function getFlatPlatformFeeInfo() external view returns (address, uint256) { + return (platformFeeRecipient, flatPlatformFee); + } + + /// @dev Returns the platform fee type. + function getPlatformFeeType() external view returns (PlatformFeeType) { + return platformFeeType; + } + + /// @dev Returns default royalty info. function getDefaultRoyaltyInfo() external view returns (address, uint16) { return (royaltyRecipient, uint16(royaltyBps)); } @@ -408,7 +457,10 @@ contract TokenERC1155 is } uint256 totalPrice = _req.pricePerToken * _req.quantity; - uint256 platformFees = (totalPrice * platformFeeBps) / MAX_BPS; + uint256 platformFees = platformFeeType == PlatformFeeType.Flat + ? flatPlatformFee + : ((totalPrice * platformFeeBps) / MAX_BPS); + require(totalPrice >= platformFees, "price less than platform fee"); if (_req.currency == CurrencyTransferLib.NATIVE_TOKEN) { require(msg.value == totalPrice, "must send total price."); diff --git a/src/test/token/TokenERC1155.t.sol b/src/test/token/TokenERC1155.t.sol index aa8c1534d..0118f9ca8 100644 --- a/src/test/token/TokenERC1155.t.sol +++ b/src/test/token/TokenERC1155.t.sol @@ -629,6 +629,115 @@ contract TokenERC1155Test is BaseTest { Unit tests: platform fee //////////////////////////////////////////////////////////////*/ + function test_state_PlatformFee_Flat_ERC20() public { + vm.warp(1000); + uint256 flatPlatformFee = 10; + + vm.startPrank(deployerSigner); + tokenContract.setFlatPlatformFeeInfo(platformFeeRecipient, flatPlatformFee); + tokenContract.setPlatformFeeType(TokenERC1155.PlatformFeeType.Flat); + vm.stopPrank(); + + // update mintrequest data + _mintrequest.pricePerToken = 1; + _mintrequest.currency = address(erc20); + _signature = signMintRequest(_mintrequest, privateKey); + + // approve erc20 tokens to tokenContract + vm.prank(recipient); + erc20.approve(address(tokenContract), _mintrequest.pricePerToken * _mintrequest.quantity); + + // initial balances and state + uint256 nextTokenId = tokenContract.nextTokenIdToMint(); + uint256 currentBalanceOfRecipient = tokenContract.balanceOf(recipient, nextTokenId); + + uint256 erc20BalanceOfSeller = erc20.balanceOf(address(saleRecipient)); + uint256 erc20BalanceOfRecipient = erc20.balanceOf(address(recipient)); + + // mint with signature + vm.prank(recipient); + tokenContract.mintWithSignature(_mintrequest, _signature); + + // check state after minting + assertEq(tokenContract.nextTokenIdToMint(), nextTokenId + 1); + assertEq(tokenContract.uri(nextTokenId), string(_mintrequest.uri)); + assertEq(tokenContract.balanceOf(recipient, nextTokenId), currentBalanceOfRecipient + _mintrequest.quantity); + + // check erc20 balances after minting + assertEq( + erc20.balanceOf(recipient), + erc20BalanceOfRecipient - (_mintrequest.pricePerToken * _mintrequest.quantity) + ); + assertEq( + erc20.balanceOf(address(saleRecipient)), + erc20BalanceOfSeller + (_mintrequest.pricePerToken * _mintrequest.quantity) - flatPlatformFee + ); + } + + function test_state_PlatformFee_NativeToken() public { + vm.warp(1000); + uint256 flatPlatformFee = 10; + + vm.startPrank(deployerSigner); + tokenContract.setFlatPlatformFeeInfo(platformFeeRecipient, flatPlatformFee); + tokenContract.setPlatformFeeType(TokenERC1155.PlatformFeeType.Flat); + vm.stopPrank(); + + // update mintrequest data + _mintrequest.pricePerToken = 1; + _mintrequest.currency = address(NATIVE_TOKEN); + _signature = signMintRequest(_mintrequest, privateKey); + + // initial balances and state + uint256 nextTokenId = tokenContract.nextTokenIdToMint(); + uint256 currentBalanceOfRecipient = tokenContract.balanceOf(recipient, nextTokenId); + + uint256 etherBalanceOfSeller = address(saleRecipient).balance; + uint256 etherBalanceOfRecipient = address(recipient).balance; + + // mint with signature + vm.prank(recipient); + tokenContract.mintWithSignature{ value: _mintrequest.pricePerToken * _mintrequest.quantity }( + _mintrequest, + _signature + ); + + // check state after minting + assertEq(tokenContract.nextTokenIdToMint(), nextTokenId + 1); + assertEq(tokenContract.uri(nextTokenId), string(_mintrequest.uri)); + assertEq(tokenContract.balanceOf(recipient, nextTokenId), currentBalanceOfRecipient + _mintrequest.quantity); + + // check balances after minting + assertEq( + address(recipient).balance, + etherBalanceOfRecipient - (_mintrequest.pricePerToken * _mintrequest.quantity) + ); + assertEq( + address(saleRecipient).balance, + etherBalanceOfSeller + (_mintrequest.pricePerToken * _mintrequest.quantity) - flatPlatformFee + ); + } + + function test_revert_PlatformFeeGreaterThanPrice() public { + vm.warp(1000); + uint256 flatPlatformFee = 1 ether; + + vm.startPrank(deployerSigner); + tokenContract.setFlatPlatformFeeInfo(platformFeeRecipient, flatPlatformFee); + tokenContract.setPlatformFeeType(TokenERC1155.PlatformFeeType.Flat); + vm.stopPrank(); + + // update mintrequest data + _mintrequest.pricePerToken = 1; + _mintrequest.currency = address(erc20); + _signature = signMintRequest(_mintrequest, privateKey); + + // mint with signature + vm.prank(recipient); + vm.expectRevert("price less than platform fee"); + tokenContract.mintWithSignature(_mintrequest, _signature); + } + function test_state_setPlatformFeeInfo() public { address _platformFeeRecipient = address(0x123); uint256 _platformFeeBps = 1000; @@ -641,6 +750,33 @@ contract TokenERC1155Test is BaseTest { assertEq(_platformFeeBps, bps); } + function test_state_setFlatPlatformFee() public { + address _platformFeeRecipient = address(0x123); + uint256 _flatFee = 1000; + + vm.prank(deployerSigner); + tokenContract.setFlatPlatformFeeInfo(_platformFeeRecipient, _flatFee); + + (address recipient_, uint256 fee) = tokenContract.getFlatPlatformFeeInfo(); + assertEq(_platformFeeRecipient, recipient_); + assertEq(_flatFee, fee); + } + + function test_state_setPlatformFeeType() public { + address _platformFeeRecipient = address(0x123); + uint256 _flatFee = 1000; + TokenERC1155.PlatformFeeType _feeType = TokenERC1155.PlatformFeeType.Flat; + + vm.prank(deployerSigner); + tokenContract.setFlatPlatformFeeInfo(_platformFeeRecipient, _flatFee); + + vm.prank(deployerSigner); + tokenContract.setPlatformFeeType(_feeType); + + TokenERC1155.PlatformFeeType updatedFeeType = tokenContract.getPlatformFeeType(); + assertTrue(updatedFeeType == _feeType); + } + function test_revert_setPlatformFeeInfo_ExceedsMaxBps() public { address _platformFeeRecipient = address(0x123); uint256 _platformFeeBps = 10001; @@ -663,6 +799,17 @@ contract TokenERC1155Test is BaseTest { ); vm.prank(address(0x1)); tokenContract.setPlatformFeeInfo(address(1), 1000); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + TWStrings.toHexString(uint160(address(0x1)), 20), + " is missing role ", + TWStrings.toHexString(uint256(role), 32) + ) + ); + vm.prank(address(0x1)); + tokenContract.setFlatPlatformFeeInfo(address(1), 1000); } function test_event_platformFeeInfo() public {