add master/owner tokens to erc20 token (#19)

This commit is contained in:
0xb337r007 2023-10-06 09:21:03 +02:00 committed by GitHub
parent ff7de4a819
commit ff01f8e818
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 110 additions and 62 deletions

View File

@ -3,10 +3,10 @@ AddEntryTest:test_RevertWhen_EntryAlreadyExists() (gas: 42644)
AddEntryTest:test_RevertWhen_InvalidAddress() (gas: 25133)
AddEntryTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 14827)
CollectibleV1Test:test_Deployment() (gas: 36386)
CommunityERC20Test:test_Deployment() (gas: 27614)
CommunityERC20Test:test_Deployment() (gas: 27659)
CommunityTokenDeployerTest:test_Deployment() (gas: 14805)
CreateTest:test_Create() (gas: 2251262)
CreateTest:test_Create() (gas: 2548166)
CreateTest:test_Create() (gas: 2251272)
CreateTest:test_Create() (gas: 2548179)
CreateTest:test_RevertWhen_InvalidOwnerTokenAddress() (gas: 15523)
CreateTest:test_RevertWhen_InvalidReceiverAddress() (gas: 15656)
CreateTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 17057)
@ -16,26 +16,26 @@ CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16421)
CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16524)
DeployContracts:test() (gas: 120)
DeployOwnerAndMasterToken:test() (gas: 120)
DeployTest:test_Deploy() (gas: 4872082)
DeployTest:test_Deploy() (gas: 4872105)
DeployTest:test_Deployment() (gas: 14947)
DeployTest:test_RevertWhen_AlreadyDeployed() (gas: 4868312)
DeployTest:test_RevertWhen_AlreadyDeployed() (gas: 4868335)
DeployTest:test_RevertWhen_InvalidCommunityAddress() (gas: 51385)
DeployTest:test_RevertWhen_InvalidDeployerAddress() (gas: 55272)
DeployTest:test_RevertWhen_InvalidDeploymentSignature() (gas: 65617)
DeployTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 53433)
DeployTest:test_RevertWhen_InvalidTokenMetadata() (gas: 2671682)
DeployTest:test_RevertWhen_InvalidTokenMetadata() (gas: 2671695)
DeploymentTest:test_Deployment() (gas: 14671)
DeploymentTest:test_Deployment() (gas: 14671)
DeploymentTest:test_Deployment() (gas: 17295)
GetEntryTest:test_ReturnZeroAddressIfEntryDoesNotExist() (gas: 11906)
MintToTest:test_Deployment() (gas: 27636)
MintToTest:test_Deployment() (gas: 27681)
MintToTest:test_Deployment() (gas: 36386)
MintToTest:test_Deployment() (gas: 83220)
MintToTest:test_MintTo() (gas: 506888)
MintToTest:test_RevertWhen_AddressesAndAmountsAreNotEqualLength() (gas: 23778)
MintToTest:test_RevertWhen_AddressesAndAmountsAreNotEqualLength() (gas: 29695)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 20653)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 502655)
MintToTest:test_RevertWhen_MaxSupplyReached() (gas: 122988)
MintToTest:test_RevertWhen_MaxSupplyReached() (gas: 128905)
MintToTest:test_RevertWhen_SenderIsNotOwner() (gas: 31544)
OwnerTokenTest:test_Deployment() (gas: 83220)
RemoteBurnTest:test_Deployment() (gas: 36386)
@ -54,13 +54,13 @@ SetMasterTokenFactoryAddressTest:test_Deployment() (gas: 14805)
SetMasterTokenFactoryAddressTest:test_RevertWhen_InvalidTokenFactoryAddress() (gas: 12992)
SetMasterTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12465)
SetMasterTokenFactoryAddressTest:test_SetOwnerTokenFactoryAddress() (gas: 22861)
SetMaxSupplyTest:test_Deployment() (gas: 27614)
SetMaxSupplyTest:test_Deployment() (gas: 27659)
SetMaxSupplyTest:test_Deployment() (gas: 83242)
SetMaxSupplyTest:test_RevertWhen_CalledBecauseMaxSupplyIsLocked() (gas: 14327)
SetMaxSupplyTest:test_RevertWhen_MaxSupplyLowerThanTotalSupply() (gas: 148572)
SetMaxSupplyTest:test_RevertWhen_MaxSupplyLowerThanTotalSupply() (gas: 155732)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 12527)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 12817)
SetMaxSupplyTest:test_SetMaxSupply() (gas: 15597)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 21402)
SetMaxSupplyTest:test_SetMaxSupply() (gas: 23955)
SetOwnerTokenFactoryAddressTest:test_Deployment() (gas: 14805)
SetOwnerTokenFactoryAddressTest:test_RevertWhen_InvalidTokenFactoryAddress() (gas: 12970)
SetOwnerTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12443)

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: Mozilla Public License 2.0
pragma solidity ^0.8.17;
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract CommunityOwnable {
error CommunityOwnable_InvalidTokenAddress();
error CommunityOwnable_NotAuthorized();
address public immutable ownerToken;
address public immutable masterToken;
constructor(address _ownerToken, address _masterToken) {
ownerToken = _ownerToken;
masterToken = _masterToken;
if (ownerToken == address(0) && masterToken == address(0)) {
revert CommunityOwnable_InvalidTokenAddress();
}
}
/// @dev Reverts if the msg.sender does not possess either an OwnerToken or a MasterToken.
modifier onlyCommunityOwnerOrTokenMaster() {
if (
(ownerToken != address(0) && IERC721(ownerToken).balanceOf(msg.sender) == 0)
&& (masterToken != address(0) && IERC721(masterToken).balanceOf(msg.sender) == 0)
) {
revert CommunityOwnable_NotAuthorized();
}
_;
}
}

