Skip to content

Latest commit

 

History

History
180 lines (127 loc) · 12 KB

README.md

File metadata and controls

180 lines (127 loc) · 12 KB

1inch Token Plugins: A Comprehensive Guide for Extending ERC20 Functionalities

Overview

Primary Benefits

Implementation

Overview of Contract Structure

Deployed Examples

Helpful Links

Overview

Token plugins are smart contracts that extend the capabilities of existing ERC20 tokens. They allow for additional accounting to be added to an existing token without the need to deposit your tokens into a contract. This is a revolutionary step in the evolution of DeFi, as the entire ecosystem has historically relied upon the transfer of tokens in and out of external contracts to achieve enhanced capabilities!

By design, token plugins prevent several common attack vectors and inherently create a fundamental level of security within every token incentive mechanism. Users no longer have to trust their funds in an external contract, whether it be for farming, borrowing/lending, or delegating, etc., therefore adding a fundamental layer of security.

Primary Benefits

  • 100% permissionless, open to all participants.
  • Risk-free participation: Token plugins can only update with an account’s balance change. They cannot approve, mint, burn, transfer, or overwrite storage.
  • Users can connect multiple plugins, allowing for simultaneous participation in multiple incentive programs or governance systems, etc.
  • Implementation is less than 100 lines of code; very simple to adopt.
  • Heavily audited by OpenZeppelin, providing the highest standard of security.
  • Built-in reentrancy protection.
  • A plugin can be represented by its very own associated ERC20 (custom inheritance).
  • NFT (ERC721) and Multi-token (ERC1155) support are coming soon!

This new system embodies the core principles of Web3 technology - decentralization, security, and user empowerment. Token Plugins are designed to seamlessly integrate with and enhance existing token contracts and incentive structures, providing a simple solution for token utility and interoperability.

Use-Cases

Token Plugins create a massive number of possibilities in the DeFi space. The sky's the limit! Here are several existing (and potential) examples:

  • Complex staking: Can offer different tiers of rewards based on the duration of the stake (or amount staked).
  • Farming: Allows token holders to participate in multiple farming activities simultaneously, providing a new level of diversification and automation, all without having to deposit tokens into a farming contract. (See 1inch Fusion pods)
  • Delegation: Useful in governance and voting, token plugins can track delegated balances, enhancing the utility of governance tokens.
  • Conditional transactions and/or escrow: A plugin could allow tokens to be locked until certain conditions are met, like the completion of a service, time-based milestones, or the achievement of any custom milestone. (Think service economy like Uber or AirBnB etc.)
  • Taxation or fee collection: Token plugins can be used to track an account’s balances and automatically deduct a specific tax percentage based on pre-specified conditions.
  • Loyalty points and rewards programs: Can be applied in systems that reward users for platform usage over time. (like airline miles, credit card points, or web3 loyalty points)

Limitations

  • Plugin Quantity: The contract deployer can establish a limit on the number of plugins managed under the plugin management contract.
  • Increased Gas Consumption: Additional gas is consumed due to the processing logic of the token plugins. A custom gas limit constructor is included to mitigate this.

Implementation

ERC20Plugins

Connecting a token contract with the 1inch Token Plugins is a straightforward process. If you’re creating a brand new token contract or migrating an existing one, you can simply inherit from the plugin-enabled ERC20 contract OR wrap an existing token and inherit plugin functionality within the wrapper (contract MyWrapper is ERC20Wrapper, ERC20Plugins { ... }). Subsequently, any plugin (deployed as a separate contract) can be connected to your plugin-enabled ERC20, enabling it to track balance updates of the underlying asset efficiently.

In other words, 1inch Token Plugins require inheritance from an independent, “plugin-enabled” ERC20 contract, which manages all related dependent plugin contracts. The plugin-enabled ERC20 contract is responsible for calling the updateBalance function with every change in an account’s balance.

All plugins will only track the balances of participating accounts. So all non-participants are represented as “0 addresses”. If an account is not participating in a plugin and receives a plugin-enabled token, the From and To amounts under _updateBalances will be represented as 0.

participants

Therefore, if a non-participant sends a plugin-enabled token to an existing participant, it will effectively “mint” the tracked balance. If a participant sends a plugin-enabled token to a non-participant, it will effectively “burn” the tracked balance.

Token Transfers

For security purposes, plugins are designed with several fail-safes, including a maximum number of usable plugins, custom gas limits, a reentrancy guard, and native isolation from the main contract state. The maximum plugins and gas limit can be initialized as state variables using MAX_PLUGINS_PER_ACCOUNT and PLUGIN_CALL_GAS_LIMIT, respectively. For reentrancy prevention, ReentrancyGuardExt is included from OpenZeppelin’s library. Finally, for native isolation from the token contract, a single method with only three arguments (To, From, and Amount) is used. This simple architecture results in a dynamic (and risk-free!) enhancement of any ERC20 contract’s capabilities.

