-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathrOUSG.sol
661 lines (593 loc) · 21 KB
/
rOUSG.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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
/**SPDX-License-Identifier: BUSL-1.1
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
import "contracts/external/openzeppelin/contracts/token/IERC20.sol";
import "contracts/external/openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "contracts/external/openzeppelin/contracts-upgradeable/token/ERC20/IERC20MetadataUpgradeable.sol";
import "contracts/external/openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
import "contracts/external/openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "contracts/external/openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "contracts/external/openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import "contracts/kyc/KYCRegistryClientUpgradeable.sol";
import "contracts/rwaOracles/IRWAOracle.sol";
/**
* @title Interest-bearing ERC20-like token for OUSG.
*
* rOUSG balances are dynamic and represent the holder's share of the underlying OUSG
* controlled by the protocol. To calculate each account's balance, we do
*
* shares[account] * ousgPrice
*
* For example, assume that we have:
*
* ousgPrice = 100.505
* sharesOf(user1) -> 100
* sharesOf(user2) -> 400
*
* Therefore:
*
* balanceOf(user1) -> 105 tokens which corresponds 105 rOUSG
* balanceOf(user2) -> 420 tokens which corresponds 420 rOUSG
*
* Since balances of all token holders change when the price of OUSG changes, this
* token cannot fully implement ERC20 standard: it only emits `Transfer` events
* upon explicit transfer between holders. In contrast, when total amount of pooled
* Cash increases, no `Transfer` events are generated: doing so would require emitting
* an event for each token holder and thus running an unbounded loop.
*
*/
contract ROUSG is
Initializable,
ContextUpgradeable,
PausableUpgradeable,
AccessControlEnumerableUpgradeable,
KYCRegistryClientUpgradeable,
IERC20Upgradeable,
IERC20MetadataUpgradeable
{
/**
* @dev rOUSG balances are dynamic and are calculated based on the accounts' shares (OUSG)
* and the the price of OUSG. Account shares aren't
* normalized, so the contract also stores the sum of all shares to calculate
* each account's token balance which equals to:
*
* shares[account] * ousgPrice
*/
mapping(address => uint256) private shares;
/// @dev Allowances are nominated in tokens, not token shares.
mapping(address => mapping(address => uint256)) private allowances;
// Total shares in existence
uint256 private totalShares;
// Address of the oracle that provides the `ousgPrice`
IRWAOracle public oracle;
// Address of the OUSG token
IERC20 public ousg;
// Used to scale up ousg amount -> shares
uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000;
// Error when redeeming shares < `OUSG_TO_ROUSG_SHARES_MULTIPLIER`
error UnwrapTooSmall();
/// @dev Role based access control roles
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURN_ROLE");
bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
address _kycRegistry,
uint256 requirementGroup,
address _ousg,
address guardian,
address _oracle
) public virtual initializer {
__rOUSG_init(_kycRegistry, requirementGroup, _ousg, guardian, _oracle);
}
function __rOUSG_init(
address _kycRegistry,
uint256 requirementGroup,
address _ousg,
address guardian,
address _oracle
) internal onlyInitializing {
__rOUSG_init_unchained(
_kycRegistry,
requirementGroup,
_ousg,
guardian,
_oracle
);
}
function __rOUSG_init_unchained(
address _kycRegistry,
uint256 _requirementGroup,
address _ousg,
address guardian,
address _oracle
) internal onlyInitializing {
__KYCRegistryClientInitializable_init(_kycRegistry, _requirementGroup);
ousg = IERC20(_ousg);
oracle = IRWAOracle(_oracle);
_grantRole(DEFAULT_ADMIN_ROLE, guardian);
_grantRole(PAUSER_ROLE, guardian);
_grantRole(BURNER_ROLE, guardian);
_grantRole(CONFIGURER_ROLE, guardian);
}
/**
* @notice An executed shares transfer from `sender` to `recipient`.
*
* @dev emitted in pair with an ERC20-defined `Transfer` event.
*/
event TransferShares(
address indexed from,
address indexed to,
uint256 sharesValue
);
/**
* @notice Emitted when the oracle address is set
*
* @param oldOracle The address of the old oracle
* @param newOracle The address of the new oracle
*/
event OracleSet(address indexed oldOracle, address indexed newOracle);
/**
* @return the name of the token.
*/
function name() public pure returns (string memory) {
return "Rebasing OUSG";
}
/**
* @return the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public pure returns (string memory) {
return "rOUSG";
}
/**
* @return the number of decimals for getting user representation of a token amount.
*/
function decimals() public pure returns (uint8) {
return 18;
}
/**
* @return the amount of tokens in existence.
*/
function totalSupply() public view returns (uint256) {
return
(totalShares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER);
}
/**
* @return the amount of tokens owned by the `_account`.
*
* @dev Balances are dynamic and equal the `_account`'s OUSG shares multiplied
* by the price of OUSG
*/
function balanceOf(address _account) public view returns (uint256) {
return
(_sharesOf(_account) * getOUSGPrice()) /
(1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER);
}
/**
* @notice Moves `_amount` tokens from the caller's account to the `_recipient` account.
*
* @return a boolean value indicating whether the operation succeeded.
* Emits a `Transfer` event.
* Emits a `TransferShares` event.
*
* Requirements:
*
* - `_recipient` cannot be the zero address.
* - the caller must have a balance of at least `_amount`.
* - the contract must not be paused.
*
* @dev The `_amount` argument is the amount of tokens, not shares.
*/
function transfer(address _recipient, uint256 _amount) public returns (bool) {
_transfer(msg.sender, _recipient, _amount);
return true;
}
/**
* @return the remaining number of tokens that `_spender` is allowed to spend
* on behalf of `_owner` through `transferFrom`. This is zero by default.
*
* @dev This value changes when `approve` or `transferFrom` is called.
*/
function allowance(
address _owner,
address _spender
) public view returns (uint256) {
return allowances[_owner][_spender];
}
/**
* @notice Sets `_amount` as the allowance of `_spender` over the caller's tokens.
*
* @return a boolean value indicating whether the operation succeeded.
* Emits an `Approval` event.
*
* Requirements:
*
* - `_spender` cannot be the zero address.
* - the contract must not be paused.
*
* @dev The `_amount` argument is the amount of tokens, not shares.
*/
function approve(address _spender, uint256 _amount) public returns (bool) {
_approve(msg.sender, _spender, _amount);
return true;
}
/**
* @notice Moves `_amount` tokens from `_sender` to `_recipient` using the
* allowance mechanism. `_amount` is then deducted from the caller's
* allowance.
*
* @return a boolean value indicating whether the operation succeeded.
*
* Emits a `Transfer` event.
* Emits a `TransferShares` event.
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `_sender` and `_recipient` cannot be the zero addresses.
* - `_sender` must have a balance of at least `_amount`.
* - the caller must have allowance for `_sender`'s tokens of at least `_amount`.
* - the contract must not be paused.
*
* @dev The `_amount` argument is the amount of tokens, not shares.
*/
function transferFrom(
address _sender,
address _recipient,
uint256 _amount
) public returns (bool) {
uint256 currentAllowance = allowances[_sender][msg.sender];
require(currentAllowance >= _amount, "TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE");
_transfer(_sender, _recipient, _amount);
_approve(_sender, msg.sender, currentAllowance - _amount);
return true;
}
/**
* @notice Atomically increases the allowance granted to `_spender` by the caller by `_addedValue`.
*
* This is an alternative to `approve` that can be used as a mitigation for
* problems described in:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `_spender` cannot be the the zero address.
* - the contract must not be paused.
*/
function increaseAllowance(
address _spender,
uint256 _addedValue
) public returns (bool) {
_approve(
msg.sender,
_spender,
allowances[msg.sender][_spender] + _addedValue
);
return true;
}
/**
* @notice Atomically decreases the allowance granted to `_spender` by the caller by `_subtractedValue`.
*
* This is an alternative to `approve` that can be used as a mitigation for
* problems described in:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `_spender` cannot be the zero address.
* - `_spender` must have allowance for the caller of at least `_subtractedValue`.
* - the contract must not be paused.
*/
function decreaseAllowance(
address _spender,
uint256 _subtractedValue
) public returns (bool) {
uint256 currentAllowance = allowances[msg.sender][_spender];
require(
currentAllowance >= _subtractedValue,
"DECREASED_ALLOWANCE_BELOW_ZERO"
);
_approve(msg.sender, _spender, currentAllowance - _subtractedValue);
return true;
}
/**
* @return the total amount of shares in existence.
*
* @dev The sum of all accounts' shares can be an arbitrary number, therefore
* it is necessary to store it in order to calculate each account's relative share.
*/
function getTotalShares() public view returns (uint256) {
return totalShares;
}
/**
* @return the amount of shares owned by `_account`.
*
* @dev This is the equivalent to the amount of OUSG wrapped by `_account`.
*/
function sharesOf(address _account) public view returns (uint256) {
return _sharesOf(_account);
}
/**
* @return the amount of shares that corresponds to `_rOUSGAmount` of rOUSG
*/
function getSharesByROUSG(
uint256 _rOUSGAmount
) public view returns (uint256) {
return
(_rOUSGAmount * 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER) / getOUSGPrice();
}
/**
* @return the amount of rOUSG that corresponds to `_shares` of OUSG.
*/
function getROUSGByShares(uint256 _shares) public view returns (uint256) {
return
(_shares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER);
}
function getOUSGPrice() public view returns (uint256 price) {
(price, ) = oracle.getPriceData();
}
/**
* @notice Moves `_sharesAmount` token shares from the caller's account to the `_recipient` account.
*
* @return amount of transferred tokens.
* Emits a `TransferShares` event.
* Emits a `Transfer` event.
*
* Requirements:
*
* - `_recipient` cannot be the zero address.
* - the caller must have at least `_sharesAmount` shares.
* - the contract must not be paused.
*
* @dev The `_sharesAmount` argument is the amount of shares, not tokens.
*/
function transferShares(
address _recipient,
uint256 _sharesAmount
) public returns (uint256) {
_transferShares(msg.sender, _recipient, _sharesAmount);
emit TransferShares(msg.sender, _recipient, _sharesAmount);
uint256 tokensAmount = getROUSGByShares(_sharesAmount);
emit Transfer(msg.sender, _recipient, tokensAmount);
return tokensAmount;
}
/**
* @notice Function called by users to wrap their OUSG tokens
*
* @param _OUSGAmount The amount of OUSG Tokens to wrap
*
* @dev KYC checks implicit in OUSG Transfer
*/
function wrap(uint256 _OUSGAmount) external whenNotPaused {
require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens");
uint256 ousgSharesAmount = _OUSGAmount * OUSG_TO_ROUSG_SHARES_MULTIPLIER;
_mintShares(msg.sender, ousgSharesAmount);
ousg.transferFrom(msg.sender, address(this), _OUSGAmount);
emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount));
emit TransferShares(address(0), msg.sender, ousgSharesAmount);
}
/**
* @notice Function called by users to unwrap their rOUSG tokens
*
* @param _rOUSGAmount The amount of rOUSG to unwrap
*
* @dev KYC checks implicit in OUSG Transfer
*/
function unwrap(uint256 _rOUSGAmount) external whenNotPaused {
require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens");
uint256 ousgSharesAmount = getSharesByROUSG(_rOUSGAmount);
if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
revert UnwrapTooSmall();
_burnShares(msg.sender, ousgSharesAmount);
ousg.transfer(
msg.sender,
ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
);
emit Transfer(msg.sender, address(0), _rOUSGAmount);
emit TransferShares(msg.sender, address(0), ousgSharesAmount);
}
/**
* @notice Moves `_amount` tokens from `_sender` to `_recipient`.
* Emits a `Transfer` event.
* Emits a `TransferShares` event.
*/
function _transfer(
address _sender,
address _recipient,
uint256 _amount
) internal {
uint256 _sharesToTransfer = getSharesByROUSG(_amount);
_transferShares(_sender, _recipient, _sharesToTransfer);
emit Transfer(_sender, _recipient, _amount);
emit TransferShares(_sender, _recipient, _sharesToTransfer);
}
/**
* @notice Sets `_amount` as the allowance of `_spender` over the `_owner` s tokens.
*
* Emits an `Approval` event.
*
* Requirements:
*
* - `_owner` cannot be the zero address.
* - `_spender` cannot be the zero address.
* - the contract must not be paused.
*/
function _approve(
address _owner,
address _spender,
uint256 _amount
) internal whenNotPaused {
require(_owner != address(0), "APPROVE_FROM_ZERO_ADDRESS");
require(_spender != address(0), "APPROVE_TO_ZERO_ADDRESS");
allowances[_owner][_spender] = _amount;
emit Approval(_owner, _spender, _amount);
}
/**
* @return the amount of shares owned by `_account`.
*/
function _sharesOf(address _account) internal view returns (uint256) {
return shares[_account];
}
/**
* @notice Moves `_sharesAmount` shares from `_sender` to `_recipient`.
*
* Requirements:
*
* - `_sender` cannot be the zero address.
* - `_recipient` cannot be the zero address.
* - `_sender` must hold at least `_sharesAmount` shares.
* - the contract must not be paused.
*/
function _transferShares(
address _sender,
address _recipient,
uint256 _sharesAmount
) internal whenNotPaused {
require(_sender != address(0), "TRANSFER_FROM_THE_ZERO_ADDRESS");
require(_recipient != address(0), "TRANSFER_TO_THE_ZERO_ADDRESS");
_beforeTokenTransfer(_sender, _recipient, _sharesAmount);
uint256 currentSenderShares = shares[_sender];
require(
_sharesAmount <= currentSenderShares,
"TRANSFER_AMOUNT_EXCEEDS_BALANCE"
);
shares[_sender] = currentSenderShares - _sharesAmount;
shares[_recipient] = shares[_recipient] + _sharesAmount;
}
/**
* @notice Creates `_sharesAmount` shares and assigns them to `_recipient`, increasing the total amount of shares.
*
* Requirements:
*
* - `_recipient` cannot be the zero address.
* - the contract must not be paused.
*/
function _mintShares(
address _recipient,
uint256 _sharesAmount
) internal whenNotPaused returns (uint256) {
require(_recipient != address(0), "MINT_TO_THE_ZERO_ADDRESS");
_beforeTokenTransfer(address(0), _recipient, _sharesAmount);
totalShares += _sharesAmount;
shares[_recipient] = shares[_recipient] + _sharesAmount;
return totalShares;
}
/**
* @notice Destroys `_sharesAmount` shares from `_account`'s holdings, decreasing the total amount of shares.
* @dev This doesn't decrease the token total supply.
*
* Requirements:
*
* - `_account` cannot be the zero address.
* - `_account` must hold at least `_sharesAmount` shares.
* - the contract must not be paused.
*/
function _burnShares(
address _account,
uint256 _sharesAmount
) internal whenNotPaused returns (uint256) {
require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS");
_beforeTokenTransfer(_account, address(0), _sharesAmount);
uint256 accountShares = shares[_account];
require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE");
totalShares -= _sharesAmount;
shares[_account] = accountShares - _sharesAmount;
return totalShares;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256
) internal view {
// Check constraints when `transferFrom` is called to facliitate
// a transfer between two parties that are not `from` or `to`.
if (from != msg.sender && to != msg.sender) {
require(_getKYCStatus(msg.sender), "rOUSG: 'sender' address not KYC'd");
}
if (from != address(0)) {
// If not minting
require(_getKYCStatus(from), "rOUSG: 'from' address not KYC'd");
}
if (to != address(0)) {
// If not burning
require(_getKYCStatus(to), "rOUSG: 'to' address not KYC'd");
}
}
/**
* @notice Sets the Oracle address
* @dev The new oracle must comply with the IRWAOracle interface
* @param _oracle Address of the new oracle
*/
function setOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) {
emit OracleSet(address(oracle), _oracle);
oracle = IRWAOracle(_oracle);
}
/**
* @notice Admin burn function to burn rOUSG tokens from any account
* @param _account The account to burn tokens from
* @param _amount The amount of rOUSG tokens to burn
* @dev Transfers burned shares (OUSG) to `msg.sender`
*/
function burn(
address _account,
uint256 _amount
) external onlyRole(BURNER_ROLE) {
uint256 ousgSharesAmount = getSharesByROUSG(_amount);
if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
revert UnwrapTooSmall();
_burnShares(_account, ousgSharesAmount);
ousg.transfer(
msg.sender,
ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
);
emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount));
emit TransferShares(_account, address(0), ousgSharesAmount);
}
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
function setKYCRegistry(
address registry
) external override onlyRole(CONFIGURER_ROLE) {
_setKYCRegistry(registry);
}
function setKYCRequirementGroup(
uint256 group
) external override onlyRole(CONFIGURER_ROLE) {
_setKYCRequirementGroup(group);
}
}