View File

@ -6,12 +6,11 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { ERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { Context } from "@openzeppelin/contracts/utils/Context.sol";
import { Counters } from "@openzeppelin/contracts/utils/Counters.sol";
import { CommunityOwnable } from "../CommunityOwnable.sol";
abstract contract BaseToken is Context, ERC721Enumerable {
abstract contract BaseToken is Context, ERC721Enumerable, CommunityOwnable {
using Counters for Counters.Counter;
error BaseToken_InvalidTokenAddress();
error BaseToken_NotAuthorized();
error BaseToken_MaxSupplyLowerThanTotalSupply();
error BaseToken_MaxSupplyReached();
error BaseToken_NotRemoteBurnable();
@ -25,10 +24,6 @@ abstract contract BaseToken is Context, ERC721Enumerable {
* If we want unlimited total supply we should set maxSupply to 2^256-1.
*/
uint256 public maxSupply;
address public immutable ownerToken;
address public immutable masterToken;
/**
* If set to true, the contract owner can burn any token.
*/
@ -52,34 +47,19 @@ abstract contract BaseToken is Context, ERC721Enumerable {
address _masterToken
)
ERC721(_name, _symbol)
CommunityOwnable(_ownerToken, _masterToken)
{
maxSupply = _maxSupply;
remoteBurnable = _remoteBurnable;
transferable = _transferable;
baseTokenURI = _baseTokenURI;
ownerToken = _ownerToken;
masterToken = _masterToken;
if (ownerToken == address(0) && masterToken == address(0)) {
revert BaseToken_InvalidTokenAddress();
}
}
modifier onlyOwner() {
if (
(ownerToken != address(0) && IERC721(ownerToken).balanceOf(msg.sender) == 0)
&& (masterToken != address(0) && IERC721(masterToken).balanceOf(msg.sender) == 0)
) {
revert BaseToken_NotAuthorized();
}
_;
}
// Events
// External functions
function setMaxSupply(uint256 newMaxSupply) external virtual onlyOwner {
function setMaxSupply(uint256 newMaxSupply) external virtual onlyCommunityOwnerOrTokenMaster {
if (newMaxSupply < totalSupply()) {
revert BaseToken_MaxSupplyLowerThanTotalSupply();
}
@ -92,7 +72,7 @@ abstract contract BaseToken is Context, ERC721Enumerable {
* URI autogenerated based on the base URI passed at construction.
*
*/
function mintTo(address[] memory addresses) public onlyOwner {
function mintTo(address[] memory addresses) public onlyCommunityOwnerOrTokenMaster {
if (_tokenIdTracker.current() + addresses.length > maxSupply) {
revert BaseToken_MaxSupplyReached();
}
@ -109,7 +89,7 @@ abstract contract BaseToken is Context, ERC721Enumerable {
* @notice remoteBurn allows the owner to burn a token
* @param tokenIds The list of token IDs to be burned
*/
function remoteBurn(uint256[] memory tokenIds) public onlyOwner {
function remoteBurn(uint256[] memory tokenIds) public onlyCommunityOwnerOrTokenMaster {
if (!remoteBurnable) revert BaseToken_NotRemoteBurnable();
for (uint256 i = 0; i < tokenIds.length; i++) {

View File

@ -4,8 +4,9 @@ pragma solidity ^0.8.17;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Context } from "@openzeppelin/contracts/utils/Context.sol";
import { CommunityOwnable } from "../CommunityOwnable.sol";
contract CommunityERC20 is Context, Ownable, ERC20 {
contract CommunityERC20 is Context, Ownable, ERC20, CommunityOwnable {
error CommunityERC20_MaxSupplyLowerThanTotalSupply();
error CommunityERC20_MaxSupplyReached();
error CommunityERC20_MismatchingAddressesAndAmountsLengths();
@ -21,9 +22,12 @@ contract CommunityERC20 is Context, Ownable, ERC20 {
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _maxSupply
uint256 _maxSupply,
address _ownerToken,
address _masterToken
)
ERC20(_name, _symbol)
CommunityOwnable(_ownerToken, _masterToken)
{
maxSupply = _maxSupply;
customDecimals = _decimals;
@ -33,7 +37,7 @@ contract CommunityERC20 is Context, Ownable, ERC20 {
// External functions
function setMaxSupply(uint256 newMaxSupply) external onlyOwner {
function setMaxSupply(uint256 newMaxSupply) external onlyCommunityOwnerOrTokenMaster {
if (newMaxSupply < totalSupply()) {
revert CommunityERC20_MaxSupplyLowerThanTotalSupply();
}
@ -45,7 +49,7 @@ contract CommunityERC20 is Context, Ownable, ERC20 {
* an amount specified in `amounts`.
*
*/
function mintTo(address[] memory addresses, uint256[] memory amounts) external onlyOwner {
function mintTo(address[] memory addresses, uint256[] memory amounts) external onlyCommunityOwnerOrTokenMaster {
if (addresses.length != amounts.length) {
revert CommunityERC20_MismatchingAddressesAndAmountsLengths();
}

View File

@ -2,6 +2,7 @@
pragma solidity ^0.8.17;
import { BaseToken } from "./BaseToken.sol";
import { CommunityOwnable } from "../CommunityOwnable.sol";
contract OwnerToken is BaseToken {
bytes public signerPublicKey;
@ -21,11 +22,11 @@ contract OwnerToken is BaseToken {
_mintTo(addresses);
}
function setMaxSupply(uint256 _newMaxSupply) external override onlyOwner {
function setMaxSupply(uint256 _newMaxSupply) external override onlyCommunityOwnerOrTokenMaster {
revert("max supply locked");
}
function setSignerPublicKey(bytes memory _newSignerPublicKey) external onlyOwner {
function setSignerPublicKey(bytes memory _newSignerPublicKey) external onlyCommunityOwnerOrTokenMaster {
signerPublicKey = _newSignerPublicKey;
}
}

View File

@ -20,7 +20,9 @@ contract DeploymentConfig is Script {
address public immutable deployer;
constructor(address _broadcaster) {
if (_broadcaster == address(0)) revert DeploymentConfig_InvalidDeployerAddress();
if (_broadcaster == address(0)) {
revert DeploymentConfig_InvalidDeployerAddress();
}
deployer = _broadcaster;
if (block.chainid == 31_337) {
(ownerTokenConfig, masterTokenConfig) = getOrCreateAnvilEthConfig();

View File

@ -4,6 +4,7 @@ pragma solidity ^0.8.17;
import { Test } from "forge-std/Test.sol";
import { DeployOwnerAndMasterToken } from "../script/DeployOwnerAndMasterToken.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { CommunityOwnable } from "../contracts/CommunityOwnable.sol";
import { BaseToken } from "../contracts/tokens/BaseToken.sol";
import { OwnerToken } from "../contracts/tokens/OwnerToken.sol";
import { MasterToken } from "../contracts/tokens/MasterToken.sol";
@ -60,7 +61,7 @@ contract MintToTest is CollectibleV1Test {
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.expectRevert(BaseToken.BaseToken_NotAuthorized.selector);
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
collectibleV1.mintTo(accounts);
}
@ -98,7 +99,7 @@ contract RemoteBurnTest is CollectibleV1Test {
function test_RevertWhen_SenderIsNotOwner() public {
uint256[] memory ids = new uint256[](1);
ids[0] = 0;
vm.expectRevert(BaseToken.BaseToken_NotAuthorized.selector);
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
collectibleV1.remoteBurn(ids);
}

View File

@ -3,11 +3,17 @@ pragma solidity ^0.8.17;
import { Test } from "forge-std/Test.sol";
import { CommunityERC20 } from "../contracts/tokens/CommunityERC20.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { DeployOwnerAndMasterToken } from "../script/DeployOwnerAndMasterToken.s.sol";
import { OwnerToken } from "../contracts/tokens/OwnerToken.sol";
import { MasterToken } from "../contracts/tokens/MasterToken.sol";
import { CommunityOwnable } from "../contracts/CommunityOwnable.sol";
contract CommunityERC20Test is Test {
CommunityERC20 internal communityToken;
address[] internal accounts = new address[](4);
address internal deployer;
string internal name = "Test";
string internal symbol = "TEST";
@ -15,7 +21,19 @@ contract CommunityERC20Test is Test {
uint8 internal decimals = 18;
function setUp() public virtual {
communityToken = new CommunityERC20(name, symbol, decimals, maxSupply);
DeployOwnerAndMasterToken deployment = new DeployOwnerAndMasterToken();
(OwnerToken ownerToken, MasterToken masterToken, DeploymentConfig deploymentConfig) = deployment.run();
deployer = deploymentConfig.deployer();
communityToken = new CommunityERC20(
name,
symbol,
decimals,
maxSupply,
address(ownerToken),
address(masterToken)
);
accounts[0] = makeAddr("one");
accounts[1] = makeAddr("two");
@ -38,7 +56,7 @@ contract SetMaxSupplyTest is CommunityERC20Test {
function test_RevertWhen_SenderIsNotOwner() public {
vm.prank(makeAddr("notOwner"));
vm.expectRevert(bytes("Ownable: caller is not the owner"));
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
communityToken.setMaxSupply(1000);
}
@ -48,12 +66,18 @@ contract SetMaxSupplyTest is CommunityERC20Test {
amounts[1] = 15;
amounts[2] = 5;
amounts[3] = 20;
vm.startPrank(deployer);
communityToken.mintTo(accounts, amounts); // totalSupply is now 50
vm.expectRevert(CommunityERC20.CommunityERC20_MaxSupplyLowerThanTotalSupply.selector);
communityToken.setMaxSupply(40);
vm.stopPrank();
}
function test_SetMaxSupply() public {
vm.prank(deployer);
communityToken.setMaxSupply(1000);
assertEq(communityToken.maxSupply(), 1000);
}
@ -71,6 +95,7 @@ contract MintToTest is CommunityERC20Test {
amounts[2] = 5;
vm.expectRevert(CommunityERC20.CommunityERC20_MismatchingAddressesAndAmountsLengths.selector);
vm.prank(deployer);
communityToken.mintTo(accounts, amounts);
}
@ -82,6 +107,7 @@ contract MintToTest is CommunityERC20Test {
amounts[3] = 1; // this should exceed max supply
vm.expectRevert(CommunityERC20.CommunityERC20_MaxSupplyReached.selector);
vm.prank(deployer);
communityToken.mintTo(accounts, amounts);
}
}

View File

@ -4,6 +4,7 @@ pragma solidity ^0.8.17;
import { Test } from "forge-std/Test.sol";
import { DeployOwnerAndMasterToken } from "../script/DeployOwnerAndMasterToken.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { CommunityOwnable } from "../contracts/CommunityOwnable.sol";
import { BaseToken } from "../contracts/tokens/BaseToken.sol";
import { OwnerToken } from "../contracts/tokens/OwnerToken.sol";
import { MasterToken } from "../contracts/tokens/MasterToken.sol";
@ -43,7 +44,7 @@ contract SetMaxSupplyTest is OwnerTokenTest {
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.expectRevert(BaseToken.BaseToken_NotAuthorized.selector);
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
ownerToken.setMaxSupply(1000);
}
@ -60,7 +61,7 @@ contract SetSignerPublicKeyTest is OwnerTokenTest {
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.expectRevert(BaseToken.BaseToken_NotAuthorized.selector);
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
ownerToken.setSignerPublicKey(bytes("some key"));
}