Skip to content

Commit 473889e

Browse files
author
Michał Sieczkowski
authored
🌳 Add TrueUSDWithPoR contract to contracts-por (#1195)
1 parent 986fefb commit 473889e

16 files changed

+852
-36
lines changed

packages/contracts-por/.compiler.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.8.16",
2+
"version": "0.6.10",
33
"settings": {
44
"optimizer": {
55
"enabled": true,

packages/contracts-por/contracts/DummyContract.sol

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.6.10;
3+
4+
import {BurnableTokenWithBounds} from "./common/BurnableTokenWithBounds.sol";
5+
6+
/**
7+
* @title TrueCurrency
8+
* @dev TrueCurrency is an ERC20 with blacklist & redemption addresses
9+
*
10+
* TrueCurrency is a compliant stablecoin with blacklist and redemption
11+
* addresses. Only the owner can blacklist accounts. Redemption addresses
12+
* are assigned automatically to the first 0x100000 addresses. Sending
13+
* tokens to the redemption address will trigger a burn operation. Only
14+
* the owner can mint or blacklist accounts.
15+
*
16+
* This contract is owned by the TokenController, which manages token
17+
* minting & admin functionality. See TokenController.sol
18+
*
19+
* See also: BurnableTokenWithBounds.sol
20+
*
21+
* ~~~~ Features ~~~~
22+
*
23+
* Redemption Addresses
24+
* - The first 0x100000 addresses are redemption addresses
25+
* - Tokens sent to redemption addresses are burned
26+
* - Redemptions are tracked off-chain
27+
* - Cannot mint tokens to redemption addresses
28+
*
29+
* Blacklist
30+
* - Owner can blacklist accounts in accordance with local regulatory bodies
31+
* - Only a court order will merit a blacklist; blacklisting is extremely rare
32+
*
33+
* Burn Bounds & CanBurn
34+
* - Owner can set min & max burn amounts
35+
* - Only accounts flagged in canBurn are allowed to burn tokens
36+
* - canBurn prevents tokens from being sent to the incorrect address
37+
*
38+
* Reclaimer Token
39+
* - ERC20 Tokens and Ether sent to this contract can be reclaimed by the owner
40+
*/
41+
abstract contract TrueCurrency is BurnableTokenWithBounds {
42+
uint256 constant CENT = 10**16;
43+
uint256 constant REDEMPTION_ADDRESS_COUNT = 0x100000;
44+
45+
/**
46+
* @dev Emitted when account blacklist status changes
47+
*/
48+
event Blacklisted(address indexed account, bool isBlacklisted);
49+
50+
/**
51+
* @dev Emitted when `value` tokens are minted for `to`
52+
* @param to address to mint tokens for
53+
* @param value amount of tokens to be minted
54+
*/
55+
event Mint(address indexed to, uint256 value);
56+
57+
/**
58+
* @dev Creates `amount` tokens and assigns them to `account`, increasing
59+
* the total supply.
60+
* @param account address to mint tokens for
61+
* @param amount amount of tokens to be minted
62+
*
63+
* Emits a {Mint} event
64+
*
65+
* Requirements
66+
*
67+
* - `account` cannot be the zero address.
68+
* - `account` cannot be blacklisted.
69+
* - `account` cannot be a redemption address.
70+
*/
71+
function mint(address account, uint256 amount) external onlyOwner {
72+
require(!isBlacklisted[account], "TrueCurrency: account is blacklisted");
73+
require(!isRedemptionAddress(account), "TrueCurrency: account is a redemption address");
74+
_mint(account, amount);
75+
emit Mint(account, amount);
76+
}
77+
78+
/**
79+
* @dev Set blacklisted status for the account.
80+
* @param account address to set blacklist flag for
81+
* @param _isBlacklisted blacklist flag value
82+
*
83+
* Requirements:
84+
*
85+
* - `msg.sender` should be owner.
86+
*/
87+
function setBlacklisted(address account, bool _isBlacklisted) external onlyOwner {
88+
require(uint256(account) >= REDEMPTION_ADDRESS_COUNT, "TrueCurrency: blacklisting of redemption address is not allowed");
89+
isBlacklisted[account] = _isBlacklisted;
90+
emit Blacklisted(account, _isBlacklisted);
91+
}
92+
93+
/**
94+
* @dev Set canBurn status for the account.
95+
* @param account address to set canBurn flag for
96+
* @param _canBurn canBurn flag value
97+
*
98+
* Requirements:
99+
*
100+
* - `msg.sender` should be owner.
101+
*/
102+
function setCanBurn(address account, bool _canBurn) external onlyOwner {
103+
canBurn[account] = _canBurn;
104+
}
105+
106+
/**
107+
* @dev Check if neither account is blacklisted before performing transfer
108+
* If transfer recipient is a redemption address, burns tokens
109+
* @notice Transfer to redemption address will burn tokens with a 1 cent precision
110+
* @param sender address of sender
111+
* @param recipient address of recipient
112+
* @param amount amount of tokens to transfer
113+
*/
114+
function _transfer(
115+
address sender,
116+
address recipient,
117+
uint256 amount
118+
) internal virtual override {
119+
require(!isBlacklisted[sender], "TrueCurrency: sender is blacklisted");
120+
require(!isBlacklisted[recipient], "TrueCurrency: recipient is blacklisted");
121+
122+
if (isRedemptionAddress(recipient)) {
123+
super._transfer(sender, recipient, amount.sub(amount.mod(CENT)));
124+
_burn(recipient, amount.sub(amount.mod(CENT)));
125+
} else {
126+
super._transfer(sender, recipient, amount);
127+
}
128+
}
129+
130+
/**
131+
* @dev Requere neither accounts to be blacklisted before approval
132+
* @param owner address of owner giving approval
133+
* @param spender address of spender to approve for
134+
* @param amount amount of tokens to approve
135+
*/
136+
function _approve(
137+
address owner,
138+
address spender,
139+
uint256 amount
140+
) internal override {
141+
require(!isBlacklisted[owner], "TrueCurrency: tokens owner is blacklisted");
142+
require(!isBlacklisted[spender] || amount == 0, "TrueCurrency: tokens spender is blacklisted");
143+
144+
super._approve(owner, spender, amount);
145+
}
146+
147+
/**
148+
* @dev Check if tokens can be burned at address before burning
149+
* @param account account to burn tokens from
150+
* @param amount amount of tokens to burn
151+
*/
152+
function _burn(address account, uint256 amount) internal override {
153+
require(canBurn[account], "TrueCurrency: cannot burn from this address");
154+
super._burn(account, amount);
155+
}
156+
157+
/**
158+
* @dev First 0x100000-1 addresses (0x0000000000000000000000000000000000000001 to 0x00000000000000000000000000000000000fffff)
159+
* are the redemption addresses.
160+
* @param account address to check is a redemption address
161+
*
162+
* All transfers to redemption address will trigger token burn.
163+
*
164+
* @notice For transfer to succeed, canBurn must be true for redemption address
165+
*
166+
* @return is `account` a redemption address
167+
*/
168+
function isRedemptionAddress(address account) internal pure returns (bool) {
169+
return uint256(account) < REDEMPTION_ADDRESS_COUNT && uint256(account) != 0;
170+
}
171+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.6.10;
3+
4+
import {TrueCurrency} from "./TrueCurrency.sol";
5+
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
6+
import {IPoRToken} from "./interface/IPoRToken.sol";
7+
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
8+
9+
/**
10+
* @title TrueCurrencyWithPoR
11+
* @dev TrueCurrencyPoR is an ERC20 with blacklist & redemption addresses.
12+
* Please see TrueCurrency for the implementation that this contract inherits from.
13+
* This contract implements an additional check against a Proof-of-Reserves feed before
14+
* allowing tokens to be minted.
15+
*/
16+
abstract contract TrueCurrencyWithPoR is TrueCurrency, IPoRToken {
17+
using SafeMath for uint256;
18+
19+
constructor() public {
20+
uint256 INITIAL_CHAIN_RESERVE_HEARTBEAT = 7 days;
21+
chainReserveHeartbeat = INITIAL_CHAIN_RESERVE_HEARTBEAT;
22+
}
23+
24+
/**
25+
* @notice Overriden mint function that checks the specified proof-of-reserves feed to
26+
* ensure that the total supply of this TrueCurrency is not greater than the reported
27+
* reserves.
28+
* @dev The proof-of-reserves check is bypassed if feed is not set.
29+
* @param account The address to mint tokens to
30+
* @param amount The amount of tokens to mint
31+
*/
32+
function _mint(address account, uint256 amount) internal virtual override {
33+
if (chainReserveFeed == address(0)) {
34+
super._mint(account, amount);
35+
return;
36+
}
37+
38+
// Get latest proof-of-reserves from the feed
39+
(, int256 signedReserves, , uint256 updatedAt, ) = AggregatorV3Interface(chainReserveFeed).latestRoundData();
40+
require(signedReserves > 0, "TrueCurrency: Invalid answer from PoR feed");
41+
uint256 reserves = uint256(signedReserves);
42+
43+
// Sanity check: is chainlink answer updatedAt in the past
44+
require(block.timestamp >= updatedAt);
45+
46+
// Check the answer is fresh enough (i.e., within the specified heartbeat)
47+
require(block.timestamp.sub(updatedAt) <= chainReserveHeartbeat, "TrueCurrency: PoR answer too old");
48+
49+
// Get required info about total supply & decimals
50+
uint8 trueDecimals = decimals();
51+
uint8 reserveDecimals = AggregatorV3Interface(chainReserveFeed).decimals();
52+
uint256 currentSupply = totalSupply();
53+
// Normalise TrueCurrency & reserve decimals
54+
if (trueDecimals < reserveDecimals) {
55+
currentSupply = currentSupply.mul(10**uint256(reserveDecimals - trueDecimals));
56+
} else if (trueDecimals > reserveDecimals) {
57+
reserves = reserves.mul(10**uint256(trueDecimals - reserveDecimals));
58+
}
59+
60+
// Check that after minting more tokens, the total supply would NOT exceed the reserves
61+
// reported by the latest valid proof-of-reserves feed.
62+
require(currentSupply + amount <= reserves, "TrueCurrency: total supply would exceed reserves after mint");
63+
super._mint(account, amount);
64+
}
65+
66+
/**
67+
* @notice Sets a new feed address
68+
* @dev Admin function to set a new feed
69+
* @param newFeed Address of the new feed
70+
*/
71+
function setChainReserveFeed(address newFeed) external override onlyOwner returns (uint256) {
72+
emit NewChainReserveFeed(chainReserveFeed, newFeed);
73+
chainReserveFeed = newFeed;
74+
}
75+
76+
/**
77+
* @notice Sets the feed's heartbeat expectation
78+
* @dev Admin function to set the heartbeat
79+
* @param newHeartbeat Value of the age of the latest update from the feed
80+
*/
81+
function setChainReserveHeartbeat(uint256 newHeartbeat) external override onlyOwner returns (uint256) {
82+
emit NewChainReserveHeartbeat(chainReserveHeartbeat, newHeartbeat);
83+
chainReserveHeartbeat = newHeartbeat;
84+
}
85+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.6.10;
3+
4+
import {ReclaimerToken} from "./ReclaimerToken.sol";
5+
6+
/**
7+
* @title BurnableTokenWithBounds
8+
* @dev Burning functions as redeeming money from the system.
9+
* The platform will keep track of who burns coins,
10+
* and will send them back the equivalent amount of money (rounded down to the nearest cent).
11+
*/
12+
abstract contract BurnableTokenWithBounds is ReclaimerToken {
13+
/**
14+
* @dev Emitted when `value` tokens are burnt from one account (`burner`)
15+
* @param burner address which burned tokens
16+
* @param value amount of tokens burned
17+
*/
18+
event Burn(address indexed burner, uint256 value);
19+
20+
/**
21+
* @dev Emitted when new burn bounds were set
22+
* @param newMin new minimum burn amount
23+
* @param newMax new maximum burn amount
24+
* @notice `newMin` should never be greater than `newMax`
25+
*/
26+
event SetBurnBounds(uint256 newMin, uint256 newMax);
27+
28+
/**
29+
* @dev Destroys `amount` tokens from `msg.sender`, reducing the
30+
* total supply.
31+
* @param amount amount of tokens to burn
32+
*
33+
* Emits a {Transfer} event with `to` set to the zero address.
34+
* Emits a {Burn} event with `burner` set to `msg.sender`
35+
*
36+
* Requirements
37+
*
38+
* - `msg.sender` must have at least `amount` tokens.
39+
*
40+
*/
41+
function burn(uint256 amount) external {
42+
_burn(msg.sender, amount);
43+
}
44+
45+
/**
46+
* @dev Change the minimum and maximum amount that can be burned at once.
47+
* Burning may be disabled by setting both to 0 (this will not be done
48+
* under normal operation, but we can't add checks to disallow it without
49+
* losing a lot of flexibility since burning could also be as good as disabled
50+
* by setting the minimum extremely high, and we don't want to lock
51+
* in any particular cap for the minimum)
52+
* @param _min minimum amount that can be burned at once
53+
* @param _max maximum amount that can be burned at once
54+
*/
55+
function setBurnBounds(uint256 _min, uint256 _max) external onlyOwner {
56+
require(_min <= _max, "BurnableTokenWithBounds: min > max");
57+
burnMin = _min;
58+
burnMax = _max;
59+
emit SetBurnBounds(_min, _max);
60+
}
61+
62+
/**
63+
* @dev Checks if amount is within allowed burn bounds and
64+
* destroys `amount` tokens from `account`, reducing the
65+
* total supply.
66+
* @param account account to burn tokens for
67+
* @param amount amount of tokens to burn
68+
*
69+
* Emits a {Burn} event
70+
*/
71+
function _burn(address account, uint256 amount) internal virtual override {
72+
require(amount >= burnMin, "BurnableTokenWithBounds: below min burn bound");
73+
require(amount <= burnMax, "BurnableTokenWithBounds: exceeds max burn bound");
74+
75+
super._burn(account, amount);
76+
emit Burn(account, amount);
77+
}
78+
}

0 commit comments

Comments
 (0)