forked from reserve-protocol/rsr
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSlowWallet.sol
More file actions
136 lines (111 loc) · 4.91 KB
/
SlowWallet.sol
File metadata and controls
136 lines (111 loc) · 4.91 KB
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
pragma solidity ^0.5.8;
/**
* @title The standard ERC20 interface
* @dev see https://eips.ethereum.org/EIPS/eip-20
*/
interface IERC20 {
function transfer(address, uint256) external returns (bool);
function approve(address, uint256) external returns (bool);
function transferFrom(address, address, uint256) external returns (bool);
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function allowance(address, address) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed holder, address indexed spender, uint256 value);
}
/// @title Time-delayed ERC-20 wallet contract.
/// Can only transfer tokens after publicly recording the intention to do so
/// at least two weeks in advance.
contract SlowWallet {
// TYPES
struct TransferProposal {
address destination;
uint256 value;
uint256 time;
string notes;
bool closed;
}
// DATA
IERC20 public token;
uint256 public constant delay = 4 weeks;
address public owner;
// PROPOSALS
mapping (uint256 => TransferProposal) public proposals;
uint256 public proposalsLength;
// EVENTS
event TransferProposed(
uint256 index,
address indexed destination,
uint256 value,
uint256 delayUntil,
string notes
);
event TransferConfirmed(uint256 index, address indexed destination, uint256 value, string notes);
event TransferCancelled(uint256 index, address indexed destination, uint256 value, string notes);
event AllTransfersCancelled();
// FUNCTIONALITY
constructor(address tokenAddress) public {
token = IERC20(tokenAddress);
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "must be owner");
_;
}
/// Propose a new transfer, which can be confirmed after two weeks.
function propose(address destination, uint256 value, string calldata notes) external onlyOwner {
// Delay by at least two weeks.
// We are relying on block.timestamp for this, and aware of the possibility of its
// manipulation by miners. But we are working at a timescale that is already much
// longer than the variance in timestamps we have observed and expect in the future,
// so we are satisfied with this choice.
// solium-disable-next-line security/no-block-members
uint256 delayUntil = now + delay;
require(delayUntil >= now, "delay overflowed");
proposals[proposalsLength] = TransferProposal({
destination: destination,
value: value,
time: delayUntil,
notes: notes,
closed: false
});
proposalsLength++;
emit TransferProposed(proposalsLength-1, destination, value, delayUntil, notes);
}
/// Cancel a proposed transfer.
function cancel(uint256 index, address addr, uint256 value) external onlyOwner {
// Check authorization.
requireMatchingOpenProposal(index, addr, value);
// Cancel transfer.
proposals[index].closed = true;
emit TransferCancelled(index, addr, value, proposals[index].notes);
}
/// Mark all proposals "void", in O(1).
function voidAll() external onlyOwner {
proposalsLength = 0;
emit AllTransfersCancelled();
}
/// Confirm and execute a proposed transfer, if enough time has passed since it was proposed.
function confirm(uint256 index, address destination, uint256 value) external onlyOwner {
// Check authorization.
requireMatchingOpenProposal(index, destination, value);
// See commentary above about using `now`.
// solium-disable-next-line security/no-block-members
require(proposals[index].time < now, "too early");
// Record execution of transfer.
proposals[index].closed = true;
emit TransferConfirmed(index, destination, value, proposals[index].notes);
// Proceed with execution of transfer.
require(token.transfer(destination, value));
}
/// Throw unless the given transfer proposal exists and matches `destination` and `value`.
function requireMatchingOpenProposal(uint256 index, address destination, uint256 value) private view {
require(index < proposalsLength, "index too high, or transfer voided");
require(!proposals[index].closed, "transfer already closed");
// Slither reports "dangerous strict equality" for each of these, but it's OK.
// These equalities are to confirm that the transfer entered is equal to the
// matching previous transfer. We're vetting data entry; strict equality is appropriate.
require(proposals[index].destination == destination, "destination mismatched");
require(proposals[index].value == value, "value mismatched");
}
}