Skip to content

Commit 42e0591

Browse files
authored
Merge pull request #32 from zer0-os/feat/token-upgrade
[DRAFT] V1 => V2 Contract Upgrade
2 parents 069f4cc + 3edd214 commit 42e0591

21 files changed

+2065
-85
lines changed

.circleci/config.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: 2.1
2+
3+
orbs:
4+
node: circleci/[email protected]
5+
6+
defaults: &defaults
7+
working_directory: ~/repo
8+
docker:
9+
- image: cimg/node:20.17.0
10+
11+
jobs:
12+
lint and build:
13+
<<: *defaults
14+
steps:
15+
- checkout
16+
- node/install-packages:
17+
pkg-manager: yarn
18+
- run: yarn build
19+
- run: yarn lint
20+
- persist_to_workspace:
21+
root: ~/repo
22+
paths: [.]
23+
workflows:
24+
version: 2
25+
build:
26+
jobs:
27+
- lint and build

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ bin
55
cache
66
artifacts
77
typechain/
8-
.env
8+
*.env*
99

1010
# OpenZeppelin
1111
.openzeppelin/dev-*.json

contracts/ERC20Mock.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MIT
2+
/* solhint-disable */
3+
pragma solidity ^0.8.3;
4+
5+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
6+
7+
/**
8+
* @title ERC20Mock
9+
* @dev Mock contract for testing purposes, allowing minting of tokens. Used as mock of MEOW token,
10+
* which is the main payment token in ZNS.
11+
*/
12+
contract ERC20Mock is ERC20 {
13+
constructor(
14+
string memory name_,
15+
string memory symbol_
16+
) ERC20(name_, symbol_) {}
17+
18+
function mint(address account, uint256 amount) public {
19+
_mint(account, amount);
20+
}
21+
22+
function decimals() public pure override returns (uint8) {
23+
return 6;
24+
}
25+
}

