Skip to content

Commit d7073bc

Browse files
authored
Merge pull request #26 from 1inch/feature/rename-pods-to-plugins
Rename Pods to Plugins
2 parents 9a37c5a + e5744cc commit d7073bc

16 files changed

+791
-788
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
# ERC20Pods
1+
# ERC20Plugins
22

3-
[![Build Status](https://github.com/1inch/erc20-pods/workflows/CI/badge.svg)](https://github.com/1inch/erc20-pods/actions)
4-
[![Coverage Status](https://codecov.io/gh/1inch/erc20-pods/branch/master/graph/badge.svg?token=Z3D5O3XUYV)](https://codecov.io/gh/1inch/erc20-pods)
5-
[![NPM Package](https://img.shields.io/npm/v/@1inch/erc20-pods.svg)](https://www.npmjs.org/package/@1inch/erc20-pods)
3+
[![Build Status](https://github.com/1inch/erc20-plugins/workflows/CI/badge.svg)](https://github.com/1inch/erc20-plugins/actions)
4+
[![Coverage Status](https://codecov.io/gh/1inch/erc20-plugins/branch/master/graph/badge.svg?token=Z3D5O3XUYV)](https://codecov.io/gh/1inch/erc20-plugins)
5+
[![NPM Package](https://img.shields.io/npm/v/@1inch/erc20-plugins.svg)](https://www.npmjs.org/package/@1inch/erc20-plugins)
66

7-
ERC20 extension enabling external smart contract based Pods to track balances of those users who opted-in to these Pods.
7+
ERC20 extension enabling external smart contract based plugins to track balances of those users who opted-in to these plugins.
88

9-
Examples of ERC20 Pods:
10-
- [FarmingPod](https://github.com/1inch/farming)
11-
- [DelegatingPod](https://github.com/1inch/delegating)
9+
Examples of ERC20 plugins:
10+
- [FarmingPlugin](https://github.com/1inch/farming)
11+
- [DelegatingPlugin](https://github.com/1inch/delegating)
1212

1313
Usage:
14-
- Inherit your tokens or tokenized protocol shares from `ERC20Pods`
14+
- Inherit your tokens or tokenized protocol shares from `ERC20Plugins`
1515

1616
Contribution:
1717
- Install dependencies: `yarn`

contracts/ERC20Plugins.sol

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
import { IERC20, ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
6+
import { AddressSet, AddressArray } from "@1inch/solidity-utils/contracts/libraries/AddressSet.sol";
7+
8+
import { IERC20Plugins } from "./interfaces/IERC20Plugins.sol";
9+
import { IPlugin } from "./interfaces/IPlugin.sol";
10+
import { ReentrancyGuardExt, ReentrancyGuardLib } from "./libs/ReentrancyGuard.sol";
11+
12+
/**
13+
* @title ERC20Plugins
14+
* @dev A base implementation of token contract to hold and manage plugins of an ERC20 token with a limited number of plugins per account.
15+
* Each plugin is a contract that implements IPlugin interface (and/or derived from plugin).
16+
*/
17+
abstract contract ERC20Plugins is ERC20, IERC20Plugins, ReentrancyGuardExt {
18+
using AddressSet for AddressSet.Data;
19+
using AddressArray for AddressArray.Data;
20+
using ReentrancyGuardLib for ReentrancyGuardLib.Data;
21+
22+
error PluginAlreadyAdded();
23+
error PluginNotFound();
24+
error InvalidPluginAddress();
25+
error InvalidTokenInPlugin();
26+
error PluginsLimitReachedForAccount();
27+
error ZeroPluginsLimit();
28+
29+
/// @dev Limit of plugins per account
30+
uint256 public immutable pluginsCountLimit;
31+
/// @dev Gas limit for a single plugin call
32+
uint256 public immutable pluginsCallGasLimit;
33+
34+
ReentrancyGuardLib.Data private _guard;
35+
mapping(address => AddressSet.Data) private _plugins;
36+
37+
/**
38+
* @dev Constructor that sets the limit of plugins per account and the gas limit for a plugin call.
39+
* @param pluginsLimit_ The limit of plugins per account.
40+
* @param pluginCallGasLimit_ The gas limit for a plugin call. Intended to prevent gas bomb attacks
41+
*/
42+
constructor(uint256 pluginsLimit_, uint256 pluginCallGasLimit_) {
43+
if (pluginsLimit_ == 0) revert ZeroPluginsLimit();
44+
pluginsCountLimit = pluginsLimit_;
45+
pluginsCallGasLimit = pluginCallGasLimit_;
46+
_guard.init();
47+
}
48+
49+
/**
50+
* @dev Returns whether an account has a specific plugin.
51+
* @param account The address of the account.
52+
* @param plugin The address of the plugin.
53+
* @return bool A boolean indicating whether the account has the specified plugin.
54+
*/
55+
function hasPlugin(address account, address plugin) public view virtual returns(bool) {
56+
return _plugins[account].contains(plugin);
57+
}
58+
59+
/**
60+
* @dev Returns the number of plugins registered for an account.
61+
* @param account The address of the account.
62+
* @return uint256 A number of plugins registered for the account.
63+
*/
64+
function pluginsCount(address account) public view virtual returns(uint256) {
65+
return _plugins[account].length();
66+
}
67+
68+
/**
69+
* @dev Returns the address of a plugin at a specified index for a given account .
70+
* @param account The address of the account.
71+
* @param index The index of the plugin to retrieve.
72+
* @return plugin The address of the plugin.
73+
*/
74+
function pluginAt(address account, uint256 index) public view virtual returns(address) {
75+
return _plugins[account].at(index);
76+
}
77+
78+
/**
79+
* @dev Returns an array of all plugins owned by a given account.
80+
* @param account The address of the account to query.
81+
* @return plugins An array of plugin addresses.
82+
*/
83+
function plugins(address account) public view virtual returns(address[] memory) {
84+
return _plugins[account].items.get();
85+
}
86+
87+
88+
/**
89+
* @dev Returns the balance of a given account.
90+
* @param account The address of the account.
91+
* @return balance The account balance.
92+
*/
93+
function balanceOf(address account) public nonReentrantView(_guard) view override(IERC20, ERC20) virtual returns(uint256) {
94+
return super.balanceOf(account);
95+
}
96+
97+
/**
98+
* @dev Returns the balance of a given account if a specified plugin is added or zero.
99+
* @param plugin The address of the plugin to query.
100+
* @param account The address of the account to query.
101+
* @return balance The account balance if the specified plugin is added and zero otherwise.
102+
*/
103+
function pluginBalanceOf(address plugin, address account) public nonReentrantView(_guard) view virtual returns(uint256) {
104+
if (hasPlugin(account, plugin)) {
105+
return super.balanceOf(account);
106+
}
107+
return 0;
108+
}
109+
110+
/**
111+
* @dev Adds a new plugin for the calling account.
112+
* @param plugin The address of the plugin to add.
113+
*/
114+
function addPlugin(address plugin) public virtual {
115+
_addPlugin(msg.sender, plugin);
116+
}
117+
118+
/**
119+
* @dev Removes a plugin for the calling account.
120+
* @param plugin The address of the plugin to remove.
121+
*/
122+
function removePlugin(address plugin) public virtual {
123+
_removePlugin(msg.sender, plugin);
124+
}
125+
126+
/**
127+
* @dev Removes all plugins for the calling account.
128+
*/
129+
function removeAllPlugins() public virtual {
130+
_removeAllPlugins(msg.sender);
131+
}
132+
133+
function _addPlugin(address account, address plugin) internal virtual {
134+
if (plugin == address(0)) revert InvalidPluginAddress();
135+
if (IPlugin(plugin).token() != IERC20Plugins(address(this))) revert InvalidTokenInPlugin();
136+
if (!_plugins[account].add(plugin)) revert PluginAlreadyAdded();
137+
if (_plugins[account].length() > pluginsCountLimit) revert PluginsLimitReachedForAccount();
138+
139+
emit PluginAdded(account, plugin);
140+
uint256 balance = balanceOf(account);
141+
if (balance > 0) {
142+
_updateBalances(plugin, address(0), account, balance);
143+
}
144+
}
145+
146+
function _removePlugin(address account, address plugin) internal virtual {
147+
if (!_plugins[account].remove(plugin)) revert PluginNotFound();
148+
149+
emit PluginRemoved(account, plugin);
150+
uint256 balance = balanceOf(account);
151+
if (balance > 0) {
152+
_updateBalances(plugin, account, address(0), balance);
153+
}
154+
}
155+
156+
function _removeAllPlugins(address account) internal virtual {
157+
address[] memory items = _plugins[account].items.get();
158+
uint256 balance = balanceOf(account);
159+
unchecked {
160+
for (uint256 i = items.length; i > 0; i--) {
161+
_plugins[account].remove(items[i - 1]);
162+
emit PluginRemoved(account, items[i - 1]);
163+
if (balance > 0) {
164+
_updateBalances(items[i - 1], account, address(0), balance);
165+
}
166+
}
167+
}
168+
}
169+
170+
/// @notice Assembly implementation of the gas limited call to avoid return gas bomb,
171+
// moreover call to a destructed plugin would also revert even inside try-catch block in Solidity 0.8.17
172+
/// @dev try IPlugin(plugin).updateBalances{gas: _PLUGIN_CALL_GAS_LIMIT}(from, to, amount) {} catch {}
173+
function _updateBalances(address plugin, address from, address to, uint256 amount) private {
174+
bytes4 selector = IPlugin.updateBalances.selector;
175+
uint256 gasLimit = pluginsCallGasLimit;
176+
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
177+
let ptr := mload(0x40)
178+
mstore(ptr, selector)
179+
mstore(add(ptr, 0x04), from)
180+
mstore(add(ptr, 0x24), to)
181+
mstore(add(ptr, 0x44), amount)
182+
183+
let gasLeft := gas()
184+
if iszero(call(gasLimit, plugin, 0, ptr, 0x64, 0, 0)) {
185+
if lt(div(mul(gasLeft, 63), 64), gasLimit) {
186+
returndatacopy(ptr, 0, returndatasize())
187+
revert(ptr, returndatasize())
188+
}
189+
}
190+
}
191+
}
192+
193+
// ERC20 Overrides
194+
195+
function _afterTokenTransfer(address from, address to, uint256 amount) internal nonReentrant(_guard) override virtual {
196+
super._afterTokenTransfer(from, to, amount);
197+
198+
unchecked {
199+
if (amount > 0 && from != to) {
200+
address[] memory a = _plugins[from].items.get();
201+
address[] memory b = _plugins[to].items.get();
202+
uint256 aLength = a.length;
203+
uint256 bLength = b.length;
204+
205+
for (uint256 i = 0; i < aLength; i++) {
206+
address plugin = a[i];
207+
208+
uint256 j;
209+
for (j = 0; j < bLength; j++) {
210+
if (plugin == b[j]) {
211+
// Both parties are participating of the same plugin
212+
_updateBalances(plugin, from, to, amount);
213+
b[j] = address(0);
214+
break;
215+
}
216+
}
217+
218+
if (j == bLength) {
219+
// Sender is participating in a plugin, but receiver is not
220+
_updateBalances(plugin, from, address(0), amount);
221+
}
222+
}
223+
224+
for (uint256 j = 0; j < bLength; j++) {
225+
address plugin = b[j];
226+
if (plugin != address(0)) {
227+
// Receiver is participating in a plugin, but sender is not
228+
_updateBalances(plugin, address(0), to, amount);
229+
}
230+
}
231+
}
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)