How do accounts (users) add or remove a plugin?

To add a plugin to an account, a user-friendly web application can be developed and integrated with any injected wallet provider for simple account connection and signature. This simplifies the process of selecting and subscribing to plugins (see 1inch resolver plugins). Alternatively, an advanced user can subscribe to a plugin by directly interacting with the smart contract using a web3 wallet and the contract's ABI. Both methods require the user to initiate a transaction to call the addPlugin function of the token contract, which subscribes their account to the chosen plugin. To remove a plugin, the account needs to call either removePlugin or removeAllPlugins, depending on its needs.


Overview of Contract Structure

ERC20Plugins.sol (Plugin-enabled ERC20 Token Contract)

  • Native inheritance from OpenZeppelin’s ERC20 library (for turnkey deployment)
  • token contract if creating a new ERC20 token, OR can be inherited as a wrapper for a pre-existing “non-plugin-enabled” token
  • Main function: updateBalances - Calls to each connected plugin

Includes

  • State Variables (initialized in the constructor):
    • MAX_PLUGINS_PER_ACCOUNT: Restricts the maximum number of plugins that a token can connect to.
    • PLUGIN_CALL_GAS_LIMIT: Specifies the maximum amount of gas that can be spent for any given call.
    • ReentrancyGuardExt: Imported reentrancy protection from OpenZeppelin’s library.

Functions

  • _updateBalances
    • Returns balance updates from the plugin upon each relevant event. This allows the plugin to execute it's custom logic and extend the plugin-enabled ERC20 token's functionality.
    • Ensures gas usage does not breach the limit set within PLUGIN_CALL_GAS_LIMIT.
  • balanceOf: Returns the entire token balance of an account.
  • pluginBalanceOf: Returns the balance of a token for a specific plugin.
  • hasPlugin: Checks to see if a specific plugin is associated with an account.
  • pluginsCount: Returns the total number of plugins associated with a specific account.
  • pluginAt: Returns the address for a specific plugin, based on its position in the list of total plugins associated with an account.
  • Plugins: Returns a list of all plugins associated with a particular account.
  • addPlugin: Connects an account with a specific plugin.
  • removePlugin: Disables a specific plugin associated with an account.
  • removeAllPlugins: Disables all plugins associated with an account.
  • Update: Overrides the classic ERC20 balance update, and is expanded to include plugin mechanics (updateBalances). Only called on mint, burn, or transfer events.

Plugin.sol (Plugin Contract)

  • A basic template for plugin creation.
  • Tracks an account’s balances through _updateBalances.
  • Provides advanced functionalities, such as farming, delegation, etc., without moving tokens.

Includes

Interfaces

  • IPlugin: Interface for plugin functionalities.
  • IERC20Plugins: Interface linking the plugin with a plugin-enabled ERC20 token.

State Variable

  • TOKEN: Public immutable variable storing the reference to the parent ERC20 token contract.

Modifiers

  • onlyToken: Ensures that certain functions can be called only by the associated plugin-enabled token contract.

Functions

  • updateBalances: An external function that updates account balances within the plugin. It can only be called by the token contract itself. (see onlyToken modifier)
  • _updateBalances: An abstract internal function, to be implemented in derived contracts. Responsible for the actual logic of updating balances within the plugin itself.
    • Note: Within _updateBalances, 'from' and 'to' amounts can be 0 for non-participants.

Examples:

Simple plugin-enabled token contract

contract NewToken is ERC20Plugins {
    constructor(string memory name, string memory symbol, uint256 maxPluginsPerAccount, uint256 pluginCallGasLimit)
        ERC20(name, symbol)
        ERC20Plugins(maxPluginsPerAccount, pluginCallGasLimit)
    {} // solhint-disable-line no-empty-blocks

    function mint(address account, uint256 amount) external {
        _mint(account, amount);
    }
}

Simple plugin contract

contract MyPlugin is ERC20, Plugin {
    constructor(string memory name, string memory symbol, IERC20Plugins token_)
        ERC20(name, symbol)
        Plugin(token_)
    {} // solhint-disable-line no-empty-blocks

    function _updateBalances(address from, address to, uint256 amount) internal override {
        if (from == address(0)) {
            _mint(to, amount);
        } else if (to == address(0)) {
            _burn(from, amount);
        } else {
            _transfer(from, to, amount);
        }
    }
}

Deployed Examples

Other Helpful Links