Update Wallet to new Wallet Code (#4805)
* Update Wallet Version * Update Wallet Library * Update Wallets Bytecodes * Typo * Separate Deploy in Contract API * Use the new Wallet ABI // Update wallet code * WIP .// Deploy from Wallet * Update Wallet contract * Contract Deployment for Wallet * Working deployments for Single Owned Wallet contracts * Linting * Create a Wallet from a Wallet * Linting * Fix Signer transactions // Add Gas Used for transactions * Deploy wallet contract fix * Fix too high gas estimate for Wallet Contract Deploys * Final piece ; deploying from Wallet owned by wallet * Update Wallet Code * Updated the Wallet Codes * Fixing Wallet Deployments * Add Support for older wallets * Linting
This commit is contained in:
parent
973bb63dca
commit
4d08e7b0ae
@ -107,34 +107,26 @@ export default class Contract {
|
||||
});
|
||||
}
|
||||
|
||||
deploy (options, values, statecb) {
|
||||
const setState = (state) => {
|
||||
if (!statecb) {
|
||||
return;
|
||||
}
|
||||
|
||||
return statecb(null, state);
|
||||
};
|
||||
|
||||
setState({ state: 'estimateGas' });
|
||||
deploy (options, values, statecb = () => {}) {
|
||||
statecb(null, { state: 'estimateGas' });
|
||||
|
||||
return this
|
||||
.deployEstimateGas(options, values)
|
||||
.then(([gasEst, gas]) => {
|
||||
options.gas = gas.toFixed(0);
|
||||
|
||||
setState({ state: 'postTransaction', gas });
|
||||
statecb(null, { state: 'postTransaction', gas });
|
||||
|
||||
const _options = this._encodeOptions(this.constructors[0], options, values);
|
||||
const encodedOptions = this._encodeOptions(this.constructors[0], options, values);
|
||||
|
||||
return this._api.parity
|
||||
.postTransaction(_options)
|
||||
.postTransaction(encodedOptions)
|
||||
.then((requestId) => {
|
||||
setState({ state: 'checkRequest', requestId });
|
||||
statecb(null, { state: 'checkRequest', requestId });
|
||||
return this._pollCheckRequest(requestId);
|
||||
})
|
||||
.then((txhash) => {
|
||||
setState({ state: 'getTransactionReceipt', txhash });
|
||||
statecb(null, { state: 'getTransactionReceipt', txhash });
|
||||
return this._pollTransactionReceipt(txhash, gas);
|
||||
})
|
||||
.then((receipt) => {
|
||||
@ -142,23 +134,23 @@ export default class Contract {
|
||||
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
|
||||
}
|
||||
|
||||
setState({ state: 'hasReceipt', receipt });
|
||||
statecb(null, { state: 'hasReceipt', receipt });
|
||||
this._receipt = receipt;
|
||||
this._address = receipt.contractAddress;
|
||||
return this._address;
|
||||
});
|
||||
})
|
||||
.then((address) => {
|
||||
setState({ state: 'getCode' });
|
||||
return this._api.eth.getCode(this._address);
|
||||
})
|
||||
.then((code) => {
|
||||
if (code === '0x') {
|
||||
throw new Error('Contract not deployed, getCode returned 0x');
|
||||
}
|
||||
})
|
||||
.then((address) => {
|
||||
statecb(null, { state: 'getCode' });
|
||||
return this._api.eth.getCode(this._address);
|
||||
})
|
||||
.then((code) => {
|
||||
if (code === '0x') {
|
||||
throw new Error('Contract not deployed, getCode returned 0x');
|
||||
}
|
||||
|
||||
setState({ state: 'completed' });
|
||||
return this._address;
|
||||
statecb(null, { state: 'completed' });
|
||||
return this._address;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
466
js/src/contracts/abi/old-wallet.json
Normal file
466
js/src/contracts/abi/old-wallet.json
Normal file
@ -0,0 +1,466 @@
|
||||
[
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_addr",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_numOwners",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_lastDay",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "resetSpentToday",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_spentToday",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_required",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_h",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "confirm",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newLimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "setDailyLimit",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "execute",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "_r",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "revoke",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newRequired",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "changeRequirement",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "hasConfirmed",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "ownerIndex",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "kill",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "changeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_dailyLimit",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owners",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"name": "_required",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_daylimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Confirmation",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Revoke",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newRequirement",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "RequirementChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "SingleTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "MultiTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "initiator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ConfirmationNeeded",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
@ -1 +1,476 @@
|
||||
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]
|
||||
[
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_addr",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_numOwners",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_lastDay",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "resetSpentToday",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_spentToday",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_required",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_h",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "confirm",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newLimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "setDailyLimit",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "execute",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "_r",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "revoke",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newRequired",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "changeRequirement",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "hasConfirmed",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "ownerIndex",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "kill",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "changeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_dailyLimit",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owners",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"name": "_required",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_daylimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Confirmation",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Revoke",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newRequirement",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "RequirementChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "created",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "SingleTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "created",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "MultiTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "initiator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ConfirmationNeeded",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
|
File diff suppressed because one or more lines are too long
@ -8,453 +8,454 @@
|
||||
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
||||
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||
// interior is executed.
|
||||
pragma solidity ^0.4.6;
|
||||
|
||||
contract multisig {
|
||||
// EVENTS
|
||||
pragma solidity ^0.4.9;
|
||||
|
||||
// this contract can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
contract WalletEvents {
|
||||
// EVENTS
|
||||
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data, address created);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
}
|
||||
|
||||
contract multisigAbi is multisig {
|
||||
function isOwner(address _addr) returns (bool);
|
||||
contract WalletAbi {
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external;
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool);
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) external;
|
||||
|
||||
function confirm(bytes32 _h) returns(bool);
|
||||
function addOwner(address _owner) external;
|
||||
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit);
|
||||
function removeOwner(address _owner) external;
|
||||
|
||||
function addOwner(address _owner);
|
||||
function changeRequirement(uint _newRequired) external;
|
||||
|
||||
function removeOwner(address _owner);
|
||||
function isOwner(address _addr) constant returns (bool);
|
||||
|
||||
function changeRequirement(uint _newRequired);
|
||||
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation);
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) external;
|
||||
|
||||
function changeOwner(address _from, address _to);
|
||||
|
||||
function execute(address _to, uint _value, bytes _data) returns(bool);
|
||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||
function confirm(bytes32 _h) returns (bool o_success);
|
||||
}
|
||||
|
||||
contract WalletLibrary is multisig {
|
||||
// TYPES
|
||||
contract WalletLibrary is WalletEvents {
|
||||
// TYPES
|
||||
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
}
|
||||
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function initMultiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
m_required = _required;
|
||||
}
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_owner)) return;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) external constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) constant returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function initDaylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
m_spentToday = 0;
|
||||
}
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function initWallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
initDaylimit(_daylimit);
|
||||
initMultiowned(_owners, _required);
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
suicide(_to);
|
||||
}
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
|
||||
// yes - just execute the call.
|
||||
address created;
|
||||
if (_to == 0) {
|
||||
created = create(_value, _data);
|
||||
} else {
|
||||
if (!_to.call.value(_value)(_data))
|
||||
throw;
|
||||
}
|
||||
SingleTransact(msg.sender, _value, _to, _data, created);
|
||||
} else {
|
||||
// determine our operation hash.
|
||||
o_hash = sha3(msg.data, block.number);
|
||||
// store if it's new
|
||||
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
|
||||
m_txs[o_hash].to = _to;
|
||||
m_txs[o_hash].value = _value;
|
||||
m_txs[o_hash].data = _data;
|
||||
}
|
||||
if (!confirm(o_hash)) {
|
||||
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
||||
assembly {
|
||||
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
||||
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
||||
}
|
||||
}
|
||||
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
|
||||
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
|
||||
address created;
|
||||
if (m_txs[_h].to == 0) {
|
||||
created = create(m_txs[_h].value, m_txs[_h].data);
|
||||
} else {
|
||||
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
|
||||
throw;
|
||||
}
|
||||
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
|
||||
for (uint i = 0; i < length; ++i) {
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
}
|
||||
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
}
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
/******************************
|
||||
***** MULTI OWNED SECTION ****
|
||||
******************************/
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
|
||||
// MODIFIERS
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
|
||||
// METHODS
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function initMultiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
m_required = _required;
|
||||
uint constant c_maxOwners = 250;
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
}
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) {
|
||||
if (isOwner(_owner)) return;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
****** DAY LIMIT SECTION *****
|
||||
******************************/
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple modifier for daily limit.
|
||||
modifier limitedDaily(uint _value) {
|
||||
if (underLimit(_value))
|
||||
_;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function initDaylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) {
|
||||
m_spentToday = 0;
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
|
||||
/******************************
|
||||
********* WALLET SECTION *****
|
||||
******************************/
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function initWallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
initMultiowned(_owners, _required);
|
||||
initDaylimit(_daylimit) ;
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) {
|
||||
suicide(_to);
|
||||
}
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if (underLimit(_value)) {
|
||||
SingleTransact(msg.sender, _value, _to, _data);
|
||||
// yes - just execute the call.
|
||||
_callValue =_to.call.value(_value)(_data);
|
||||
} else {
|
||||
// determine our operation hash.
|
||||
bytes32 _r = sha3(msg.data, block.number);
|
||||
if (!confirm(_r) && m_txs[_r].to == 0) {
|
||||
m_txs[_r].to = _to;
|
||||
m_txs[_r].value = _value;
|
||||
m_txs[_r].data = _data;
|
||||
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
|
||||
if (m_txs[_h].to != 0) {
|
||||
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function clearWalletPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
clearPending();
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint m_numOwners;
|
||||
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
uint constant c_maxOwners = 250;
|
||||
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
}
|
||||
|
||||
contract Wallet is WalletEvents {
|
||||
|
||||
contract Wallet is multisig {
|
||||
// WALLET CONSTRUCTOR
|
||||
// calls the `initWallet` method of the Library in this context
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
// Signature of the Wallet Library's init function
|
||||
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
|
||||
address target = _walletLibrary;
|
||||
|
||||
// WALLET CONSTRUCTOR
|
||||
// calls the `initWallet` method of the Library in this context
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
// Signature of the Wallet Library's init function
|
||||
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
|
||||
address target = _walletLibrary;
|
||||
// Compute the size of the call data : arrays has 2
|
||||
// 32bytes for offset and length, plus 32bytes per element ;
|
||||
// plus 2 32bytes for each uint
|
||||
uint argarraysize = (2 + _owners.length);
|
||||
uint argsize = (2 + argarraysize) * 32;
|
||||
|
||||
// Compute the size of the call data : arrays has 2
|
||||
// 32bytes for offset and length, plus 32bytes per element ;
|
||||
// plus 2 32bytes for each uint
|
||||
uint argarraysize = (2 + _owners.length);
|
||||
uint argsize = (2 + argarraysize) * 32;
|
||||
|
||||
assembly {
|
||||
// Add the signature first to memory
|
||||
mstore(0x0, sig)
|
||||
// Add the call data, which is at the end of the
|
||||
// code
|
||||
codecopy(0x4, sub(codesize, argsize), argsize)
|
||||
// Delegate call to the library
|
||||
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
|
||||
}
|
||||
assembly {
|
||||
// Add the signature first to memory
|
||||
mstore(0x0, sig)
|
||||
// Add the call data, which is at the end of the
|
||||
// code
|
||||
codecopy(0x4, sub(codesize, argsize), argsize)
|
||||
// Delegate call to the library
|
||||
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
|
||||
}
|
||||
}
|
||||
|
||||
// METHODS
|
||||
// METHODS
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
else if (msg.data.length > 0)
|
||||
_walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
else if (msg.data.length > 0)
|
||||
_walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
// As return statement unavailable in fallback, explicit the method here
|
||||
// As return statement unavailable in fallback, explicit the method here
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
function isOwner(address _addr) constant returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
}
|
||||
|
@ -8,221 +8,222 @@
|
||||
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
||||
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||
// interior is executed.
|
||||
pragma solidity ^0.4.6;
|
||||
|
||||
pragma solidity ^0.4.9;
|
||||
|
||||
contract multiowned {
|
||||
|
||||
// TYPES
|
||||
// TYPES
|
||||
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
}
|
||||
|
||||
// EVENTS
|
||||
|
||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function multiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
m_required = _required;
|
||||
}
|
||||
|
||||
// EVENTS
|
||||
|
||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_owner)) return;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) external constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) constant returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function multiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
m_required = _required;
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
// FIELDS
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_owner)) return;
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) external constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
uint constant c_maxOwners = 250;
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
uint constant c_maxOwners = 250;
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
}
|
||||
|
||||
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
|
||||
@ -230,79 +231,70 @@ contract multiowned {
|
||||
// uses is specified in the modifier.
|
||||
contract daylimit is multiowned {
|
||||
|
||||
// MODIFIERS
|
||||
// METHODS
|
||||
|
||||
// simple modifier for daily limit.
|
||||
modifier limitedDaily(uint _value) {
|
||||
if (underLimit(_value))
|
||||
_;
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function daylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
m_spentToday = 0;
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function daylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
m_spentToday = 0;
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
// INTERNAL METHODS
|
||||
// FIELDS
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
// FIELDS
|
||||
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
}
|
||||
|
||||
// interface contract for multisig proxy contracts; see below for docs.
|
||||
contract multisig {
|
||||
|
||||
// EVENTS
|
||||
// EVENTS
|
||||
|
||||
// logged events:
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
// logged events:
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data, address created);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
|
||||
// FUNCTIONS
|
||||
// FUNCTIONS
|
||||
|
||||
// TODO: document
|
||||
function changeOwner(address _from, address _to) external;
|
||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32);
|
||||
function confirm(bytes32 _h) returns (bool);
|
||||
// TODO: document
|
||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||
function confirm(bytes32 _h) external returns (bool o_success);
|
||||
}
|
||||
|
||||
// usage:
|
||||
@ -310,79 +302,102 @@ contract multisig {
|
||||
// Wallet(w).from(anotherOwner).confirm(h);
|
||||
contract Wallet is multisig, multiowned, daylimit {
|
||||
|
||||
// TYPES
|
||||
// TYPES
|
||||
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit)
|
||||
multiowned(_owners, _required) daylimit(_daylimit) {
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
suicide(_to);
|
||||
}
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
|
||||
// yes - just execute the call.
|
||||
address created;
|
||||
if (_to == 0) {
|
||||
created = create(_value, _data);
|
||||
} else {
|
||||
if (!_to.call.value(_value)(_data))
|
||||
throw;
|
||||
}
|
||||
SingleTransact(msg.sender, _value, _to, _data, created);
|
||||
} else {
|
||||
// determine our operation hash.
|
||||
o_hash = sha3(msg.data, block.number);
|
||||
// store if it's new
|
||||
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
|
||||
m_txs[o_hash].to = _to;
|
||||
m_txs[o_hash].value = _value;
|
||||
m_txs[o_hash].data = _data;
|
||||
}
|
||||
if (!confirm(o_hash)) {
|
||||
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit)
|
||||
multiowned(_owners, _required) daylimit(_daylimit) {
|
||||
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
||||
assembly {
|
||||
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
||||
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
||||
}
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
suicide(_to);
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
|
||||
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
|
||||
address created;
|
||||
if (m_txs[_h].to == 0) {
|
||||
created = create(m_txs[_h].value, m_txs[_h].data);
|
||||
} else {
|
||||
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
|
||||
throw;
|
||||
}
|
||||
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
// INTERNAL METHODS
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if (underLimit(_value)) {
|
||||
SingleTransact(msg.sender, _value, _to, _data);
|
||||
// yes - just execute the call.
|
||||
_to.call.value(_value)(_data);
|
||||
return 0;
|
||||
}
|
||||
// determine our operation hash.
|
||||
_r = sha3(msg.data, block.number);
|
||||
if (!confirm(_r) && m_txs[_r].to == 0) {
|
||||
m_txs[_r].to = _to;
|
||||
m_txs[_r].value = _value;
|
||||
m_txs[_r].data = _data;
|
||||
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
super.clearPending();
|
||||
}
|
||||
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
|
||||
if (m_txs[_h].to != 0) {
|
||||
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// FIELDS
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
super.clearPending();
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
}
|
||||
|
@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { omitBy } from 'lodash';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@ -106,9 +105,6 @@ export default class WalletDetails extends Component {
|
||||
renderMultisigDetails () {
|
||||
const { accounts, wallet, errors } = this.props;
|
||||
|
||||
// Wallets cannot create contracts
|
||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Input
|
||||
@ -148,7 +144,7 @@ export default class WalletDetails extends Component {
|
||||
/>
|
||||
|
||||
<AddressSelect
|
||||
accounts={ _accounts }
|
||||
accounts={ accounts }
|
||||
error={ errors.account }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
|
@ -22,10 +22,11 @@ import Contract from '~/api/contract';
|
||||
import { ERROR_CODES } from '~/api/transport/error';
|
||||
import Contracts from '~/contracts';
|
||||
import { wallet as walletAbi } from '~/contracts/abi';
|
||||
import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
|
||||
import { wallet as walletCode, walletLibrary as walletLibraryCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
|
||||
|
||||
import { validateUint, validateAddress, validateName } from '~/util/validation';
|
||||
import { toWei } from '~/api/util/wei';
|
||||
import { deploy } from '~/util/tx';
|
||||
import WalletsUtils from '~/util/wallets';
|
||||
|
||||
const STEPS = {
|
||||
@ -179,6 +180,8 @@ export default class CreateWalletStore {
|
||||
this.wallet.owners = owners;
|
||||
this.wallet.required = require.toNumber();
|
||||
this.wallet.dailylimit = dailylimit.limit;
|
||||
|
||||
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||
});
|
||||
|
||||
return this.addWallet(this.wallet);
|
||||
@ -202,21 +205,51 @@ export default class CreateWalletStore {
|
||||
return null; // exception when registry is not available
|
||||
})
|
||||
.then((address) => {
|
||||
const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase();
|
||||
const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress)
|
||||
? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress)
|
||||
: fullWalletCode;
|
||||
if (!address || /^(0x)?0*$/.test(address)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check that it's actually the expected code
|
||||
return this.api.eth
|
||||
.getCode(address)
|
||||
.then((code) => {
|
||||
const strippedCode = code.replace(/^0x/, '');
|
||||
|
||||
// The actual deployed code is included in the wallet
|
||||
// library code (which might have some more data)
|
||||
if (walletLibraryCode.indexOf(strippedCode) >= 0) {
|
||||
return address;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
})
|
||||
.then((address) => {
|
||||
let code = fullWalletCode;
|
||||
|
||||
if (address) {
|
||||
const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase();
|
||||
|
||||
code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress);
|
||||
} else {
|
||||
console.warn('wallet library has not been found in the registry');
|
||||
}
|
||||
|
||||
const options = {
|
||||
data: code,
|
||||
from: account
|
||||
};
|
||||
|
||||
return this.api
|
||||
.newContract(walletAbi)
|
||||
.deploy(options, [ owners, required, daylimit ], this.onDeploymentState);
|
||||
const contract = this.api.newContract(walletAbi);
|
||||
|
||||
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||
return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState);
|
||||
})
|
||||
.then((address) => {
|
||||
if (!address || /^(0x)?0*$/.test(address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.deployed = true;
|
||||
this.wallet.address = address;
|
||||
return this.addWallet(this.wallet);
|
||||
@ -233,26 +266,37 @@ export default class CreateWalletStore {
|
||||
}
|
||||
|
||||
@action addWallet = (wallet) => {
|
||||
const { address, name, description } = wallet;
|
||||
const { address, name, metadata } = wallet;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this.api.parity.setAccountName(address, name),
|
||||
this.api.parity.setAccountMeta(address, {
|
||||
abi: walletAbi,
|
||||
wallet: true,
|
||||
timestamp: Date.now(),
|
||||
deleted: false,
|
||||
description,
|
||||
name,
|
||||
tags: ['wallet']
|
||||
})
|
||||
this.api.parity.setAccountMeta(address, metadata)
|
||||
])
|
||||
.then(() => {
|
||||
this.step = 'INFO';
|
||||
});
|
||||
}
|
||||
|
||||
getWalletWithMeta = (wallet) => {
|
||||
const { name, description } = wallet;
|
||||
|
||||
const metadata = {
|
||||
abi: walletAbi,
|
||||
wallet: true,
|
||||
timestamp: Date.now(),
|
||||
deleted: false,
|
||||
tags: [ 'wallet' ],
|
||||
description,
|
||||
name
|
||||
};
|
||||
|
||||
return {
|
||||
...wallet,
|
||||
metadata
|
||||
};
|
||||
}
|
||||
|
||||
onDeploymentState = (error, data) => {
|
||||
if (error) {
|
||||
return console.error('createWallet::onDeploymentState', error);
|
||||
@ -298,6 +342,15 @@ export default class CreateWalletStore {
|
||||
);
|
||||
return;
|
||||
|
||||
case 'confirmationNeeded':
|
||||
this.deployState = (
|
||||
<FormattedMessage
|
||||
id='createWallet.states.confirmationNeeded'
|
||||
defaultMessage='The contract deployment needs confirmations from other owners of the Wallet'
|
||||
/>
|
||||
);
|
||||
return;
|
||||
|
||||
case 'completed':
|
||||
this.deployState = (
|
||||
<FormattedMessage
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { pick, omitBy } from 'lodash';
|
||||
import { pick } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
@ -23,6 +23,7 @@ import { connect } from 'react-redux';
|
||||
import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
|
||||
import { CancelIcon, DoneIcon } from '~/ui/Icons';
|
||||
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
||||
import { deploy, deployEstimateGas } from '~/util/tx';
|
||||
|
||||
import DetailsStep from './DetailsStep';
|
||||
import ParametersStep from './ParametersStep';
|
||||
@ -73,7 +74,7 @@ class DeployContract extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired,
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
@ -422,9 +423,9 @@ class DeployContract extends Component {
|
||||
from: fromAddress
|
||||
};
|
||||
|
||||
api
|
||||
.newContract(abiParsed)
|
||||
.deployEstimateGas(options, params)
|
||||
const contract = api.newContract(abiParsed);
|
||||
|
||||
deployEstimateGas(contract, options, params)
|
||||
.then(([gasEst, gas]) => {
|
||||
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||
this.gasStore.setGas(gas.toFixed(0));
|
||||
@ -490,6 +491,17 @@ class DeployContract extends Component {
|
||||
const { api, store } = this.context;
|
||||
const { source } = this.props;
|
||||
const { abiParsed, code, description, name, params, fromAddress } = this.state;
|
||||
|
||||
const metadata = {
|
||||
abi: abiParsed,
|
||||
contract: true,
|
||||
deleted: false,
|
||||
timestamp: Date.now(),
|
||||
name,
|
||||
description,
|
||||
source
|
||||
};
|
||||
|
||||
const options = {
|
||||
data: code,
|
||||
from: fromAddress
|
||||
@ -499,28 +511,25 @@ class DeployContract extends Component {
|
||||
|
||||
const contract = api.newContract(abiParsed);
|
||||
|
||||
contract
|
||||
.deploy(options, params, this.onDeploymentState)
|
||||
deploy(contract, options, params, metadata, this.onDeploymentState)
|
||||
.then((address) => {
|
||||
const blockNumber = contract._receipt
|
||||
// No contract address given, might need some confirmations
|
||||
// from the wallet owners...
|
||||
if (!address || /^(0x)?0*$/.test(address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
metadata.blockNumber = contract._receipt
|
||||
? contract.receipt.blockNumber.toNumber()
|
||||
: null;
|
||||
|
||||
return Promise.all([
|
||||
api.parity.setAccountName(address, name),
|
||||
api.parity.setAccountMeta(address, {
|
||||
abi: abiParsed,
|
||||
contract: true,
|
||||
timestamp: Date.now(),
|
||||
deleted: false,
|
||||
blockNumber,
|
||||
description,
|
||||
source
|
||||
})
|
||||
api.parity.setAccountMeta(address, metadata)
|
||||
])
|
||||
.then(() => {
|
||||
console.log(`contract deployed at ${address}`);
|
||||
this.setState({ step: 'DEPLOYMENT', address });
|
||||
this.setState({ step: 'COMPLETED', address });
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -589,6 +598,17 @@ class DeployContract extends Component {
|
||||
});
|
||||
return;
|
||||
|
||||
case 'confirmationNeeded':
|
||||
this.setState({
|
||||
deployState: (
|
||||
<FormattedMessage
|
||||
id='deployContract.state.confirmationNeeded'
|
||||
defaultMessage='The operation needs confirmations from the other owners of the contract'
|
||||
/>
|
||||
)
|
||||
});
|
||||
return;
|
||||
|
||||
case 'completed':
|
||||
this.setState({
|
||||
deployState: (
|
||||
@ -614,17 +634,14 @@ class DeployContract extends Component {
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const { accounts } = initProps;
|
||||
|
||||
// Skip Wallet accounts : they can't create Contracts
|
||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||
|
||||
const fromAddresses = Object.keys(_accounts);
|
||||
const fromAddresses = Object.keys(accounts);
|
||||
|
||||
return (state) => {
|
||||
const balances = pick(state.balances.balances, fromAddresses);
|
||||
const { gasLimit } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
accounts: _accounts,
|
||||
accounts,
|
||||
balances,
|
||||
gasLimit
|
||||
};
|
||||
|
@ -401,6 +401,7 @@ class WalletSettings extends Component {
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancelBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.cancel'
|
||||
@ -414,6 +415,7 @@ class WalletSettings extends Component {
|
||||
const closeBtn = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='closeBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.close'
|
||||
@ -427,6 +429,7 @@ class WalletSettings extends Component {
|
||||
const sendingBtn = (
|
||||
<Button
|
||||
icon={ <DoneIcon /> }
|
||||
key='sendingBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.sending'
|
||||
@ -440,6 +443,7 @@ class WalletSettings extends Component {
|
||||
const nextBtn = (
|
||||
<Button
|
||||
icon={ <NextIcon /> }
|
||||
key='nextBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.next'
|
||||
@ -454,6 +458,7 @@ class WalletSettings extends Component {
|
||||
const sendBtn = (
|
||||
<Button
|
||||
icon={ <NextIcon /> }
|
||||
key='sendBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.send'
|
||||
|
@ -49,7 +49,7 @@ export default class Personal {
|
||||
.filter((address) => {
|
||||
const account = accountsInfo[address];
|
||||
|
||||
return !account.uuid && account.meta.deleted;
|
||||
return !account.uuid && account.meta && account.meta.deleted;
|
||||
})
|
||||
.map((address) => this._api.parity.removeAddress(address))
|
||||
);
|
||||
|
@ -26,7 +26,6 @@ import WalletsUtils from '~/util/wallets';
|
||||
import { wallet as WalletAbi } from '~/contracts/abi';
|
||||
|
||||
export function personalAccountsInfo (accountsInfo) {
|
||||
const addresses = [];
|
||||
const accounts = {};
|
||||
const contacts = {};
|
||||
const contracts = {};
|
||||
@ -35,10 +34,9 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
|
||||
Object.keys(accountsInfo || {})
|
||||
.map((address) => Object.assign({}, accountsInfo[address], { address }))
|
||||
.filter((account) => account.uuid || !account.meta.deleted)
|
||||
.filter((account) => account.meta && (account.uuid || !account.meta.deleted))
|
||||
.forEach((account) => {
|
||||
if (account.uuid) {
|
||||
addresses.push(account.address);
|
||||
accounts[account.address] = account;
|
||||
} else if (account.meta.wallet) {
|
||||
account.wallet = true;
|
||||
@ -87,18 +85,45 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
return [];
|
||||
})
|
||||
.then((_wallets) => {
|
||||
_wallets.forEach((wallet) => {
|
||||
const owners = wallet.owners.map((o) => o.address);
|
||||
// We want to separate owned wallets and other wallets
|
||||
// However, wallets can be owned by wallets, that can
|
||||
// be owned by an account...
|
||||
let otherWallets = [].concat(_wallets);
|
||||
let prevLength;
|
||||
let nextLength;
|
||||
|
||||
// Owners ∩ Addresses not null : Wallet is owned
|
||||
// by one of the accounts
|
||||
if (intersection(owners, addresses).length > 0) {
|
||||
accounts[wallet.address] = wallet;
|
||||
} else {
|
||||
contacts[wallet.address] = wallet;
|
||||
}
|
||||
// If no more other wallets, or if the size decreased, continue...
|
||||
do {
|
||||
prevLength = otherWallets.length;
|
||||
|
||||
otherWallets = otherWallets
|
||||
.map((wallet) => {
|
||||
const addresses = Object.keys(accounts);
|
||||
const owners = wallet.owners.map((o) => o.address);
|
||||
|
||||
// Owners ∩ Addresses not null : Wallet is owned
|
||||
// by one of the accounts
|
||||
if (intersection(owners, addresses).length > 0) {
|
||||
accounts[wallet.address] = wallet;
|
||||
return false;
|
||||
}
|
||||
|
||||
return wallet;
|
||||
})
|
||||
.filter((wallet) => wallet);
|
||||
|
||||
nextLength = otherWallets.length;
|
||||
} while (nextLength < prevLength);
|
||||
|
||||
// And other wallets to contacts...
|
||||
otherWallets.forEach((wallet) => {
|
||||
contacts[wallet.address] = wallet;
|
||||
});
|
||||
|
||||
// Cache the _real_ accounts for
|
||||
// WalletsUtils (used for sending transactions)
|
||||
WalletsUtils.cacheAccounts(accounts);
|
||||
|
||||
dispatch(_personalAccountsInfo({
|
||||
accountsInfo,
|
||||
accounts,
|
||||
|
@ -431,22 +431,7 @@ function parseLogs (logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { wallet } = getState();
|
||||
const { contract } = wallet;
|
||||
const walletInstance = contract.instance;
|
||||
|
||||
const signatures = {
|
||||
OwnerChanged: toHex(walletInstance.OwnerChanged.signature),
|
||||
OwnerAdded: toHex(walletInstance.OwnerAdded.signature),
|
||||
OwnerRemoved: toHex(walletInstance.OwnerRemoved.signature),
|
||||
RequirementChanged: toHex(walletInstance.RequirementChanged.signature),
|
||||
Confirmation: toHex(walletInstance.Confirmation.signature),
|
||||
Revoke: toHex(walletInstance.Revoke.signature),
|
||||
Deposit: toHex(walletInstance.Deposit.signature),
|
||||
SingleTransact: toHex(walletInstance.SingleTransact.signature),
|
||||
MultiTransact: toHex(walletInstance.MultiTransact.signature),
|
||||
ConfirmationNeeded: toHex(walletInstance.ConfirmationNeeded.signature)
|
||||
};
|
||||
const WalletSignatures = WalletsUtils.getWalletSignatures();
|
||||
|
||||
const updates = {};
|
||||
|
||||
@ -459,25 +444,25 @@ function parseLogs (logs) {
|
||||
};
|
||||
|
||||
switch (eventSignature) {
|
||||
case signatures.OwnerChanged:
|
||||
case signatures.OwnerAdded:
|
||||
case signatures.OwnerRemoved:
|
||||
case WalletSignatures.OwnerChanged:
|
||||
case WalletSignatures.OwnerAdded:
|
||||
case WalletSignatures.OwnerRemoved:
|
||||
updates[address] = {
|
||||
...prev,
|
||||
[ UPDATE_OWNERS ]: true
|
||||
};
|
||||
return;
|
||||
|
||||
case signatures.RequirementChanged:
|
||||
case WalletSignatures.RequirementChanged:
|
||||
updates[address] = {
|
||||
...prev,
|
||||
[ UPDATE_REQUIRE ]: true
|
||||
};
|
||||
return;
|
||||
|
||||
case signatures.ConfirmationNeeded:
|
||||
case signatures.Confirmation:
|
||||
case signatures.Revoke:
|
||||
case WalletSignatures.ConfirmationNeeded:
|
||||
case WalletSignatures.Confirmation:
|
||||
case WalletSignatures.Revoke:
|
||||
const operation = bytesToHex(log.params.operation.value);
|
||||
|
||||
updates[address] = {
|
||||
@ -489,9 +474,11 @@ function parseLogs (logs) {
|
||||
|
||||
return;
|
||||
|
||||
case signatures.Deposit:
|
||||
case signatures.SingleTransact:
|
||||
case signatures.MultiTransact:
|
||||
case WalletSignatures.Deposit:
|
||||
case WalletSignatures.SingleTransact:
|
||||
case WalletSignatures.MultiTransact:
|
||||
case WalletSignatures.Old.SingleTransact:
|
||||
case WalletSignatures.Old.MultiTransact:
|
||||
updates[address] = {
|
||||
...prev,
|
||||
[ UPDATE_TRANSACTIONS ]: true
|
||||
|
@ -120,6 +120,18 @@ class MethodDecoding extends Component {
|
||||
<span className={ styles.highlight }>
|
||||
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
|
||||
</span>
|
||||
{
|
||||
transaction.gasUsed
|
||||
? (
|
||||
<span>
|
||||
<span>used</span>
|
||||
<span className={ styles.highlight }>
|
||||
{ transaction.gasUsed.toFormat(0) } gas
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<span> for a total transaction value of </span>
|
||||
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
||||
{ this.renderMinBlock() }
|
||||
|
@ -51,6 +51,7 @@ export default class Store {
|
||||
|
||||
return bnB.comparedTo(bnA);
|
||||
});
|
||||
|
||||
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
|
||||
});
|
||||
}
|
||||
@ -85,26 +86,53 @@ export default class Store {
|
||||
this._subscriptionId = 0;
|
||||
}
|
||||
|
||||
loadTransactions (_txhashes) {
|
||||
const txhashes = _txhashes.filter((hash) => !this.transactions[hash] || this._pendingHashes.includes(hash));
|
||||
loadTransactions (_txhashes = []) {
|
||||
const promises = _txhashes
|
||||
.filter((txhash) => !this.transactions[txhash] || this._pendingHashes.includes(txhash))
|
||||
.map((txhash) => {
|
||||
return Promise
|
||||
.all([
|
||||
this._api.eth.getTransactionByHash(txhash),
|
||||
this._api.eth.getTransactionReceipt(txhash)
|
||||
])
|
||||
.then(([
|
||||
transaction = {},
|
||||
transactionReceipt = {}
|
||||
]) => {
|
||||
return {
|
||||
...transactionReceipt,
|
||||
...transaction
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
if (!txhashes || !txhashes.length) {
|
||||
if (!promises.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
Promise
|
||||
.all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash)))
|
||||
.all(promises)
|
||||
.then((_transactions) => {
|
||||
const transactions = _transactions.filter((tx) => tx);
|
||||
const blockNumbers = [];
|
||||
const transactions = _transactions
|
||||
.filter((tx) => tx && tx.hash)
|
||||
.reduce((txs, tx) => {
|
||||
txs[tx.hash] = tx;
|
||||
|
||||
this.addTransactions(
|
||||
transactions.reduce((transactions, tx, index) => {
|
||||
transactions[txhashes[index]] = tx;
|
||||
return transactions;
|
||||
}, {})
|
||||
);
|
||||
if (tx.blockNumber && tx.blockNumber.gt(0)) {
|
||||
blockNumbers.push(tx.blockNumber.toNumber());
|
||||
}
|
||||
|
||||
this.loadBlocks(transactions.map((tx) => tx.blockNumber ? tx.blockNumber.toNumber() : 0));
|
||||
return txs;
|
||||
}, {});
|
||||
|
||||
// No need to add transactions if there are none
|
||||
if (Object.keys(transactions).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.addTransactions(transactions);
|
||||
this.loadBlocks(blockNumbers);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('loadTransactions', error);
|
||||
|
@ -27,7 +27,7 @@ import styles from './txList.css';
|
||||
class TxList extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
@ -36,7 +36,7 @@ class TxList extends Component {
|
||||
PropTypes.object
|
||||
]).isRequired,
|
||||
netVersion: PropTypes.string.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
store = new Store(this.context.api);
|
||||
|
||||
|
@ -61,17 +61,6 @@ export function estimateGas (_func, _options, _values = []) {
|
||||
const { func, options, values } = callArgs;
|
||||
|
||||
return func._estimateGas(options, values);
|
||||
})
|
||||
.then((gas) => {
|
||||
return WalletsUtils
|
||||
.isWallet(_func.contract.api, _options.from)
|
||||
.then((isWallet) => {
|
||||
if (isWallet) {
|
||||
return gas.mul(1.5);
|
||||
}
|
||||
|
||||
return gas;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -84,6 +73,114 @@ export function postTransaction (_func, _options, _values = []) {
|
||||
});
|
||||
}
|
||||
|
||||
export function deploy (contract, _options, values, metadata = {}, statecb = () => {}) {
|
||||
const options = { ..._options };
|
||||
const { api } = contract;
|
||||
const address = options.from;
|
||||
|
||||
return WalletsUtils
|
||||
.isWallet(api, address)
|
||||
.then((isWallet) => {
|
||||
if (!isWallet) {
|
||||
return contract.deploy(options, values, statecb);
|
||||
}
|
||||
|
||||
statecb(null, { state: 'estimateGas' });
|
||||
|
||||
return deployEstimateGas(contract, options, values)
|
||||
.then(([gasEst, gas]) => {
|
||||
options.gas = gas.toFixed(0);
|
||||
|
||||
statecb(null, { state: 'postTransaction', gas });
|
||||
|
||||
return WalletsUtils.getDeployArgs(contract, options, values);
|
||||
})
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
|
||||
return func._postTransaction(options, values)
|
||||
.then((requestId) => {
|
||||
statecb(null, { state: 'checkRequest', requestId });
|
||||
return contract._pollCheckRequest(requestId);
|
||||
})
|
||||
.then((txhash) => {
|
||||
statecb(null, { state: 'getTransactionReceipt', txhash });
|
||||
return contract._pollTransactionReceipt(txhash, options.gas);
|
||||
})
|
||||
.then((receipt) => {
|
||||
if (receipt.gasUsed.eq(options.gas)) {
|
||||
throw new Error(`Contract not deployed, gasUsed == ${options.gas.toFixed(0)}`);
|
||||
}
|
||||
|
||||
const logs = WalletsUtils.parseLogs(api, receipt.logs || []);
|
||||
|
||||
const confirmationLog = logs.find((log) => log.event === 'ConfirmationNeeded');
|
||||
const transactionLog = logs.find((log) => log.event === 'SingleTransact');
|
||||
|
||||
if (!confirmationLog && !transactionLog) {
|
||||
throw new Error('Something went wrong in the Wallet Contract (no logs have been emitted)...');
|
||||
}
|
||||
|
||||
// Confirmations are needed from the other owners
|
||||
if (confirmationLog) {
|
||||
const operationHash = api.util.bytesToHex(confirmationLog.params.operation.value);
|
||||
|
||||
// Add the contract to pending contracts
|
||||
WalletsUtils.addPendingContract(address, operationHash, metadata);
|
||||
statecb(null, { state: 'confirmationNeeded' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the contract address in the receip
|
||||
receipt.contractAddress = transactionLog.params.created.value;
|
||||
|
||||
const contractAddress = receipt.contractAddress;
|
||||
|
||||
statecb(null, { state: 'hasReceipt', receipt });
|
||||
contract._receipt = receipt;
|
||||
contract._address = contractAddress;
|
||||
|
||||
statecb(null, { state: 'getCode' });
|
||||
|
||||
return api.eth.getCode(contractAddress)
|
||||
.then((code) => {
|
||||
if (code === '0x') {
|
||||
throw new Error('Contract not deployed, getCode returned 0x');
|
||||
}
|
||||
|
||||
statecb(null, { state: 'completed' });
|
||||
return contractAddress;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function deployEstimateGas (contract, _options, values) {
|
||||
const options = { ..._options };
|
||||
const { api } = contract;
|
||||
const address = options.from;
|
||||
|
||||
return WalletsUtils
|
||||
.isWallet(api, address)
|
||||
.then((isWallet) => {
|
||||
if (!isWallet) {
|
||||
return contract.deployEstimateGas(options, values);
|
||||
}
|
||||
|
||||
return WalletsUtils
|
||||
.getDeployArgs(contract, options, values)
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
|
||||
return func.estimateGas(options, values);
|
||||
})
|
||||
.then((gasEst) => {
|
||||
return [gasEst, gasEst.mul(1.05)];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function patchApi (api) {
|
||||
api.patch = {
|
||||
...api.patch,
|
||||
|
@ -16,59 +16,154 @@
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { intersection, range, uniq } from 'lodash';
|
||||
import store from 'store';
|
||||
|
||||
import Abi from '~/abi';
|
||||
import Contract from '~/api/contract';
|
||||
import { bytesToHex, toHex } from '~/api/util/format';
|
||||
import { validateAddress } from '~/util/validation';
|
||||
import WalletAbi from '~/contracts/abi/wallet.json';
|
||||
import OldWalletAbi from '~/contracts/abi/old-wallet.json';
|
||||
|
||||
const LS_PENDING_CONTRACTS_KEY = '_parity::wallets::pendingContracts';
|
||||
|
||||
const _cachedWalletLookup = {};
|
||||
let _cachedAccounts = {};
|
||||
|
||||
const walletAbi = new Abi(WalletAbi);
|
||||
const oldWalletAbi = new Abi(OldWalletAbi);
|
||||
|
||||
const walletEvents = walletAbi.events.reduce((events, event) => {
|
||||
events[event.name] = event;
|
||||
return events;
|
||||
}, {});
|
||||
|
||||
const oldWalletEvents = oldWalletAbi.events.reduce((events, event) => {
|
||||
events[event.name] = event;
|
||||
return events;
|
||||
}, {});
|
||||
|
||||
const WalletSignatures = {
|
||||
OwnerChanged: toHex(walletEvents.OwnerChanged.signature),
|
||||
OwnerAdded: toHex(walletEvents.OwnerAdded.signature),
|
||||
OwnerRemoved: toHex(walletEvents.OwnerRemoved.signature),
|
||||
RequirementChanged: toHex(walletEvents.RequirementChanged.signature),
|
||||
Confirmation: toHex(walletEvents.Confirmation.signature),
|
||||
Revoke: toHex(walletEvents.Revoke.signature),
|
||||
Deposit: toHex(walletEvents.Deposit.signature),
|
||||
SingleTransact: toHex(walletEvents.SingleTransact.signature),
|
||||
MultiTransact: toHex(walletEvents.MultiTransact.signature),
|
||||
ConfirmationNeeded: toHex(walletEvents.ConfirmationNeeded.signature),
|
||||
|
||||
Old: {
|
||||
SingleTransact: toHex(oldWalletEvents.SingleTransact.signature),
|
||||
MultiTransact: toHex(oldWalletEvents.MultiTransact.signature)
|
||||
}
|
||||
};
|
||||
|
||||
export default class WalletsUtils {
|
||||
static getWalletSignatures () {
|
||||
return WalletSignatures;
|
||||
}
|
||||
|
||||
static getPendingContracts () {
|
||||
return store.get(LS_PENDING_CONTRACTS_KEY) || {};
|
||||
}
|
||||
|
||||
static setPendingContracts (contracts = {}) {
|
||||
return store.set(LS_PENDING_CONTRACTS_KEY, contracts);
|
||||
}
|
||||
|
||||
static removePendingContract (operationHash) {
|
||||
const nextContracts = WalletsUtils.getPendingContracts();
|
||||
|
||||
delete nextContracts[operationHash];
|
||||
WalletsUtils.setPendingContracts(nextContracts);
|
||||
}
|
||||
|
||||
static addPendingContract (address, operationHash, metadata) {
|
||||
const nextContracts = {
|
||||
...WalletsUtils.getPendingContracts(),
|
||||
[ operationHash ]: {
|
||||
address,
|
||||
metadata,
|
||||
operationHash
|
||||
}
|
||||
};
|
||||
|
||||
WalletsUtils.setPendingContracts(nextContracts);
|
||||
}
|
||||
|
||||
static cacheAccounts (accounts) {
|
||||
_cachedAccounts = accounts;
|
||||
}
|
||||
|
||||
static getCallArgs (api, options, values = []) {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
const walletAddress = options.from;
|
||||
|
||||
const promises = [
|
||||
api.parity.accountsInfo(),
|
||||
WalletsUtils.fetchOwners(walletContract.at(options.from))
|
||||
];
|
||||
return WalletsUtils
|
||||
.fetchOwners(walletContract.at(walletAddress))
|
||||
.then((owners) => {
|
||||
const addresses = Object.keys(_cachedAccounts);
|
||||
const ownerAddress = intersection(addresses, owners).pop();
|
||||
|
||||
return Promise
|
||||
.all(promises)
|
||||
.then(([ accounts, owners ]) => {
|
||||
const addresses = Object.keys(accounts);
|
||||
const owner = intersection(addresses, owners).pop();
|
||||
|
||||
if (!owner) {
|
||||
if (!ownerAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return owner;
|
||||
})
|
||||
.then((owner) => {
|
||||
if (!owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const _options = Object.assign({}, options);
|
||||
const { from, to, value = new BigNumber(0), data } = options;
|
||||
const account = _cachedAccounts[ownerAddress];
|
||||
const _options = { ...options };
|
||||
const { to, value = new BigNumber(0), data } = _options;
|
||||
|
||||
delete _options.data;
|
||||
|
||||
const nextValues = [ to, value, data ];
|
||||
const nextOptions = {
|
||||
..._options,
|
||||
from: owner,
|
||||
to: from,
|
||||
from: ownerAddress,
|
||||
to: walletAddress,
|
||||
value: new BigNumber(0)
|
||||
};
|
||||
|
||||
const execFunc = walletContract.instance.execute;
|
||||
const callArgs = { func: execFunc, options: nextOptions, values: nextValues };
|
||||
|
||||
return { func: execFunc, options: nextOptions, values: nextValues };
|
||||
if (!account.wallet) {
|
||||
return callArgs;
|
||||
}
|
||||
|
||||
const nextData = walletContract.getCallData(execFunc, nextOptions, nextValues);
|
||||
|
||||
return WalletsUtils.getCallArgs(api, { ...nextOptions, data: nextData }, nextValues);
|
||||
});
|
||||
}
|
||||
|
||||
static getDeployArgs (contract, options, values) {
|
||||
const { api } = contract;
|
||||
const func = contract.constructors[0];
|
||||
|
||||
options.data = contract.getCallData(func, options, values);
|
||||
options.to = '0x';
|
||||
|
||||
return WalletsUtils
|
||||
.getCallArgs(api, options, values)
|
||||
.then((callArgs) => {
|
||||
if (!callArgs) {
|
||||
console.error('no call args', callArgs);
|
||||
throw new Error('you do not own this wallet');
|
||||
}
|
||||
|
||||
return callArgs;
|
||||
});
|
||||
}
|
||||
|
||||
static parseLogs (api, logs = []) {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
|
||||
return walletContract.parseEventLogs(logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given address could be
|
||||
* a Wallet. The result is cached in order not
|
||||
@ -199,16 +294,18 @@ export default class WalletsUtils {
|
||||
}
|
||||
|
||||
static fetchTransactions (walletContract) {
|
||||
const walletInstance = walletContract.instance;
|
||||
const signatures = {
|
||||
single: toHex(walletInstance.SingleTransact.signature),
|
||||
multi: toHex(walletInstance.MultiTransact.signature),
|
||||
deposit: toHex(walletInstance.Deposit.signature)
|
||||
};
|
||||
const { api } = walletContract;
|
||||
const pendingContracts = WalletsUtils.getPendingContracts();
|
||||
|
||||
return walletContract
|
||||
.getAllLogs({
|
||||
topics: [ [ signatures.single, signatures.multi, signatures.deposit ] ]
|
||||
topics: [ [
|
||||
WalletSignatures.SingleTransact,
|
||||
WalletSignatures.MultiTransact,
|
||||
WalletSignatures.Deposit,
|
||||
WalletSignatures.Old.SingleTransact,
|
||||
WalletSignatures.Old.MultiTransact
|
||||
] ]
|
||||
})
|
||||
.then((logs) => {
|
||||
return logs.sort((logA, logB) => {
|
||||
@ -226,11 +323,11 @@ export default class WalletsUtils {
|
||||
const signature = toHex(log.topics[0]);
|
||||
|
||||
const value = log.params.value.value;
|
||||
const from = signature === signatures.deposit
|
||||
const from = signature === WalletSignatures.Deposit
|
||||
? log.params['_from'].value
|
||||
: walletContract.address;
|
||||
|
||||
const to = signature === signatures.deposit
|
||||
const to = signature === WalletSignatures.Deposit
|
||||
? walletContract.address
|
||||
: log.params.to.value;
|
||||
|
||||
@ -240,8 +337,53 @@ export default class WalletsUtils {
|
||||
from, to, value
|
||||
};
|
||||
|
||||
if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
|
||||
transaction.creates = log.params.created.value;
|
||||
delete transaction.to;
|
||||
}
|
||||
|
||||
if (log.params.operation) {
|
||||
transaction.operation = bytesToHex(log.params.operation.value);
|
||||
const operation = bytesToHex(log.params.operation.value);
|
||||
|
||||
// Add the pending contract to the contracts
|
||||
if (pendingContracts[operation]) {
|
||||
const { metadata } = pendingContracts[operation];
|
||||
const contractName = metadata.name;
|
||||
|
||||
metadata.blockNumber = log.blockNumber;
|
||||
|
||||
// The contract creation might not be in the same log,
|
||||
// but must be in the same transaction (eg. Contract creation
|
||||
// from Wallet within a Wallet)
|
||||
api.eth
|
||||
.getTransactionReceipt(log.transactionHash)
|
||||
.then((transactionReceipt) => {
|
||||
const transactionLogs = WalletsUtils.parseLogs(api, transactionReceipt.logs);
|
||||
const creationLog = transactionLogs.find((log) => {
|
||||
return log.params.created && !/^(0x)?0*$/.test(log.params.created.value);
|
||||
});
|
||||
|
||||
if (!creationLog) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contractAddress = creationLog.params.created.value;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
api.parity.setAccountName(contractAddress, contractName),
|
||||
api.parity.setAccountMeta(contractAddress, metadata)
|
||||
])
|
||||
.then(() => {
|
||||
WalletsUtils.removePendingContract(operation);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('adding wallet contract', error);
|
||||
});
|
||||
}
|
||||
|
||||
transaction.operation = operation;
|
||||
}
|
||||
|
||||
if (log.params.data) {
|
||||
|
@ -41,8 +41,9 @@ export default class SignerStore {
|
||||
this.balances = Object.assign({}, this.balances, balances);
|
||||
}
|
||||
|
||||
@action setLocalHashes = (localHashes) => {
|
||||
if (!isEqual(localHashes, this.localHashes)) {
|
||||
@action setLocalHashes = (localHashes = []) => {
|
||||
// Use slice to make sure they are both Arrays (MobX uses Objects for Observable Arrays)
|
||||
if (!isEqual(localHashes.slice(), this.localHashes.slice())) {
|
||||
this.localHashes = localHashes;
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ export default class WalletTransactions extends Component {
|
||||
}
|
||||
|
||||
const txRows = transactions.slice(0, 15).map((transaction, index) => {
|
||||
const { transactionHash, blockNumber, from, to, value, data } = transaction;
|
||||
const { transactionHash, data } = transaction;
|
||||
|
||||
return (
|
||||
<TxRow
|
||||
@ -79,12 +79,9 @@ export default class WalletTransactions extends Component {
|
||||
netVersion={ netVersion }
|
||||
key={ `${transactionHash}_${index}` }
|
||||
tx={ {
|
||||
blockNumber,
|
||||
from,
|
||||
hash: transactionHash,
|
||||
input: data && bytesToHex(data) || '',
|
||||
to,
|
||||
value
|
||||
...transaction
|
||||
} }
|
||||
/>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user