diff --git a/contracts/Token/MultichainToken.sol b/contracts/Token/MultichainToken.sol index e1adec6..da8f615 100644 --- a/contracts/Token/MultichainToken.sol +++ b/contracts/Token/MultichainToken.sol @@ -3,21 +3,22 @@ pragma solidity 0.8.13; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { TokenController } from "./utils/TokenController.sol"; +import { MultichainTokenController } from "./utils/MultichainTokenController.sol"; /** * @title MultichainToken * @author Venus * @notice MultichainToken contract serves as a customized ERC-20 token with additional minting and burning functionality. - * It also incorporates access control features provided by the "TokenController" contract to ensure proper governance and restrictions on minting and burning operations. + * It also incorporates access control features provided by the "MultichainTokenController" contract to ensure proper governance and + * restrictions on minting and burning operations. */ -contract MultichainToken is ERC20, TokenController { +contract MultichainToken is ERC20, MultichainTokenController { constructor( address accessControlManager_, string memory name_, string memory symbol_ - ) ERC20(name_, symbol_) TokenController(accessControlManager_) {} + ) ERC20(name_, symbol_) MultichainTokenController(accessControlManager_) {} /** * @notice Creates `amount_` tokens and assigns them to `account_`, increasing @@ -30,7 +31,7 @@ contract MultichainToken is ERC20, TokenController { */ function mint(address account_, uint256 amount_) external whenNotPaused { _ensureAllowed("mint(address,uint256)"); - _isEligibleToMint(msg.sender, account_, amount_); + _isEligibleToMint(msg.sender, amount_); _mint(account_, amount_); } diff --git a/contracts/Token/TokenControllerSrc.sol b/contracts/Token/TokenBridgeController.sol similarity index 86% rename from contracts/Token/TokenControllerSrc.sol rename to contracts/Token/TokenBridgeController.sol index 52040ec..c6785e6 100644 --- a/contracts/Token/TokenControllerSrc.sol +++ b/contracts/Token/TokenBridgeController.sol @@ -2,23 +2,23 @@ pragma solidity 0.8.13; import { IMultichainToken } from "./../interfaces/IMultichainToken.sol"; -import { TokenController } from "./utils/TokenController.sol"; +import { MultichainTokenController } from "./utils/MultichainTokenController.sol"; import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol"; /** - * @title TokenControllerSrc + * @title TokenBridgeController * @author Venus - * @notice TokenControllerSrc contract serves as a intermidiary contract between bridge and token. It controls the mint and burn operation via bridge contract. + * @notice TokenBridgeController contract serves as a intermidiary contract between bridge and token. It controls the mint and burn operation via bridge contract. * It also incorporates access control features provided by the "TokenController" contract to ensure proper governance and restrictions on minting and burning operations. */ -contract TokenControllerSrc is TokenController { +contract TokenBridgeController is MultichainTokenController { /** * @notice Address of the token which is controlled by this contract. */ IMultichainToken public immutable INNER_TOKEN; - constructor(address accessControlManager_, address innerToken_) TokenController(accessControlManager_) { + constructor(address accessControlManager_, address innerToken_) MultichainTokenController(accessControlManager_) { ensureNonzeroAddress(innerToken_); INNER_TOKEN = IMultichainToken(innerToken_); } @@ -35,7 +35,7 @@ contract TokenControllerSrc is TokenController { function mint(address account_, uint256 amount_) external whenNotPaused { _ensureAllowed("mint(address,uint256)"); _beforeTokenTransfer(msg.sender, account_); - _isEligibleToMint(msg.sender, account_, amount_); + _isEligibleToMint(msg.sender, amount_); INNER_TOKEN.mint(account_, amount_); } diff --git a/contracts/Token/utils/MultichainTokenController.sol b/contracts/Token/utils/MultichainTokenController.sol new file mode 100644 index 0000000..ef6c734 --- /dev/null +++ b/contracts/Token/utils/MultichainTokenController.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.13; +import { IAccessControlManagerV8 } from "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol"; +import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol"; + +/** + * @title MultichainTokenController + * @author Venus + * @notice MultichainTokenController contract acts as a governance and access control mechanism, + * allowing the owner to manage minting restrictions and blacklist certain addresses to maintain control and security within the token ecosystem. + * It provides a flexible framework for token-related operations. + */ + +contract MultichainTokenController is Ownable, Pausable { + /** + * @notice Access control manager contract address. + */ + address public accessControlManager; + /** + * @notice A Mapping used to keep track of the blacklist status of addresses. + */ + mapping(address => bool) internal _blacklist; + /** + * @notice A mapping is used to keep track of the maximum amount a minter is permitted to mint. + */ + mapping(address => uint256) public minterToCap; + /** + * @notice A Mapping used to keep track of the amount i.e already minted by minter. + */ + mapping(address => uint256) public minterToMintedAmount; + + /** + * @notice Emitted when the blacklist status of a user is updated. + */ + event BlacklistUpdated(address indexed user, bool value); + /** + * @notice Emitted when the minting limit for a minter is increased. + */ + event MintLimitIncreased(address indexed minter, uint256 newLimit); + /** + * @notice Emitted when the minting limit for a minter is decreased. + */ + event MintLimitDecreased(address indexed minter, uint256 newLimit); + /** + * @notice Emitted when the minting cap for a minter is changed. + */ + event MintCapChanged(address indexed minter, uint256 amount); + /** + * @notice Emitted when the address of the access control manager of the contract is updated. + */ + event NewAccessControlManager(address indexed oldAccessControlManager, address indexed newAccessControlManager); + /** + * @notice Emitted when all minted tokens are migrated from one minter to another. + */ + event MintedTokensMigrated(address indexed source, address indexed destination); + + /** + * @notice This error is used to indicate that the minting limit has been exceeded. It is typically thrown when a minting operation would surpass the defined cap. + */ + error MintLimitExceed(); + /** + * @notice This error is used to indicate that `mint` `burn` and `transfer` actions are not allowed for the user address. + */ + error AccountBlacklisted(address user); + /** + * @notice This error is used to indicate that sender is not allowed to perform this action. + */ + error Unauthorized(); + /** + * @notice This error is used to indicate that the new cap is greater than the previously minted tokens for the minter. + */ + error NewCapNotGreaterThanMintedTokens(); + /** + * @notice This error is used to indicate that the addresses must be different. + */ + error AddressesMustDiffer(); + /** + * @notice This error is used to indicate that the minter did not mint the required amount of tokens. + */ + error MintedAmountExceed(); + + /** + * @param accessControlManager_ Address of access control manager contract. + * @custom:error ZeroAddressNotAllowed is thrown when accessControlManager contract address is zero. + */ + constructor(address accessControlManager_) { + ensureNonzeroAddress(accessControlManager_); + accessControlManager = accessControlManager_; + } + + /** + * @notice Pauses Token + * @custom:access Controlled by AccessControlManager. + */ + function pause() external { + _ensureAllowed("pause()"); + _pause(); + } + + /** + * @notice Resumes Token + * @custom:access Controlled by AccessControlManager. + */ + function unpause() external { + _ensureAllowed("unpause()"); + _unpause(); + } + + /** + * @notice Function to update blacklist. + * @param user_ User address to be affected. + * @param value_ Boolean to toggle value. + * @custom:access Controlled by AccessControlManager. + * @custom:event Emits BlacklistUpdated event. + */ + function updateBlacklist(address user_, bool value_) external { + _ensureAllowed("updateBlacklist(address,bool)"); + _blacklist[user_] = value_; + emit BlacklistUpdated(user_, value_); + } + + /** + * @notice Sets the minting cap for minter. + * @param minter_ Minter address. + * @param amount_ Cap for the minter. + * @custom:access Controlled by AccessControlManager. + * @custom:event Emits MintCapChanged. + */ + function setMintCap(address minter_, uint256 amount_) external { + _ensureAllowed("setMintCap(address,uint256)"); + + if (amount_ < minterToMintedAmount[minter_]) { + revert NewCapNotGreaterThanMintedTokens(); + } + + minterToCap[minter_] = amount_; + emit MintCapChanged(minter_, amount_); + } + + /** + * @notice Sets the address of the access control manager of this contract. + * @dev Admin function to set the access control address. + * @param newAccessControlAddress_ New address for the access control. + * @custom:access Only owner. + * @custom:event Emits NewAccessControlManager. + * @custom:error ZeroAddressNotAllowed is thrown when newAccessControlAddress_ contract address is zero. + */ + function setAccessControlManager(address newAccessControlAddress_) external onlyOwner { + ensureNonzeroAddress(newAccessControlAddress_); + emit NewAccessControlManager(accessControlManager, newAccessControlAddress_); + accessControlManager = newAccessControlAddress_; + } + + /** + * @notice Migrates all minted tokens from one minter to another. This function is useful when we want to permanent take down a bridge. + * @param source_ Minter address to migrate tokens from. + * @param destination_ Minter address to migrate tokens to. + * @custom:access Controlled by AccessControlManager. + * @custom:error MintLimitExceed is thrown when the minting limit exceeds the cap after migration. + * @custom:error AddressesMustDiffer is thrown when the source_ and destination_ addresses are the same. + * @custom:event Emits MintLimitIncreased and MintLimitDecreased events for 'source' and 'destination'. + * @custom:event Emits MintedTokensMigrated. + */ + function migrateMinterTokens(address source_, address destination_) external { + _ensureAllowed("migrateMinterTokens(address,address)"); + + if (source_ == destination_) { + revert AddressesMustDiffer(); + } + + uint256 sourceCap = minterToCap[source_]; + uint256 destinationCap = minterToCap[destination_]; + + uint256 sourceMinted = minterToMintedAmount[source_]; + uint256 destinationMinted = minterToMintedAmount[destination_]; + uint256 newDestinationMinted = destinationMinted + sourceMinted; + + if (newDestinationMinted > destinationCap) { + revert MintLimitExceed(); + } + + minterToMintedAmount[source_] = 0; + minterToMintedAmount[destination_] = newDestinationMinted; + uint256 availableLimit; + unchecked { + availableLimit = destinationCap - newDestinationMinted; + } + + emit MintLimitDecreased(destination_, availableLimit); + emit MintLimitIncreased(source_, sourceCap); + emit MintedTokensMigrated(source_, destination_); + } + + /** + * @notice Returns the blacklist status of the address. + * @param user_ Address of user to check blacklist status. + * @return bool status of blacklist. + */ + function isBlackListed(address user_) external view returns (bool) { + return _blacklist[user_]; + } + + /** + * @dev Checks the minter cap and eligibility of receiver to receive tokens. + * @param from_ Minter address. + * @param amount_ Amount to be mint. + * @custom:error MintLimitExceed is thrown when minting limit exceeds the cap. + * @custom:event Emits MintLimitDecreased with minter address and available limits. + */ + function _isEligibleToMint(address from_, uint256 amount_) internal { + uint256 mintingCap = minterToCap[from_]; + uint256 totalMintedOld = minterToMintedAmount[from_]; + uint256 totalMintedNew = totalMintedOld + amount_; + + if (totalMintedNew > mintingCap) { + revert MintLimitExceed(); + } + minterToMintedAmount[from_] = totalMintedNew; + uint256 availableLimit; + unchecked { + availableLimit = mintingCap - totalMintedNew; + } + emit MintLimitDecreased(from_, availableLimit); + } + + /** + * @dev This is post hook of burn function, increases minting limit of the minter. + * @param from_ Minter address. + * @param amount_ Amount burned. + * @custom:error MintedAmountExceed is thrown when `amount_` is greater than the tokens minted by `from_`. + * @custom:event Emits MintLimitIncreased with minter address and availabe limit. + */ + function _increaseMintLimit(address from_, uint256 amount_) internal { + uint256 totalMintedOld = minterToMintedAmount[msg.sender]; + uint256 amountToIncrease = totalMintedOld >= amount_ ? amount_ : totalMintedOld; + + uint256 totalMintedNew; + unchecked { + totalMintedNew = totalMintedOld - amountToIncrease; + } + minterToMintedAmount[from_] = totalMintedNew; + uint256 availableLimit = minterToCap[from_] - totalMintedNew; + emit MintLimitIncreased(from_, availableLimit); + } + + /** + * @dev Checks the caller is allowed to call the specified fuction. + * @param functionSig_ Function signatureon which access is to be checked. + * @custom:error Unauthorized, thrown when unauthorised user try to access function. + */ + function _ensureAllowed(string memory functionSig_) internal view { + if (!IAccessControlManagerV8(accessControlManager).isAllowedToCall(msg.sender, functionSig_)) { + revert Unauthorized(); + } + } +}