Skip to content

Latest commit

 

History

History
328 lines (222 loc) · 17.2 KB

README.md

File metadata and controls

328 lines (222 loc) · 17.2 KB

LikesTokenV2

LikesTokenV2 is an enhanced ERC20 token contract with a rich set of features, designed to offer more than just basic token transactions. The contract incorporates various roles and functionalities to provide a comprehensive utility for its users and administrators.

Features

  • Standard ERC20 Functions: Supports all standard ERC20 functionalities including minting and burning.

  • Role-Based Access Control: Integrated roles for administrative tasks, price updating, minting, and airdropping.

  • Price Feed: Leverages Chainlink's AggregatorV3Interface to fetch the latest ETH/USD price, allowing dynamic token pricing.

  • Airdropping: Built-in airdrop mechanism to distribute tokens to a list of recipients. New recipients can be easily added.

  • Token and Ether Management: Capable of receiving, holding, and transferring both ERC20 tokens and Ether.

  • Modular Architecture: Ability to add and execute external modules, offering extended functionalities.

  • Pause and Unpause: Provides an option to pause and unpause token transfers, enhancing administrative control.

Roles

  • Price Updater: Responsible for updating the token price based on the ETH/USD price feed.

  • Airdropper: Manages the list of airdrop recipients and initiates airdrops.

  • Minter: Authorized to mint new tokens within the contract's max supply limit.

  • Module Admin: Manages the external modules that can be executed by the contract.

License

This project is licensed under the MIT License.


This LikesToken smart contract, leveraging OpenZeppelin's library and Chainlink's oracle, is a comprehensive implementation encompassing various functionalities. Here's a detailed overview of its components and functionalities:

  1. Import Statements: The contract imports key components from OpenZeppelin for ERC20 token standards, utilities like SafeERC20, extensions such as ERC20Burnable, security features including Pausable and ReentrancyGuard, and the AccessControl system. It also imports the Chainlink Aggregator interface for price feeds.

  2. Contract Declaration: Inherits from multiple OpenZeppelin contracts to provide standard functionalities for ERC20 tokens, along with additional features like burnability, pausing capabilities, access control, and reentrancy protection.

  3. Constructor: Sets up initial roles, including DEFAULT_ADMIN_ROLE, PRICE_UPDATER_ROLE, AIRDROPPER_ROLE, MINTER_ROLE, and MODULE_ADMIN_ROLE. It also initializes the airdrop list with provided recipients and amounts.

  4. Role Definitions: Defines several custom roles using the AccessControl framework, including GNOSIS_SAFE_ROLE (although you mentioned considering its removal).

  5. Price Feed and Token Economics: Integrates with Chainlink's oracle to fetch ETH/USD prices, which influences the token's price.

  6. Token Minting: Includes an override for the _mint function with a maximum supply cap.

  7. Token Price Management: Provides functionalities to update and set the token price.

  8. Pausable Functionalities: Includes the ability to pause and unpause the contract's functionalities.

  9. Funds Management: Functions to withdraw funds, both Ether and ERC20 tokens, restricted to certain roles.

  10. Airdrop Functionality: Includes functions to conduct airdrops and add airdrop recipients.

  11. Modular Functionality: Allows for the addition, removal, and execution of external modules, with addModule and removeModule controlled by the MODULE_ADMIN_ROLE, and executeModule restricted to the Gnosis Safe.

  12. Event Declarations: Defines events for logging significant actions like price updates and airdrops.

  13. Fallback and Receive Functions: Implements the receive function to handle direct Ether transfers to the contract.

The contract is quite robust, integrating several layers of functionalities and controls. The use of roles for governance and the integration with Chainlink for price data add significant depth to the contract's capabilities. The modular approach also offers flexibility in extending the contract's functionalities in the future.

While the contract appears well-structured, remember the importance of thorough testing, especially given its complexity and the critical nature of its functionalities. Consider deploying it on a testnet and conducting extensive tests to ensure everything operates as intended and to identify any potential security vulnerabilities.


Add DAO_ROLE and contract modifications to reflect the future updateable variables

In Solidity, the smart contract language for Ethereum, you have access to a variety of data types that you can use for your variables. Here's a rundown of some of the most commonly used data types, including those you mentioned:

Primary Data Types

  1. Boolean: bool - Represents a true/false value.

    bool isActive;
  2. Integer:

    • Unsigned Integers: uint / uint256 (most commonly used due to its wide range). Represents non-negative integers.
    • Signed Integers: int / int256. Represents both positive and negative integers.
    uint256 balance;
    int256 temperature;
  3. Address:

    • address. Holds a 20-byte value (size of an Ethereum address).
    • address payable. Similar to address, but with added functionality to receive Ether.
    address owner;
    address payable recipient;
  4. Byte Arrays:

    • Fixed-size byte arrays: bytes1, bytes2, ..., bytes32.
    • Dynamically-sized byte array: bytes.
    bytes32 hash;
    bytes data;
  5. String: Dynamically-sized UTF-8-encoded string.

    string name;

