-
Notifications
You must be signed in to change notification settings - Fork 7
/
SOAS.sol
264 lines (219 loc) · 7.73 KB
/
SOAS.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// SPDX-License-Identifier: MIT
pragma solidity 0.8.12;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// Invalid mint destinaition.
error InvalidDestination();
// Already claimer address.
error AlreadyClaimer();
// Invalid since or until.
error InvalidClaimPeriod();
// OAS is zero.
error NoAmount();
// Invalid minter address.
error InvalidMinter();
// Over claimable OAS.
error OverAmount();
// OAS transfer failed.
error TransferFailed();
// Cannot renounce.
error CannotRenounce();
// Only staking contracts or burn are allowed.
error UnauthorizedTransfer();
/**
* @title SOAS
* @dev The SOAS is non-transferable but stakable token.
* It is possible to gradually convert from since to until period to OAS.
*/
contract SOAS is ERC20 {
/**********
* Struct *
**********/
struct ClaimInfo {
uint256 amount;
uint256 claimed;
uint64 since;
uint64 until;
address from;
}
/**********************
* Contract Variables *
**********************/
address[] public allowedAddresses;
mapping(address => ClaimInfo) public claimInfo;
mapping(address => address) public originalClaimer;
/**********
* Events *
**********/
event Mint(address indexed to, uint256 amount, uint256 since, uint256 until);
event Claim(address indexed holder, uint256 amount);
event Renounce(address indexed holder, uint256 amount);
event Allow(address indexed original, address indexed transferable);
/***************
* Constructor *
***************/
/**
* @param _allowedAddresses List of the preallowed contract address.
*/
constructor(address[] memory _allowedAddresses) ERC20("Stakable OAS", "SOAS") {
allowedAddresses = _allowedAddresses;
}
/********************
* Public Functions *
********************/
/**
* Mint the SOAS by depositing the OAS.
* @param to Destination address for the SOAS.
* @param since Unixtime to start converting the SOAS to the OAS.
* @param until Unixtime when all the SOAS can be converted to the OAS
*/
function mint(
address to,
uint64 since,
uint64 until
) external payable {
if (to == address(0) || _contains(allowedAddresses, to)) revert InvalidDestination();
if (originalClaimer[to] != address(0)) revert AlreadyClaimer();
if (since <= block.timestamp || since >= until) revert InvalidClaimPeriod();
if (msg.value == 0) revert NoAmount();
_mint(to, msg.value);
claimInfo[to] = ClaimInfo(msg.value, 0, since, until, msg.sender);
originalClaimer[to] = to;
emit Mint(to, msg.value, since, until);
}
/**
* Allow the transferable address for the claimer address.
* @param original Address of the claimer.
* @param allowed Transferable address.
*/
function allow(address original, address allowed) external {
if (claimInfo[original].from != msg.sender) revert InvalidMinter();
_allow(original, allowed);
}
/**
* Bulk allow
* @param original Address of the claimer.
* @param alloweds List of allowed address.
*/
function allow(address original, address[] memory alloweds) external {
if (claimInfo[original].from != msg.sender) revert InvalidMinter();
for (uint256 i; i < alloweds.length; i++) {
_allow(original, alloweds[i]);
}
}
/**
* Convert the SOAS to the OAS.
* @param amount Amount of the SOAS.
*/
function claim(uint256 amount) external {
if (amount == 0) revert NoAmount();
ClaimInfo storage originalClaimInfo = claimInfo[originalClaimer[msg.sender]];
uint256 currentClaimableOAS = getClaimableOAS(originalClaimer[msg.sender]) -
originalClaimInfo.claimed;
if (amount > currentClaimableOAS) revert OverAmount();
originalClaimInfo.claimed += amount;
_burn(msg.sender, amount);
(bool success, ) = msg.sender.call{ value: amount }("");
if (!success) revert TransferFailed();
emit Claim(originalClaimer[msg.sender], amount);
}
/**
* Return the SOAS as the OAS to the address that minted it.
* @param amount Amount of the SOAS.
*/
function renounce(uint256 amount) external {
if (amount == 0) revert NoAmount();
ClaimInfo storage originalClaimInfo = claimInfo[originalClaimer[msg.sender]];
if (amount > originalClaimInfo.amount - originalClaimInfo.claimed) revert OverAmount();
_burn(msg.sender, amount);
(bool success, ) = originalClaimInfo.from.call{ value: amount }("");
if (!success) revert TransferFailed();
emit Renounce(originalClaimer[msg.sender], amount);
}
/**
* Bulk transfer
* @param tos List of receipient address.
* @param amounts List of amount.
*/
function transfer(address[] memory tos, uint256[] memory amounts) public returns (bool) {
require(tos.length == amounts.length, "SOAS: bulk transfer args must be equals");
address owner = _msgSender();
for (uint256 i; i < tos.length; i++) {
_transfer(owner, tos[i], amounts[i]);
}
return true;
}
/**
* Bulk transferFrom
* @param froms List of sender address.
* @param tos List of receipient address.
* @param amounts List of amount.
*/
function transferFrom(
address[] memory froms,
address[] memory tos,
uint256[] memory amounts
) public returns (bool) {
require(
froms.length == tos.length && tos.length == amounts.length,
"SOAS: bulk transferFrom args must be equals"
);
for (uint256 i; i < froms.length; i++) {
transferFrom(froms[i], tos[i], amounts[i]);
}
return true;
}
/**
* Get current amount of the SOAS available for conversion.
* @param original Holder of the SOAS token.
*/
function getClaimableOAS(address original) public view returns (uint256) {
ClaimInfo memory originalClaimInfo = claimInfo[original];
if (originalClaimInfo.amount == 0) {
return 0;
}
if (block.timestamp < originalClaimInfo.since) {
return 0;
}
uint256 amount = (originalClaimInfo.amount * (block.timestamp - originalClaimInfo.since)) /
(originalClaimInfo.until - originalClaimInfo.since);
if (amount > originalClaimInfo.amount) {
return originalClaimInfo.amount;
}
return amount;
}
/**********************
* Internal Functions *
**********************/
/**
* The SOAS is allowed to mint, burn and transfer with the Staking contract.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal view override {
if (from == address(0) || to == address(0)) return;
if (_contains(allowedAddresses, from) || _contains(allowedAddresses, to)) return;
if (originalClaimer[from] == originalClaimer[to]) return;
revert UnauthorizedTransfer();
}
/**
* Whether list of the address contains the item address.
*/
function _contains(address[] memory list, address item) internal pure returns (bool) {
for (uint256 index = 0; index < list.length; index++) {
if (list[index] == item) {
return true;
}
}
return false;
}
/**
* Allow the transferable address for the claimer address.
*/
function _allow(address original, address allowed) internal {
if (originalClaimer[allowed] != address(0)) revert AlreadyClaimer();
originalClaimer[allowed] = original;
emit Allow(original, allowed);
}
}