diff --git a/src/test/drop/drop-erc20/_beforeClaim/_beforeClaim.t.sol b/src/test/drop/drop-erc20/_beforeClaim/_beforeClaim.t.sol new file mode 100644 index 000000000..c6c3e681b --- /dev/null +++ b/src/test/drop/drop-erc20/_beforeClaim/_beforeClaim.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC20 } from "contracts/prebuilts/drop/DropERC20.sol"; +import { TWProxy } from "contracts/infra/TWProxy.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract HarnessDropERC20BeforeClaim is DropERC20 { + bytes private emptyBytes = bytes(""); + + function harness_beforeClaim(uint256 quantity, AllowlistProof calldata _proof) public view { + _beforeClaim(address(0), quantity, address(0), 0, _proof, emptyBytes); + } +} + +contract DropERC20Test_beforeClaim is BaseTest { + address public dropImp; + HarnessDropERC20BeforeClaim public proxy; + + uint256 private mintQty; + + function setUp() public override { + super.setUp(); + + bytes memory initializeData = abi.encodeCall( + DropERC20.initialize, + (deployer, NAME, SYMBOL, CONTRACT_URI, forwarders(), saleRecipient, platformFeeRecipient, platformFeeBps) + ); + + dropImp = address(new HarnessDropERC20BeforeClaim()); + proxy = HarnessDropERC20BeforeClaim(address(new TWProxy(dropImp, initializeData))); + } + + modifier setMaxTotalSupply() { + vm.prank(deployer); + proxy.setMaxTotalSupply(100); + _; + } + + modifier qtyExceedMaxTotalSupply() { + mintQty = 101; + _; + } + + function test_revert_MaxSupplyExceeded() public setMaxTotalSupply qtyExceedMaxTotalSupply { + DropERC20.AllowlistProof memory proof; + vm.expectRevert("exceed max total supply."); + proxy.harness_beforeClaim(mintQty, proof); + } +} diff --git a/src/test/drop/drop-erc20/_beforeClaim/_beforeClaim.tree b/src/test/drop/drop-erc20/_beforeClaim/_beforeClaim.tree new file mode 100644 index 000000000..f1cf867a4 --- /dev/null +++ b/src/test/drop/drop-erc20/_beforeClaim/_beforeClaim.tree @@ -0,0 +1,10 @@ +function _beforeClaim( + address, + uint256 _quantity, + address, + uint256, + AllowlistProof calldata, + bytes memory +) +└── when maxTotalSupply does not equal to 0 and totalSupply() + _quantity is greater than _maxTotalSupply + └── it should revert ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc20/_canSetFunctions/_canSetFunctions.t.sol b/src/test/drop/drop-erc20/_canSetFunctions/_canSetFunctions.t.sol new file mode 100644 index 000000000..2b17cdd08 --- /dev/null +++ b/src/test/drop/drop-erc20/_canSetFunctions/_canSetFunctions.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC20 } from "contracts/prebuilts/drop/DropERC20.sol"; +import { TWProxy } from "contracts/infra/TWProxy.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract HarnessDropERC20CanSet is DropERC20 { + function canSetPlatformFeeInfo() external view returns (bool) { + return _canSetPlatformFeeInfo(); + } + + function canSetPrimarySaleRecipient() external view returns (bool) { + return _canSetPrimarySaleRecipient(); + } + + function canSetContractURI() external view returns (bool) { + return _canSetContractURI(); + } + + function canSetClaimConditions() external view returns (bool) { + return _canSetClaimConditions(); + } +} + +contract DropERC20Test_canSet is BaseTest { + address public dropImp; + + HarnessDropERC20CanSet public proxy; + + function setUp() public override { + super.setUp(); + + bytes memory initializeData = abi.encodeCall( + DropERC20.initialize, + (deployer, NAME, SYMBOL, CONTRACT_URI, forwarders(), saleRecipient, platformFeeRecipient, platformFeeBps) + ); + + dropImp = address(new HarnessDropERC20CanSet()); + proxy = HarnessDropERC20CanSet(address(new TWProxy(dropImp, initializeData))); + } + + modifier callerHasDefaultAdminRole() { + vm.startPrank(deployer); + _; + } + + modifier callerDoesNotHaveDefaultAdminRole() { + _; + } + + function test_canSetPlatformFee_returnTrue() public callerHasDefaultAdminRole { + bool status = proxy.canSetPlatformFeeInfo(); + assertEq(status, true); + } + + function test_canSetPlatformFee_returnFalse() public callerDoesNotHaveDefaultAdminRole { + bool status = proxy.canSetPlatformFeeInfo(); + assertEq(status, false); + } + + function test_canSetPrimarySaleRecipient_returnTrue() public callerHasDefaultAdminRole { + bool status = proxy.canSetPrimarySaleRecipient(); + assertEq(status, true); + } + + function test_canSetPrimarySaleRecipient_returnFalse() public callerDoesNotHaveDefaultAdminRole { + bool status = proxy.canSetPrimarySaleRecipient(); + assertEq(status, false); + } + + function test_canSetContractURI_returnTrue() public callerHasDefaultAdminRole { + bool status = proxy.canSetContractURI(); + assertEq(status, true); + } + + function test_canSetContractURI_returnFalse() public callerDoesNotHaveDefaultAdminRole { + bool status = proxy.canSetContractURI(); + assertEq(status, false); + } + + function test_canSetClaimConditions_returnTrue() public callerHasDefaultAdminRole { + bool status = proxy.canSetClaimConditions(); + assertEq(status, true); + } + + function test_canSetClaimConditions_returnFalse() public callerDoesNotHaveDefaultAdminRole { + bool status = proxy.canSetClaimConditions(); + assertEq(status, false); + } +} diff --git a/src/test/drop/drop-erc20/_canSetFunctions/_canSetFunctions.tree b/src/test/drop/drop-erc20/_canSetFunctions/_canSetFunctions.tree new file mode 100644 index 000000000..2f2da72e4 --- /dev/null +++ b/src/test/drop/drop-erc20/_canSetFunctions/_canSetFunctions.tree @@ -0,0 +1,23 @@ +function _canSetPlatformFeeInfo() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetPrimarySaleRecipient() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetContractURI() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetClaimConditions() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ diff --git a/src/test/drop/drop-erc20/_collectPriceOnClaim/_collectPriceOnClaim.t.sol b/src/test/drop/drop-erc20/_collectPriceOnClaim/_collectPriceOnClaim.t.sol new file mode 100644 index 000000000..b78b3eed9 --- /dev/null +++ b/src/test/drop/drop-erc20/_collectPriceOnClaim/_collectPriceOnClaim.t.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC20 } from "contracts/prebuilts/drop/DropERC20.sol"; +import { TWProxy } from "contracts/infra/TWProxy.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract HarnessDropERC20CollectPriceOnClaim is DropERC20 { + function harness_collectPrice( + address _primarySaleRecipient, + uint256 _quantityToClaim, + address _currency, + uint256 _pricePerToken + ) public payable { + _collectPriceOnClaim(_primarySaleRecipient, _quantityToClaim, _currency, _pricePerToken); + } +} + +contract DropERC20Test_collectPrice is BaseTest { + address public dropImp; + HarnessDropERC20CollectPriceOnClaim public proxy; + + address private currency; + address private primarySaleRecipient; + uint256 private msgValue; + uint256 private pricePerToken; + + function setUp() public override { + super.setUp(); + + bytes memory initializeData = abi.encodeCall( + DropERC20.initialize, + (deployer, NAME, SYMBOL, CONTRACT_URI, forwarders(), saleRecipient, platformFeeRecipient, platformFeeBps) + ); + + dropImp = address(new HarnessDropERC20CollectPriceOnClaim()); + proxy = HarnessDropERC20CollectPriceOnClaim(address(new TWProxy(dropImp, initializeData))); + } + + modifier pricePerTokenZero() { + _; + } + + modifier pricePerTokenNotZero() { + pricePerToken = 1 ether; + _; + } + + modifier msgValueZero() { + _; + } + + modifier msgValueNotZero() { + msgValue = 1 ether; + _; + } + + modifier valuePriceMismatch() { + msgValue = 1 ether; + pricePerToken = 2 ether; + _; + } + + modifier primarySaleRecipientZeroAddress() { + primarySaleRecipient = address(0); + _; + } + + modifier primarySaleRecipientNotZeroAddress() { + primarySaleRecipient = address(0x0999); + _; + } + + modifier currencyNativeToken() { + currency = NATIVE_TOKEN; + _; + } + + modifier currencyNotNativeToken() { + currency = address(erc20); + _; + } + + function test_revert_pricePerTokenZeroMsgValueNotZero() public pricePerTokenZero msgValueNotZero { + vm.expectRevert("!Value"); + proxy.harness_collectPrice{ value: msgValue }(primarySaleRecipient, 1 ether, currency, pricePerToken); + } + + function test_revert_nativeCurrencyTotalPriceZero() public pricePerTokenNotZero msgValueZero currencyNativeToken { + vm.expectRevert("quantity too low"); + proxy.harness_collectPrice{ value: msgValue }(primarySaleRecipient, 0, currency, pricePerToken); + } + + function test_revert_nativeCurrencyValuePriceMismatch() public currencyNativeToken valuePriceMismatch { + vm.expectRevert("Invalid msg value"); + proxy.harness_collectPrice{ value: msgValue }(primarySaleRecipient, 1 ether, currency, pricePerToken); + } + + function test_revert_erc20ValuePriceMismatch() public currencyNotNativeToken valuePriceMismatch { + vm.expectRevert("Invalid msg value"); + proxy.harness_collectPrice{ value: msgValue }(primarySaleRecipient, 1 ether, currency, pricePerToken); + } + + function test_state_nativeCurrency() + public + currencyNativeToken + pricePerTokenNotZero + msgValueNotZero + primarySaleRecipientNotZeroAddress + { + (address platformFeeRecipient, uint16 platformFeeBps) = proxy.getPlatformFeeInfo(); + uint256 beforeBalancePrimarySaleRecipient = address(primarySaleRecipient).balance; + uint256 beforeBalancePlatformFeeRecipient = address(platformFeeRecipient).balance; + + proxy.harness_collectPrice{ value: msgValue }(primarySaleRecipient, 1 ether, currency, pricePerToken); + + uint256 afterBalancePrimarySaleRecipient = address(primarySaleRecipient).balance; + uint256 afterBalancePlatformFeeRecipient = address(platformFeeRecipient).balance; + + uint256 platformFeeVal = (msgValue * platformFeeBps) / MAX_BPS; + uint256 primarySaleRecipientVal = msgValue - platformFeeVal; + + assertEq(beforeBalancePrimarySaleRecipient + primarySaleRecipientVal, afterBalancePrimarySaleRecipient); + assertEq(beforeBalancePlatformFeeRecipient + platformFeeVal, afterBalancePlatformFeeRecipient); + } + + function test_revert_erc20_msgValueNotZero() + public + currencyNotNativeToken + msgValueNotZero + primarySaleRecipientNotZeroAddress + { + vm.expectRevert("!Value"); + proxy.harness_collectPrice{ value: msgValue }(primarySaleRecipient, msgValue, currency, pricePerToken); + } + + function test_state_erc20() public currencyNotNativeToken pricePerTokenNotZero primarySaleRecipientNotZeroAddress { + (address platformFeeRecipient, uint16 platformFeeBps) = proxy.getPlatformFeeInfo(); + + erc20.mint(address(this), pricePerToken); + ERC20(erc20).approve(address(proxy), pricePerToken); + uint256 beforeBalancePrimarySaleRecipient = erc20.balanceOf(primarySaleRecipient); + uint256 beforeBalancePlatformFeeRecipient = erc20.balanceOf(platformFeeRecipient); + + proxy.harness_collectPrice(primarySaleRecipient, pricePerToken, currency, pricePerToken); + + uint256 afterBalancePrimarySaleRecipient = erc20.balanceOf(primarySaleRecipient); + uint256 afterBalancePlatformFeeRecipient = erc20.balanceOf(platformFeeRecipient); + + uint256 platformFeeVal = (pricePerToken * platformFeeBps) / MAX_BPS; + uint256 primarySaleRecipientVal = 1 ether - platformFeeVal; + + assertEq(beforeBalancePrimarySaleRecipient + primarySaleRecipientVal, afterBalancePrimarySaleRecipient); + assertEq(beforeBalancePlatformFeeRecipient + platformFeeVal, afterBalancePlatformFeeRecipient); + } + + function test_state_erc20StoredPrimarySaleRecipient() + public + currencyNotNativeToken + pricePerTokenNotZero + primarySaleRecipientZeroAddress + { + (address platformFeeRecipient, uint16 platformFeeBps) = proxy.getPlatformFeeInfo(); + address storedPrimarySaleRecipient = proxy.primarySaleRecipient(); + + erc20.mint(address(this), pricePerToken); + ERC20(erc20).approve(address(proxy), pricePerToken); + uint256 beforeBalancePrimarySaleRecipient = erc20.balanceOf(storedPrimarySaleRecipient); + uint256 beforeBalancePlatformFeeRecipient = erc20.balanceOf(platformFeeRecipient); + + proxy.harness_collectPrice(primarySaleRecipient, pricePerToken, currency, pricePerToken); + + uint256 afterBalancePrimarySaleRecipient = erc20.balanceOf(storedPrimarySaleRecipient); + uint256 afterBalancePlatformFeeRecipient = erc20.balanceOf(platformFeeRecipient); + + uint256 platformFeeVal = (pricePerToken * platformFeeBps) / MAX_BPS; + uint256 primarySaleRecipientVal = 1 ether - platformFeeVal; + + assertEq(beforeBalancePrimarySaleRecipient + primarySaleRecipientVal, afterBalancePrimarySaleRecipient); + assertEq(beforeBalancePlatformFeeRecipient + platformFeeVal, afterBalancePlatformFeeRecipient); + } + + function test_state_nativeCurrencyStoredPrimarySaleRecipient() + public + currencyNativeToken + pricePerTokenNotZero + primarySaleRecipientZeroAddress + msgValueNotZero + { + (address platformFeeRecipient, uint16 platformFeeBps) = proxy.getPlatformFeeInfo(); + address storedPrimarySaleRecipient = proxy.primarySaleRecipient(); + + uint256 beforeBalancePrimarySaleRecipient = address(storedPrimarySaleRecipient).balance; + uint256 beforeBalancePlatformFeeRecipient = address(platformFeeRecipient).balance; + + proxy.harness_collectPrice{ value: msgValue }(primarySaleRecipient, 1 ether, currency, pricePerToken); + + uint256 afterBalancePrimarySaleRecipient = address(storedPrimarySaleRecipient).balance; + uint256 afterBalancePlatformFeeRecipient = address(platformFeeRecipient).balance; + + uint256 platformFeeVal = (msgValue * platformFeeBps) / MAX_BPS; + uint256 primarySaleRecipientVal = msgValue - platformFeeVal; + + assertEq(beforeBalancePrimarySaleRecipient + primarySaleRecipientVal, afterBalancePrimarySaleRecipient); + assertEq(beforeBalancePlatformFeeRecipient + platformFeeVal, afterBalancePlatformFeeRecipient); + } +} diff --git a/src/test/drop/drop-erc20/_collectPriceOnClaim/_collectPriceOnClaim.tree b/src/test/drop/drop-erc20/_collectPriceOnClaim/_collectPriceOnClaim.tree new file mode 100644 index 000000000..933ae6877 --- /dev/null +++ b/src/test/drop/drop-erc20/_collectPriceOnClaim/_collectPriceOnClaim.tree @@ -0,0 +1,44 @@ +function _collectPriceOnClaim( + address _primarySaleRecipient, + uint256 _quantityToClaim, + address _currency, + uint256 _pricePerToken +) +├── when _pricePerToken is equal to zero +│ ├── when msg.value does not equal to zero +│ │ └── it should revert ✅ +│ └── when msg.value is equal to zero +│ └── it should return ✅ +└── when _pricePerToken is not equal to zero + ├── when _primarySaleRecipient is equal to address(0) + │ ├── when totalPrice is equal to zero + │ │ └── it should revert ✅ + │ └── when total price is not equal to zero + │ ├── when currency is native token + │ │ ├── when msg.value does not equal totalPrice + │ │ │ └── it should revert ✅ + │ │ └── when msg.value does equal totalPrice + │ │ ├── platformFees (totalPrice * platformFeeBps / MAX_BPS) should be transfered to platformFeeRecipient ✅ + │ │ └── totalPrice - platformFees should be transfered to primarySaleRecipient() ✅ + │ └── when currency is not native token + │ ├── when msg.value is not equal to zero + │ │ └── it should revert ✅ + │ └── when msg.value is equal to zero + │ ├── platformFees (totalPrice * platformFeeBps / MAX_BPS) should be transfered to platformFeeRecipient ✅ + │ └── totalPrice - platformFees should be transfered to primarySaleRecipient() ✅ + └── when _primarySaleRecipient is not equal to address(0) + ├── when totalPrice is equal to zero + │ └── it should revert ✅ + └── when total price is not equal to zero + ├── when currency is not native token + │ ├── when msg.value does not equal totalPrice + │ │ └── it should revert ✅ + │ └── when msg.value does equal totalPrice + │ ├── platformFees (totalPrice * platformFeeBps / MAX_BPS) should be transfered to platformFeeRecipient ✅ + │ └── totalPrice - platformFees should be transfered to _primarySaleRecipient ✅ + └── when currency is not native token + ├── when msg.value is not equal to zero + │ └── it should revert ✅ + └── when msg.value is equal to zero + ├── platformFees (totalPrice * platformFeeBps / MAX_BPS) should be transfered to platformFeeRecipient ✅ + └── totalPrice - platformFees should be transfered to _primarySaleRecipient ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc20/initialize/initialize.t.sol b/src/test/drop/drop-erc20/initialize/initialize.t.sol new file mode 100644 index 000000000..74714744e --- /dev/null +++ b/src/test/drop/drop-erc20/initialize/initialize.t.sol @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC20 } from "contracts/prebuilts/drop/DropERC20.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract DropERC20Test_initializer is BaseTest { + DropERC20 public newDropContract; + + event ContractURIUpdated(string prevURI, string newURI); + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + event PlatformFeeInfoUpdated(address indexed platformFeeRecipient, uint256 platformFeeBps); + event PrimarySaleRecipientUpdated(address indexed recipient); + + function setUp() public override { + super.setUp(); + } + + modifier platformFeeBPSTooHigh() { + platformFeeBps = 10001; + _; + } + + function test_state() public { + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + + newDropContract = DropERC20(getContract("DropERC20")); + (address _platformFeeRecipient, uint128 _platformFeeBps) = newDropContract.getPlatformFeeInfo(); + address _saleRecipient = newDropContract.primarySaleRecipient(); + + for (uint256 i = 0; i < forwarders().length; i++) { + assertEq(newDropContract.isTrustedForwarder(forwarders()[i]), true); + } + + assertEq(newDropContract.name(), NAME); + assertEq(newDropContract.symbol(), SYMBOL); + assertEq(newDropContract.contractURI(), CONTRACT_URI); + assertEq(_platformFeeRecipient, platformFeeRecipient); + assertEq(_platformFeeBps, platformFeeBps); + assertEq(_saleRecipient, saleRecipient); + } + + function test_revert_PlatformFeeBPSTooHigh() public platformFeeBPSTooHigh { + vm.expectRevert("Exceeds max bps"); + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + } + + function test_event_ContractURIUpdated() public { + vm.expectEmit(false, false, false, true); + emit ContractURIUpdated("", CONTRACT_URI); + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + } + + function test_event_RoleGrantedDefaultAdminRole() public { + bytes32 role = bytes32(0x00); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, deployer, 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + } + + function test_event_RoleGrantedTransferRole() public { + bytes32 role = keccak256("TRANSFER_ROLE"); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, deployer, 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + } + + function test_event_RoleGrantedTransferRoleZeroAddress() public { + bytes32 role = keccak256("TRANSFER_ROLE"); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, address(0), 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + } + + function test_event_PlatformFeeInfoUpdated() public { + vm.expectEmit(true, false, false, true); + emit PlatformFeeInfoUpdated(platformFeeRecipient, platformFeeBps); + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + } + + function test_event_PrimarySaleRecipientUpdated() public { + vm.expectEmit(true, false, false, false); + emit PrimarySaleRecipientUpdated(saleRecipient); + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + } + + function test_roleCheck() public { + deployContractProxy( + "DropERC20", + abi.encodeCall( + DropERC20.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + platformFeeRecipient, + platformFeeBps + ) + ) + ); + + newDropContract = DropERC20(getContract("DropERC20")); + + assertEq(newDropContract.hasRole(bytes32(0x00), deployer), true); + assertEq(newDropContract.hasRole(keccak256("TRANSFER_ROLE"), deployer), true); + assertEq(newDropContract.hasRole(keccak256("TRANSFER_ROLE"), address(0)), true); + } +} diff --git a/src/test/drop/drop-erc20/initialize/intialize.tree b/src/test/drop/drop-erc20/initialize/intialize.tree new file mode 100644 index 000000000..6731bc81e --- /dev/null +++ b/src/test/drop/drop-erc20/initialize/intialize.tree @@ -0,0 +1,33 @@ +function initialize( + address _defaultAdmin, + string memory _name, + string memory _symbol, + string memory _contractURI, + address[] memory _trustedForwarders, + address _saleRecipient, + address _platformFeeRecipient, + uint128 _platformFeeBps +) +├── when _trustedForwarders.length > 0 +│ └── it should set _trustedForwarder[_trustedForwarders[i]] as true for each address in _trustedForwarders ✅ +├── it should set contractURI as _contractURI ✅ +├── it should emit ContractURIUpdated with the parameters: prevURI, _uri ✅ +├── it should set _defaultAdmin as the owner of the contract ✅ +├── it should emit OwnerUpdated with the parameters: _prevOwner, _defaultAdmin ✅ +├── it should assign the role DEFAULT_ADMIN_ROLE to _defaultAdmin ✅ +├── it should emit RoleGranted with the parameters: DEFAULT_ADMIN_ROLE, _defaultAdmin, msg.sender ✅ +├── it should assign the role _transferRole to _defaultAdmin ✅ +├── it should emit RoleGranted with the parameters: _transferRole, _defaultAdmin, msg.sender ✅ +├── it should assign the role _transferRole to address(0) ✅ +├── it should emit RoleGranted with the parameters: _transferRole, address(0), msg.sender ✅ +├── when _platformFeeBps is greater than 10_000 +│ └── it should revert ✅ +├── when _platformFeeBps is less than or equal to 10_000 +│ ├── it should set platformFeeBps to uint16(_platformFeeBps); ✅ +│ ├── it should set platformFeeRecipient to _platformFeeRecipient ✅ +│ └── it should emit PlatformFeeInfoUpdated with the following parameters: _platformFeeRecipient, _platformFeeBps ✅ +├── it should set recipient as _primarySaleRecipient ✅ +├── it should emit PrimarySaleRecipientUpdated with the parameters _primarySaleRecipient ✅ +├── it should set transferRole as keccak256("TRANSFER_ROLE")✅ +├── it should set _name as _name ✅ +└── it should set _symbol as _symbol ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc20/miscellaneous/miscellaneous.t.sol b/src/test/drop/drop-erc20/miscellaneous/miscellaneous.t.sol new file mode 100644 index 000000000..c484a97b8 --- /dev/null +++ b/src/test/drop/drop-erc20/miscellaneous/miscellaneous.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC20 } from "contracts/prebuilts/drop/DropERC20.sol"; +import { TWProxy } from "contracts/infra/TWProxy.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract HarnessDropERC20Misc is DropERC20 { + bytes32 private transferRole = keccak256("TRANSFER_ROLE"); + + function msgData() public view returns (bytes memory) { + return _msgData(); + } + + function transferTokensOnClaim(address _to, uint256 _quantityBeingClaimed) public returns (uint256) { + return _transferTokensOnClaim(_to, _quantityBeingClaimed); + } + + function beforeTokenTransfer( + address from, + address to, + uint256 amount + ) public { + _beforeTokenTransfer(from, to, amount); + } + + function mint(address to, uint256 amount) public { + _mint(to, amount); + } + + function burn(address from, uint256 amount) public { + _burn(from, amount); + } + + function hasTransferRole(address _account) public view returns (bool) { + return hasRole(transferRole, _account); + } +} + +contract DropERC20Test_misc is BaseTest { + address public dropImp; + HarnessDropERC20Misc public proxy; + + function setUp() public override { + super.setUp(); + + bytes memory initializeData = abi.encodeCall( + DropERC20.initialize, + (deployer, NAME, SYMBOL, CONTRACT_URI, forwarders(), saleRecipient, platformFeeRecipient, platformFeeBps) + ); + + dropImp = address(new HarnessDropERC20Misc()); + proxy = HarnessDropERC20Misc(address(new TWProxy(dropImp, initializeData))); + } + + function test_contractType_returnValue() public { + assertEq(proxy.contractType(), "DropERC20"); + } + + function test_contractVersion_returnValue() public { + assertEq(proxy.contractVersion(), uint8(4)); + } + + function test_msgData_returnValue() public { + bytes memory msgData = proxy.msgData(); + bytes4 expectedData = proxy.msgData.selector; + assertEq(bytes4(msgData), expectedData); + } + + function test_state_transferTokensOnClaim() public { + uint256 initialBalance = proxy.balanceOf(deployer); + uint256 quantityBeingClaimed = 1; + proxy.transferTokensOnClaim(deployer, quantityBeingClaimed); + assertEq(proxy.balanceOf(deployer), initialBalance + quantityBeingClaimed); + } + + function test_returnValue_transferTokensOnClaim() public { + uint256 quantityBeingClaimed = 1; + uint256 returnValue = proxy.transferTokensOnClaim(deployer, quantityBeingClaimed); + assertEq(returnValue, 0); + } + + function test_beforeTokenTransfer_revert_addressZeroNoTransferRole() public { + vm.prank(deployer); + proxy.revokeRole(keccak256("TRANSFER_ROLE"), address(0)); + vm.expectRevert("transfers restricted."); + proxy.beforeTokenTransfer(address(0x01), address(0x02), 1); + } + + function test_beforeTokenTransfer_doesNotRevert_addressZeroNoTransferRole_burnMint() public { + vm.prank(deployer); + proxy.revokeRole(keccak256("TRANSFER_ROLE"), address(0)); + proxy.beforeTokenTransfer(address(0), address(0x02), 1); + proxy.beforeTokenTransfer(address(0x01), address(0), 1); + } + + function test_state_mint() public { + uint256 initialBalance = proxy.balanceOf(deployer); + uint256 amount = 1; + proxy.mint(deployer, amount); + assertEq(proxy.balanceOf(deployer), initialBalance + amount); + } + + function test_state_burn() public { + proxy.mint(deployer, 1); + uint256 initialBalance = proxy.balanceOf(deployer); + uint256 amount = 1; + proxy.burn(deployer, amount); + assertEq(proxy.balanceOf(deployer), initialBalance - amount); + } + + function test_transfer_drop() public { + //deal erc20 drop to address(0x1) + deal(address(proxy), address(0x1), 1); + vm.prank(address(0x1)); + proxy.transfer(address(0x2), 1); + assertEq(proxy.balanceOf(address(0x2)), 1); + } +} diff --git a/src/test/drop/drop-erc20/miscellaneous/miscellaneous.tree b/src/test/drop/drop-erc20/miscellaneous/miscellaneous.tree new file mode 100644 index 000000000..61c35271e --- /dev/null +++ b/src/test/drop/drop-erc20/miscellaneous/miscellaneous.tree @@ -0,0 +1,30 @@ +function contractType() +└── it should return bytes32("DropERC20") ✅ + +function contractVersion() +└── it should return uint8(4) ✅ + +function _mint(address account, uint256 amount) +└── it should mint amount tokens to account ✅ + +function _burn(address account, uint256 amount) +└── it should burn amount tokens from account ✅ + +function _afterTokenTransfer( + address from, + address to, + uint256 amount +) +└── it should call _afterTokenTransfer logic from ERC20VotesUpgradeable + +function _msgData() +└── it should return msg.data ✅ + +function _beforeTokenTransfer(address from, address to, uint256 amount) +└── when address(0) does not have transferRole and from does not equal address(0) and from does not equal address(0) + └── when from does not have transfer role and to does not have transferRole + └── it should revert ✅ + +function _transferTokensOnClaim(address _to, uint256 _quantityBeingClaimed) +├── it should mint _quantityBeingClaimed tokens to _to ✅ +└── it should return 0 ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc20/setMaxTotalSupply/setMaxTotalSupply.t.sol b/src/test/drop/drop-erc20/setMaxTotalSupply/setMaxTotalSupply.t.sol new file mode 100644 index 000000000..1f99ef019 --- /dev/null +++ b/src/test/drop/drop-erc20/setMaxTotalSupply/setMaxTotalSupply.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC20 } from "contracts/prebuilts/drop/DropERC20.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract DropERC20Test_setMaxTotalSupply is BaseTest { + event MaxTotalSupplyUpdated(uint256 maxTotalSupply); + + DropERC20 public drop; + + function setUp() public override { + super.setUp(); + + drop = DropERC20(getContract("DropERC20")); + } + + modifier callerHasDefaultAdminRole() { + vm.startPrank(deployer); + _; + } + + modifier callerDoesNotHaveDefaultAdminRole() { + _; + } + + function test_revert_doesNotHaveAdminRole() public callerDoesNotHaveDefaultAdminRole { + bytes32 role = bytes32(0x00); + vm.expectRevert( + abi.encodePacked( + "Permissions: account ", + TWStrings.toHexString(uint160(address(this)), 20), + " is missing role ", + TWStrings.toHexString(uint256(role), 32) + ) + ); + drop.setMaxTotalSupply(0); + } + + function test_state_callerHasDefaultAdminRole() public callerHasDefaultAdminRole { + drop.setMaxTotalSupply(100); + assertEq(drop.maxTotalSupply(), 100); + } + + function test_event_callerHasDefaultAdminRole() public callerHasDefaultAdminRole { + vm.expectEmit(false, false, false, true); + emit MaxTotalSupplyUpdated(100); + drop.setMaxTotalSupply(100); + } +} diff --git a/src/test/drop/drop-erc20/setMaxTotalSupply/setMaxTotalSupply.tree b/src/test/drop/drop-erc20/setMaxTotalSupply/setMaxTotalSupply.tree new file mode 100644 index 000000000..a8b23e9ca --- /dev/null +++ b/src/test/drop/drop-erc20/setMaxTotalSupply/setMaxTotalSupply.tree @@ -0,0 +1,6 @@ +function setMaxTotalSupply(uint256 _maxTotalSupply) +├── when the caller does not have DEFAULT_ADMIN_ROLE +│ └── it should revert ✅ +└── when the caller does have DEFAULT_ADMIN_ROLE + ├── it should set maxTotalSupply as _maxTotalSupply ✅ + └── it should emit MaxTotalSupplyUpdated with the parameters _maxTotalSupply ✅ \ No newline at end of file