-
Notifications
You must be signed in to change notification settings - Fork 4
/
AugmintToken.sol
233 lines (189 loc) · 10.6 KB
/
AugmintToken.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
/* Generic Augmint Token implementation (ERC20 token)
This contract manages:
* Balances of Augmint holders and transactions between them
* Issues/burns tokens
TODO:
- reconsider delegatedTransfer and how to structure it
- shall we allow change of txDelegator?
- consider generic bytes arg instead of uint for transferAndNotify
- consider separate transfer fee params and calculation to separate contract (to feeAccount?)
*/
pragma solidity 0.4.24;
import "../interfaces/AugmintTokenInterface.sol";
import "./ECRecovery.sol";
import "../interfaces/TransferFeeInterface.sol";
import "./Restricted.sol";
contract AugmintToken is AugmintTokenInterface {
event FeeAccountChanged(TransferFeeInterface newFeeAccount);
constructor(address permissionGranterContract, string _name, string _symbol, bytes32 _peggedSymbol, uint8 _decimals, TransferFeeInterface _feeAccount)
public Restricted(permissionGranterContract) {
require(_feeAccount != address(0), "feeAccount must be set");
require(bytes(_name).length > 0, "name must be set");
require(bytes(_symbol).length > 0, "symbol must be set");
name = _name;
symbol = _symbol;
peggedSymbol = _peggedSymbol;
decimals = _decimals;
feeAccount = _feeAccount;
}
function transfer(address to, uint256 amount) external returns (bool) {
_transfer(msg.sender, to, amount, "");
return true;
}
/* Transfers based on an offline signed transfer instruction. */
function delegatedTransfer(address from, address to, uint amount, string narrative,
uint maxExecutorFeeInToken, /* client provided max fee for executing the tx */
bytes32 nonce, /* random nonce generated by client */
/* ^^^^ end of signed data ^^^^ */
bytes signature,
uint requestedExecutorFeeInToken /* the executor can decide to request lower fee */
)
external {
bytes32 txHash = keccak256(abi.encodePacked(this, from, to, amount, narrative, maxExecutorFeeInToken, nonce));
_checkHashAndTransferExecutorFee(txHash, signature, from, maxExecutorFeeInToken, requestedExecutorFeeInToken);
_transfer(from, to, amount, narrative);
}
function approve(address _spender, uint256 amount) external returns (bool) {
require(_spender != 0x0, "spender must be set");
allowed[msg.sender][_spender] = amount;
emit Approval(msg.sender, _spender, amount);
return true;
}
/**
ERC20 transferFrom attack protection: https://github.com/DecentLabs/dcm-poc/issues/57
approve should be called when allowed[_spender] == 0. To increment allowed value is better
to use this function to avoid 2 calls (and wait until the first transaction is mined)
Based on MonolithDAO Token.sol */
function increaseApproval(address _spender, uint _addedValue) external {
require(_spender != 0x0, "spender must be set");
mapping (address => uint256) allowances = allowed[msg.sender];
uint newValue = allowances[_spender].add(_addedValue);
allowances[_spender] = newValue;
emit Approval(msg.sender, _spender, newValue);
}
function decreaseApproval(address _spender, uint _subtractedValue) external {
require(_spender != 0x0, "spender must be set");
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
_transferFrom(from, to, amount, "");
return true;
}
// Issue tokens. See MonetarySupervisor but as a rule of thumb issueTo is only allowed:
// - on new loan (by trusted Lender contracts)
// - when converting old tokens using MonetarySupervisor
// - strictly to reserve by Stability Board (via MonetarySupervisor)
function issueTo(address to, uint amount) external restrict("MonetarySupervisor") {
balances[to] = balances[to].add(amount);
totalSupply = totalSupply.add(amount);
emit Transfer(0x0, to, amount);
emit AugmintTransfer(0x0, to, amount, "", 0);
}
// Burn tokens. Anyone can burn from its own account. YOLO.
// Used by to burn from Augmint reserve or by Lender contract after loan repayment
function burn(uint amount) external {
require(balances[msg.sender] >= amount, "balance must be >= amount");
balances[msg.sender] = balances[msg.sender].sub(amount);
totalSupply = totalSupply.sub(amount);
emit Transfer(msg.sender, 0x0, amount);
emit AugmintTransfer(msg.sender, 0x0, amount, "", 0);
}
/* to upgrade feeAccount (eg. for fee calculation changes) */
function setFeeAccount(TransferFeeInterface newFeeAccount) external restrict("StabilityBoard") {
feeAccount = newFeeAccount;
emit FeeAccountChanged(newFeeAccount);
}
/* transferAndNotify can be used by contracts which require tokens to have only 1 tx (instead of approve + call)
Eg. repay loan, lock funds, token sell order on exchange
Reverts on failue:
- transfer fails
- if transferNotification fails (callee must revert on failure)
- if targetContract is an account or targetContract doesn't have neither transferNotification or fallback fx
TODO: make data param generic bytes (see receiver code attempt in Locker.transferNotification)
*/
function transferAndNotify(TokenReceiver target, uint amount, uint data) external {
_transfer(msg.sender, target, amount, "");
target.transferNotification(msg.sender, amount, data);
}
/* transferAndNotify based on an instruction signed offline */
function delegatedTransferAndNotify(address from, TokenReceiver target, uint amount, uint data,
uint maxExecutorFeeInToken, /* client provided max fee for executing the tx */
bytes32 nonce, /* random nonce generated by client */
/* ^^^^ end of signed data ^^^^ */
bytes signature,
uint requestedExecutorFeeInToken /* the executor can decide to request lower fee */
)
external {
bytes32 txHash = keccak256(abi.encodePacked(this, from, target, amount, data, maxExecutorFeeInToken, nonce));
_checkHashAndTransferExecutorFee(txHash, signature, from, maxExecutorFeeInToken, requestedExecutorFeeInToken);
_transfer(from, target, amount, "");
target.transferNotification(from, amount, data);
}
function transferWithNarrative(address to, uint256 amount, string narrative) external {
_transfer(msg.sender, to, amount, narrative);
}
function transferFromWithNarrative(address from, address to, uint256 amount, string narrative) external {
_transferFrom(from, to, amount, narrative);
}
/* Allow Stability Board to change the name when a new token contract version
is deployed and ready for production use. So that older token contracts
are identifiable in 3rd party apps. */
function setName(string _name) external restrict("StabilityBoard") {
name = _name;
}
/* Allow Stability Board to change the symbol when a new token contract version
is deployed and ready for production use. So that older token contracts
are identifiable in 3rd party apps. */
function setSymbol(string _symbol) external restrict("StabilityBoard") {
symbol = _symbol;
}
function balanceOf(address _owner) external view returns (uint256 balance) {
return balances[_owner];
}
function allowance(address _owner, address _spender) external view returns (uint256 remaining) {
return allowed[_owner][_spender];
}
function _checkHashAndTransferExecutorFee(bytes32 txHash, bytes signature, address signer,
uint maxExecutorFeeInToken, uint requestedExecutorFeeInToken) private {
require(requestedExecutorFeeInToken <= maxExecutorFeeInToken, "requestedExecutorFee must be <= maxExecutorFee");
require(!delegatedTxHashesUsed[txHash], "txHash already used");
delegatedTxHashesUsed[txHash] = true;
address recovered = ECRecovery.recover(ECRecovery.toEthSignedMessageHash(txHash), signature);
require(recovered == signer, "invalid signature");
_transfer(signer, msg.sender, requestedExecutorFeeInToken, "Delegated transfer fee", 0);
}
function _transferFrom(address from, address to, uint256 amount, string narrative) private {
uint fee = feeAccount.calculateTransferFee(from, to, amount);
uint amountWithFee = amount.add(fee);
/* NB: fee is deducted from owner, so transferFrom could fail
if amount + fee is not available on owner balance, or allowance */
require(balances[from] >= amountWithFee, "balance must be >= amount + fee");
require(allowed[from][msg.sender] >= amountWithFee, "allowance must be >= amount + fee");
_transfer(from, to, amount, narrative, fee);
allowed[from][msg.sender] = allowed[from][msg.sender].sub(amountWithFee);
}
function _transfer(address from, address to, uint transferAmount, string narrative) private {
uint fee = feeAccount.calculateTransferFee(from, to, transferAmount);
_transfer(from, to, transferAmount, narrative, fee);
}
function _transfer(address from, address to, uint transferAmount, string narrative, uint fee) private {
require(to != 0x0, "to must be set");
uint amountWithFee = transferAmount.add(fee);
// to emit proper reason instead of failing on from.sub()
require(balances[from] >= amountWithFee, "balance must be >= amount + transfer fee");
balances[from] = balances[from].sub(amountWithFee);
balances[to] = balances[to].add(transferAmount);
emit Transfer(from, to, transferAmount);
if (fee > 0) {
balances[feeAccount] = balances[feeAccount].add(fee);
emit Transfer(from, feeAccount, fee);
}
emit AugmintTransfer(from, to, transferAmount, narrative, fee);
}
}