diff --git a/contracts/CATFactory.sol b/contracts/CATFactory.sol index e6070dbf..abdbc3c8 100644 --- a/contracts/CATFactory.sol +++ b/contracts/CATFactory.sol @@ -7,11 +7,11 @@ import "./ContributionAccountingToken.sol"; contract CATFactory is Ownable { uint256 private _nextTokenId; - // Mapping from owner address to token addresses - mapping(address => address[]) public administerableTokens; - mapping(address => address[]) public mintableTokens; + mapping(address => address[]) public creatorTokens; // Mapping from owner address to token addresses + mapping(address => address[]) public minterTokens; + + mapping(address => mapping(address => bool)) public isMinterForCAT; - // Event emitted when a new CAT is created event CATCreated(address indexed owner, address catAddress, uint256 tokenId); constructor() Ownable(msg.sender) {} @@ -34,6 +34,7 @@ contract CATFactory is Ownable { ) public returns (address) { ContributionAccountingToken newCAT = new ContributionAccountingToken( msg.sender, + address(this), maxSupply, thresholdSupply, maxExpansionRate, @@ -42,32 +43,54 @@ contract CATFactory is Ownable { ); address catAddress = address(newCAT); - administerableTokens[msg.sender].push(catAddress); + creatorTokens[msg.sender].push(catAddress); emit CATCreated(msg.sender, catAddress, _nextTokenId); - _nextTokenId++; // Increment tokenId for the next contract + _nextTokenId++; return catAddress; } /** - * @dev Grants minter role to an address in the CAT contract. - * @param catAddress The address of the CAT contract. - * @param minter The address to grant the minter role. + * @dev Notifies the factory that a minter role has been granted in a CAT contract. + * This function is called by CAT contracts when their admins grant minter roles. */ - function grantMinterRole(address catAddress, address minter) public onlyOwner { - ContributionAccountingToken(catAddress).grantMinterRole(minter); - mintableTokens[minter].push(catAddress); // Update mintable tokens mapping + function onMinterRoleGranted(address minter) external { + if (!isMinterForCAT[minter][msg.sender]) { + isMinterForCAT[minter][msg.sender] = true; + minterTokens[minter].push(msg.sender); + } } /** - * @dev Returns the total number of CATs created. - * @return The total number of CATs. + * @dev Internal function to get a subarray from any address array with pagination. + * @param tokens The storage reference to the array of token addresses. start and end are the indexes of the subarray. + * @return An array of addresses for the specified range. */ - function totalCATs() public view returns (uint256) { - return _nextTokenId; + function _getSubArray(address[] storage tokens, uint256 start, uint256 end) internal view returns (address[] memory) { + require(start <= end, "Start index must be less than or equal to end index"); + require(start <= tokens.length, "Start index out of bounds"); + + if (end >= tokens.length) { + end = tokens.length; + } + + uint256 resultLength = end - start; + address[] memory result = new address[](resultLength); + + for (uint256 i = 0; i < resultLength; i++) { + result[i] = tokens[start + i]; + } + return result; } - function getCATAddresses(address _creator) external view returns (address[] memory) { - return administerableTokens[_creator]; + function getCreatorCATAddresses(address _creator, uint256 start, uint256 end) external view returns (address[] memory) { + return _getSubArray(creatorTokens[_creator], start, end); + } + function getMinterCATAddresses(address _minter, uint256 start, uint256 end) external view returns (address[] memory) { + return _getSubArray(minterTokens[_minter], start, end); } -} + + function totalCATs() public view returns (uint256) { return _nextTokenId; } + function getCreatorCATCount(address _creator) external view returns (uint256) { return creatorTokens[_creator].length; } + function getMinterCATCount(address _minter) external view returns (uint256) { return minterTokens[_minter].length; } +} \ No newline at end of file diff --git a/contracts/ContributionAccountingToken.sol b/contracts/ContributionAccountingToken.sol index ca1986cb..61284945 100644 --- a/contracts/ContributionAccountingToken.sol +++ b/contracts/ContributionAccountingToken.sol @@ -2,10 +2,16 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; -contract ContributionAccountingToken is ERC20, ERC20Permit, AccessControl { +interface ICATFactory { + function grantMinterRole(address catAddress, address minter) external; + function onMinterRoleGranted(address minter) external; +} + +contract ContributionAccountingToken is ERC20Burnable, ERC20Permit, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant ADMIN_ROLE = DEFAULT_ADMIN_ROLE; @@ -15,16 +21,17 @@ contract ContributionAccountingToken is ERC20, ERC20Permit, AccessControl { bool public transferRestricted = true; uint256 public constant clowderFee = 500; // 0.5% fee address public immutable clowderTreasury = 0x355e559BCA86346B82D58be0460d661DB481E05e; // Address to receive minting fees + address public immutable factory; // Reference to the factory contract that created this CAT uint256 public lastMintTimestamp; string public tokenName; // Token name string public tokenSymbol; // Token symbol - // Constant denominator for fee calculations - uint256 constant denominator = 100000; + uint256 constant denominator = 100000; // Constant denominator for fee calculations constructor( address defaultAdmin, + address _factory, uint256 _maxSupply, uint256 _thresholdSupply, uint256 _maxExpansionRate, @@ -34,6 +41,7 @@ contract ContributionAccountingToken is ERC20, ERC20Permit, AccessControl { _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); _grantRole(MINTER_ROLE, defaultAdmin); + factory = _factory; maxSupply = _maxSupply; thresholdSupply = _thresholdSupply; maxExpansionRate = _maxExpansionRate; @@ -45,31 +53,27 @@ contract ContributionAccountingToken is ERC20, ERC20Permit, AccessControl { function maxMintableAmount() public view returns (uint256) { uint256 currentSupply = totalSupply(); - // If current supply is less than threshold, return remaining amount to threshold if (currentSupply < thresholdSupply) { return thresholdSupply - currentSupply; } - - // Calculate based on expansion rate uint256 elapsedTime = block.timestamp - lastMintTimestamp; uint256 maxMintableAmount = (currentSupply * maxExpansionRate * elapsedTime) / (365 days * 100); - - // Also check against remaining max supply uint256 remainingSupply = maxSupply - currentSupply; return maxMintableAmount < remainingSupply ? maxMintableAmount : remainingSupply; } + function userAmountAfterFees(uint256 amount) public pure returns (uint256 userAmount, uint256 feeAmount) { + feeAmount = (amount * clowderFee) / denominator; + userAmount = amount - feeAmount; + } + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + require(amount <= maxMintableAmount(), "Exceeds maximum mintable amount"); - // Minting fee calculation - uint256 feeAmount = (amount * clowderFee) / denominator; - - // Check against max mintable amount - require(amount + feeAmount <= maxMintableAmount(), "Exceeds maximum mintable amount"); + (uint256 userAmount, uint256 feeAmount) = userAmountAfterFees(amount); - // Perform the actual minting - _mint(to, amount); + _mint(to, userAmount); _mint(clowderTreasury, feeAmount); lastMintTimestamp = block.timestamp; } @@ -101,10 +105,10 @@ contract ContributionAccountingToken is ERC20, ERC20Permit, AccessControl { } function grantMinterRole(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { - grantRole(MINTER_ROLE, account); + _grantRole(MINTER_ROLE, account); + ICATFactory(factory).onMinterRoleGranted(account); } - function revokeMinterRole(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { revokeRole(MINTER_ROLE, account); } -} +} \ No newline at end of file