Complex Data Types

  1. Arrays: Can be fixed-size or dynamically-sized and can contain any type.

    uint256[] dynamicArray;
    uint256[10] fixedArray;
  2. Structs: Custom-defined types that can group several variables.

    struct User {
        string name;
        uint256 age;
    }
  3. Mappings: Key-value pairs, where keys are unique and values can be of any type.

    mapping(address => uint256) balances;
  4. Enums: User-defined types consisting of a set of named constants.

    enum State { Active, Inactive }

Special Data Types

  1. Function: A type for holding functions.
    function (uint256) external returns (bool) func;

Points to Remember

  • Data Location: Solidity has three data locations – storage, memory, and calldata – that determine where data is stored.
  • Gas Cost: Certain data types (like bytes and string) can be more costly in terms of gas, especially when their size is large or not fixed.
  • Secure Practices: Always choose the smallest data type suitable for your purpose to optimize contract efficiency. For example, if a variable will never exceed a certain value, consider uint8 or uint16 instead of uint256.

These data types can be combined and used in various ways to create complex data structures, fitting the needs of this smart contract.

Inheriting from OpenZeppelin's upgradeable-core contracts does provide a framework for creating upgradeable contracts, but it doesn't automatically future-proof your contract against all potential changes, especially when it comes to adding new state variables or manipulating data types.

Here’s how it works and what you need to consider:

Future Proofing State Variables in Upgradeable Contracts

  1. Adding New Variables: When using upgradeable contracts, you can add new state variables in future versions. However, these new variables should be added at the end of the contract to maintain the storage layout. You cannot insert new variables in between existing ones.

  2. Modifying Existing Variables: You should avoid changing the data type of existing state variables. If a variable was declared as uint256, it should remain uint256 in all future versions of the contract.

  3. Initial State Variables: The state variables declared in the initial version of your contract (the one you first deploy as a proxy) set the foundation. It’s crucial to carefully plan these initial variables, considering possible future needs.

  4. Use of Structs and Arrays: Be cautious with structs and arrays. While you can add new elements to the end of a struct or array, modifying or removing existing elements can disrupt the storage layout.

How OpenZeppelin Upgradeable Contracts Help

  • Initializers Over Constructors: OpenZeppelin upgradeable contracts use initializer functions instead of constructors. This approach is crucial for setting up state in a proxy pattern.
  • Consistency Checks: The OpenZeppelin upgrades plugin for development tools like Hardhat and Truffle can help identify storage layout changes that could cause issues.

Best Practices for Future Compatibility

  1. Careful Initial Design: Spend time on the initial design of your contract, considering potential future expansions.
  2. Reserve Space: Some developers reserve space in their contracts by including state variables like uint256[50] private __gap; that can be replaced in future versions.
  3. Modular Design: Keep your contract logic modular, separating concerns and functionalities as much as possible.

Conclusion

While inheriting from OpenZeppelin’s upgradeable contracts provides a solid foundation for upgradeability, it doesn’t remove the need for careful planning regarding state variables and contract architecture. Future-proofing an upgradeable contract involves thoughtful initial design and adherence to best practices in contract upgradeability. Modifying your LikesToken contract to mint only a portion of the tokens at deployment and to include functionality for distributing rewards tokens is a strategic approach that can add flexibility to your token economics. Here's how you can implement these changes:

Minting a Portion at Deployment

  1. Initial Minting: In your initialize function, mint only a portion of the total supply (e.g., 25%). This initial minting could be to a specific wallet, a set of initial holders, or for liquidity purposes.

    Example:

    // Mint 25% of MAX_SUPPLY to the deployer or a specified address
    uint256 initialSupply = MAX_SUPPLY / 4; // 25%
    _mint(msg.sender, initialSupply);
  2. Allowance for Future Minting: Since you're not minting the full supply initially, ensure that the mint function respects the MAX_SUPPLY limit for any future minting.

Distributing Rewards Tokens

  1. Rewards Mechanism: Implement a mechanism in your LikesToken contract to distribute rewards. This could be based on user interactions, staking, or other criteria.

  2. Rewards Distribution Function:

    • Create a function to calculate and distribute rewards. This function can use the mint function to issue new tokens as rewards.
    • Ensure that the distribution logic aligns with your tokenomics and is transparent to users.

    Example:

    function distributeRewards(address recipient, uint256 rewardAmount) public onlyRole(REWARDS_DISTRIBUTOR_ROLE) {
        require(totalSupply() + rewardAmount <= MAX_SUPPLY, "Exceeds max supply");
        _mint(recipient, rewardAmount);
    }
  3. Role-Based Access Control: Use role-based access control to manage who can distribute rewards. For instance, a REWARDS_DISTRIBUTOR_ROLE can be created and assigned to an account or set of accounts responsible for managing rewards.

