Skip to content
This repository was archived by the owner on Jan 14, 2025. It is now read-only.

Commit a7116b7

Browse files
committed
Add initial implementation of multi-sig wallet.
1 parent eeac137 commit a7116b7

1 file changed

Lines changed: 246 additions & 0 deletions

File tree

wallet/wallet.sol

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a
2+
// single, or, crucially, each of a number of, designated owners.
3+
// usage:
4+
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
5+
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
6+
// interior is executed.
7+
contract multiowned {
8+
// struct for the status of a pending operation.
9+
struct PendingState {
10+
uint yetNeeded;
11+
uint ownersDone;
12+
}
13+
14+
// this contract only has one type of event: it can accept a confirmation, in which case
15+
// we record owner and operation (hash) alongside it.
16+
event Confirmation(address owner, hash operation);
17+
18+
// constructor is given number of sigs required to do protected "onlyowners" transactions
19+
// as well as the selection of addresses capable of confirming them.
20+
function multisig(uint _required, address[] _owners) {
21+
m_required = _required;
22+
m_owners = _owners;
23+
}
24+
25+
// simple single-sig function modifier.
26+
modifier onlyowner {
27+
if (m_owners.find(msg.sender) != notfound)
28+
_
29+
}
30+
31+
// multi-sig function modifier: the operation must have an intrinsic hash in order
32+
// that later attempts can be realised as the same underlying operation and
33+
// thus count as confirmations.
34+
modifier onlymanyowners(hash _operation) {
35+
if (confirm(_operation))
36+
_
37+
}
38+
39+
function confirm(hash _operation) protected returns (bool _r) {
40+
// determine what index the present sender is:
41+
uint ownerIndex = m_owners.find(msg.sender);
42+
43+
// make sure they're an owner
44+
if (ownerIndex != notfound) {
45+
// if we're not yet working on this operation, switch over and reset the confirmation status.
46+
if (!m_pending[_operation].yetNeeded) {
47+
// reset count of confirmations needed.
48+
m_pending[_operation].yetNeeded = _sigs.size();
49+
// reset which owners have confirmed (none) - set our bitmap to 0.
50+
m_pending[_operation].ownersDone = 0;
51+
}
52+
// determine the bit to set for this owner.
53+
uint ownerIndexBit = 1 << ownerIndex;
54+
55+
// make sure we (the message sender) haven't confirmed this operation previously.
56+
assert(m_pending[_operation].yetNeeded > 0);
57+
if (!(m_pending[_operation].ownersDone & ownerIndexBit)) {
58+
Confirmation(msg.sender, _operation);
59+
// ok - check if count is enough to go ahead.
60+
if (m_pending[_operation].yetNeeded == 1) {
61+
// enough confirmations: reset and run interior.
62+
delete m_pending[_operation];
63+
_r = true;
64+
}
65+
else
66+
{
67+
// not enough: record that this owner in particular confirmed.
68+
m_pending[_operation].yetNeeded--;
69+
m_pending[_operation].ownersDone |= ownerIndexBit;
70+
}
71+
}
72+
}
73+
}
74+
75+
// replaces an owner `_from` with another `_to`.
76+
function changeOwner(address _from, address _to) external multisig(sha3(msg.sig, _from, _to)) {
77+
uint ownerIndex = m_owners.find(_from);
78+
if (ownerIndex != notfound)
79+
m_owners[ownerIndex] = _to;
80+
}
81+
82+
// the number of owners that must confirm the same operation before it is run.
83+
constant uint m_required;
84+
85+
//stateset owners {
86+
set address[] m_owners;
87+
// set meaning you can do a fast look up into it to get the index.
88+
// suggested impl as combination of count-prefixed contiguous series and normal mapping for the reverse lookup:
89+
// sha3(BASE + 0) => N,
90+
// sha3(BASE + 1) => address[0],
91+
// ...
92+
// sha3(BASE + N) => address[N-1],
93+
// sha3(BASE ++ address[0]) => 0,
94+
// ...
95+
// sha3(BASE ++ address[N-1]) -> N-1
96+
//
97+
// provides:
98+
// size: m_owners.size()
99+
// dereference: m_owners[0], ..., m_owners[m_owners.size() - 1]
100+
// alteration: m_owners[2] = newValue; (original m_owners[2] is removed)
101+
// find: m_owners.find(m_owners[0]) == 0, ..., m_owners.find(m_owners[m_owners.size() - 1]) == m_owners.size() - 1, m_owners.find(...) == (uint)-1 == notfound
102+
// append: m_owners.insert(n): m_owners[m_owners.size() - 1] == n, m_owners.lookup(n) == m_owners.size() - 1
103+
// delete: delete m_owners[n]
104+
// clear: m_owners.clear(): m_owners.size() == 0
105+
//}
106+
107+
/*
108+
// for now could just be:
109+
uint m_ownersCount;
110+
mapping (uint => address) m_owners;
111+
mapping (address => uint) m_ownersFind;
112+
*/
113+
114+
// the ongoing operations.
115+
mapping { hash => PendingState } m_pending;
116+
}
117+
118+
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
119+
// on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method
120+
// uses is specified in the modifier.
121+
contract daylimit is multiowned {
122+
// constructor - just records the present day's index.
123+
function daylimit() {
124+
m_lastDay = today();
125+
}
126+
127+
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
128+
function setDailyLimit(uint _newLimit) external onlyowners(sha3(msg.sig, _newLimit)) {
129+
m_dailyLimit = _newLimit;
130+
}
131+
132+
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
133+
function resetSpentToday() external onlyowners(sha3(msg.sig)) {
134+
m_dailyLimit = _newLimit;
135+
}
136+
137+
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
138+
// returns true. otherwise just returns false.
139+
function underLimit(uint _value) protected onlyowner {
140+
// reset the spend limit if we're on a different day to last time.
141+
if (today() > m_lastDay) {
142+
m_spentToday = 0;
143+
m_lastDay = today();
144+
}
145+
// check to see if there's enough left - if so, subtract and return true.
146+
if (m_spentToday + _value <= m_dailyLimit) {
147+
m_spendToday += _value;
148+
return true;
149+
}
150+
return false;
151+
}
152+
153+
// simple modifier for daily limit.
154+
modifier limitedDaily(uint _value) {
155+
if (underLimit(_value))
156+
_
157+
}
158+
159+
// determines today's index.
160+
function today() private constant returns (uint r) { r = block.timestamp / (60 * 60 * 24); }
161+
162+
uint m_spentToday;
163+
uint m_dailyLimit;
164+
uint m_lastDay;
165+
}
166+
167+
// interface contract for multisig proxy contracts.
168+
contract multisig {
169+
function changeOwner(address _from, address _to);
170+
function transact(address _to, uint _value) returns (hash _r);
171+
function confirm(hash _h);
172+
}
173+
174+
// usage
175+
// hash h = Wallet(w).from(oneOwner).transact(to, value, data);
176+
// Wallet(w).from(anotherOwner).transact(
177+
178+
contract Wallet is multisig multiowned daylimit {
179+
structure Transaction {
180+
address to;
181+
uint value;
182+
byte[] data;
183+
}
184+
185+
// log entries
186+
event CashIn(uint value);
187+
event SingleTransact(indexed string32 = "out", uint value, address owner, address to);
188+
event MultiTransact(indexed string32 = "out", address owner, hash operation, uint value, address to, byte[] data);
189+
190+
function Wallet(address[] _owners) onlyowners(2, _owners) {}
191+
192+
// kills the contract sending everything to `_to`.
193+
function kill(address _to) external onlyowners(sha3("kill", _to)) {
194+
this.suicide(_to);
195+
}
196+
197+
// gets called when no other function matches
198+
function() {
199+
// just being sent some cash?
200+
if (msg.value) {
201+
CashIn(msg.value);
202+
}
203+
}
204+
205+
// Outside-visible transact entry point. Executes transacion immediately if below daily spend limit.
206+
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
207+
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
208+
// and _data arguments). They still get the option of using them if they want, anyways.
209+
function transact(address _to, uint _value, bytes[] _data) external returns (hash _r) {
210+
// first, take the opportunity to check that we're under the daily limit.
211+
if (underLimit(_value)) {
212+
log SingleTransact(_value, _to);
213+
// yes - just execute the call.
214+
_to.call(_value, _data);
215+
return 0;
216+
}
217+
218+
// determine our operation hash.
219+
_r = sha3("transact", _to, _value, _data);
220+
if (!confirm(_r) && m_txs[_r].to == 0) {
221+
m_txs[_r].to = _to;
222+
m_txs[_r].value = _value;
223+
m_txs[_r].data = _data;
224+
}
225+
}
226+
227+
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
228+
// to determine the body of the transaction from the hash provided.
229+
function confirm(hash _h) external onlyowners(_h) {
230+
if (m_txs[_h].to != 0) {
231+
m_txs[_h].to.call(m_txs[_h].value, m_txs[_h].data);
232+
MultiTransact(m_txs[_h].value, m_txs[_h].to);
233+
delete m_txs[_h];
234+
}
235+
}
236+
237+
// internally confirm transaction with all of the info. returns true iff confirmed good and executed.
238+
function confirm(hash _h, address _to, uint _value, bytes[] _data) private onlyowners(_h) returns (bool _r) {
239+
_to.call(_value, _data);
240+
MultiTransact(_value, _to);
241+
_r = true;
242+
}
243+
244+
// pending transactions we have at present.
245+
mapping (hash => Transaction) m_txs;
246+
}

0 commit comments

Comments
 (0)