contracts/ZeroDAOTokenV2.sol

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.3;
3+
4+
// Slight modifiations from base Open Zeppelin Contracts
5+
// Consult /oz/README.md for more information
6+
import "./oz/ERC20Upgradeable.sol";
7+
import "./oz/ERC20SnapshotUpgradeable.sol";
8+
import "./oz/ERC20PausableUpgradeable.sol";
9+
10+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
11+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
12+
13+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
14+
15+
contract ZeroDAOTokenV2 is
16+
OwnableUpgradeable,
17+
ERC20Upgradeable,
18+
ERC20PausableUpgradeable,
19+
ERC20SnapshotUpgradeable
20+
{
21+
using SafeERC20 for IERC20;
22+
23+
event AuthorizedSnapshotter(address account);
24+
event DeauthorizedSnapshotter(address account);
25+
event ERC20TokenWithdrawn(
26+
IERC20 indexed token,
27+
address indexed to,
28+
uint256 indexed amount
29+
);
30+
31+
// Mapping which stores all addresses allowed to snapshot
32+
mapping(address => bool) authorizedToSnapshot;
33+
34+
function initialize(
35+
string memory name,
36+
string memory symbol
37+
) public initializer {
38+
__Ownable_init();
39+
__ERC20_init(name, symbol);
40+
__ERC20Snapshot_init();
41+
__ERC20Pausable_init();
42+
}
43+
44+
// Call this on the implementation contract (not the proxy)
45+
function initializeImplementation() public initializer {
46+
__Ownable_init();
47+
_pause();
48+
}
49+
50+
/**
51+
* Mints new tokens.
52+
* @param account the account to mint the tokens for
53+
* @param amount the amount of tokens to mint.
54+
*/
55+
function mint(address account, uint256 amount) external onlyOwner {
56+
_mint(account, amount);
57+
}
58+
59+
/**
60+
* Burns tokens from an address.
61+
* @param account the account to mint the tokens for
62+
* @param amount the amount of tokens to mint.
63+
*/
64+
function burn(address account, uint256 amount) external onlyOwner {
65+
_burn(account, amount);
66+
}
67+
68+
/**
69+
* Pauses the token contract preventing any token mint/transfer/burn operations.
70+
* Can only be called if the contract is unpaused.
71+
*/
72+
function pause() external onlyOwner {
73+
_pause();
74+
}
75+
76+
/**
77+
* Unpauses the token contract preventing any token mint/transfer/burn operations
78+
* Can only be called if the contract is paused.
79+
*/
80+
function unpause() external onlyOwner {
81+
_unpause();
82+
}
83+
84+
/**
85+
* Creates a token balance snapshot. Ideally this would be called by the
86+
* controlling DAO whenever a proposal is made.
87+
*/
88+
function snapshot() external returns (uint256) {
89+
require(
90+
authorizedToSnapshot[_msgSender()] || _msgSender() == owner(),
91+
"zDAOToken: Not authorized to snapshot"
92+
);
93+
return _snapshot();
94+
}
95+
96+
/**
97+
* Authorizes an account to take snapshots
98+
* @param account The account to authorize
99+
*/
100+
function authorizeSnapshotter(address account) external onlyOwner {
101+
require(
102+
!authorizedToSnapshot[account],
103+
"zDAOToken: Account already authorized"
104+
);
105+
106+
authorizedToSnapshot[account] = true;
107+
emit AuthorizedSnapshotter(account);
108+
}
109+
110+
/**
111+
* Deauthorizes an account to take snapshots
112+
* @param account The account to de-authorize
113+
*/
114+
function deauthorizeSnapshotter(address account) external onlyOwner {
115+
require(authorizedToSnapshot[account], "zDAOToken: Account not authorized");
116+
117+
authorizedToSnapshot[account] = false;
118+
emit DeauthorizedSnapshotter(account);
119+
}
120+
121+
/**
122+
* Withdraws ERC20 tokens that are stuck in this contract.
123+
* @param token The ERC20 token contract address to withdraw
124+
* @param to The address to send the tokens to
125+
* @param amount The amount of tokens to withdraw (0 means withdraw all available)
126+
*/
127+
function withdrawERC20(
128+
IERC20 token,
129+
address to,
130+
uint256 amount
131+
) external onlyOwner {
132+
require(
133+
address(token) != address(0),
134+
"zDAOToken: Token address cannot be zero"
135+
);
136+
require(
137+
address(token) != address(this),
138+
"zDAOToken: Token address cannot be this token"
139+
);
140+
require(to != address(0), "zDAOToken: Recipient address cannot be zero");
141+
142+
uint256 withdrawAmount;
143+
if (amount == 0) {
144+
// If amount is 0, withdraw all available tokens
145+
withdrawAmount = token.balanceOf(address(this));
146+
} else {
147+
// Otherwise, ensure the requested amount doesn't exceed the contract's balance
148+
withdrawAmount = amount;
149+
}
150+
151+
token.safeTransfer(to, withdrawAmount);
152+
153+
emit ERC20TokenWithdrawn(token, to, withdrawAmount);
154+
}
155+
156+
/**
157+
* Utility function to transfer tokens to many addresses at once.
158+
* @param recipients The addresses to send tokens to
159+
* @param amount The amount of tokens to send
160+
* @return Boolean if the transfer was a success
161+
*/
162+
function transferBulk(
163+
address[] calldata recipients,
164+
uint256 amount
165+
) external returns (bool) {
166+
address sender = _msgSender();
167+
168+
uint256 total = amount * recipients.length;
169+
require(
170+
_balances[sender] >= total,
171+
"ERC20: transfer amount exceeds balance"
172+
);
173+
174+
require(!paused(), "ERC20Pausable: token transfer while paused");
175+
176+
_balances[sender] -= total;
177+
_updateAccountSnapshot(sender);
178+
179+
for (uint256 i = 0; i < recipients.length; ++i) {
180+
address recipient = recipients[i];
181+
require(recipient != address(0), "ERC20: transfer to the zero address");
182+
183+
// Note: _beforeTokenTransfer isn't called here
184+
// This function emulates what it would do (paused and snapshot)
185+
186+
_balances[recipient] += amount;
187+
188+
_updateAccountSnapshot(recipient);
189+
190+
emit Transfer(sender, recipient, amount);
191+
}
192+
193+
return true;
194+
}
195+
196+
/**
197+
* Utility function to transfer tokens to many addresses at once.
198+
* @param sender The address to send the tokens from
199+
* @param recipients The addresses to send tokens to
200+
* @param amount The amount of tokens to send
201+
* @return Boolean if the transfer was a success
202+
*/
203+
function transferFromBulk(
204+
address sender,
205+
address[] calldata recipients,
206+
uint256 amount
207+
) external returns (bool) {
208+
require(!paused(), "ERC20Pausable: token transfer while paused");
209+
210+
uint256 total = amount * recipients.length;
211+
require(
212+
_balances[sender] >= total,
213+
"ERC20: transfer amount exceeds balance"
214+
);
215+
216+
// Ensure enough allowance
217+
uint256 currentAllowance = _allowances[sender][_msgSender()];
218+
require(
219+
currentAllowance >= total,
220+
"ERC20: transfer total exceeds allowance"
221+
);
222+
_approve(sender, _msgSender(), currentAllowance - total);
223+
224+
_balances[sender] -= total;
225+
_updateAccountSnapshot(sender);
226+
227+
for (uint256 i = 0; i < recipients.length; ++i) {
228+
address recipient = recipients[i];
229+
require(recipient != address(0), "ERC20: transfer to the zero address");
230+
231+
// Note: _beforeTokenTransfer isn't called here
232+
// This function emulates what it would do (paused and snapshot)
233+
234+
_balances[recipient] += amount;
235+
236+
_updateAccountSnapshot(recipient);
237+
238+
emit Transfer(sender, recipient, amount);
239+
}
240+
241+
return true;
242+
}
243+
244+
function _beforeTokenTransfer(
245+
address from,
246+
address to,
247+
uint256 amount
248+
)
249+
internal
250+
virtual
251+
override(
252+
ERC20PausableUpgradeable,
253+
ERC20SnapshotUpgradeable,
254+
ERC20Upgradeable
255+
)
256+
{
257+
super._beforeTokenTransfer(from, to, amount);
258+
}
259+
}

0 commit comments

Comments
 (0)