Considerations

  • Tokenomics and Governance: Clearly define and document how tokens will be minted and distributed over time. If your project has a DAO or governance system, consider involving your community in these decisions.

  • Testing and Security: Thoroughly test these new functionalities, especially the rewards distribution logic, to ensure they work as intended and do not introduce security vulnerabilities.

  • Transparency: Keep your community informed about how tokens are minted and distributed, especially regarding rewards, as this impacts token value and user trust.

  • Upgradeability: If your contract is upgradeable, you can iteratively develop and improve your rewards mechanism based on user feedback and changing needs.

By implementing these features, you will add a dynamic aspect to your token supply management and create incentives for user engagement or other desired behaviors within your ecosystem.

Extensive Comments Adding comments to complex code blocks and sensitive functions is crucial for maintainability and readability, especially in smart contract development where clarity and security are paramount. Here's an extensively commented version of your LikesToken contract, focusing on complex and sensitive areas:

// SPDX-License-Identifier: MIT pragma solidity 0.8.20;

// Importing necessary components from OpenZeppelin, including ERC20 standards and security utilities import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; ... // Other imports

// Interface definition for external modules interface IModule { // Define what an external module can execute function execute(address target, uint256 value, bytes calldata data) external returns (bool, bytes memory); // Event to log when a new module is added event ModuleAdded(address indexed module); }

// Using directive for SafeERC20, ensuring safe interactions with ERC20 tokens using SafeERC20Upgradeable for IERC20Upgradeable;

// Contract declaration for LikesToken // Inherits from multiple OpenZeppelin contracts for ERC20, burnability, pausability, access control, etc. contract LikesToken is Initializable, ReentrancyGuardUpgradeable, ERC20Upgradeable, ... {

// Initialize function for setting up the contract during deployment
function initialize(address[] memory _recipients, uint256[] memory _amounts) public initializer {
    // Basic initializations for various functionalities like ERC20, Pausable, etc.
    __Context_init_unchained();
    __ERC20_init_unchained("LikesToken", "LTXO");
    ... // Other initializations

    // Emit an event indicating the ownership transfer (common pattern in Ownable contracts)
    emit OwnershipTransferred(address(0), msg.sender);

    // Defining roles for access control
    bytes32 public constant GNOSIS_SAFE_ROLE = keccak256(abi.encodePacked("GNOSIS_SAFE_ROLE"));
    ... // Other roles

    // Set up for price feed and economic variables
    AggregatorV3Interface internal priceFeedETHUSD;
    uint256 public tokenPrice;
    ... // Other related variables

    // Mappings for tracking airdrop recipients and allowed modules
    mapping(address => uint256) public airdropRecipients;
    mapping(address => bool) public allowedModules;

    // Event definitions for logging various activities in the contract
    event ModuleExecuted(...);
    ... // Other events

    // Struct and array for managing airdrop recipients
    struct AirdropRecipient { ... }
    AirdropRecipient[] public airdropList;
    address public gnosisSafe;

    // Modifier to restrict function access to only the Gnosis Safe
    modifier onlyGnosisSafe() { ... }

    // Validating input arrays for airdrops
    require(_recipients.length == _amounts.length, "Arrays must be of equal length");

    ... // Logic for populating airdrop list and setting initial states

    // Important: Granting roles to appropriate entities
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender) onlyGnosisSafe nonReentrant { ... }
    ... // Other role grants

    // Minting initial supply as a fraction of MAX_SUPPLY for liquidity and sales
    uint256 initialSupply = MAX_SUPPLY / 4; // 25%
    _mint(msg.sender, initialSupply);
    require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
    ... // Mint event emission

    // Overriding the _mint function to enforce maximum supply cap
    function _mint(address account, uint256 amount) internal override {
        require(account != address(0), "ERC20: mint to the zero address");
        require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
        super._mint(account, amount);
        emit TokensMinted(account, amount);
    }

    ... // Other functions like getLatestETHPriceInUSD, updatePrice, etc.

    // Function to allow token purchase, considering the contract's paused state and reentrancy attacks
    function purchaseTokens(uint256 numberOfTokens) public payable whenNotPaused nonReentrant { ... }

    ... // Functions for rewards distribution, token burning, pausing/unpausing the contract

    // Airdrop-related functions, ensuring proper authorization and input validation
    function airdropTokens(address[] memory recipients) external onlyRole(AIRDROPPER_ROLE) nonReentrant { ... }
    ... // Other airdrop functions

    // Receive function for handling incoming Ether transactions
    receive() external payable { ... }

    // Function to safely withdraw funds, ensuring only authorized access and proper execution
    function withdrawFunds() external onlyGnosisSafe nonReentrant {
        uint256 balance = address(this).balance;
        require(balance > 0, "No funds to withdraw");
        (bool success, ) = payable