|
| 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