Skip to content

Commit

Permalink
Merge pull request #29 from 1inch/feature/optimizations
Browse files Browse the repository at this point in the history
Minor optimizations and renames
  • Loading branch information
ZumZoom committed Aug 18, 2023
2 parents 8191c20 + 9bd7ce3 commit 8c2b804
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 67 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ Below is the example of token implementing plugin support and a simple plugin th
```solidity
// Simple token contract with plugin support
contract HostTokenExample is ERC20Plugins {
constructor(string memory name, string memory symbol, uint256 pluginsCountLimit, uint256 pluginsCallGasLimit)
constructor(string memory name, string memory symbol, uint256 maxPluginsPerAccount, uint256 pluginCallGasLimit)
ERC20(name, symbol)
ERC20Plugins(pluginsCountLimit, pluginsCallGasLimit)
ERC20Plugins(maxPluginsPerAccount, pluginCallGasLimit)
{} // solhint-disable-line no-empty-blocks
function mint(address account, uint256 amount) external {
Expand All @@ -95,4 +95,4 @@ contract PluginExample is ERC20, Plugin {
}
}
}
```
```
58 changes: 18 additions & 40 deletions contracts/ERC20Plugins.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,10 @@ abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
using AddressArray for AddressArray.Data;
using ReentrancyGuardLib for ReentrancyGuardLib.Data;

error PluginAlreadyAdded();
error PluginNotFound();
error InvalidPluginAddress();
error InvalidTokenInPlugin();
error PluginsLimitReachedForAccount();
error ZeroPluginsLimit();

/// @dev Limit of plugins per account
uint256 public immutable pluginsCountLimit;
uint256 public immutable maxPluginsPerAccount;
/// @dev Gas limit for a single plugin call
uint256 public immutable pluginsCallGasLimit;
uint256 public immutable pluginCallGasLimit;

ReentrancyGuardLib.Data private _guard;
mapping(address => AddressSet.Data) private _plugins;
Expand All @@ -41,50 +34,39 @@ abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
*/
constructor(uint256 pluginsLimit_, uint256 pluginCallGasLimit_) {
if (pluginsLimit_ == 0) revert ZeroPluginsLimit();
pluginsCountLimit = pluginsLimit_;
pluginsCallGasLimit = pluginCallGasLimit_;
maxPluginsPerAccount = pluginsLimit_;
pluginCallGasLimit = pluginCallGasLimit_;
_guard.init();
}

/**
* @dev Returns whether an account has a specific plugin.
* @param account The address of the account.
* @param plugin The address of the plugin.
* @return bool A boolean indicating whether the account has the specified plugin.
* @notice See {IERC20Plugins-hasPlugin}.
*/
function hasPlugin(address account, address plugin) public view virtual returns(bool) {
return _plugins[account].contains(plugin);
}

/**
* @dev Returns the number of plugins registered for an account.
* @param account The address of the account.
* @return uint256 A number of plugins registered for the account.
* @notice See {IERC20Plugins-pluginsCount}.
*/
function pluginsCount(address account) public view virtual returns(uint256) {
return _plugins[account].length();
}

/**
* @dev Returns the address of a plugin at a specified index for a given account .
* @param account The address of the account.
* @param index The index of the plugin to retrieve.
* @return plugin The address of the plugin.
* @notice See {IERC20Plugins-pluginAt}.
*/
function pluginAt(address account, uint256 index) public view virtual returns(address) {
return _plugins[account].at(index);
}

/**
* @dev Returns an array of all plugins owned by a given account.
* @param account The address of the account to query.
* @return plugins An array of plugin addresses.
* @notice See {IERC20Plugins-plugins}.
*/
function plugins(address account) public view virtual returns(address[] memory) {
return _plugins[account].items.get();
}


/**
* @dev Returns the balance of a given account.
* @param account The address of the account.
Expand All @@ -95,10 +77,7 @@ abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
}

/**
* @dev Returns the balance of a given account if a specified plugin is added or zero.
* @param plugin The address of the plugin to query.
* @param account The address of the account to query.
* @return balance The account balance if the specified plugin is added and zero otherwise.
* @notice See {IERC20Plugins-pluginBalanceOf}.
*/
function pluginBalanceOf(address plugin, address account) public nonReentrantView(_guard) view virtual returns(uint256) {
if (hasPlugin(account, plugin)) {
Expand All @@ -108,23 +87,21 @@ abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
}

/**
* @dev Adds a new plugin for the calling account.
* @param plugin The address of the plugin to add.
* @notice See {IERC20Plugins-addPlugin}.
*/
function addPlugin(address plugin) public virtual {
_addPlugin(msg.sender, plugin);
}

/**
* @dev Removes a plugin for the calling account.
* @param plugin The address of the plugin to remove.
* @notice See {IERC20Plugins-removePlugin}.
*/
function removePlugin(address plugin) public virtual {
_removePlugin(msg.sender, plugin);
}

/**
* @dev Removes all plugins for the calling account.
* @notice See {IERC20Plugins-removeAllPlugins}.
*/
function removeAllPlugins() public virtual {
_removeAllPlugins(msg.sender);
Expand All @@ -134,7 +111,7 @@ abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
if (plugin == address(0)) revert InvalidPluginAddress();
if (IPlugin(plugin).token() != IERC20Plugins(address(this))) revert InvalidTokenInPlugin();
if (!_plugins[account].add(plugin)) revert PluginAlreadyAdded();
if (_plugins[account].length() > pluginsCountLimit) revert PluginsLimitReachedForAccount();
if (_plugins[account].length() > maxPluginsPerAccount) revert PluginsLimitReachedForAccount();

emit PluginAdded(account, plugin);
uint256 balance = balanceOf(account);
Expand All @@ -158,10 +135,11 @@ abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
uint256 balance = balanceOf(account);
unchecked {
for (uint256 i = items.length; i > 0; i--) {
_plugins[account].remove(items[i - 1]);
emit PluginRemoved(account, items[i - 1]);
address item = items[i-1];
_plugins[account].remove(item);
emit PluginRemoved(account, item);
if (balance > 0) {
_updateBalances(items[i - 1], account, address(0), balance);
_updateBalances(item, account, address(0), balance);
}
}
}
Expand All @@ -172,7 +150,7 @@ abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
/// @dev try IPlugin(plugin).updateBalances{gas: _PLUGIN_CALL_GAS_LIMIT}(from, to, amount) {} catch {}
function _updateBalances(address plugin, address from, address to, uint256 amount) private {
bytes4 selector = IPlugin.updateBalances.selector;
uint256 gasLimit = pluginsCallGasLimit;
uint256 gasLimit = pluginCallGasLimit;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
let ptr := mload(0x40)
mstore(ptr, selector)
Expand Down
26 changes: 14 additions & 12 deletions contracts/Plugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,27 @@ abstract contract Plugin is IPlugin {
_;
}

/// @dev Creates a new plugin contract, initialized with a reference to the parent token contract.
/// @param token_ The address of the token contract
/**
* @dev Creates a new plugin contract, initialized with a reference to the parent token contract.
* @param token_ The address of the token contract
*/
constructor(IERC20Plugins token_) {
token = token_;
}

/// @dev Updates the balances of two addresses in the plugin as a result of any balance changes.
/// Only the Token contract is allowed to call this function.
/// @param from The address from which tokens were transferred
/// @param to The address to which tokens were transferred
/// @param amount The amount of tokens transferred
/**
* @notice See {IPlugin-updateBalances}.
*/
function updateBalances(address from, address to, uint256 amount) external onlyToken {
_updateBalances(from, to, amount);
}

/// @dev Updates the balances of two addresses in the plugin as a result of any balance changes.
/// Only the Token contract is allowed to call this function.
/// @param from The address from which tokens were transferred
/// @param to The address to which tokens were transferred
/// @param amount The amount of tokens transferred
/**
* @dev Updates the balances of two addresses in the plugin as a result of any balance changes.
* Only the Token contract is allowed to call this function.
* @param from The address from which tokens were transferred
* @param to The address to which tokens were transferred
* @param amount The amount of tokens transferred
*/
function _updateBalances(address from, address to, uint256 amount) internal virtual;
}
76 changes: 69 additions & 7 deletions contracts/interfaces/IERC20Plugins.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,77 @@ interface IERC20Plugins is IERC20 {
event PluginAdded(address account, address plugin);
event PluginRemoved(address account, address plugin);

function pluginsCountLimit() external view returns(uint256);
function pluginsCallGasLimit() external view returns(uint256);
function hasPlugin(address account, address plugin) external view returns(bool);
function pluginsCount(address account) external view returns(uint256);
function pluginAt(address account, uint256 index) external view returns(address);
function plugins(address account) external view returns(address[] memory);
function pluginBalanceOf(address plugin, address account) external view returns(uint256);
error PluginAlreadyAdded();
error PluginNotFound();
error InvalidPluginAddress();
error InvalidTokenInPlugin();
error PluginsLimitReachedForAccount();
error ZeroPluginsLimit();

/**
* @dev Returns the maximum allowed number of plugins per account.
* @return pluginsLimit The maximum allowed number of plugins per account.
*/
function maxPluginsPerAccount() external view returns(uint256 pluginsLimit);

/**
* @dev Returns the gas limit allowed to be spend by plugin per call.
* @return gasLimit The gas limit allowed to be spend by plugin per call.
*/
function pluginCallGasLimit() external view returns(uint256 gasLimit);

/**
* @dev Returns whether an account has a specific plugin.
* @param account The address of the account.
* @param plugin The address of the plugin.
* @return hasPlugin A boolean indicating whether the account has the specified plugin.
*/
function hasPlugin(address account, address plugin) external view returns(bool hasPlugin);

/**
* @dev Returns the number of plugins registered for an account.
* @param account The address of the account.
* @return count The number of plugins registered for the account.
*/
function pluginsCount(address account) external view returns(uint256 count);

/**
* @dev Returns the address of a plugin at a specified index for a given account.
* @param account The address of the account.
* @param index The index of the plugin to retrieve.
* @return plugin The address of the plugin.
*/
function pluginAt(address account, uint256 index) external view returns(address plugin);

/**
* @dev Returns an array of all plugins owned by a given account.
* @param account The address of the account to query.
* @return plugins An array of plugin addresses.
*/
function plugins(address account) external view returns(address[] memory plugins);

/**
* @dev Returns the balance of a given account if a specified plugin is added or zero.
* @param plugin The address of the plugin to query.
* @param account The address of the account to query.
* @return balance The account balance if the specified plugin is added and zero otherwise.
*/
function pluginBalanceOf(address plugin, address account) external view returns(uint256 balance);

/**
* @dev Adds a new plugin for the calling account.
* @param plugin The address of the plugin to add.
*/
function addPlugin(address plugin) external;

/**
* @dev Removes a plugin for the calling account.
* @param plugin The address of the plugin to remove.
*/
function removePlugin(address plugin) external;

/**
* @dev Removes all plugins for the calling account.
*/
function removeAllPlugins() external;
}
8 changes: 8 additions & 0 deletions contracts/interfaces/IPlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@ import { IERC20Plugins } from "./IERC20Plugins.sol";

interface IPlugin {
function token() external view returns(IERC20Plugins);

/**
* @dev Updates the balances of two addresses in the plugin as a result of any balance changes.
* Only the Token contract is allowed to call this function.
* @param from The address from which tokens were transferred
* @param to The address to which tokens were transferred
* @param amount The amount of tokens transferred
*/
function updateBalances(address from, address to, uint256 amount) external;
}
4 changes: 2 additions & 2 deletions contracts/mocks/ERC20PluginsMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Plugins } from "../ERC20Plugins.sol";

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

function mint(address account, uint256 amount) external {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@1inch/token-plugins",
"version": "1.1.2",
"version": "1.2.0",
"description": "ERC20 extension enabling external smart contract based plugins to track balances of those users who opted-in to those plugins",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions test/behaviors/ERC20Plugins.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ function shouldBehaveLikeERC20Plugins (initContracts) {

it('should not add more plugins than limit', async function () {
const { erc20Plugins, plugins } = await loadFixture(initAndCreatePlugins);
const pluginsCountLimit = await erc20Plugins.pluginsCountLimit();
for (let i = 0; i < pluginsCountLimit; i++) {
const maxPluginsPerAccount = await erc20Plugins.maxPluginsPerAccount();
for (let i = 0; i < maxPluginsPerAccount; i++) {
await erc20Plugins.addPlugin(plugins[i]);
}

Expand Down

0 comments on commit 8c2b804

Please sign in to comment.