[beta] UI backports (#4809)
* 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 * SMS Faucet (#4774) * Faucet * Remove flakey button-index testing * Only display faucet when sms verified (mainnet) * simplify availability checks * WIP * Resuest from verified -> verified * Update endpoint, display response text * Error icon on errors * Parse hash text response * Use /api/:address endpoint * hash -> data * Adjust sms-certified message * Fix SectionList hovering issue (#4749) * Fix SectionList Items hover when <3 items * Even easier... * lint (new)
This commit is contained in:
parent
3b56e8eded
commit
c4196a5de3
@ -107,34 +107,26 @@ export default class Contract {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy (options, values, statecb) {
|
deploy (options, values, statecb = () => {}) {
|
||||||
const setState = (state) => {
|
statecb(null, { state: 'estimateGas' });
|
||||||
if (!statecb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return statecb(null, state);
|
|
||||||
};
|
|
||||||
|
|
||||||
setState({ state: 'estimateGas' });
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
.deployEstimateGas(options, values)
|
.deployEstimateGas(options, values)
|
||||||
.then(([gasEst, gas]) => {
|
.then(([gasEst, gas]) => {
|
||||||
options.gas = gas.toFixed(0);
|
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
|
return this._api.parity
|
||||||
.postTransaction(_options)
|
.postTransaction(encodedOptions)
|
||||||
.then((requestId) => {
|
.then((requestId) => {
|
||||||
setState({ state: 'checkRequest', requestId });
|
statecb(null, { state: 'checkRequest', requestId });
|
||||||
return this._pollCheckRequest(requestId);
|
return this._pollCheckRequest(requestId);
|
||||||
})
|
})
|
||||||
.then((txhash) => {
|
.then((txhash) => {
|
||||||
setState({ state: 'getTransactionReceipt', txhash });
|
statecb(null, { state: 'getTransactionReceipt', txhash });
|
||||||
return this._pollTransactionReceipt(txhash, gas);
|
return this._pollTransactionReceipt(txhash, gas);
|
||||||
})
|
})
|
||||||
.then((receipt) => {
|
.then((receipt) => {
|
||||||
@ -142,23 +134,23 @@ export default class Contract {
|
|||||||
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
|
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({ state: 'hasReceipt', receipt });
|
statecb(null, { state: 'hasReceipt', receipt });
|
||||||
this._receipt = receipt;
|
this._receipt = receipt;
|
||||||
this._address = receipt.contractAddress;
|
this._address = receipt.contractAddress;
|
||||||
return this._address;
|
return this._address;
|
||||||
});
|
})
|
||||||
})
|
.then((address) => {
|
||||||
.then((address) => {
|
statecb(null, { state: 'getCode' });
|
||||||
setState({ state: 'getCode' });
|
return this._api.eth.getCode(this._address);
|
||||||
return this._api.eth.getCode(this._address);
|
})
|
||||||
})
|
.then((code) => {
|
||||||
.then((code) => {
|
if (code === '0x') {
|
||||||
if (code === '0x') {
|
throw new Error('Contract not deployed, getCode returned 0x');
|
||||||
throw new Error('Contract not deployed, getCode returned 0x');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setState({ state: 'completed' });
|
statecb(null, { state: 'completed' });
|
||||||
return this._address;
|
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
|
// 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
|
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||||
// interior is executed.
|
// interior is executed.
|
||||||
pragma solidity ^0.4.6;
|
|
||||||
|
|
||||||
contract multisig {
|
pragma solidity ^0.4.9;
|
||||||
// EVENTS
|
|
||||||
|
|
||||||
// this contract can accept a confirmation, in which case
|
contract WalletEvents {
|
||||||
// we record owner and operation (hash) alongside it.
|
// EVENTS
|
||||||
event Confirmation(address owner, bytes32 operation);
|
|
||||||
event Revoke(address owner, bytes32 operation);
|
|
||||||
|
|
||||||
// some others are in the case of an owner changing.
|
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||||
event OwnerChanged(address oldOwner, address newOwner);
|
// we record owner and operation (hash) alongside it.
|
||||||
event OwnerAdded(address newOwner);
|
event Confirmation(address owner, bytes32 operation);
|
||||||
event OwnerRemoved(address oldOwner);
|
event Revoke(address owner, bytes32 operation);
|
||||||
|
|
||||||
// the last one is emitted if the required signatures change
|
// some others are in the case of an owner changing.
|
||||||
event RequirementChanged(uint newRequirement);
|
event OwnerChanged(address oldOwner, address newOwner);
|
||||||
|
event OwnerAdded(address newOwner);
|
||||||
|
event OwnerRemoved(address oldOwner);
|
||||||
|
|
||||||
// Funds has arrived into the wallet (record how much).
|
// the last one is emitted if the required signatures change
|
||||||
event Deposit(address _from, uint value);
|
event RequirementChanged(uint newRequirement);
|
||||||
// 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);
|
// Funds has arrived into the wallet (record how much).
|
||||||
// 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 Deposit(address _from, uint value);
|
||||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
|
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||||
// Confirmation still needed for a transaction.
|
event SingleTransact(address owner, uint value, address to, bytes data, address created);
|
||||||
event ConfirmationNeeded(bytes32 operation, address initiator, 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, address created);
|
||||||
|
// Confirmation still needed for a transaction.
|
||||||
|
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract multisigAbi is multisig {
|
contract WalletAbi {
|
||||||
function isOwner(address _addr) returns (bool);
|
// 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 removeOwner(address _owner) external;
|
||||||
function setDailyLimit(uint _newLimit);
|
|
||||||
|
|
||||||
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
|
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||||
function revoke(bytes32 _operation);
|
function setDailyLimit(uint _newLimit) external;
|
||||||
|
|
||||||
function changeOwner(address _from, address _to);
|
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||||
|
function confirm(bytes32 _h) returns (bool o_success);
|
||||||
function execute(address _to, uint _value, bytes _data) returns(bool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contract WalletLibrary is multisig {
|
contract WalletLibrary is WalletEvents {
|
||||||
// TYPES
|
// TYPES
|
||||||
|
|
||||||
// struct for the status of a pending operation.
|
// struct for the status of a pending operation.
|
||||||
struct PendingState {
|
struct PendingState {
|
||||||
uint yetNeeded;
|
uint yetNeeded;
|
||||||
uint ownersDone;
|
uint ownersDone;
|
||||||
uint index;
|
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.
|
delete m_pendingIndex;
|
||||||
struct Transaction {
|
}
|
||||||
address to;
|
|
||||||
uint value;
|
|
||||||
bytes data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************
|
// FIELDS
|
||||||
***** MULTI OWNED SECTION ****
|
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.
|
uint public m_dailyLimit;
|
||||||
modifier onlyowner {
|
uint public m_spentToday;
|
||||||
if (isOwner(msg.sender))
|
uint public m_lastDay;
|
||||||
_;
|
|
||||||
}
|
|
||||||
// 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
|
// list of owners
|
||||||
|
uint[256] m_owners;
|
||||||
|
|
||||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
uint constant c_maxOwners = 250;
|
||||||
// as well as the selection of addresses capable of confirming them.
|
// index on the list of owners to allow reverse lookup
|
||||||
function initMultiowned(address[] _owners, uint _required) {
|
mapping(uint => uint) m_ownerIndex;
|
||||||
m_numOwners = _owners.length + 1;
|
// the ongoing operations.
|
||||||
m_owners[1] = uint(msg.sender);
|
mapping(bytes32 => PendingState) m_pending;
|
||||||
m_ownerIndex[uint(msg.sender)] = 1;
|
bytes32[] m_pendingIndex;
|
||||||
m_required = _required;
|
|
||||||
|
|
||||||
for (uint i = 0; i < _owners.length; ++i)
|
// pending transactions we have at present.
|
||||||
{
|
mapping (bytes32 => Transaction) m_txs;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Compute the size of the call data : arrays has 2
|
||||||
// calls the `initWallet` method of the Library in this context
|
// 32bytes for offset and length, plus 32bytes per element ;
|
||||||
function Wallet(address[] _owners, uint _required, uint _daylimit) {
|
// plus 2 32bytes for each uint
|
||||||
// Signature of the Wallet Library's init function
|
uint argarraysize = (2 + _owners.length);
|
||||||
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
|
uint argsize = (2 + argarraysize) * 32;
|
||||||
address target = _walletLibrary;
|
|
||||||
|
|
||||||
// Compute the size of the call data : arrays has 2
|
assembly {
|
||||||
// 32bytes for offset and length, plus 32bytes per element ;
|
// Add the signature first to memory
|
||||||
// plus 2 32bytes for each uint
|
mstore(0x0, sig)
|
||||||
uint argarraysize = (2 + _owners.length);
|
// Add the call data, which is at the end of the
|
||||||
uint argsize = (2 + argarraysize) * 32;
|
// code
|
||||||
|
codecopy(0x4, sub(codesize, argsize), argsize)
|
||||||
assembly {
|
// Delegate call to the library
|
||||||
// Add the signature first to memory
|
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
|
||||||
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
|
// gets called when no other function matches
|
||||||
function() payable {
|
function() payable {
|
||||||
// just being sent some cash?
|
// just being sent some cash?
|
||||||
if (msg.value > 0)
|
if (msg.value > 0)
|
||||||
Deposit(msg.sender, msg.value);
|
Deposit(msg.sender, msg.value);
|
||||||
else if (msg.data.length > 0)
|
else if (msg.data.length > 0)
|
||||||
_walletLibrary.delegatecall(msg.data);
|
_walletLibrary.delegatecall(msg.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||||
function getOwner(uint ownerIndex) constant returns (address) {
|
function getOwner(uint ownerIndex) constant returns (address) {
|
||||||
return address(m_owners[ownerIndex + 1]);
|
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) {
|
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
||||||
return _walletLibrary.delegatecall(msg.data);
|
return _walletLibrary.delegatecall(msg.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOwner(address _addr) returns (bool) {
|
function isOwner(address _addr) constant returns (bool) {
|
||||||
return _walletLibrary.delegatecall(msg.data);
|
return _walletLibrary.delegatecall(msg.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIELDS
|
// FIELDS
|
||||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||||
|
|
||||||
// the number of owners that must confirm the same operation before it is run.
|
// the number of owners that must confirm the same operation before it is run.
|
||||||
uint public m_required;
|
uint public m_required;
|
||||||
// pointer used to find a free slot in m_owners
|
// pointer used to find a free slot in m_owners
|
||||||
uint public m_numOwners;
|
uint public m_numOwners;
|
||||||
|
|
||||||
uint public m_dailyLimit;
|
uint public m_dailyLimit;
|
||||||
uint public m_spentToday;
|
uint public m_spentToday;
|
||||||
uint public m_lastDay;
|
uint public m_lastDay;
|
||||||
|
|
||||||
// list of owners
|
// list of owners
|
||||||
uint[256] m_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
|
// 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
|
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||||
// interior is executed.
|
// interior is executed.
|
||||||
pragma solidity ^0.4.6;
|
|
||||||
|
pragma solidity ^0.4.9;
|
||||||
|
|
||||||
contract multiowned {
|
contract multiowned {
|
||||||
|
|
||||||
// TYPES
|
// TYPES
|
||||||
|
|
||||||
// struct for the status of a pending operation.
|
// struct for the status of a pending operation.
|
||||||
struct PendingState {
|
struct PendingState {
|
||||||
uint yetNeeded;
|
uint yetNeeded;
|
||||||
uint ownersDone;
|
uint ownersDone;
|
||||||
uint index;
|
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
|
// Revokes a prior confirmation of the given operation
|
||||||
|
function revoke(bytes32 _operation) external {
|
||||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||||
// we record owner and operation (hash) alongside it.
|
// make sure they're an owner
|
||||||
event Confirmation(address owner, bytes32 operation);
|
if (ownerIndex == 0) return;
|
||||||
event Revoke(address owner, bytes32 operation);
|
uint ownerIndexBit = 2**ownerIndex;
|
||||||
// some others are in the case of an owner changing.
|
var pending = m_pending[_operation];
|
||||||
event OwnerChanged(address oldOwner, address newOwner);
|
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||||
event OwnerAdded(address newOwner);
|
pending.yetNeeded++;
|
||||||
event OwnerRemoved(address oldOwner);
|
pending.ownersDone -= ownerIndexBit;
|
||||||
// the last one is emitted if the required signatures change
|
Revoke(msg.sender, _operation);
|
||||||
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.
|
// Replaces an owner `_from` with another `_to`.
|
||||||
modifier onlymanyowners(bytes32 _operation) {
|
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||||
if (confirmAndCheck(_operation))
|
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;
|
||||||
}
|
}
|
||||||
|
// determine the bit to set for this owner.
|
||||||
// METHODS
|
uint ownerIndexBit = 2**ownerIndex;
|
||||||
|
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||||
// as well as the selection of addresses capable of confirming them.
|
Confirmation(msg.sender, _operation);
|
||||||
function multiowned(address[] _owners, uint _required) {
|
// ok - check if count is enough to go ahead.
|
||||||
m_numOwners = _owners.length + 1;
|
if (pending.yetNeeded <= 1) {
|
||||||
m_owners[1] = uint(msg.sender);
|
// enough confirmations: reset and run interior.
|
||||||
m_ownerIndex[uint(msg.sender)] = 1;
|
delete m_pendingIndex[m_pending[_operation].index];
|
||||||
for (uint i = 0; i < _owners.length; ++i)
|
delete m_pending[_operation];
|
||||||
{
|
return true;
|
||||||
m_owners[2 + i] = uint(_owners[i]);
|
}
|
||||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
else
|
||||||
}
|
{
|
||||||
m_required = _required;
|
// not enough: record that this owner in particular confirmed.
|
||||||
|
pending.yetNeeded--;
|
||||||
|
pending.ownersDone |= ownerIndexBit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Revokes a prior confirmation of the given operation
|
function reorganizeOwners() private {
|
||||||
function revoke(bytes32 _operation) external {
|
uint free = 1;
|
||||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
while (free < m_numOwners)
|
||||||
// make sure they're an owner
|
{
|
||||||
if (ownerIndex == 0) return;
|
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||||
uint ownerIndexBit = 2**ownerIndex;
|
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||||
var pending = m_pending[_operation];
|
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
{
|
||||||
pending.yetNeeded++;
|
m_owners[free] = m_owners[m_numOwners];
|
||||||
pending.ownersDone -= ownerIndexBit;
|
m_ownerIndex[m_owners[free]] = free;
|
||||||
Revoke(msg.sender, _operation);
|
m_owners[m_numOwners] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Replaces an owner `_from` with another `_to`.
|
function clearPending() internal {
|
||||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
uint length = m_pendingIndex.length;
|
||||||
if (isOwner(_to)) return;
|
for (uint i = 0; i < length; ++i)
|
||||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
if (m_pendingIndex[i] != 0)
|
||||||
if (ownerIndex == 0) return;
|
delete m_pending[m_pendingIndex[i]];
|
||||||
|
delete m_pendingIndex;
|
||||||
|
}
|
||||||
|
|
||||||
clearPending();
|
// FIELDS
|
||||||
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 {
|
// the number of owners that must confirm the same operation before it is run.
|
||||||
if (isOwner(_owner)) return;
|
uint public m_required;
|
||||||
|
// pointer used to find a free slot in m_owners
|
||||||
|
uint public m_numOwners;
|
||||||
|
|
||||||
clearPending();
|
// list of owners
|
||||||
if (m_numOwners >= c_maxOwners)
|
uint[256] m_owners;
|
||||||
reorganizeOwners();
|
uint constant c_maxOwners = 250;
|
||||||
if (m_numOwners >= c_maxOwners)
|
// index on the list of owners to allow reverse lookup
|
||||||
return;
|
mapping(uint => uint) m_ownerIndex;
|
||||||
m_numOwners++;
|
// the ongoing operations.
|
||||||
m_owners[m_numOwners] = uint(_owner);
|
mapping(bytes32 => PendingState) m_pending;
|
||||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
bytes32[] m_pendingIndex;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
|
// 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.
|
// uses is specified in the modifier.
|
||||||
contract daylimit is multiowned {
|
contract daylimit is multiowned {
|
||||||
|
|
||||||
// MODIFIERS
|
// METHODS
|
||||||
|
|
||||||
// simple modifier for daily limit.
|
// constructor - stores initial daily limit and records the present day's index.
|
||||||
modifier limitedDaily(uint _value) {
|
function daylimit(uint _limit) {
|
||||||
if (underLimit(_value))
|
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();
|
||||||
}
|
}
|
||||||
|
// check to see if there's enough left - if so, subtract and return true.
|
||||||
// METHODS
|
// overflow protection // dailyLimit check
|
||||||
|
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||||
// constructor - stores initial daily limit and records the present day's index.
|
m_spentToday += _value;
|
||||||
function daylimit(uint _limit) {
|
return true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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
|
uint public m_dailyLimit;
|
||||||
// returns true. otherwise just returns false.
|
uint public m_spentToday;
|
||||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
uint public m_lastDay;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// interface contract for multisig proxy contracts; see below for docs.
|
// interface contract for multisig proxy contracts; see below for docs.
|
||||||
contract multisig {
|
contract multisig {
|
||||||
|
|
||||||
// EVENTS
|
// EVENTS
|
||||||
|
|
||||||
// logged events:
|
// logged events:
|
||||||
// Funds has arrived into the wallet (record how much).
|
// Funds has arrived into the wallet (record how much).
|
||||||
event Deposit(address _from, uint value);
|
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).
|
// 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);
|
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).
|
// 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);
|
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
|
||||||
// Confirmation still needed for a transaction.
|
// Confirmation still needed for a transaction.
|
||||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||||
|
|
||||||
// FUNCTIONS
|
// FUNCTIONS
|
||||||
|
|
||||||
// TODO: document
|
// TODO: document
|
||||||
function changeOwner(address _from, address _to) external;
|
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32);
|
function confirm(bytes32 _h) external returns (bool o_success);
|
||||||
function confirm(bytes32 _h) returns (bool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// usage:
|
// usage:
|
||||||
@ -310,79 +302,102 @@ contract multisig {
|
|||||||
// Wallet(w).from(anotherOwner).confirm(h);
|
// Wallet(w).from(anotherOwner).confirm(h);
|
||||||
contract Wallet is multisig, multiowned, daylimit {
|
contract Wallet is multisig, multiowned, daylimit {
|
||||||
|
|
||||||
// TYPES
|
// TYPES
|
||||||
|
|
||||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||||
struct Transaction {
|
struct Transaction {
|
||||||
address to;
|
address to;
|
||||||
uint value;
|
uint value;
|
||||||
bytes data;
|
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
|
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
||||||
|
assembly {
|
||||||
// constructor - just pass on the owner array to the multiowned and
|
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
||||||
// the limit to daylimit
|
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
||||||
function Wallet(address[] _owners, uint _required, uint _daylimit)
|
|
||||||
multiowned(_owners, _required) daylimit(_daylimit) {
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// kills the contract sending everything to `_to`.
|
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
// to determine the body of the transaction from the hash provided.
|
||||||
suicide(_to);
|
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
|
// INTERNAL METHODS
|
||||||
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.
|
function clearPending() internal {
|
||||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
uint length = m_pendingIndex.length;
|
||||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
for (uint i = 0; i < length; ++i)
|
||||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
delete m_txs[m_pendingIndex[i]];
|
||||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) {
|
super.clearPending();
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
// FIELDS
|
||||||
// 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
|
// pending transactions we have at present.
|
||||||
|
mapping (bytes32 => Transaction) m_txs;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
|
|
||||||
import { Chip } from 'material-ui';
|
import { Chip } from 'material-ui';
|
||||||
|
|
||||||
import IdentityIcon from '../IdentityIcon' ;
|
import IdentityIcon from '../IdentityIcon';
|
||||||
|
|
||||||
import styles from './chip.css';
|
import styles from './chip.css';
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { omitBy } from 'lodash';
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
@ -106,9 +105,6 @@ export default class WalletDetails extends Component {
|
|||||||
renderMultisigDetails () {
|
renderMultisigDetails () {
|
||||||
const { accounts, wallet, errors } = this.props;
|
const { accounts, wallet, errors } = this.props;
|
||||||
|
|
||||||
// Wallets cannot create contracts
|
|
||||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<Input
|
<Input
|
||||||
@ -148,7 +144,7 @@ export default class WalletDetails extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
accounts={ _accounts }
|
accounts={ accounts }
|
||||||
error={ errors.account }
|
error={ errors.account }
|
||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -22,10 +22,11 @@ import Contract from '~/api/contract';
|
|||||||
import { ERROR_CODES } from '~/api/transport/error';
|
import { ERROR_CODES } from '~/api/transport/error';
|
||||||
import Contracts from '~/contracts';
|
import Contracts from '~/contracts';
|
||||||
import { wallet as walletAbi } from '~/contracts/abi';
|
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 { validateUint, validateAddress, validateName } from '~/util/validation';
|
||||||
import { toWei } from '~/api/util/wei';
|
import { toWei } from '~/api/util/wei';
|
||||||
|
import { deploy } from '~/util/tx';
|
||||||
import WalletsUtils from '~/util/wallets';
|
import WalletsUtils from '~/util/wallets';
|
||||||
|
|
||||||
const STEPS = {
|
const STEPS = {
|
||||||
@ -179,6 +180,8 @@ export default class CreateWalletStore {
|
|||||||
this.wallet.owners = owners;
|
this.wallet.owners = owners;
|
||||||
this.wallet.required = require.toNumber();
|
this.wallet.required = require.toNumber();
|
||||||
this.wallet.dailylimit = dailylimit.limit;
|
this.wallet.dailylimit = dailylimit.limit;
|
||||||
|
|
||||||
|
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.addWallet(this.wallet);
|
return this.addWallet(this.wallet);
|
||||||
@ -202,21 +205,51 @@ export default class CreateWalletStore {
|
|||||||
return null; // exception when registry is not available
|
return null; // exception when registry is not available
|
||||||
})
|
})
|
||||||
.then((address) => {
|
.then((address) => {
|
||||||
const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase();
|
if (!address || /^(0x)?0*$/.test(address)) {
|
||||||
const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress)
|
return null;
|
||||||
? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress)
|
}
|
||||||
: fullWalletCode;
|
|
||||||
|
// 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 = {
|
const options = {
|
||||||
data: code,
|
data: code,
|
||||||
from: account
|
from: account
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.api
|
const contract = this.api.newContract(walletAbi);
|
||||||
.newContract(walletAbi)
|
|
||||||
.deploy(options, [ owners, required, daylimit ], this.onDeploymentState);
|
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||||
|
return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState);
|
||||||
})
|
})
|
||||||
.then((address) => {
|
.then((address) => {
|
||||||
|
if (!address || /^(0x)?0*$/.test(address)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.deployed = true;
|
this.deployed = true;
|
||||||
this.wallet.address = address;
|
this.wallet.address = address;
|
||||||
return this.addWallet(this.wallet);
|
return this.addWallet(this.wallet);
|
||||||
@ -233,26 +266,37 @@ export default class CreateWalletStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action addWallet = (wallet) => {
|
@action addWallet = (wallet) => {
|
||||||
const { address, name, description } = wallet;
|
const { address, name, metadata } = wallet;
|
||||||
|
|
||||||
return Promise
|
return Promise
|
||||||
.all([
|
.all([
|
||||||
this.api.parity.setAccountName(address, name),
|
this.api.parity.setAccountName(address, name),
|
||||||
this.api.parity.setAccountMeta(address, {
|
this.api.parity.setAccountMeta(address, metadata)
|
||||||
abi: walletAbi,
|
|
||||||
wallet: true,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
deleted: false,
|
|
||||||
description,
|
|
||||||
name,
|
|
||||||
tags: ['wallet']
|
|
||||||
})
|
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.step = 'INFO';
|
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) => {
|
onDeploymentState = (error, data) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return console.error('createWallet::onDeploymentState', error);
|
return console.error('createWallet::onDeploymentState', error);
|
||||||
@ -298,6 +342,15 @@ export default class CreateWalletStore {
|
|||||||
);
|
);
|
||||||
return;
|
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':
|
case 'completed':
|
||||||
this.deployState = (
|
this.deployState = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// 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 { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
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 { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
|
||||||
import { CancelIcon, DoneIcon } from '~/ui/Icons';
|
import { CancelIcon, DoneIcon } from '~/ui/Icons';
|
||||||
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
||||||
|
import { deploy, deployEstimateGas } from '~/util/tx';
|
||||||
|
|
||||||
import DetailsStep from './DetailsStep';
|
import DetailsStep from './DetailsStep';
|
||||||
import ParametersStep from './ParametersStep';
|
import ParametersStep from './ParametersStep';
|
||||||
@ -73,7 +74,7 @@ class DeployContract extends Component {
|
|||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
@ -422,9 +423,9 @@ class DeployContract extends Component {
|
|||||||
from: fromAddress
|
from: fromAddress
|
||||||
};
|
};
|
||||||
|
|
||||||
api
|
const contract = api.newContract(abiParsed);
|
||||||
.newContract(abiParsed)
|
|
||||||
.deployEstimateGas(options, params)
|
deployEstimateGas(contract, options, params)
|
||||||
.then(([gasEst, gas]) => {
|
.then(([gasEst, gas]) => {
|
||||||
this.gasStore.setEstimated(gasEst.toFixed(0));
|
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||||
this.gasStore.setGas(gas.toFixed(0));
|
this.gasStore.setGas(gas.toFixed(0));
|
||||||
@ -490,6 +491,17 @@ class DeployContract extends Component {
|
|||||||
const { api, store } = this.context;
|
const { api, store } = this.context;
|
||||||
const { source } = this.props;
|
const { source } = this.props;
|
||||||
const { abiParsed, code, description, name, params, fromAddress } = this.state;
|
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 = {
|
const options = {
|
||||||
data: code,
|
data: code,
|
||||||
from: fromAddress
|
from: fromAddress
|
||||||
@ -499,28 +511,25 @@ class DeployContract extends Component {
|
|||||||
|
|
||||||
const contract = api.newContract(abiParsed);
|
const contract = api.newContract(abiParsed);
|
||||||
|
|
||||||
contract
|
deploy(contract, options, params, metadata, this.onDeploymentState)
|
||||||
.deploy(options, params, this.onDeploymentState)
|
|
||||||
.then((address) => {
|
.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()
|
? contract.receipt.blockNumber.toNumber()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
api.parity.setAccountName(address, name),
|
api.parity.setAccountName(address, name),
|
||||||
api.parity.setAccountMeta(address, {
|
api.parity.setAccountMeta(address, metadata)
|
||||||
abi: abiParsed,
|
|
||||||
contract: true,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
deleted: false,
|
|
||||||
blockNumber,
|
|
||||||
description,
|
|
||||||
source
|
|
||||||
})
|
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`contract deployed at ${address}`);
|
console.log(`contract deployed at ${address}`);
|
||||||
this.setState({ step: 'DEPLOYMENT', address });
|
this.setState({ step: 'COMPLETED', address });
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -589,6 +598,17 @@ class DeployContract extends Component {
|
|||||||
});
|
});
|
||||||
return;
|
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':
|
case 'completed':
|
||||||
this.setState({
|
this.setState({
|
||||||
deployState: (
|
deployState: (
|
||||||
@ -614,17 +634,14 @@ class DeployContract extends Component {
|
|||||||
function mapStateToProps (initState, initProps) {
|
function mapStateToProps (initState, initProps) {
|
||||||
const { accounts } = initProps;
|
const { accounts } = initProps;
|
||||||
|
|
||||||
// Skip Wallet accounts : they can't create Contracts
|
const fromAddresses = Object.keys(accounts);
|
||||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
|
||||||
|
|
||||||
const fromAddresses = Object.keys(_accounts);
|
|
||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const balances = pick(state.balances.balances, fromAddresses);
|
const balances = pick(state.balances.balances, fromAddresses);
|
||||||
const { gasLimit } = state.nodeStatus;
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts: _accounts,
|
accounts,
|
||||||
balances,
|
balances,
|
||||||
gasLimit
|
gasLimit
|
||||||
};
|
};
|
||||||
|
162
js/src/modals/Faucet/faucet.js
Normal file
162
js/src/modals/Faucet/faucet.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { txLink } from '~/3rdparty/etherscan/links';
|
||||||
|
import { Button, ModalBox, Portal, ShortenedHash } from '~/ui';
|
||||||
|
import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import Store from './store';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Faucet extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
address: PropTypes.string.isRequired,
|
||||||
|
netVersion: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
store = new Store(this.props.netVersion, this.props.address);
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { error, isBusy, isCompleted } = this.store;
|
||||||
|
|
||||||
|
let icon = <DialIcon />;
|
||||||
|
|
||||||
|
if (isCompleted) {
|
||||||
|
icon = error
|
||||||
|
? <ErrorIcon />
|
||||||
|
: <DoneIcon />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal
|
||||||
|
buttons={ this.renderActions() }
|
||||||
|
busy={ isBusy }
|
||||||
|
isSmallModal
|
||||||
|
onClose={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.title'
|
||||||
|
defaultMessage='Kovan ETH Faucet'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ModalBox
|
||||||
|
icon={ icon }
|
||||||
|
summary={
|
||||||
|
isCompleted
|
||||||
|
? this.renderSummaryDone()
|
||||||
|
: this.renderSummaryRequest()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActions = () => {
|
||||||
|
const { canTransact, isBusy, isCompleted } = this.store;
|
||||||
|
|
||||||
|
return isCompleted || isBusy
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
disabled={ isBusy }
|
||||||
|
icon={ <DoneIcon /> }
|
||||||
|
key='done'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.buttons.done'
|
||||||
|
defaultMessage='close'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: [
|
||||||
|
<Button
|
||||||
|
icon={ <CloseIcon /> }
|
||||||
|
key='close'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.buttons.close'
|
||||||
|
defaultMessage='close'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
disabled={ !canTransact }
|
||||||
|
icon={ <SendIcon /> }
|
||||||
|
key='request'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.buttons.request'
|
||||||
|
defaultMessage='request'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onExecute }
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSummaryDone () {
|
||||||
|
const { error, responseText, responseTxHash } = this.store;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.summary.done'
|
||||||
|
defaultMessage='Your Kovan ETH has been requested from the faucet which responded with -'
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
error
|
||||||
|
? (
|
||||||
|
<p>{ error }</p>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<p>
|
||||||
|
<span>{ responseText } </span>
|
||||||
|
<a href={ txLink(responseTxHash, false, '42') } target='_blank'>
|
||||||
|
<ShortenedHash data={ responseTxHash } />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSummaryRequest () {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.summary.info'
|
||||||
|
defaultMessage='To request a deposit of Kovan ETH to this address, you need to ensure that the address is sms-verified on the mainnet. Once executed the faucet will deposit Kovan ETH into the current account.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
onExecute = () => {
|
||||||
|
return this.store.makeItRain();
|
||||||
|
}
|
||||||
|
}
|
17
js/src/modals/Faucet/index.js
Normal file
17
js/src/modals/Faucet/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './faucet';
|
126
js/src/modals/Faucet/store.js
Normal file
126
js/src/modals/Faucet/store.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { action, computed, observable, transaction } from 'mobx';
|
||||||
|
import apiutil from '~/api/util';
|
||||||
|
|
||||||
|
const ENDPOINT = 'http://faucet.kovan.network/api/';
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable addressReceive = null;
|
||||||
|
@observable addressVerified = null;
|
||||||
|
@observable error = null;
|
||||||
|
@observable responseText = null;
|
||||||
|
@observable responseTxHash = null;
|
||||||
|
@observable isBusy = false;
|
||||||
|
@observable isCompleted = false;
|
||||||
|
@observable isDestination = false;
|
||||||
|
@observable isDone = false;
|
||||||
|
|
||||||
|
constructor (netVersion, address) {
|
||||||
|
transaction(() => {
|
||||||
|
this.setDestination(netVersion === '42');
|
||||||
|
|
||||||
|
this.setAddressReceive(address);
|
||||||
|
this.setAddressVerified(address);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get canTransact () {
|
||||||
|
return !this.isBusy && this.addressReceiveValid && this.addressVerifiedValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get addressReceiveValid () {
|
||||||
|
return apiutil.isAddressValid(this.addressReceive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get addressVerifiedValid () {
|
||||||
|
return apiutil.isAddressValid(this.addressVerified);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAddressReceive = (address) => {
|
||||||
|
this.addressReceive = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAddressVerified = (address) => {
|
||||||
|
this.addressVerified = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setBusy = (isBusy) => {
|
||||||
|
this.isBusy = isBusy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setCompleted = (isCompleted) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.setBusy(false);
|
||||||
|
this.isCompleted = isCompleted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setDestination = (isDestination) => {
|
||||||
|
this.isDestination = isDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setError = (error) => {
|
||||||
|
if (error.indexOf('not certified') !== -1) {
|
||||||
|
this.error = `${error}. Please ensure that this account is sms certified on the mainnet.`;
|
||||||
|
} else {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setResponse = (response) => {
|
||||||
|
this.responseText = response.result;
|
||||||
|
this.responseTxHash = response.tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeItRain = () => {
|
||||||
|
this.setBusy(true);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'cors'
|
||||||
|
};
|
||||||
|
const url = `${ENDPOINT}${this.addressVerified}`;
|
||||||
|
|
||||||
|
return fetch(url, options)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
transaction(() => {
|
||||||
|
if (!response || response.error) {
|
||||||
|
this.setError(
|
||||||
|
response
|
||||||
|
? response.error
|
||||||
|
: 'Unable to complete request to the faucet, the server may be unavailable. Please try again later.'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.setResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setCompleted(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -401,6 +401,7 @@ class WalletSettings extends Component {
|
|||||||
const cancelBtn = (
|
const cancelBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <CancelIcon /> }
|
icon={ <CancelIcon /> }
|
||||||
|
key='cancelBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.cancel'
|
id='walletSettings.buttons.cancel'
|
||||||
@ -414,6 +415,7 @@ class WalletSettings extends Component {
|
|||||||
const closeBtn = (
|
const closeBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <CancelIcon /> }
|
icon={ <CancelIcon /> }
|
||||||
|
key='closeBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.close'
|
id='walletSettings.buttons.close'
|
||||||
@ -427,6 +429,7 @@ class WalletSettings extends Component {
|
|||||||
const sendingBtn = (
|
const sendingBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <DoneIcon /> }
|
icon={ <DoneIcon /> }
|
||||||
|
key='sendingBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.sending'
|
id='walletSettings.buttons.sending'
|
||||||
@ -440,6 +443,7 @@ class WalletSettings extends Component {
|
|||||||
const nextBtn = (
|
const nextBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <NextIcon /> }
|
icon={ <NextIcon /> }
|
||||||
|
key='nextBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.next'
|
id='walletSettings.buttons.next'
|
||||||
@ -454,6 +458,7 @@ class WalletSettings extends Component {
|
|||||||
const sendBtn = (
|
const sendBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <NextIcon /> }
|
icon={ <NextIcon /> }
|
||||||
|
key='sendBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.send'
|
id='walletSettings.buttons.send'
|
||||||
|
@ -24,6 +24,7 @@ export DeleteAccount from './DeleteAccount';
|
|||||||
export DeployContract from './DeployContract';
|
export DeployContract from './DeployContract';
|
||||||
export EditMeta from './EditMeta';
|
export EditMeta from './EditMeta';
|
||||||
export ExecuteContract from './ExecuteContract';
|
export ExecuteContract from './ExecuteContract';
|
||||||
|
export Faucet from './Faucet';
|
||||||
export FirstRun from './FirstRun';
|
export FirstRun from './FirstRun';
|
||||||
export LoadContract from './LoadContract';
|
export LoadContract from './LoadContract';
|
||||||
export PasswordManager from './PasswordManager';
|
export PasswordManager from './PasswordManager';
|
||||||
|
@ -49,7 +49,7 @@ export default class Personal {
|
|||||||
.filter((address) => {
|
.filter((address) => {
|
||||||
const account = accountsInfo[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))
|
.map((address) => this._api.parity.removeAddress(address))
|
||||||
);
|
);
|
||||||
|
@ -26,7 +26,6 @@ import WalletsUtils from '~/util/wallets';
|
|||||||
import { wallet as WalletAbi } from '~/contracts/abi';
|
import { wallet as WalletAbi } from '~/contracts/abi';
|
||||||
|
|
||||||
export function personalAccountsInfo (accountsInfo) {
|
export function personalAccountsInfo (accountsInfo) {
|
||||||
const addresses = [];
|
|
||||||
const accounts = {};
|
const accounts = {};
|
||||||
const contacts = {};
|
const contacts = {};
|
||||||
const contracts = {};
|
const contracts = {};
|
||||||
@ -35,10 +34,9 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
|
|
||||||
Object.keys(accountsInfo || {})
|
Object.keys(accountsInfo || {})
|
||||||
.map((address) => Object.assign({}, accountsInfo[address], { address }))
|
.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) => {
|
.forEach((account) => {
|
||||||
if (account.uuid) {
|
if (account.uuid) {
|
||||||
addresses.push(account.address);
|
|
||||||
accounts[account.address] = account;
|
accounts[account.address] = account;
|
||||||
} else if (account.meta.wallet) {
|
} else if (account.meta.wallet) {
|
||||||
account.wallet = true;
|
account.wallet = true;
|
||||||
@ -87,18 +85,45 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
return [];
|
return [];
|
||||||
})
|
})
|
||||||
.then((_wallets) => {
|
.then((_wallets) => {
|
||||||
_wallets.forEach((wallet) => {
|
// We want to separate owned wallets and other wallets
|
||||||
const owners = wallet.owners.map((o) => o.address);
|
// 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
|
// If no more other wallets, or if the size decreased, continue...
|
||||||
// by one of the accounts
|
do {
|
||||||
if (intersection(owners, addresses).length > 0) {
|
prevLength = otherWallets.length;
|
||||||
accounts[wallet.address] = wallet;
|
|
||||||
} else {
|
otherWallets = otherWallets
|
||||||
contacts[wallet.address] = wallet;
|
.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({
|
dispatch(_personalAccountsInfo({
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
accounts,
|
accounts,
|
||||||
|
@ -431,22 +431,7 @@ function parseLogs (logs) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wallet } = getState();
|
const WalletSignatures = WalletsUtils.getWalletSignatures();
|
||||||
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 updates = {};
|
const updates = {};
|
||||||
|
|
||||||
@ -459,25 +444,25 @@ function parseLogs (logs) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
switch (eventSignature) {
|
switch (eventSignature) {
|
||||||
case signatures.OwnerChanged:
|
case WalletSignatures.OwnerChanged:
|
||||||
case signatures.OwnerAdded:
|
case WalletSignatures.OwnerAdded:
|
||||||
case signatures.OwnerRemoved:
|
case WalletSignatures.OwnerRemoved:
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
...prev,
|
...prev,
|
||||||
[ UPDATE_OWNERS ]: true
|
[ UPDATE_OWNERS ]: true
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case signatures.RequirementChanged:
|
case WalletSignatures.RequirementChanged:
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
...prev,
|
...prev,
|
||||||
[ UPDATE_REQUIRE ]: true
|
[ UPDATE_REQUIRE ]: true
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case signatures.ConfirmationNeeded:
|
case WalletSignatures.ConfirmationNeeded:
|
||||||
case signatures.Confirmation:
|
case WalletSignatures.Confirmation:
|
||||||
case signatures.Revoke:
|
case WalletSignatures.Revoke:
|
||||||
const operation = bytesToHex(log.params.operation.value);
|
const operation = bytesToHex(log.params.operation.value);
|
||||||
|
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
@ -489,9 +474,11 @@ function parseLogs (logs) {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case signatures.Deposit:
|
case WalletSignatures.Deposit:
|
||||||
case signatures.SingleTransact:
|
case WalletSignatures.SingleTransact:
|
||||||
case signatures.MultiTransact:
|
case WalletSignatures.MultiTransact:
|
||||||
|
case WalletSignatures.Old.SingleTransact:
|
||||||
|
case WalletSignatures.Old.MultiTransact:
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
...prev,
|
...prev,
|
||||||
[ UPDATE_TRANSACTIONS ]: true
|
[ UPDATE_TRANSACTIONS ]: true
|
||||||
|
@ -31,6 +31,7 @@ export DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
|||||||
export DeleteIcon from 'material-ui/svg-icons/action/delete';
|
export DeleteIcon from 'material-ui/svg-icons/action/delete';
|
||||||
export DevelopIcon from 'material-ui/svg-icons/action/description';
|
export DevelopIcon from 'material-ui/svg-icons/action/description';
|
||||||
export DoneIcon from 'material-ui/svg-icons/action/done-all';
|
export DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||||
|
export DialIcon from 'material-ui/svg-icons/communication/dialpad';
|
||||||
export EditIcon from 'material-ui/svg-icons/content/create';
|
export EditIcon from 'material-ui/svg-icons/content/create';
|
||||||
export ErrorIcon from 'material-ui/svg-icons/alert/error';
|
export ErrorIcon from 'material-ui/svg-icons/alert/error';
|
||||||
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
|
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
|
||||||
|
@ -120,6 +120,18 @@ class MethodDecoding extends Component {
|
|||||||
<span className={ styles.highlight }>
|
<span className={ styles.highlight }>
|
||||||
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
|
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
|
||||||
</span>
|
</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> for a total transaction value of </span>
|
||||||
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
||||||
{ this.renderMinBlock() }
|
{ this.renderMinBlock() }
|
||||||
|
@ -22,13 +22,13 @@ import styles from './modalBox.css';
|
|||||||
|
|
||||||
export default class ModalBox extends Component {
|
export default class ModalBox extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node,
|
||||||
icon: PropTypes.node.isRequired,
|
icon: PropTypes.node.isRequired,
|
||||||
summary: nodeOrStringProptype()
|
summary: nodeOrStringProptype()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, icon } = this.props;
|
const { icon } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.body }>
|
<div className={ styles.body }>
|
||||||
@ -37,14 +37,26 @@ export default class ModalBox extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className={ styles.content }>
|
<div className={ styles.content }>
|
||||||
{ this.renderSummary() }
|
{ this.renderSummary() }
|
||||||
<div className={ styles.body }>
|
{ this.renderBody() }
|
||||||
{ children }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderBody () {
|
||||||
|
const { children } = this.props;
|
||||||
|
|
||||||
|
if (!children) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.body }>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderSummary () {
|
renderSummary () {
|
||||||
const { summary } = this.props;
|
const { summary } = this.props;
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
$transition: all 0.25s;
|
$transition: all 0.25s;
|
||||||
$widthNormal: 33.33%;
|
$widthNormal: 33.33%;
|
||||||
$widthShrunk: 29%;
|
|
||||||
$widthExpanded: 42%;
|
$widthExpanded: 42%;
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
@ -39,18 +38,19 @@ $widthExpanded: 42%;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
/* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */
|
/* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for
|
||||||
/* case where <> 3 columns are required should the need arrise from a UI pov. */
|
/* case where <> 3 columns are required should the need arrise from a UI pov. */
|
||||||
.item {
|
.item {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 1 $widthNormal;
|
flex: 0 1 $widthNormal;
|
||||||
max-width: $widthNormal;
|
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
|
||||||
/* https://www.binarymoon.co.uk/2014/02/fixing-css-transitions-in-google-chrome/ */
|
/* https://www.binarymoon.co.uk/2014/02/fixing-css-transitions-in-google-chrome/ */
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
transition: $transition;
|
transition: $transition;
|
||||||
|
width: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -58,22 +58,13 @@ $widthExpanded: 42%;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
.item.stretchOn:hover {
|
||||||
.item {
|
flex: 0 0 $widthExpanded;
|
||||||
&.stretchOn {
|
max-width: $widthExpanded;
|
||||||
flex: 0 1 $widthShrunk;
|
|
||||||
max-width: $widthShrunk;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
flex: 0 0 $widthExpanded;
|
|
||||||
max-width: $widthExpanded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section+.section {
|
.section + .section {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ export default class Store {
|
|||||||
|
|
||||||
return bnB.comparedTo(bnA);
|
return bnB.comparedTo(bnA);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
|
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -85,26 +86,53 @@ export default class Store {
|
|||||||
this._subscriptionId = 0;
|
this._subscriptionId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTransactions (_txhashes) {
|
loadTransactions (_txhashes = []) {
|
||||||
const txhashes = _txhashes.filter((hash) => !this.transactions[hash] || this._pendingHashes.includes(hash));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash)))
|
.all(promises)
|
||||||
.then((_transactions) => {
|
.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(
|
if (tx.blockNumber && tx.blockNumber.gt(0)) {
|
||||||
transactions.reduce((transactions, tx, index) => {
|
blockNumbers.push(tx.blockNumber.toNumber());
|
||||||
transactions[txhashes[index]] = tx;
|
}
|
||||||
return transactions;
|
|
||||||
}, {})
|
|
||||||
);
|
|
||||||
|
|
||||||
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) => {
|
.catch((error) => {
|
||||||
console.warn('loadTransactions', error);
|
console.warn('loadTransactions', error);
|
||||||
|
@ -27,7 +27,7 @@ import styles from './txList.css';
|
|||||||
class TxList extends Component {
|
class TxList extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
@ -36,7 +36,7 @@ class TxList extends Component {
|
|||||||
PropTypes.object
|
PropTypes.object
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
netVersion: PropTypes.string.isRequired
|
netVersion: PropTypes.string.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
store = new Store(this.context.api);
|
store = new Store(this.context.api);
|
||||||
|
|
||||||
|
@ -61,17 +61,6 @@ export function estimateGas (_func, _options, _values = []) {
|
|||||||
const { func, options, values } = callArgs;
|
const { func, options, values } = callArgs;
|
||||||
|
|
||||||
return func._estimateGas(options, values);
|
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) {
|
export function patchApi (api) {
|
||||||
api.patch = {
|
api.patch = {
|
||||||
...api.patch,
|
...api.patch,
|
||||||
|
@ -16,59 +16,154 @@
|
|||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { intersection, range, uniq } from 'lodash';
|
import { intersection, range, uniq } from 'lodash';
|
||||||
|
import store from 'store';
|
||||||
|
|
||||||
|
import Abi from '~/abi';
|
||||||
import Contract from '~/api/contract';
|
import Contract from '~/api/contract';
|
||||||
import { bytesToHex, toHex } from '~/api/util/format';
|
import { bytesToHex, toHex } from '~/api/util/format';
|
||||||
import { validateAddress } from '~/util/validation';
|
import { validateAddress } from '~/util/validation';
|
||||||
import WalletAbi from '~/contracts/abi/wallet.json';
|
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 = {};
|
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 {
|
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 = []) {
|
static getCallArgs (api, options, values = []) {
|
||||||
const walletContract = new Contract(api, WalletAbi);
|
const walletContract = new Contract(api, WalletAbi);
|
||||||
|
const walletAddress = options.from;
|
||||||
|
|
||||||
const promises = [
|
return WalletsUtils
|
||||||
api.parity.accountsInfo(),
|
.fetchOwners(walletContract.at(walletAddress))
|
||||||
WalletsUtils.fetchOwners(walletContract.at(options.from))
|
.then((owners) => {
|
||||||
];
|
const addresses = Object.keys(_cachedAccounts);
|
||||||
|
const ownerAddress = intersection(addresses, owners).pop();
|
||||||
|
|
||||||
return Promise
|
if (!ownerAddress) {
|
||||||
.all(promises)
|
|
||||||
.then(([ accounts, owners ]) => {
|
|
||||||
const addresses = Object.keys(accounts);
|
|
||||||
const owner = intersection(addresses, owners).pop();
|
|
||||||
|
|
||||||
if (!owner) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return owner;
|
const account = _cachedAccounts[ownerAddress];
|
||||||
})
|
const _options = { ...options };
|
||||||
.then((owner) => {
|
const { to, value = new BigNumber(0), data } = _options;
|
||||||
if (!owner) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _options = Object.assign({}, options);
|
|
||||||
const { from, to, value = new BigNumber(0), data } = options;
|
|
||||||
|
|
||||||
delete _options.data;
|
delete _options.data;
|
||||||
|
|
||||||
const nextValues = [ to, value, data ];
|
const nextValues = [ to, value, data ];
|
||||||
const nextOptions = {
|
const nextOptions = {
|
||||||
..._options,
|
..._options,
|
||||||
from: owner,
|
from: ownerAddress,
|
||||||
to: from,
|
to: walletAddress,
|
||||||
value: new BigNumber(0)
|
value: new BigNumber(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
const execFunc = walletContract.instance.execute;
|
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
|
* Check whether the given address could be
|
||||||
* a Wallet. The result is cached in order not
|
* a Wallet. The result is cached in order not
|
||||||
@ -199,16 +294,18 @@ export default class WalletsUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fetchTransactions (walletContract) {
|
static fetchTransactions (walletContract) {
|
||||||
const walletInstance = walletContract.instance;
|
const { api } = walletContract;
|
||||||
const signatures = {
|
const pendingContracts = WalletsUtils.getPendingContracts();
|
||||||
single: toHex(walletInstance.SingleTransact.signature),
|
|
||||||
multi: toHex(walletInstance.MultiTransact.signature),
|
|
||||||
deposit: toHex(walletInstance.Deposit.signature)
|
|
||||||
};
|
|
||||||
|
|
||||||
return walletContract
|
return walletContract
|
||||||
.getAllLogs({
|
.getAllLogs({
|
||||||
topics: [ [ signatures.single, signatures.multi, signatures.deposit ] ]
|
topics: [ [
|
||||||
|
WalletSignatures.SingleTransact,
|
||||||
|
WalletSignatures.MultiTransact,
|
||||||
|
WalletSignatures.Deposit,
|
||||||
|
WalletSignatures.Old.SingleTransact,
|
||||||
|
WalletSignatures.Old.MultiTransact
|
||||||
|
] ]
|
||||||
})
|
})
|
||||||
.then((logs) => {
|
.then((logs) => {
|
||||||
return logs.sort((logA, logB) => {
|
return logs.sort((logA, logB) => {
|
||||||
@ -226,11 +323,11 @@ export default class WalletsUtils {
|
|||||||
const signature = toHex(log.topics[0]);
|
const signature = toHex(log.topics[0]);
|
||||||
|
|
||||||
const value = log.params.value.value;
|
const value = log.params.value.value;
|
||||||
const from = signature === signatures.deposit
|
const from = signature === WalletSignatures.Deposit
|
||||||
? log.params['_from'].value
|
? log.params['_from'].value
|
||||||
: walletContract.address;
|
: walletContract.address;
|
||||||
|
|
||||||
const to = signature === signatures.deposit
|
const to = signature === WalletSignatures.Deposit
|
||||||
? walletContract.address
|
? walletContract.address
|
||||||
: log.params.to.value;
|
: log.params.to.value;
|
||||||
|
|
||||||
@ -240,8 +337,53 @@ export default class WalletsUtils {
|
|||||||
from, to, value
|
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) {
|
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) {
|
if (log.params.data) {
|
||||||
|
@ -22,11 +22,11 @@ import { bindActionCreators } from 'redux';
|
|||||||
|
|
||||||
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
||||||
import HardwareStore from '~/mobx/hardwareStore';
|
import HardwareStore from '~/mobx/hardwareStore';
|
||||||
import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals';
|
import { DeleteAccount, EditMeta, Faucet, PasswordManager, Shapeshift, Transfer, Verification } from '~/modals';
|
||||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||||
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
||||||
import { Actionbar, Button, Page } from '~/ui';
|
import { Actionbar, Button, Page } from '~/ui';
|
||||||
import { DeleteIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
|
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import DeleteAddress from '../Address/Delete';
|
import DeleteAddress from '../Address/Delete';
|
||||||
|
|
||||||
@ -48,6 +48,8 @@ class Account extends Component {
|
|||||||
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
balances: PropTypes.object,
|
||||||
|
certifications: PropTypes.object,
|
||||||
|
netVersion: PropTypes.string.isRequired,
|
||||||
params: PropTypes.object
|
params: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +99,7 @@ class Account extends Component {
|
|||||||
<div>
|
<div>
|
||||||
{ this.renderDeleteDialog(account) }
|
{ this.renderDeleteDialog(account) }
|
||||||
{ this.renderEditDialog(account) }
|
{ this.renderEditDialog(account) }
|
||||||
|
{ this.renderFaucetDialog() }
|
||||||
{ this.renderFundDialog() }
|
{ this.renderFundDialog() }
|
||||||
{ this.renderPasswordDialog(account) }
|
{ this.renderPasswordDialog(account) }
|
||||||
{ this.renderTransferDialog(account, balance) }
|
{ this.renderTransferDialog(account, balance) }
|
||||||
@ -117,8 +120,35 @@ class Account extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isKovan = (netVersion) => {
|
||||||
|
return netVersion === '42';
|
||||||
|
}
|
||||||
|
|
||||||
|
isMainnet = (netVersion) => {
|
||||||
|
return netVersion === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
isFaucettable = (netVersion, certifications, address) => {
|
||||||
|
return this.isKovan(netVersion) || (
|
||||||
|
this.isMainnet(netVersion) &&
|
||||||
|
this.isSmsCertified(certifications, address)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSmsCertified = (_certifications, address) => {
|
||||||
|
const certifications = _certifications && _certifications[address]
|
||||||
|
? _certifications[address].filter((cert) => cert.name.indexOf('smsverification') === 0)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return certifications.length !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
renderActionbar (account, balance) {
|
renderActionbar (account, balance) {
|
||||||
|
const { certifications, netVersion } = this.props;
|
||||||
|
const { address } = this.props.params;
|
||||||
const showTransferButton = !!(balance && balance.tokens);
|
const showTransferButton = !!(balance && balance.tokens);
|
||||||
|
const isVerifiable = this.isMainnet(netVersion);
|
||||||
|
const isFaucettable = this.isFaucettable(netVersion, certifications, address);
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
@ -149,17 +179,36 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
onClick={ this.store.toggleFundDialog }
|
onClick={ this.store.toggleFundDialog }
|
||||||
/>,
|
/>,
|
||||||
<Button
|
isVerifiable
|
||||||
icon={ <VerifyIcon /> }
|
? (
|
||||||
key='sms-verification'
|
<Button
|
||||||
label={
|
icon={ <VerifyIcon /> }
|
||||||
<FormattedMessage
|
key='verification'
|
||||||
id='account.button.verify'
|
label={
|
||||||
defaultMessage='verify'
|
<FormattedMessage
|
||||||
|
id='account.button.verify'
|
||||||
|
defaultMessage='verify'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleVerificationDialog }
|
||||||
/>
|
/>
|
||||||
}
|
)
|
||||||
onClick={ this.store.toggleVerificationDialog }
|
: null,
|
||||||
/>,
|
isFaucettable
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
icon={ <DialIcon /> }
|
||||||
|
key='faucet'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.button.faucet'
|
||||||
|
defaultMessage='Kovan ETH'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleFaucetDialog }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null,
|
||||||
<Button
|
<Button
|
||||||
icon={ <EditIcon /> }
|
icon={ <EditIcon /> }
|
||||||
key='editmeta'
|
key='editmeta'
|
||||||
@ -253,6 +302,24 @@ class Account extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderFaucetDialog () {
|
||||||
|
const { netVersion } = this.props;
|
||||||
|
|
||||||
|
if (!this.store.isFaucetVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { address } = this.props.params;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Faucet
|
||||||
|
address={ address }
|
||||||
|
netVersion={ netVersion }
|
||||||
|
onClose={ this.store.toggleFaucetDialog }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderFundDialog () {
|
renderFundDialog () {
|
||||||
if (!this.store.isFundVisible) {
|
if (!this.store.isFundVisible) {
|
||||||
return null;
|
return null;
|
||||||
@ -317,10 +384,14 @@ class Account extends Component {
|
|||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts } = state.personal;
|
const { accounts } = state.personal;
|
||||||
const { balances } = state.balances;
|
const { balances } = state.balances;
|
||||||
|
const certifications = state.certifications;
|
||||||
|
const { netVersion } = state.nodeStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
balances
|
balances,
|
||||||
|
certifications,
|
||||||
|
netVersion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,57 +80,16 @@ describe('views/Account', () => {
|
|||||||
describe('sub-renderers', () => {
|
describe('sub-renderers', () => {
|
||||||
describe('renderActionBar', () => {
|
describe('renderActionBar', () => {
|
||||||
let bar;
|
let bar;
|
||||||
let barShallow;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render();
|
render();
|
||||||
|
|
||||||
bar = instance.renderActionbar({ tokens: {} });
|
bar = instance.renderActionbar({ tokens: {} });
|
||||||
barShallow = shallow(bar);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the bar', () => {
|
it('renders the bar', () => {
|
||||||
expect(bar.type).to.match(/Actionbar/);
|
expect(bar.type).to.match(/Actionbar/);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Finding by index is not optimal, however couldn't find a better method atm
|
|
||||||
// since we cannot find by key (prop not visible in shallow debug())
|
|
||||||
describe('clicks', () => {
|
|
||||||
it('toggles transfer on click', () => {
|
|
||||||
barShallow.find('Button').at(0).simulate('click');
|
|
||||||
expect(store.isTransferVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles fund on click', () => {
|
|
||||||
barShallow.find('Button').at(1).simulate('click');
|
|
||||||
expect(store.isFundVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles fund on click', () => {
|
|
||||||
barShallow.find('Button').at(1).simulate('click');
|
|
||||||
expect(store.isFundVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles verify on click', () => {
|
|
||||||
barShallow.find('Button').at(2).simulate('click');
|
|
||||||
expect(store.isVerificationVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles edit on click', () => {
|
|
||||||
barShallow.find('Button').at(3).simulate('click');
|
|
||||||
expect(store.isEditVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles password on click', () => {
|
|
||||||
barShallow.find('Button').at(4).simulate('click');
|
|
||||||
expect(store.isPasswordVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles delete on click', () => {
|
|
||||||
barShallow.find('Button').at(5).simulate('click');
|
|
||||||
expect(store.isDeleteVisible).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renderDeleteDialog', () => {
|
describe('renderDeleteDialog', () => {
|
||||||
|
@ -19,6 +19,7 @@ import { action, observable } from 'mobx';
|
|||||||
export default class Store {
|
export default class Store {
|
||||||
@observable isDeleteVisible = false;
|
@observable isDeleteVisible = false;
|
||||||
@observable isEditVisible = false;
|
@observable isEditVisible = false;
|
||||||
|
@observable isFaucetVisible = false;
|
||||||
@observable isFundVisible = false;
|
@observable isFundVisible = false;
|
||||||
@observable isPasswordVisible = false;
|
@observable isPasswordVisible = false;
|
||||||
@observable isTransferVisible = false;
|
@observable isTransferVisible = false;
|
||||||
@ -32,6 +33,10 @@ export default class Store {
|
|||||||
this.isEditVisible = !this.isEditVisible;
|
this.isEditVisible = !this.isEditVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action toggleFaucetDialog = () => {
|
||||||
|
this.isFaucetVisible = !this.isFaucetVisible;
|
||||||
|
}
|
||||||
|
|
||||||
@action toggleFundDialog = () => {
|
@action toggleFundDialog = () => {
|
||||||
this.isFundVisible = !this.isFundVisible;
|
this.isFundVisible = !this.isFundVisible;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ describe('views/Account/Store', () => {
|
|||||||
it('sets all modal visibility to false', () => {
|
it('sets all modal visibility to false', () => {
|
||||||
expect(store.isDeleteVisible).to.be.false;
|
expect(store.isDeleteVisible).to.be.false;
|
||||||
expect(store.isEditVisible).to.be.false;
|
expect(store.isEditVisible).to.be.false;
|
||||||
|
expect(store.isFaucetVisible).to.be.false;
|
||||||
expect(store.isFundVisible).to.be.false;
|
expect(store.isFundVisible).to.be.false;
|
||||||
expect(store.isPasswordVisible).to.be.false;
|
expect(store.isPasswordVisible).to.be.false;
|
||||||
expect(store.isTransferVisible).to.be.false;
|
expect(store.isTransferVisible).to.be.false;
|
||||||
@ -53,6 +54,13 @@ describe('views/Account/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toggleFaucetDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.toggleFaucetDialog();
|
||||||
|
expect(store.isFaucetVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('toggleFundDialog', () => {
|
describe('toggleFundDialog', () => {
|
||||||
it('toggles the visibility', () => {
|
it('toggles the visibility', () => {
|
||||||
store.toggleFundDialog();
|
store.toggleFundDialog();
|
||||||
|
@ -41,8 +41,9 @@ export default class SignerStore {
|
|||||||
this.balances = Object.assign({}, this.balances, balances);
|
this.balances = Object.assign({}, this.balances, balances);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setLocalHashes = (localHashes) => {
|
@action setLocalHashes = (localHashes = []) => {
|
||||||
if (!isEqual(localHashes, this.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;
|
this.localHashes = localHashes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ export default class WalletTransactions extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const txRows = transactions.slice(0, 15).map((transaction, index) => {
|
const txRows = transactions.slice(0, 15).map((transaction, index) => {
|
||||||
const { transactionHash, blockNumber, from, to, value, data } = transaction;
|
const { transactionHash, data } = transaction;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TxRow
|
<TxRow
|
||||||
@ -79,12 +79,9 @@ export default class WalletTransactions extends Component {
|
|||||||
netVersion={ netVersion }
|
netVersion={ netVersion }
|
||||||
key={ `${transactionHash}_${index}` }
|
key={ `${transactionHash}_${index}` }
|
||||||
tx={ {
|
tx={ {
|
||||||
blockNumber,
|
|
||||||
from,
|
|
||||||
hash: transactionHash,
|
hash: transactionHash,
|
||||||
input: data && bytesToHex(data) || '',
|
input: data && bytesToHex(data) || '',
|
||||||
to,
|
...transaction
|
||||||
value
|
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user