Merge pull request #21 from gnosis/feature/WA-238-withdraw-eth
WA-238 - Withdraw ETH
This commit is contained in:
commit
5db96a8de5
|
@ -17,7 +17,7 @@ module.file_ext=.css
|
|||
module.file_ext=.scss
|
||||
|
||||
module.name_mapper='^~' ->'<PROJECT_ROOT>/src'
|
||||
module.name_mapper='^#' ->'<PROJECT_ROOT>/gnosis-safe-contracts/build/contracts'
|
||||
module.name_mapper='^#' ->'<PROJECT_ROOT>/safe-contracts/build/contracts'
|
||||
module.name_mapper='.*\(.s?css\)' -> '{}'
|
||||
|
||||
[strict]
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
node_modules/
|
||||
build/
|
||||
build_webpack/
|
||||
build_storybook/
|
||||
build/contracts/
|
||||
truffle-config.js
|
||||
gnosis-safe-contracts/
|
||||
.DS_Store
|
|
@ -4,20 +4,17 @@ node_js:
|
|||
os:
|
||||
- linux
|
||||
before_script:
|
||||
- yarn global add truffle@4.1.3
|
||||
- yarn global add surge
|
||||
- git clone https://github.com/gnosis/gnosis-safe-contracts.git
|
||||
- cd gnosis-safe-contracts
|
||||
- truffle compile && cd ..
|
||||
- export NODE_ENV=testing
|
||||
after_success:
|
||||
- yarn build-storybook
|
||||
- yarn build
|
||||
- |
|
||||
if [ ${TRAVIS_BRANCH} = "master" ]; then
|
||||
export NODE_ENV=production;
|
||||
else
|
||||
export NODE_ENV=development;
|
||||
fi
|
||||
- yarn build-storybook
|
||||
- yarn build
|
||||
- cd build_webpack/ && cp index.html 200.html && cd ..
|
||||
- chmod ugo+x ./config/deploy/deploy.sh
|
||||
- ./config/deploy/deploy.sh
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
jest.setTimeout(30000)
|
|
@ -38,7 +38,7 @@ module.exports = {
|
|||
appIndexJs: resolveApp('src/index.js'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
appSrc: resolveApp('src'),
|
||||
appContracts: resolveApp('gnosis-safe-contracts/build/contracts'),
|
||||
appContracts: resolveApp('safe-contracts/build/contracts'),
|
||||
yarnLockFile: resolveApp('yarn.lock'),
|
||||
testsSetup: resolveApp('src/setupTests.js'),
|
||||
appNodeModules: resolveApp('node_modules'),
|
||||
|
|
|
@ -77,6 +77,8 @@ module.exports = {
|
|||
bail: true,
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: "all",
|
||||
/* https://stackoverflow.com/questions/48985780/webpack-4-create-vendor-chunk
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
test: /node_modules/,
|
||||
|
@ -86,6 +88,7 @@ module.exports = {
|
|||
minSize: 1,
|
||||
},
|
||||
},
|
||||
*/
|
||||
},
|
||||
},
|
||||
entry: [
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"~/*":["src/*"],
|
||||
"@/*":["gnosis-safe-contracts/build/contracts"]
|
||||
"@/*":["safe-contracts/build/contracts"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
|
|
20
package.json
20
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gnosis-team-safe",
|
||||
"version": "1.0.0",
|
||||
"name": "safe-react",
|
||||
"version": "0.3.2",
|
||||
"description": "Allowing crypto users manage funds in a safer way",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
|
@ -9,7 +9,7 @@
|
|||
"start": "node scripts/start.js",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "run-with-testrpc -l 40000000 'node scripts/test.js --env=jsdom'",
|
||||
"test-local": "node scripts/test.js --env=jsdom",
|
||||
"test-local": "NODE_ENV=test && node scripts/test.js --env=jsdom",
|
||||
"precommit": "./precommit.sh",
|
||||
"flow": "flow",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
|
@ -17,14 +17,14 @@
|
|||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gnosis/gnosis-team-safe"
|
||||
"url": "https://github.com/gnosis/safe-react"
|
||||
},
|
||||
"author": "Gnosis Team",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/gnosis/gnosis-team-safe/issues"
|
||||
"url": "https://github.com/gnosis/safe-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/gnosis/gnosis-team-safe#readme",
|
||||
"homepage": "https://github.com/gnosis/safe-react#readme",
|
||||
"pre-commit": [
|
||||
"precommit"
|
||||
],
|
||||
|
@ -50,6 +50,7 @@
|
|||
"babel-plugin-dynamic-import-node": "^1.2.0",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
||||
"bignumber.js": "^7.2.1",
|
||||
"classnames": "^2.2.5",
|
||||
"css-loader": "^0.28.10",
|
||||
"detect-port": "^1.2.2",
|
||||
|
@ -87,6 +88,7 @@
|
|||
"storybook-host": "^4.1.5",
|
||||
"storybook-router": "^0.3.3",
|
||||
"style-loader": "^0.20.2",
|
||||
"truffle": "git://github.com/trufflesuite/truffle.git#develop",
|
||||
"truffle-contract": "^1.1.8",
|
||||
"truffle-solidity-loader": "0.0.8",
|
||||
"uglifyjs-webpack-plugin": "^1.2.2",
|
||||
|
@ -104,13 +106,15 @@
|
|||
"material-ui-icons": "^1.0.0-beta.35",
|
||||
"react-final-form": "^3.1.2",
|
||||
"react-loadable": "^5.3.1",
|
||||
"react-router-dom": "^4.2.2"
|
||||
"react-router-dom": "^4.2.2",
|
||||
"recompose": "^0.27.0"
|
||||
},
|
||||
"jest": {
|
||||
"verbose": true,
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx}"
|
||||
],
|
||||
"setupTestFrameworkScriptFile": "<rootDir>/config/jest/jest.setup.js",
|
||||
"setupFiles": [
|
||||
"<rootDir>/config/webpack.config.test.js",
|
||||
"<rootDir>/config/polyfills.js",
|
||||
|
@ -134,7 +138,7 @@
|
|||
],
|
||||
"moduleNameMapper": {
|
||||
"~(.*)$": "<rootDir>/src/$1",
|
||||
"#(.*)$": "<rootDir>/gnosis-safe-contracts/build/contracts/$1",
|
||||
"#(.*)$": "<rootDir>/safe-contracts/build/contracts/$1",
|
||||
"^react-native$": "react-native-web"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ What things you need to install the software and how to install them
|
|||
npm install truffle // recommended usage of -g flag
|
||||
npm install ganache-cli // recommended usage of -g flag
|
||||
npm install flow-type // recommended usage of -g flag
|
||||
git clone https://github.com/gnosis/gnosis-safe-contracts.git
|
||||
git clone https://github.com/gnosis/safe-contracts.git
|
||||
```
|
||||
|
||||
### Installing
|
||||
|
@ -28,7 +28,7 @@ ganache-cli -b 3
|
|||
|
||||
Start the project in the other one
|
||||
```
|
||||
cd gnosis-safe-contracts && truffle compile && truffle migrate && cd ..
|
||||
cd safe-contracts && truffle compile && truffle migrate && cd ..
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,666 @@
|
|||
{
|
||||
"contractName": "DelegateConstructorProxy",
|
||||
"abi": [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "proxyType",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "implementation",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_masterCopy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "initializer",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x608060405234801561001057600080fd5b506040516102fc3803806102fc83398101806040528101908080519060200190929190805182019291905050508160008173ffffffffffffffffffffffffffffffffffffffff16141515156100f3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506000815111156101805773ffffffffffffffffffffffffffffffffffffffff60005416600080835160208501846127105a03f46040513d6000823e600082141561017c573d81fd5b5050505b505061016b806101916000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600a165627a7a72305820bab5357556c704bffef0f96326dd27742408be175057ffd8f4f58237314cfd520029",
|
||||
"deployedBytecode": "0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600a165627a7a72305820bab5357556c704bffef0f96326dd27742408be175057ffd8f4f58237314cfd520029",
|
||||
"sourceMap": "355:882:0:-;;;610:625;8:9:-1;5:2;;;30:1;27;20:12;5:2;610:625:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;668:11;593:1:11;578:11;:16;;;;570:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;658:11;645:10;;:24;;;;;;;;;;;;;;;;;;508:168;735:1:0;714:11;:18;:22;710:519;;;879:42;875:1;869:8;865:57;1043:1;1040;1026:11;1020:18;1013:4;1000:11;996:22;984:10;976:5;971:3;967:15;954:91;1079:4;1073:11;1124:14;1121:1;1116:3;1101:38;1171:1;1162:7;1159:14;1156:2;;;1188:14;1183:3;1176:27;1156:2;829:390;;;;610:625;;355:882;;;;;;",
|
||||
"deployedSourceMap": "355:882:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;955:42:11;951:1;945:8;941:57;1030:14;1027:1;1024;1011:34;1125:1;1122;1106:14;1103:1;1091:10;1086:3;1073:54;1161:16;1158:1;1155;1140:38;1206:1;1197:7;1194:14;1191:2;;;1221:16;1218:1;1211:27;1191:2;1263:16;1260:1;1253:27;1426:104;;8:9:-1;5:2;;;30:1;27;20:12;5:2;1426:104:11;;;;;;;;;;;;;;;;;;;;;;;1302:118;;8:9:-1;5:2;;;30:1;27;20:12;5:2;1302:118:11;;;;;;;;;;;;;;;;;;;;;;;;;;;1426:104;1492:7;1522:1;1515:8;;1426:104;:::o;1302:118::-;1373:7;1403:10;;;;;;;;;;;1396:17;;1302:118;:::o",
|
||||
"source": "pragma solidity 0.4.24;\nimport \"./Proxy.sol\";\n\n\n/// @title Delegate Constructor Proxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. It is possible to send along initialization data with the constructor.\n/// @author Stefan George - <stefan@gnosis.pm>\n/// @author Richard Meissner - <richard@gnosis.pm>\ncontract DelegateConstructorProxy is Proxy {\n\n /// @dev Constructor function sets address of master copy contract.\n /// @param _masterCopy Master copy address.\n /// @param initializer Data used for a delegate call to initialize the contract.\n constructor(address _masterCopy, bytes initializer) Proxy(_masterCopy)\n public\n {\n if (initializer.length > 0) {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n let masterCopy := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)\n let success := delegatecall(sub(gas, 10000), masterCopy, add(initializer, 0x20), mload(initializer), 0, 0)\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize)\n if eq(success, 0) { revert(ptr, returndatasize) }\n }\n }\n }\n}\n",
|
||||
"sourcePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/DelegateConstructorProxy.sol",
|
||||
"ast": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/DelegateConstructorProxy.sol",
|
||||
"exportedSymbols": {
|
||||
"DelegateConstructorProxy": [
|
||||
23
|
||||
]
|
||||
},
|
||||
"id": 24,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:0"
|
||||
},
|
||||
{
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/Proxy.sol",
|
||||
"file": "./Proxy.sol",
|
||||
"id": 2,
|
||||
"nodeType": "ImportDirective",
|
||||
"scope": 24,
|
||||
"sourceUnit": 1603,
|
||||
"src": "24:21:0",
|
||||
"symbolAliases": [],
|
||||
"unitAlias": ""
|
||||
},
|
||||
{
|
||||
"baseContracts": [
|
||||
{
|
||||
"arguments": null,
|
||||
"baseName": {
|
||||
"contractScope": null,
|
||||
"id": 3,
|
||||
"name": "Proxy",
|
||||
"nodeType": "UserDefinedTypeName",
|
||||
"referencedDeclaration": 1602,
|
||||
"src": "392:5:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_contract$_Proxy_$1602",
|
||||
"typeString": "contract Proxy"
|
||||
}
|
||||
},
|
||||
"id": 4,
|
||||
"nodeType": "InheritanceSpecifier",
|
||||
"src": "392:5:0"
|
||||
}
|
||||
],
|
||||
"contractDependencies": [
|
||||
1602
|
||||
],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title Delegate Constructor Proxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. It is possible to send along initialization data with the constructor.\n @author Stefan George - <stefan@gnosis.pm>\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 23,
|
||||
"linearizedBaseContracts": [
|
||||
23,
|
||||
1602
|
||||
],
|
||||
"name": "DelegateConstructorProxy",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"body": {
|
||||
"id": 21,
|
||||
"nodeType": "Block",
|
||||
"src": "700:535:0",
|
||||
"statements": [
|
||||
{
|
||||
"condition": {
|
||||
"argumentTypes": null,
|
||||
"commonType": {
|
||||
"typeIdentifier": "t_uint256",
|
||||
"typeString": "uint256"
|
||||
},
|
||||
"id": 17,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftExpression": {
|
||||
"argumentTypes": null,
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"id": 14,
|
||||
"name": "initializer",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 8,
|
||||
"src": "714:11:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_memory_ptr",
|
||||
"typeString": "bytes memory"
|
||||
}
|
||||
},
|
||||
"id": 15,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"memberName": "length",
|
||||
"nodeType": "MemberAccess",
|
||||
"referencedDeclaration": null,
|
||||
"src": "714:18:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_uint256",
|
||||
"typeString": "uint256"
|
||||
}
|
||||
},
|
||||
"nodeType": "BinaryOperation",
|
||||
"operator": ">",
|
||||
"rightExpression": {
|
||||
"argumentTypes": null,
|
||||
"hexValue": "30",
|
||||
"id": 16,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "number",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "735:1:0",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_rational_0_by_1",
|
||||
"typeString": "int_const 0"
|
||||
},
|
||||
"value": "0"
|
||||
},
|
||||
"src": "714:22:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
}
|
||||
},
|
||||
"falseBody": null,
|
||||
"id": 20,
|
||||
"nodeType": "IfStatement",
|
||||
"src": "710:519:0",
|
||||
"trueBody": {
|
||||
"id": 19,
|
||||
"nodeType": "Block",
|
||||
"src": "738:491:0",
|
||||
"statements": [
|
||||
{
|
||||
"externalReferences": [
|
||||
{
|
||||
"initializer": {
|
||||
"declaration": 8,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1000:11:0",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"initializer": {
|
||||
"declaration": 8,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1026:11:0",
|
||||
"valueSize": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": 18,
|
||||
"nodeType": "InlineAssembly",
|
||||
"operations": "{\n let masterCopy := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)\n let success := delegatecall(sub(gas(), 10000), masterCopy, add(initializer, 0x20), mload(initializer), 0, 0)\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n if eq(success, 0)\n {\n revert(ptr, returndatasize())\n }\n}",
|
||||
"src": "820:409:0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": "@dev Constructor function sets address of master copy contract.\n @param _masterCopy Master copy address.\n @param initializer Data used for a delegate call to initialize the contract.",
|
||||
"id": 22,
|
||||
"implemented": true,
|
||||
"isConstructor": true,
|
||||
"isDeclaredConst": false,
|
||||
"modifiers": [
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"id": 11,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 6,
|
||||
"src": "668:11:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": 12,
|
||||
"modifierName": {
|
||||
"argumentTypes": null,
|
||||
"id": 10,
|
||||
"name": "Proxy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 1602,
|
||||
"src": "662:5:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_type$_t_contract$_Proxy_$1602_$",
|
||||
"typeString": "type(contract Proxy)"
|
||||
}
|
||||
},
|
||||
"nodeType": "ModifierInvocation",
|
||||
"src": "662:18:0"
|
||||
}
|
||||
],
|
||||
"name": "",
|
||||
"nodeType": "FunctionDefinition",
|
||||
"parameters": {
|
||||
"id": 9,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 6,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 22,
|
||||
"src": "622:19:0",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 5,
|
||||
"name": "address",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "622:7:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"id": 8,
|
||||
"name": "initializer",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 22,
|
||||
"src": "643:17:0",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_memory_ptr",
|
||||
"typeString": "bytes"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 7,
|
||||
"name": "bytes",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "643:5:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_storage_ptr",
|
||||
"typeString": "bytes"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"src": "621:40:0"
|
||||
},
|
||||
"payable": false,
|
||||
"returnParameters": {
|
||||
"id": 13,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "700:0:0"
|
||||
},
|
||||
"scope": 23,
|
||||
"src": "610:625:0",
|
||||
"stateMutability": "nonpayable",
|
||||
"superFunction": null,
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"scope": 24,
|
||||
"src": "355:882:0"
|
||||
}
|
||||
],
|
||||
"src": "0:1238:0"
|
||||
},
|
||||
"legacyAST": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/DelegateConstructorProxy.sol",
|
||||
"exportedSymbols": {
|
||||
"DelegateConstructorProxy": [
|
||||
23
|
||||
]
|
||||
},
|
||||
"id": 24,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:0"
|
||||
},
|
||||
{
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/Proxy.sol",
|
||||
"file": "./Proxy.sol",
|
||||
"id": 2,
|
||||
"nodeType": "ImportDirective",
|
||||
"scope": 24,
|
||||
"sourceUnit": 1603,
|
||||
"src": "24:21:0",
|
||||
"symbolAliases": [],
|
||||
"unitAlias": ""
|
||||
},
|
||||
{
|
||||
"baseContracts": [
|
||||
{
|
||||
"arguments": null,
|
||||
"baseName": {
|
||||
"contractScope": null,
|
||||
"id": 3,
|
||||
"name": "Proxy",
|
||||
"nodeType": "UserDefinedTypeName",
|
||||
"referencedDeclaration": 1602,
|
||||
"src": "392:5:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_contract$_Proxy_$1602",
|
||||
"typeString": "contract Proxy"
|
||||
}
|
||||
},
|
||||
"id": 4,
|
||||
"nodeType": "InheritanceSpecifier",
|
||||
"src": "392:5:0"
|
||||
}
|
||||
],
|
||||
"contractDependencies": [
|
||||
1602
|
||||
],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title Delegate Constructor Proxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. It is possible to send along initialization data with the constructor.\n @author Stefan George - <stefan@gnosis.pm>\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 23,
|
||||
"linearizedBaseContracts": [
|
||||
23,
|
||||
1602
|
||||
],
|
||||
"name": "DelegateConstructorProxy",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"body": {
|
||||
"id": 21,
|
||||
"nodeType": "Block",
|
||||
"src": "700:535:0",
|
||||
"statements": [
|
||||
{
|
||||
"condition": {
|
||||
"argumentTypes": null,
|
||||
"commonType": {
|
||||
"typeIdentifier": "t_uint256",
|
||||
"typeString": "uint256"
|
||||
},
|
||||
"id": 17,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftExpression": {
|
||||
"argumentTypes": null,
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"id": 14,
|
||||
"name": "initializer",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 8,
|
||||
"src": "714:11:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_memory_ptr",
|
||||
"typeString": "bytes memory"
|
||||
}
|
||||
},
|
||||
"id": 15,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"memberName": "length",
|
||||
"nodeType": "MemberAccess",
|
||||
"referencedDeclaration": null,
|
||||
"src": "714:18:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_uint256",
|
||||
"typeString": "uint256"
|
||||
}
|
||||
},
|
||||
"nodeType": "BinaryOperation",
|
||||
"operator": ">",
|
||||
"rightExpression": {
|
||||
"argumentTypes": null,
|
||||
"hexValue": "30",
|
||||
"id": 16,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "number",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "735:1:0",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_rational_0_by_1",
|
||||
"typeString": "int_const 0"
|
||||
},
|
||||
"value": "0"
|
||||
},
|
||||
"src": "714:22:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
}
|
||||
},
|
||||
"falseBody": null,
|
||||
"id": 20,
|
||||
"nodeType": "IfStatement",
|
||||
"src": "710:519:0",
|
||||
"trueBody": {
|
||||
"id": 19,
|
||||
"nodeType": "Block",
|
||||
"src": "738:491:0",
|
||||
"statements": [
|
||||
{
|
||||
"externalReferences": [
|
||||
{
|
||||
"initializer": {
|
||||
"declaration": 8,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1000:11:0",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"initializer": {
|
||||
"declaration": 8,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1026:11:0",
|
||||
"valueSize": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": 18,
|
||||
"nodeType": "InlineAssembly",
|
||||
"operations": "{\n let masterCopy := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)\n let success := delegatecall(sub(gas(), 10000), masterCopy, add(initializer, 0x20), mload(initializer), 0, 0)\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n if eq(success, 0)\n {\n revert(ptr, returndatasize())\n }\n}",
|
||||
"src": "820:409:0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": "@dev Constructor function sets address of master copy contract.\n @param _masterCopy Master copy address.\n @param initializer Data used for a delegate call to initialize the contract.",
|
||||
"id": 22,
|
||||
"implemented": true,
|
||||
"isConstructor": true,
|
||||
"isDeclaredConst": false,
|
||||
"modifiers": [
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"id": 11,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 6,
|
||||
"src": "668:11:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": 12,
|
||||
"modifierName": {
|
||||
"argumentTypes": null,
|
||||
"id": 10,
|
||||
"name": "Proxy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 1602,
|
||||
"src": "662:5:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_type$_t_contract$_Proxy_$1602_$",
|
||||
"typeString": "type(contract Proxy)"
|
||||
}
|
||||
},
|
||||
"nodeType": "ModifierInvocation",
|
||||
"src": "662:18:0"
|
||||
}
|
||||
],
|
||||
"name": "",
|
||||
"nodeType": "FunctionDefinition",
|
||||
"parameters": {
|
||||
"id": 9,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 6,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 22,
|
||||
"src": "622:19:0",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 5,
|
||||
"name": "address",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "622:7:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"id": 8,
|
||||
"name": "initializer",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 22,
|
||||
"src": "643:17:0",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_memory_ptr",
|
||||
"typeString": "bytes"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 7,
|
||||
"name": "bytes",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "643:5:0",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_storage_ptr",
|
||||
"typeString": "bytes"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"src": "621:40:0"
|
||||
},
|
||||
"payable": false,
|
||||
"returnParameters": {
|
||||
"id": 13,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "700:0:0"
|
||||
},
|
||||
"scope": 23,
|
||||
"src": "610:625:0",
|
||||
"stateMutability": "nonpayable",
|
||||
"superFunction": null,
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"scope": 24,
|
||||
"src": "355:882:0"
|
||||
}
|
||||
],
|
||||
"src": "0:1238:0"
|
||||
},
|
||||
"compiler": {
|
||||
"name": "solc",
|
||||
"version": "0.4.24+commit.e67f0147.Emscripten.clang"
|
||||
},
|
||||
"networks": {},
|
||||
"schemaVersion": "2.0.0",
|
||||
"updatedAt": "2018-05-28T05:59:52.697Z"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,151 @@
|
|||
{
|
||||
"contractName": "Enum",
|
||||
"abi": [],
|
||||
"bytecode": "0x6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820641ab8b295edfaa2b1c8a8e0ae7d17ea2f4c8b95ea27e45d8947ed9a4799ca1f0029",
|
||||
"deployedBytecode": "0x6080604052600080fd00a165627a7a72305820641ab8b295edfaa2b1c8a8e0ae7d17ea2f4c8b95ea27e45d8947ed9a4799ca1f0029",
|
||||
"sourceMap": "115:95:1:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;115:95:1;;;;;;;",
|
||||
"deployedSourceMap": "115:95:1:-;;;;;",
|
||||
"source": "pragma solidity 0.4.24;\n\n\n/// @title Enum - Collection of enums\n/// @author Richard Meissner - <richard@gnosis.pm>\ncontract Enum {\n enum Operation {\n Call,\n DelegateCall,\n Create\n }\n}\n",
|
||||
"sourcePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/Enum.sol",
|
||||
"ast": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/Enum.sol",
|
||||
"exportedSymbols": {
|
||||
"Enum": [
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": 31,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 25,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:1"
|
||||
},
|
||||
{
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title Enum - Collection of enums\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 30,
|
||||
"linearizedBaseContracts": [
|
||||
30
|
||||
],
|
||||
"name": "Enum",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"canonicalName": "Enum.Operation",
|
||||
"id": 29,
|
||||
"members": [
|
||||
{
|
||||
"id": 26,
|
||||
"name": "Call",
|
||||
"nodeType": "EnumValue",
|
||||
"src": "160:4:1"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "DelegateCall",
|
||||
"nodeType": "EnumValue",
|
||||
"src": "174:12:1"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Create",
|
||||
"nodeType": "EnumValue",
|
||||
"src": "196:6:1"
|
||||
}
|
||||
],
|
||||
"name": "Operation",
|
||||
"nodeType": "EnumDefinition",
|
||||
"src": "135:73:1"
|
||||
}
|
||||
],
|
||||
"scope": 31,
|
||||
"src": "115:95:1"
|
||||
}
|
||||
],
|
||||
"src": "0:211:1"
|
||||
},
|
||||
"legacyAST": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/Enum.sol",
|
||||
"exportedSymbols": {
|
||||
"Enum": [
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": 31,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 25,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:1"
|
||||
},
|
||||
{
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title Enum - Collection of enums\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 30,
|
||||
"linearizedBaseContracts": [
|
||||
30
|
||||
],
|
||||
"name": "Enum",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"canonicalName": "Enum.Operation",
|
||||
"id": 29,
|
||||
"members": [
|
||||
{
|
||||
"id": 26,
|
||||
"name": "Call",
|
||||
"nodeType": "EnumValue",
|
||||
"src": "160:4:1"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "DelegateCall",
|
||||
"nodeType": "EnumValue",
|
||||
"src": "174:12:1"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Create",
|
||||
"nodeType": "EnumValue",
|
||||
"src": "196:6:1"
|
||||
}
|
||||
],
|
||||
"name": "Operation",
|
||||
"nodeType": "EnumDefinition",
|
||||
"src": "135:73:1"
|
||||
}
|
||||
],
|
||||
"scope": 31,
|
||||
"src": "115:95:1"
|
||||
}
|
||||
],
|
||||
"src": "0:211:1"
|
||||
},
|
||||
"compiler": {
|
||||
"name": "solc",
|
||||
"version": "0.4.24+commit.e67f0147.Emscripten.clang"
|
||||
},
|
||||
"networks": {},
|
||||
"schemaVersion": "2.0.0",
|
||||
"updatedAt": "2018-05-28T05:59:52.697Z"
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,718 @@
|
|||
{
|
||||
"contractName": "MasterCopy",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_masterCopy",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "changeMasterCopy",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x608060405234801561001057600080fd5b50610276806100206000396000f300608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637de7edef14610046575b600080fd5b34801561005257600080fd5b50610087600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610089565b005b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610152576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001807f4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207481526020017f68697320636f6e7472616374000000000000000000000000000000000000000081525060400191505060405180910390fd5b60008173ffffffffffffffffffffffffffffffffffffffff1614151515610207576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505600a165627a7a72305820243ca7a44eb0464a47c14309cc3a29e407df6e966674981a787df22c0d9280220029",
|
||||
"deployedBytecode": "0x608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637de7edef14610046575b600080fd5b34801561005257600080fd5b50610087600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610089565b005b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610152576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001807f4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207481526020017f68697320636f6e7472616374000000000000000000000000000000000000000081525060400191505060405180910390fd5b60008173ffffffffffffffffffffffffffffffffffffffff1614151515610207576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505600a165627a7a72305820243ca7a44eb0464a47c14309cc3a29e407df6e966674981a787df22c0d9280220029",
|
||||
"sourceMap": "203:673:5:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;203:673:5;;;;;;;",
|
||||
"deployedSourceMap": "203:673:5:-;;;;;;;;;;;;;;;;;;;;;;;;626:248;;8:9:-1;5:2;;;30:1;27;20:12;5:2;626:248:5;;;;;;;;;;;;;;;;;;;;;;;;;;;;;244:4:13;222:27;;:10;:27;;;214:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;791:1:5;776:11;:16;;;;768:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;856:11;843:10;;:24;;;;;;;;;;;;;;;;;;626:248;:::o",
|
||||
"source": "pragma solidity 0.4.24;\nimport \"./SelfAuthorized.sol\";\n\n\n/// @title MasterCopy - Base for master copy contracts (should always be first super contract)\n/// @author Richard Meissner - <richard@gnosis.pm>\ncontract MasterCopy is SelfAuthorized {\n // masterCopy always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract.\n // It should also always be ensured that the address is stored alone (uses a full word)\n address masterCopy;\n\n /// @dev Allows to upgrade the contract. This can only be done via a Safe transaction.\n /// @param _masterCopy New contract address.\n function changeMasterCopy(address _masterCopy)\n public\n authorized\n {\n // Master copy address cannot be null.\n require(_masterCopy != 0, \"Invalid master copy address provided\");\n masterCopy = _masterCopy;\n }\n}\n",
|
||||
"sourcePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/MasterCopy.sol",
|
||||
"ast": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/MasterCopy.sol",
|
||||
"exportedSymbols": {
|
||||
"MasterCopy": [
|
||||
662
|
||||
]
|
||||
},
|
||||
"id": 663,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 637,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:5"
|
||||
},
|
||||
{
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
|
||||
"file": "./SelfAuthorized.sol",
|
||||
"id": 638,
|
||||
"nodeType": "ImportDirective",
|
||||
"scope": 663,
|
||||
"sourceUnit": 1655,
|
||||
"src": "24:30:5",
|
||||
"symbolAliases": [],
|
||||
"unitAlias": ""
|
||||
},
|
||||
{
|
||||
"baseContracts": [
|
||||
{
|
||||
"arguments": null,
|
||||
"baseName": {
|
||||
"contractScope": null,
|
||||
"id": 639,
|
||||
"name": "SelfAuthorized",
|
||||
"nodeType": "UserDefinedTypeName",
|
||||
"referencedDeclaration": 1654,
|
||||
"src": "226:14:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_contract$_SelfAuthorized_$1654",
|
||||
"typeString": "contract SelfAuthorized"
|
||||
}
|
||||
},
|
||||
"id": 640,
|
||||
"nodeType": "InheritanceSpecifier",
|
||||
"src": "226:14:5"
|
||||
}
|
||||
],
|
||||
"contractDependencies": [
|
||||
1654
|
||||
],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title MasterCopy - Base for master copy contracts (should always be first super contract)\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 662,
|
||||
"linearizedBaseContracts": [
|
||||
662,
|
||||
1654
|
||||
],
|
||||
"name": "MasterCopy",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 642,
|
||||
"name": "masterCopy",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 662,
|
||||
"src": "465:18:5",
|
||||
"stateVariable": true,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 641,
|
||||
"name": "address",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "465:7:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
},
|
||||
{
|
||||
"body": {
|
||||
"id": 660,
|
||||
"nodeType": "Block",
|
||||
"src": "711:163:5",
|
||||
"statements": [
|
||||
{
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"commonType": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"id": 652,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftExpression": {
|
||||
"argumentTypes": null,
|
||||
"id": 650,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 644,
|
||||
"src": "776:11:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"nodeType": "BinaryOperation",
|
||||
"operator": "!=",
|
||||
"rightExpression": {
|
||||
"argumentTypes": null,
|
||||
"hexValue": "30",
|
||||
"id": 651,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "number",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "791:1:5",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_rational_0_by_1",
|
||||
"typeString": "int_const 0"
|
||||
},
|
||||
"value": "0"
|
||||
},
|
||||
"src": "776:16:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"hexValue": "496e76616c6964206d617374657220636f707920616464726573732070726f7669646564",
|
||||
"id": 653,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "string",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "794:38:5",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87",
|
||||
"typeString": "literal_string \"Invalid master copy address provided\""
|
||||
},
|
||||
"value": "Invalid master copy address provided"
|
||||
}
|
||||
],
|
||||
"expression": {
|
||||
"argumentTypes": [
|
||||
{
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
},
|
||||
{
|
||||
"typeIdentifier": "t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87",
|
||||
"typeString": "literal_string \"Invalid master copy address provided\""
|
||||
}
|
||||
],
|
||||
"id": 649,
|
||||
"name": "require",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [
|
||||
2657,
|
||||
2658
|
||||
],
|
||||
"referencedDeclaration": 2658,
|
||||
"src": "768:7:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
|
||||
"typeString": "function (bool,string memory) pure"
|
||||
}
|
||||
},
|
||||
"id": 654,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"kind": "functionCall",
|
||||
"lValueRequested": false,
|
||||
"names": [],
|
||||
"nodeType": "FunctionCall",
|
||||
"src": "768:65:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_tuple$__$",
|
||||
"typeString": "tuple()"
|
||||
}
|
||||
},
|
||||
"id": 655,
|
||||
"nodeType": "ExpressionStatement",
|
||||
"src": "768:65:5"
|
||||
},
|
||||
{
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"id": 658,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftHandSide": {
|
||||
"argumentTypes": null,
|
||||
"id": 656,
|
||||
"name": "masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 642,
|
||||
"src": "843:10:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"nodeType": "Assignment",
|
||||
"operator": "=",
|
||||
"rightHandSide": {
|
||||
"argumentTypes": null,
|
||||
"id": 657,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 644,
|
||||
"src": "856:11:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"src": "843:24:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"id": 659,
|
||||
"nodeType": "ExpressionStatement",
|
||||
"src": "843:24:5"
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": "@dev Allows to upgrade the contract. This can only be done via a Safe transaction.\n @param _masterCopy New contract address.",
|
||||
"id": 661,
|
||||
"implemented": true,
|
||||
"isConstructor": false,
|
||||
"isDeclaredConst": false,
|
||||
"modifiers": [
|
||||
{
|
||||
"arguments": null,
|
||||
"id": 647,
|
||||
"modifierName": {
|
||||
"argumentTypes": null,
|
||||
"id": 646,
|
||||
"name": "authorized",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 1653,
|
||||
"src": "696:10:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_modifier$__$",
|
||||
"typeString": "modifier ()"
|
||||
}
|
||||
},
|
||||
"nodeType": "ModifierInvocation",
|
||||
"src": "696:10:5"
|
||||
}
|
||||
],
|
||||
"name": "changeMasterCopy",
|
||||
"nodeType": "FunctionDefinition",
|
||||
"parameters": {
|
||||
"id": 645,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 644,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 661,
|
||||
"src": "652:19:5",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 643,
|
||||
"name": "address",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "652:7:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"src": "651:21:5"
|
||||
},
|
||||
"payable": false,
|
||||
"returnParameters": {
|
||||
"id": 648,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "711:0:5"
|
||||
},
|
||||
"scope": 662,
|
||||
"src": "626:248:5",
|
||||
"stateMutability": "nonpayable",
|
||||
"superFunction": null,
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"scope": 663,
|
||||
"src": "203:673:5"
|
||||
}
|
||||
],
|
||||
"src": "0:877:5"
|
||||
},
|
||||
"legacyAST": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/MasterCopy.sol",
|
||||
"exportedSymbols": {
|
||||
"MasterCopy": [
|
||||
662
|
||||
]
|
||||
},
|
||||
"id": 663,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 637,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:5"
|
||||
},
|
||||
{
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
|
||||
"file": "./SelfAuthorized.sol",
|
||||
"id": 638,
|
||||
"nodeType": "ImportDirective",
|
||||
"scope": 663,
|
||||
"sourceUnit": 1655,
|
||||
"src": "24:30:5",
|
||||
"symbolAliases": [],
|
||||
"unitAlias": ""
|
||||
},
|
||||
{
|
||||
"baseContracts": [
|
||||
{
|
||||
"arguments": null,
|
||||
"baseName": {
|
||||
"contractScope": null,
|
||||
"id": 639,
|
||||
"name": "SelfAuthorized",
|
||||
"nodeType": "UserDefinedTypeName",
|
||||
"referencedDeclaration": 1654,
|
||||
"src": "226:14:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_contract$_SelfAuthorized_$1654",
|
||||
"typeString": "contract SelfAuthorized"
|
||||
}
|
||||
},
|
||||
"id": 640,
|
||||
"nodeType": "InheritanceSpecifier",
|
||||
"src": "226:14:5"
|
||||
}
|
||||
],
|
||||
"contractDependencies": [
|
||||
1654
|
||||
],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title MasterCopy - Base for master copy contracts (should always be first super contract)\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 662,
|
||||
"linearizedBaseContracts": [
|
||||
662,
|
||||
1654
|
||||
],
|
||||
"name": "MasterCopy",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 642,
|
||||
"name": "masterCopy",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 662,
|
||||
"src": "465:18:5",
|
||||
"stateVariable": true,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 641,
|
||||
"name": "address",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "465:7:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
},
|
||||
{
|
||||
"body": {
|
||||
"id": 660,
|
||||
"nodeType": "Block",
|
||||
"src": "711:163:5",
|
||||
"statements": [
|
||||
{
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"commonType": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"id": 652,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftExpression": {
|
||||
"argumentTypes": null,
|
||||
"id": 650,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 644,
|
||||
"src": "776:11:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"nodeType": "BinaryOperation",
|
||||
"operator": "!=",
|
||||
"rightExpression": {
|
||||
"argumentTypes": null,
|
||||
"hexValue": "30",
|
||||
"id": 651,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "number",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "791:1:5",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_rational_0_by_1",
|
||||
"typeString": "int_const 0"
|
||||
},
|
||||
"value": "0"
|
||||
},
|
||||
"src": "776:16:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"hexValue": "496e76616c6964206d617374657220636f707920616464726573732070726f7669646564",
|
||||
"id": 653,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "string",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "794:38:5",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87",
|
||||
"typeString": "literal_string \"Invalid master copy address provided\""
|
||||
},
|
||||
"value": "Invalid master copy address provided"
|
||||
}
|
||||
],
|
||||
"expression": {
|
||||
"argumentTypes": [
|
||||
{
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
},
|
||||
{
|
||||
"typeIdentifier": "t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87",
|
||||
"typeString": "literal_string \"Invalid master copy address provided\""
|
||||
}
|
||||
],
|
||||
"id": 649,
|
||||
"name": "require",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [
|
||||
2657,
|
||||
2658
|
||||
],
|
||||
"referencedDeclaration": 2658,
|
||||
"src": "768:7:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
|
||||
"typeString": "function (bool,string memory) pure"
|
||||
}
|
||||
},
|
||||
"id": 654,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"kind": "functionCall",
|
||||
"lValueRequested": false,
|
||||
"names": [],
|
||||
"nodeType": "FunctionCall",
|
||||
"src": "768:65:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_tuple$__$",
|
||||
"typeString": "tuple()"
|
||||
}
|
||||
},
|
||||
"id": 655,
|
||||
"nodeType": "ExpressionStatement",
|
||||
"src": "768:65:5"
|
||||
},
|
||||
{
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"id": 658,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftHandSide": {
|
||||
"argumentTypes": null,
|
||||
"id": 656,
|
||||
"name": "masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 642,
|
||||
"src": "843:10:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"nodeType": "Assignment",
|
||||
"operator": "=",
|
||||
"rightHandSide": {
|
||||
"argumentTypes": null,
|
||||
"id": 657,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 644,
|
||||
"src": "856:11:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"src": "843:24:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"id": 659,
|
||||
"nodeType": "ExpressionStatement",
|
||||
"src": "843:24:5"
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": "@dev Allows to upgrade the contract. This can only be done via a Safe transaction.\n @param _masterCopy New contract address.",
|
||||
"id": 661,
|
||||
"implemented": true,
|
||||
"isConstructor": false,
|
||||
"isDeclaredConst": false,
|
||||
"modifiers": [
|
||||
{
|
||||
"arguments": null,
|
||||
"id": 647,
|
||||
"modifierName": {
|
||||
"argumentTypes": null,
|
||||
"id": 646,
|
||||
"name": "authorized",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 1653,
|
||||
"src": "696:10:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_modifier$__$",
|
||||
"typeString": "modifier ()"
|
||||
}
|
||||
},
|
||||
"nodeType": "ModifierInvocation",
|
||||
"src": "696:10:5"
|
||||
}
|
||||
],
|
||||
"name": "changeMasterCopy",
|
||||
"nodeType": "FunctionDefinition",
|
||||
"parameters": {
|
||||
"id": 645,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 644,
|
||||
"name": "_masterCopy",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 661,
|
||||
"src": "652:19:5",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 643,
|
||||
"name": "address",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "652:7:5",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"src": "651:21:5"
|
||||
},
|
||||
"payable": false,
|
||||
"returnParameters": {
|
||||
"id": 648,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "711:0:5"
|
||||
},
|
||||
"scope": 662,
|
||||
"src": "626:248:5",
|
||||
"stateMutability": "nonpayable",
|
||||
"superFunction": null,
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"scope": 663,
|
||||
"src": "203:673:5"
|
||||
}
|
||||
],
|
||||
"src": "0:877:5"
|
||||
},
|
||||
"compiler": {
|
||||
"name": "solc",
|
||||
"version": "0.4.24+commit.e67f0147.Emscripten.clang"
|
||||
},
|
||||
"networks": {},
|
||||
"schemaVersion": "2.0.0",
|
||||
"updatedAt": "2018-05-28T05:59:52.701Z"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,367 @@
|
|||
{
|
||||
"contractName": "MultiSend",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "transactions",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "multiSend",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x608060405234801561001057600080fd5b5061013a806100206000396000f300608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638d80ff0a14610046575b600080fd5b34801561005257600080fd5b506100ad600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506100af565b005b805160205b8181101561010957808301516020820184015160608301850151608084018601600080838386885af1600081146100ea576100ef565b600080fd5b50602080601f8401040260800185019450505050506100b4565b5050505600a165627a7a72305820d762b9a26edc788e8139b309555c37a91970e3acfe5f209dff1eaf14345bdbe60029",
|
||||
"deployedBytecode": "0x608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638d80ff0a14610046575b600080fd5b34801561005257600080fd5b506100ad600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506100af565b005b805160205b8181101561010957808301516020820184015160608301850151608084018601600080838386885af1600081146100ea576100ef565b600080fd5b50602080601f8401040260800185019450505050506100b4565b5050505600a165627a7a72305820d762b9a26edc788e8139b309555c37a91970e3acfe5f209dff1eaf14345bdbe60029",
|
||||
"sourceMap": "253:1073:16:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;253:1073:16;;;;;;;",
|
||||
"deployedSourceMap": "253:1073:16:-;;;;;;;;;;;;;;;;;;;;;;;;593:731;;8:9:-1;5:2;;;30:1;27;20:12;5:2;593:731:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;768:12;762:19;803:4;820:488;834:6;831:1;828:13;820:488;;;898:1;884:12;880:20;874:27;962:4;959:1;955:12;941;937:31;931:38;1035:4;1032:1;1028:12;1014;1010:31;1004:38;1096:4;1093:1;1089:12;1075;1071:31;1168:1;1165;1153:10;1147:4;1140:5;1136:2;1131:3;1126:44;1192:1;1187:23;;;;1119:91;;1187:23;1206:1;1203;1196:12;1119:91;;1287:4;1280;1273;1261:10;1257:21;1253:32;1249:43;1243:4;1239:54;1236:1;1232:62;1227:67;;846:462;;;;820:488;;;734:584;;;:::o",
|
||||
"source": "pragma solidity 0.4.24;\n\n\n/// @title Multi Send - Allows to batch multiple transactions into one.\n/// @author Nick Dodson - <nick.dodson@consensys.net>\n/// @author Gonçalo Sá - <goncalo.sa@consensys.net>\n/// @author Stefan George - <stefan@gnosis.pm>\ncontract MultiSend {\n\n /// @dev Sends multiple transactions and reverts all if one fails.\n /// @param transactions Encoded transactions. Each transaction is encoded as\n /// a tuple(address,uint256,bytes). The bytes of all\n /// encoded transactions are concatenated to form the input.\n function multiSend(bytes transactions)\n public\n {\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n let length := mload(transactions)\n let i := 0x20\n for { } lt(i, length) { } {\n let to := mload(add(transactions, i))\n let value := mload(add(transactions, add(i, 0x20)))\n let dataLength := mload(add(transactions, add(i, 0x60)))\n let data := add(transactions, add(i, 0x80))\n switch call(gas, to, value, data, dataLength, 0, 0)\n case 0 { revert(0, 0) }\n i := add(i, add(0x80, mul(div(add(dataLength, 0x1f), 0x20), 0x20)))\n }\n }\n }\n}\n",
|
||||
"sourcePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/libraries/MultiSend.sol",
|
||||
"ast": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/libraries/MultiSend.sol",
|
||||
"exportedSymbols": {
|
||||
"MultiSend": [
|
||||
1775
|
||||
]
|
||||
},
|
||||
"id": 1776,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1767,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:16"
|
||||
},
|
||||
{
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title Multi Send - Allows to batch multiple transactions into one.\n @author Nick Dodson - <nick.dodson@consensys.net>\n @author Gonçalo Sá - <goncalo.sa@consensys.net>\n @author Stefan George - <stefan@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 1775,
|
||||
"linearizedBaseContracts": [
|
||||
1775
|
||||
],
|
||||
"name": "MultiSend",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"body": {
|
||||
"id": 1773,
|
||||
"nodeType": "Block",
|
||||
"src": "651:673:16",
|
||||
"statements": [
|
||||
{
|
||||
"externalReferences": [
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "768:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "884:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "941:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1014:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1075:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": 1772,
|
||||
"nodeType": "InlineAssembly",
|
||||
"operations": "{\n let length := mload(transactions)\n let i := 0x20\n for {\n }\n lt(i, length)\n {\n }\n {\n let to := mload(add(transactions, i))\n let value := mload(add(transactions, add(i, 0x20)))\n let dataLength := mload(add(transactions, add(i, 0x60)))\n let data := add(transactions, add(i, 0x80))\n switch call(gas(), to, value, data, dataLength, 0, 0)\n case 0 {\n revert(0, 0)\n }\n i := add(i, add(0x80, mul(div(add(dataLength, 0x1f), 0x20), 0x20)))\n }\n}",
|
||||
"src": "725:599:16"
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": "@dev Sends multiple transactions and reverts all if one fails.\n @param transactions Encoded transactions. Each transaction is encoded as\n a tuple(address,uint256,bytes). The bytes of all\n encoded transactions are concatenated to form the input.",
|
||||
"id": 1774,
|
||||
"implemented": true,
|
||||
"isConstructor": false,
|
||||
"isDeclaredConst": false,
|
||||
"modifiers": [],
|
||||
"name": "multiSend",
|
||||
"nodeType": "FunctionDefinition",
|
||||
"parameters": {
|
||||
"id": 1770,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 1769,
|
||||
"name": "transactions",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 1774,
|
||||
"src": "612:18:16",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_memory_ptr",
|
||||
"typeString": "bytes"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 1768,
|
||||
"name": "bytes",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "612:5:16",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_storage_ptr",
|
||||
"typeString": "bytes"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"src": "611:20:16"
|
||||
},
|
||||
"payable": false,
|
||||
"returnParameters": {
|
||||
"id": 1771,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "651:0:16"
|
||||
},
|
||||
"scope": 1775,
|
||||
"src": "593:731:16",
|
||||
"stateMutability": "nonpayable",
|
||||
"superFunction": null,
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"scope": 1776,
|
||||
"src": "253:1073:16"
|
||||
}
|
||||
],
|
||||
"src": "0:1327:16"
|
||||
},
|
||||
"legacyAST": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/libraries/MultiSend.sol",
|
||||
"exportedSymbols": {
|
||||
"MultiSend": [
|
||||
1775
|
||||
]
|
||||
},
|
||||
"id": 1776,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1767,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:16"
|
||||
},
|
||||
{
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title Multi Send - Allows to batch multiple transactions into one.\n @author Nick Dodson - <nick.dodson@consensys.net>\n @author Gonçalo Sá - <goncalo.sa@consensys.net>\n @author Stefan George - <stefan@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 1775,
|
||||
"linearizedBaseContracts": [
|
||||
1775
|
||||
],
|
||||
"name": "MultiSend",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"body": {
|
||||
"id": 1773,
|
||||
"nodeType": "Block",
|
||||
"src": "651:673:16",
|
||||
"statements": [
|
||||
{
|
||||
"externalReferences": [
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "768:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "884:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "941:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1014:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactions": {
|
||||
"declaration": 1769,
|
||||
"isOffset": false,
|
||||
"isSlot": false,
|
||||
"src": "1075:12:16",
|
||||
"valueSize": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": 1772,
|
||||
"nodeType": "InlineAssembly",
|
||||
"operations": "{\n let length := mload(transactions)\n let i := 0x20\n for {\n }\n lt(i, length)\n {\n }\n {\n let to := mload(add(transactions, i))\n let value := mload(add(transactions, add(i, 0x20)))\n let dataLength := mload(add(transactions, add(i, 0x60)))\n let data := add(transactions, add(i, 0x80))\n switch call(gas(), to, value, data, dataLength, 0, 0)\n case 0 {\n revert(0, 0)\n }\n i := add(i, add(0x80, mul(div(add(dataLength, 0x1f), 0x20), 0x20)))\n }\n}",
|
||||
"src": "725:599:16"
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": "@dev Sends multiple transactions and reverts all if one fails.\n @param transactions Encoded transactions. Each transaction is encoded as\n a tuple(address,uint256,bytes). The bytes of all\n encoded transactions are concatenated to form the input.",
|
||||
"id": 1774,
|
||||
"implemented": true,
|
||||
"isConstructor": false,
|
||||
"isDeclaredConst": false,
|
||||
"modifiers": [],
|
||||
"name": "multiSend",
|
||||
"nodeType": "FunctionDefinition",
|
||||
"parameters": {
|
||||
"id": 1770,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [
|
||||
{
|
||||
"constant": false,
|
||||
"id": 1769,
|
||||
"name": "transactions",
|
||||
"nodeType": "VariableDeclaration",
|
||||
"scope": 1774,
|
||||
"src": "612:18:16",
|
||||
"stateVariable": false,
|
||||
"storageLocation": "default",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_memory_ptr",
|
||||
"typeString": "bytes"
|
||||
},
|
||||
"typeName": {
|
||||
"id": 1768,
|
||||
"name": "bytes",
|
||||
"nodeType": "ElementaryTypeName",
|
||||
"src": "612:5:16",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bytes_storage_ptr",
|
||||
"typeString": "bytes"
|
||||
}
|
||||
},
|
||||
"value": null,
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"src": "611:20:16"
|
||||
},
|
||||
"payable": false,
|
||||
"returnParameters": {
|
||||
"id": 1771,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "651:0:16"
|
||||
},
|
||||
"scope": 1775,
|
||||
"src": "593:731:16",
|
||||
"stateMutability": "nonpayable",
|
||||
"superFunction": null,
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"scope": 1776,
|
||||
"src": "253:1073:16"
|
||||
}
|
||||
],
|
||||
"src": "0:1327:16"
|
||||
},
|
||||
"compiler": {
|
||||
"name": "solc",
|
||||
"version": "0.4.24+commit.e67f0147.Emscripten.clang"
|
||||
},
|
||||
"networks": {
|
||||
"4": {
|
||||
"events": {},
|
||||
"links": {},
|
||||
"address": "0xa95bcb648df34c679b070cd7f5992ec4aa4e5275",
|
||||
"transactionHash": "0x7260ac1ca4cdf29c28bc941de22f64e7dd24bc928841bebee1f2b4d5d84037c8"
|
||||
},
|
||||
"1527420696956": {
|
||||
"events": {},
|
||||
"links": {},
|
||||
"address": "0x8e9d29708810d650d0b43058b41687ea02c0baee",
|
||||
"transactionHash": "0xd044f1662e339061a8cabf2b06ac94a9f86fcccf3f5d80ebd1bea2a7542d4021"
|
||||
},
|
||||
"1527678155804": {
|
||||
"events": {},
|
||||
"links": {},
|
||||
"address": "0x6655c7792ebda0b1dbea3607591995ea391c8a9e",
|
||||
"transactionHash": "0x70aa0d9af1bc4978d61d3f0c67523717246c4a588a4c1808d45609e0adb04a05"
|
||||
},
|
||||
"1528109761438": {
|
||||
"events": {},
|
||||
"links": {},
|
||||
"address": "0x3946fcaaa0ba21aaffc5e06a3cc45debc9e07f7f",
|
||||
"transactionHash": "0xd044f1662e339061a8cabf2b06ac94a9f86fcccf3f5d80ebd1bea2a7542d4021"
|
||||
}
|
||||
},
|
||||
"schemaVersion": "2.0.0",
|
||||
"updatedAt": "2018-06-04T10:56:37.139Z"
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,479 @@
|
|||
{
|
||||
"contractName": "SelfAuthorized",
|
||||
"abi": [],
|
||||
"bytecode": "0x6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820ec80f1b4520aa5197e4181778f1e2e4fc460002d4a40e2e8e6709c8986067c220029",
|
||||
"deployedBytecode": "0x6080604052600080fd00a165627a7a72305820ec80f1b4520aa5197e4181778f1e2e4fc460002d4a40e2e8e6709c8986067c220029",
|
||||
"sourceMap": "152:166:13:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;152:166:13;;;;;;;",
|
||||
"deployedSourceMap": "152:166:13:-;;;;;",
|
||||
"source": "pragma solidity 0.4.24;\n\n\n/// @title SelfAuthorized - authorizes current contract to perform actions\n/// @author Richard Meissner - <richard@gnosis.pm>\ncontract SelfAuthorized {\n modifier authorized() {\n require(msg.sender == address(this), \"Method can only be called from this contract\");\n _;\n }\n}\n",
|
||||
"sourcePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
|
||||
"ast": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
|
||||
"exportedSymbols": {
|
||||
"SelfAuthorized": [
|
||||
1654
|
||||
]
|
||||
},
|
||||
"id": 1655,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1639,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:13"
|
||||
},
|
||||
{
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title SelfAuthorized - authorizes current contract to perform actions\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 1654,
|
||||
"linearizedBaseContracts": [
|
||||
1654
|
||||
],
|
||||
"name": "SelfAuthorized",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"body": {
|
||||
"id": 1652,
|
||||
"nodeType": "Block",
|
||||
"src": "204:112:13",
|
||||
"statements": [
|
||||
{
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"commonType": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"id": 1647,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftExpression": {
|
||||
"argumentTypes": null,
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"id": 1642,
|
||||
"name": "msg",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 2654,
|
||||
"src": "222:3:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_magic_message",
|
||||
"typeString": "msg"
|
||||
}
|
||||
},
|
||||
"id": 1643,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"memberName": "sender",
|
||||
"nodeType": "MemberAccess",
|
||||
"referencedDeclaration": null,
|
||||
"src": "222:10:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"nodeType": "BinaryOperation",
|
||||
"operator": "==",
|
||||
"rightExpression": {
|
||||
"argumentTypes": null,
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"id": 1645,
|
||||
"name": "this",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 2673,
|
||||
"src": "244:4:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_contract$_SelfAuthorized_$1654",
|
||||
"typeString": "contract SelfAuthorized"
|
||||
}
|
||||
}
|
||||
],
|
||||
"expression": {
|
||||
"argumentTypes": [
|
||||
{
|
||||
"typeIdentifier": "t_contract$_SelfAuthorized_$1654",
|
||||
"typeString": "contract SelfAuthorized"
|
||||
}
|
||||
],
|
||||
"id": 1644,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"lValueRequested": false,
|
||||
"nodeType": "ElementaryTypeNameExpression",
|
||||
"src": "236:7:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_type$_t_address_$",
|
||||
"typeString": "type(address)"
|
||||
},
|
||||
"typeName": "address"
|
||||
},
|
||||
"id": 1646,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"kind": "typeConversion",
|
||||
"lValueRequested": false,
|
||||
"names": [],
|
||||
"nodeType": "FunctionCall",
|
||||
"src": "236:13:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"src": "222:27:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"hexValue": "4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207468697320636f6e7472616374",
|
||||
"id": 1648,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "string",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "251:46:13",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733",
|
||||
"typeString": "literal_string \"Method can only be called from this contract\""
|
||||
},
|
||||
"value": "Method can only be called from this contract"
|
||||
}
|
||||
],
|
||||
"expression": {
|
||||
"argumentTypes": [
|
||||
{
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
},
|
||||
{
|
||||
"typeIdentifier": "t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733",
|
||||
"typeString": "literal_string \"Method can only be called from this contract\""
|
||||
}
|
||||
],
|
||||
"id": 1641,
|
||||
"name": "require",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [
|
||||
2657,
|
||||
2658
|
||||
],
|
||||
"referencedDeclaration": 2658,
|
||||
"src": "214:7:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
|
||||
"typeString": "function (bool,string memory) pure"
|
||||
}
|
||||
},
|
||||
"id": 1649,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"kind": "functionCall",
|
||||
"lValueRequested": false,
|
||||
"names": [],
|
||||
"nodeType": "FunctionCall",
|
||||
"src": "214:84:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_tuple$__$",
|
||||
"typeString": "tuple()"
|
||||
}
|
||||
},
|
||||
"id": 1650,
|
||||
"nodeType": "ExpressionStatement",
|
||||
"src": "214:84:13"
|
||||
},
|
||||
{
|
||||
"id": 1651,
|
||||
"nodeType": "PlaceholderStatement",
|
||||
"src": "308:1:13"
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": null,
|
||||
"id": 1653,
|
||||
"name": "authorized",
|
||||
"nodeType": "ModifierDefinition",
|
||||
"parameters": {
|
||||
"id": 1640,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "201:2:13"
|
||||
},
|
||||
"src": "182:134:13",
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"scope": 1655,
|
||||
"src": "152:166:13"
|
||||
}
|
||||
],
|
||||
"src": "0:319:13"
|
||||
},
|
||||
"legacyAST": {
|
||||
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
|
||||
"exportedSymbols": {
|
||||
"SelfAuthorized": [
|
||||
1654
|
||||
]
|
||||
},
|
||||
"id": 1655,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1639,
|
||||
"literals": [
|
||||
"solidity",
|
||||
"0.4",
|
||||
".24"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "0:23:13"
|
||||
},
|
||||
{
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"documentation": "@title SelfAuthorized - authorizes current contract to perform actions\n @author Richard Meissner - <richard@gnosis.pm>",
|
||||
"fullyImplemented": true,
|
||||
"id": 1654,
|
||||
"linearizedBaseContracts": [
|
||||
1654
|
||||
],
|
||||
"name": "SelfAuthorized",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [
|
||||
{
|
||||
"body": {
|
||||
"id": 1652,
|
||||
"nodeType": "Block",
|
||||
"src": "204:112:13",
|
||||
"statements": [
|
||||
{
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"commonType": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
},
|
||||
"id": 1647,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"leftExpression": {
|
||||
"argumentTypes": null,
|
||||
"expression": {
|
||||
"argumentTypes": null,
|
||||
"id": 1642,
|
||||
"name": "msg",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 2654,
|
||||
"src": "222:3:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_magic_message",
|
||||
"typeString": "msg"
|
||||
}
|
||||
},
|
||||
"id": 1643,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"lValueRequested": false,
|
||||
"memberName": "sender",
|
||||
"nodeType": "MemberAccess",
|
||||
"referencedDeclaration": null,
|
||||
"src": "222:10:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"nodeType": "BinaryOperation",
|
||||
"operator": "==",
|
||||
"rightExpression": {
|
||||
"argumentTypes": null,
|
||||
"arguments": [
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"id": 1645,
|
||||
"name": "this",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [],
|
||||
"referencedDeclaration": 2673,
|
||||
"src": "244:4:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_contract$_SelfAuthorized_$1654",
|
||||
"typeString": "contract SelfAuthorized"
|
||||
}
|
||||
}
|
||||
],
|
||||
"expression": {
|
||||
"argumentTypes": [
|
||||
{
|
||||
"typeIdentifier": "t_contract$_SelfAuthorized_$1654",
|
||||
"typeString": "contract SelfAuthorized"
|
||||
}
|
||||
],
|
||||
"id": 1644,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"lValueRequested": false,
|
||||
"nodeType": "ElementaryTypeNameExpression",
|
||||
"src": "236:7:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_type$_t_address_$",
|
||||
"typeString": "type(address)"
|
||||
},
|
||||
"typeName": "address"
|
||||
},
|
||||
"id": 1646,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"kind": "typeConversion",
|
||||
"lValueRequested": false,
|
||||
"names": [],
|
||||
"nodeType": "FunctionCall",
|
||||
"src": "236:13:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_address",
|
||||
"typeString": "address"
|
||||
}
|
||||
},
|
||||
"src": "222:27:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"argumentTypes": null,
|
||||
"hexValue": "4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207468697320636f6e7472616374",
|
||||
"id": 1648,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": true,
|
||||
"kind": "string",
|
||||
"lValueRequested": false,
|
||||
"nodeType": "Literal",
|
||||
"src": "251:46:13",
|
||||
"subdenomination": null,
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733",
|
||||
"typeString": "literal_string \"Method can only be called from this contract\""
|
||||
},
|
||||
"value": "Method can only be called from this contract"
|
||||
}
|
||||
],
|
||||
"expression": {
|
||||
"argumentTypes": [
|
||||
{
|
||||
"typeIdentifier": "t_bool",
|
||||
"typeString": "bool"
|
||||
},
|
||||
{
|
||||
"typeIdentifier": "t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733",
|
||||
"typeString": "literal_string \"Method can only be called from this contract\""
|
||||
}
|
||||
],
|
||||
"id": 1641,
|
||||
"name": "require",
|
||||
"nodeType": "Identifier",
|
||||
"overloadedDeclarations": [
|
||||
2657,
|
||||
2658
|
||||
],
|
||||
"referencedDeclaration": 2658,
|
||||
"src": "214:7:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
|
||||
"typeString": "function (bool,string memory) pure"
|
||||
}
|
||||
},
|
||||
"id": 1649,
|
||||
"isConstant": false,
|
||||
"isLValue": false,
|
||||
"isPure": false,
|
||||
"kind": "functionCall",
|
||||
"lValueRequested": false,
|
||||
"names": [],
|
||||
"nodeType": "FunctionCall",
|
||||
"src": "214:84:13",
|
||||
"typeDescriptions": {
|
||||
"typeIdentifier": "t_tuple$__$",
|
||||
"typeString": "tuple()"
|
||||
}
|
||||
},
|
||||
"id": 1650,
|
||||
"nodeType": "ExpressionStatement",
|
||||
"src": "214:84:13"
|
||||
},
|
||||
{
|
||||
"id": 1651,
|
||||
"nodeType": "PlaceholderStatement",
|
||||
"src": "308:1:13"
|
||||
}
|
||||
]
|
||||
},
|
||||
"documentation": null,
|
||||
"id": 1653,
|
||||
"name": "authorized",
|
||||
"nodeType": "ModifierDefinition",
|
||||
"parameters": {
|
||||
"id": 1640,
|
||||
"nodeType": "ParameterList",
|
||||
"parameters": [],
|
||||
"src": "201:2:13"
|
||||
},
|
||||
"src": "182:134:13",
|
||||
"visibility": "internal"
|
||||
}
|
||||
],
|
||||
"scope": 1655,
|
||||
"src": "152:166:13"
|
||||
}
|
||||
],
|
||||
"src": "0:319:13"
|
||||
},
|
||||
"compiler": {
|
||||
"name": "solc",
|
||||
"version": "0.4.24+commit.e67f0147.Emscripten.clang"
|
||||
},
|
||||
"networks": {},
|
||||
"schemaVersion": "2.0.0",
|
||||
"updatedAt": "2018-05-28T05:59:52.707Z"
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -20,7 +20,7 @@ const Header = ({ provider, reloadWallet }: Props) => (
|
|||
<Col xs={12} center="xs" sm={6} start="sm" margin="lg">
|
||||
<Img src={logo} height={40} alt="Gnosis Safe" />
|
||||
</Col>
|
||||
<Col xs={12} center="xs" sm={6} end="sm" margin="lg">
|
||||
<Col xs={12} center="xs" sm={6} end="sm" middle="xs" margin="lg">
|
||||
{ provider ? <Connected provider={provider} /> : <NotConnected /> }
|
||||
<Refresh callback={reloadWallet} />
|
||||
</Col>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { ListItemText } from 'material-ui/List'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import { type WithStyles } from '~/theme/mui'
|
||||
|
||||
type Props = WithStyles & {
|
||||
primary: string,
|
||||
secondary: string,
|
||||
cut?: boolean,
|
||||
}
|
||||
|
||||
const styles = {
|
||||
itemTextSecondary: {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
}
|
||||
|
||||
const GnoListItemText = ({
|
||||
primary, secondary, classes, cut = false,
|
||||
}: Props) => {
|
||||
const cutStyle = cut ? {
|
||||
secondary: classes.itemTextSecondary,
|
||||
} : undefined
|
||||
|
||||
return (
|
||||
<ListItemText
|
||||
classes={cutStyle}
|
||||
inset
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles)(GnoListItemText)
|
|
@ -1,30 +1,6 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Link from '~/components/layout/Link'
|
||||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||
|
||||
type NextButtonProps = {
|
||||
text: string,
|
||||
disabled: boolean,
|
||||
}
|
||||
|
||||
const NextButton = ({ text, disabled }: NextButtonProps) => (
|
||||
<Button
|
||||
variant="raised"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
)
|
||||
|
||||
const GoButton = () => (
|
||||
<Link to={SAFELIST_ADDRESS}>
|
||||
<NextButton text="VISIT SAFES" disabled={false} />
|
||||
</Link>
|
||||
)
|
||||
|
||||
type ControlProps = {
|
||||
next: string,
|
||||
|
@ -44,12 +20,20 @@ const ControlButtons = ({
|
|||
>
|
||||
Back
|
||||
</Button>
|
||||
<NextButton text={next} disabled={submitting} />
|
||||
<Button
|
||||
variant="raised"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
>
|
||||
{next}
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
type Props = {
|
||||
finishedTx: boolean,
|
||||
finishedButton: React$Node,
|
||||
onPrevious: () => void,
|
||||
firstPage: boolean,
|
||||
lastPage: boolean,
|
||||
|
@ -57,19 +41,16 @@ type Props = {
|
|||
}
|
||||
|
||||
const Controls = ({
|
||||
finishedTx, onPrevious, firstPage, lastPage, submitting,
|
||||
finishedTx, finishedButton, onPrevious, firstPage, lastPage, submitting,
|
||||
}: Props) => (
|
||||
<React.Fragment>
|
||||
{ finishedTx
|
||||
? <GoButton />
|
||||
: <ControlButtons
|
||||
submitting={submitting}
|
||||
next={lastPage ? 'Finish' : 'Next'}
|
||||
firstPage={firstPage}
|
||||
onPrevious={onPrevious}
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
finishedTx
|
||||
? <React.Fragment>{finishedButton}</React.Fragment>
|
||||
: <ControlButtons
|
||||
submitting={submitting}
|
||||
next={lastPage ? 'Finish' : 'Next'}
|
||||
firstPage={firstPage}
|
||||
onPrevious={onPrevious}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Controls
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// @flow
|
||||
import Stepper, { Step as FormStep, StepLabel } from 'material-ui/Stepper'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import * as React from 'react'
|
||||
import type { FormApi } from 'react-final-form'
|
||||
import GnoForm from '~/components/forms/GnoForm'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Col from '~/components/layout/Col'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Controls from './Controls'
|
||||
|
@ -10,10 +12,13 @@ import Controls from './Controls'
|
|||
export { default as Step } from './Step'
|
||||
|
||||
type Props = {
|
||||
classes: Object,
|
||||
steps: string[],
|
||||
finishedTransaction: boolean,
|
||||
finishedButton: React$Node,
|
||||
initialValues?: Object,
|
||||
children: React$Node,
|
||||
onReset?: () => void,
|
||||
onSubmit: (values: Object, form: FormApi, callback: ?(errors: ?Object) => void) => ?Object | Promise<?Object> | void,
|
||||
}
|
||||
|
||||
|
@ -29,6 +34,13 @@ type PageProps = {
|
|||
class GnoStepper extends React.PureComponent<Props, State> {
|
||||
static Page = ({ children }: PageProps) => children
|
||||
|
||||
static FinishButton = ({
|
||||
component, to, title, ...props
|
||||
}) => (
|
||||
<Button component={component} to={to} variant="raised" color="primary" {...props}>
|
||||
{title}
|
||||
</Button>
|
||||
)
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
|
@ -38,6 +50,17 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
const resetCallback = this.props.onReset
|
||||
if (resetCallback) {
|
||||
resetCallback()
|
||||
}
|
||||
this.setState(() => ({
|
||||
page: 0,
|
||||
values: this.props.initialValues || {},
|
||||
}))
|
||||
}
|
||||
|
||||
getActivePageFrom = (pages: React$Node) => {
|
||||
const activePageProps = React.Children.toArray(pages)[this.state.page].props
|
||||
const { children, ...props } = activePageProps
|
||||
|
@ -75,14 +98,17 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { steps, children, finishedTransaction } = this.props
|
||||
const {
|
||||
steps, children, finishedTransaction, finishedButton, classes,
|
||||
} = this.props
|
||||
const { page, values } = this.state
|
||||
const activePage = this.getActivePageFrom(children)
|
||||
const isLastPage = page === steps.length - 1
|
||||
const finished = React.cloneElement(React.Children.only(finishedButton), { onClick: this.onReset })
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Stepper activeStep={page} alternativeLabel>
|
||||
<Stepper classes={{ root: classes.root }} activeStep={page} alternativeLabel>
|
||||
{steps.map(label => (
|
||||
<FormStep key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
|
@ -102,6 +128,7 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
<Controls
|
||||
submitting={submitting}
|
||||
finishedTx={finishedTransaction}
|
||||
finishedButton={finished}
|
||||
onPrevious={this.previous}
|
||||
firstPage={page === 0}
|
||||
lastPage={isLastPage}
|
||||
|
@ -115,4 +142,10 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
export default GnoStepper
|
||||
const styles = {
|
||||
root: {
|
||||
flex: '1 1 auto',
|
||||
},
|
||||
}
|
||||
|
||||
export default withStyles(styles)(GnoStepper)
|
||||
|
|
|
@ -5,11 +5,22 @@ type Field = boolean | string
|
|||
|
||||
export const required = (value: Field) => (value ? undefined : 'Required')
|
||||
|
||||
export const mustBeNumber = (value: number) =>
|
||||
export const mustBeInteger = (value: string) =>
|
||||
(!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined)
|
||||
|
||||
export const mustBeFloat = (value: number) =>
|
||||
(Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
|
||||
|
||||
export const greaterThan = (min: number) => (value: string) => {
|
||||
if (Number.isNaN(Number(value)) || Number.parseFloat(value) > Number(min)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `Should be greater than ${min}`
|
||||
}
|
||||
|
||||
export const minValue = (min: number) => (value: string) => {
|
||||
if (Number.isNaN(Number(value)) || Number.parseInt(value, 10) >= Number(min)) {
|
||||
if (Number.isNaN(Number(value)) || Number.parseFloat(value) >= Number(min)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
@ -39,3 +50,13 @@ export const uniqueAddress = (addresses: string[]) => (value: string) =>
|
|||
|
||||
export const composeValidators = (...validators: Function[]) => (value: Field) =>
|
||||
validators.reduce((error, validator) => error || validator(value), undefined)
|
||||
|
||||
export const inLimit = (limit: number, base: number, baseText: string) => (value: string) => {
|
||||
const amount = Number(value)
|
||||
const max = limit - base
|
||||
if (amount <= max) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `Should not exceed ${max} ETH (amount to reach ${baseText})`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// @flow
|
||||
import { withStateHandlers } from 'recompose'
|
||||
|
||||
export type Open = {
|
||||
open: boolean,
|
||||
toggle: () => void,
|
||||
}
|
||||
|
||||
export default withStateHandlers(
|
||||
() => ({ open: false }),
|
||||
{ toggle: ({ open }) => () => ({ open: !open }) },
|
||||
)
|
|
@ -11,7 +11,7 @@ type Size = 'sm' | 'md' | 'lg' | 'xl'
|
|||
type Props = {
|
||||
margin?: Size,
|
||||
padding?: Size,
|
||||
center?: boolean,
|
||||
align?: 'center' | 'right',
|
||||
children: React$Node,
|
||||
className?: string,
|
||||
}
|
||||
|
@ -19,12 +19,12 @@ type Props = {
|
|||
class Block extends PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
margin, padding, center, children, className, ...props
|
||||
margin, padding, align, children, className, ...props
|
||||
} = this.props
|
||||
|
||||
const paddingStyle = padding ? capitalize(padding, 'padding') : undefined
|
||||
return (
|
||||
<div className={cx(className, 'block', margin, paddingStyle, { center })} {...props}>
|
||||
<div className={cx(className, 'block', margin, paddingStyle, align)} {...props}>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
.block {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -37,7 +36,13 @@
|
|||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
|
@ -16,6 +16,8 @@ type Props = {
|
|||
around?: 'xs' | 'sm' | 'md' | 'lg',
|
||||
between?: 'xs' | 'sm' | 'md' | 'lg',
|
||||
margin?: 'sm' | 'md' | 'lg' | 'xl',
|
||||
layout?: 'inherit' | 'block' | 'column',
|
||||
overflow?: boolean,
|
||||
xs?: number | boolean,
|
||||
sm?: number | boolean,
|
||||
md?: number | boolean,
|
||||
|
@ -29,7 +31,7 @@ type Props = {
|
|||
}
|
||||
|
||||
const Col = ({
|
||||
children, margin,
|
||||
children, margin, layout = 'inherit', overflow,
|
||||
xs, sm, md, lg,
|
||||
start, center, end, top, middle, bottom, around, between,
|
||||
xsOffset, smOffset, mdOffset, lgOffset,
|
||||
|
@ -54,6 +56,8 @@ const Col = ({
|
|||
smOffset ? capitalize(smOffset, 'smOffset') : undefined,
|
||||
mdOffset ? capitalize(mdOffset, 'mdOffset') : undefined,
|
||||
lgOffset ? capitalize(lgOffset, 'lgOffset') : undefined,
|
||||
{ overflow },
|
||||
layout,
|
||||
props.className,
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,23 @@
|
|||
.col {
|
||||
flex: 1 1 auto;
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.inherit {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.marginSm {
|
||||
|
@ -22,117 +38,117 @@
|
|||
|
||||
@define-mixin col $size {
|
||||
.$(size)1 {
|
||||
flex-basis: 8.333%;
|
||||
max-width: 8.333%;
|
||||
flex-basis: 8.333%;
|
||||
max-width: 8.333%;
|
||||
}
|
||||
|
||||
.$(size)2 {
|
||||
flex-basis: 16.667%;
|
||||
max-width: 16.667%;
|
||||
flex-basis: 16.667%;
|
||||
max-width: 16.667%;
|
||||
}
|
||||
|
||||
.$(size)3 {
|
||||
flex-basis: 25%;
|
||||
max-width: 25%;
|
||||
flex-basis: 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
.$(size)4 {
|
||||
flex-basis: 33.333%;
|
||||
max-width: 33.333%;
|
||||
flex-basis: 33.333%;
|
||||
max-width: 33.333%;
|
||||
}
|
||||
|
||||
.$(size)5 {
|
||||
flex-basis: 41.667%;
|
||||
max-width: 41.667%;
|
||||
flex-basis: 41.667%;
|
||||
max-width: 41.667%;
|
||||
}
|
||||
|
||||
.$(size)6 {
|
||||
flex-basis: 50%;
|
||||
max-width: 50%;
|
||||
flex-basis: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.$(size)7 {
|
||||
flex-basis: 58.333%;
|
||||
max-width: 58.333%;
|
||||
flex-basis: 58.333%;
|
||||
max-width: 58.333%;
|
||||
}
|
||||
|
||||
.$(size)8 {
|
||||
flex-basis: 66.667%;
|
||||
max-width: 66.667%;
|
||||
flex-basis: 66.667%;
|
||||
max-width: 66.667%;
|
||||
}
|
||||
|
||||
.$(size)9 {
|
||||
flex-basis: 75%;
|
||||
max-width: 75%;
|
||||
flex-basis: 75%;
|
||||
max-width: 75%;
|
||||
}
|
||||
|
||||
.$(size)10 {
|
||||
flex-basis: 83.333%;
|
||||
max-width: 83.333%;
|
||||
flex-basis: 83.333%;
|
||||
max-width: 83.333%;
|
||||
}
|
||||
|
||||
.$(size)11 {
|
||||
flex-basis: 91.667%;
|
||||
max-width: 91.667%;
|
||||
flex-basis: 91.667%;
|
||||
max-width: 91.667%;
|
||||
}
|
||||
|
||||
.$(size)12 {
|
||||
flex-basis: 100%;
|
||||
max-width: 100%;
|
||||
flex-basis: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.$(size)Offset1 {
|
||||
margin-left: 8.333%;
|
||||
margin-left: 8.333%;
|
||||
}
|
||||
|
||||
.$(size)Offset2 {
|
||||
margin-left: 16.667%;
|
||||
margin-left: 16.667%;
|
||||
}
|
||||
|
||||
.$(size)Offset3 {
|
||||
margin-left: 25%;
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.$(size)Offset4 {
|
||||
margin-left: 33.333%;
|
||||
margin-left: 33.333%;
|
||||
}
|
||||
|
||||
.$(size)Offset5 {
|
||||
margin-left: 41.667%;
|
||||
margin-left: 41.667%;
|
||||
}
|
||||
|
||||
.$(size)Offset6 {
|
||||
margin-left: 50%;
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.$(size)Offset7 {
|
||||
margin-left: 58.333%;
|
||||
margin-left: 58.333%;
|
||||
}
|
||||
|
||||
.$(size)Offset8 {
|
||||
margin-left: 66.667%;
|
||||
margin-left: 66.667%;
|
||||
}
|
||||
|
||||
.$(size)Offset9 {
|
||||
margin-left: 75%;
|
||||
margin-left: 75%;
|
||||
}
|
||||
|
||||
.$(size)Offset10 {
|
||||
margin-left: 83.333%;
|
||||
margin-left: 83.333%;
|
||||
}
|
||||
|
||||
.$(size)Offset11 {
|
||||
margin-left: 91.667%;
|
||||
margin-left: 91.667%;
|
||||
}
|
||||
}
|
||||
|
||||
@define-mixin autoWidth $size {
|
||||
.$(size) {
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
-ms-flex-preferred-size: 0;
|
||||
flex-basis: 0;
|
||||
max-width: 100%;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
-ms-flex-preferred-size: 0;
|
||||
flex-basis: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,16 +172,16 @@
|
|||
|
||||
@define-mixin row $size {
|
||||
.start$(size) {
|
||||
justify-content: flex-start;
|
||||
text-align: start;
|
||||
justify-content: flex-start;
|
||||
text-align: start;
|
||||
}
|
||||
.center$(size) {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
.end$(size) {
|
||||
justify-content: flex-end;
|
||||
text-align: end;
|
||||
justify-content: flex-end;
|
||||
text-align: end;
|
||||
}
|
||||
.top$(size) {
|
||||
align-items: flex-start;
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
|
||||
const hairlineStyle = {
|
||||
width: '100%',
|
||||
height: '2px',
|
||||
backgroundColor: '#d5d4d6',
|
||||
margin: '20px 0px',
|
||||
}
|
||||
|
||||
const Hairline = () => (
|
||||
<div style={hairlineStyle} />
|
||||
)
|
||||
|
||||
export default Hairline
|
|
@ -0,0 +1,14 @@
|
|||
// @flow
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import * as React from 'react'
|
||||
import { host } from 'storybook-host'
|
||||
import Component from './index'
|
||||
|
||||
storiesOf('Components', module)
|
||||
.addDecorator(host({
|
||||
title: 'Hairline',
|
||||
align: 'center',
|
||||
height: 5,
|
||||
width: '100%',
|
||||
}))
|
||||
.add('Hairline', () => <Component />)
|
|
@ -6,7 +6,7 @@ import styles from './index.scss'
|
|||
const cx = classNames.bind(styles)
|
||||
|
||||
type Props = {
|
||||
center?: boolean,
|
||||
align?: 'right' | 'center' | 'left',
|
||||
noMargin?: boolean,
|
||||
bold?: boolean,
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl',
|
||||
|
@ -17,11 +17,11 @@ type Props = {
|
|||
class Paragraph extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
bold, children, color, center, size, noMargin, ...props
|
||||
bold, children, color, align, size, noMargin, ...props
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<p className={cx(styles.paragraph, { bold }, { noMargin }, size, { center })} {...props}>
|
||||
<p className={cx(styles.paragraph, { bold }, { noMargin }, size, align)} {...props}>
|
||||
{ children }
|
||||
</p>
|
||||
)
|
||||
|
|
|
@ -24,7 +24,15 @@
|
|||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.sm {
|
||||
|
|
|
@ -35,10 +35,10 @@ export default ({ address, tx }: Props) => ({ submitting }: FormProps) => {
|
|||
<Block>
|
||||
{ !txFinished &&
|
||||
<React.Fragment>
|
||||
<Paragraph center size="lg">
|
||||
<Paragraph align="center" size="lg">
|
||||
You are about to create a Safe for keeping your funds more secure.
|
||||
</Paragraph>
|
||||
<Paragraph center size="lg">
|
||||
<Paragraph align="center" size="lg">
|
||||
Remember to check you have enough funds in your wallet.
|
||||
</Paragraph>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -5,6 +5,8 @@ import Stepper from '~/components/Stepper'
|
|||
import Confirmation from '~/routes/open/components/FormConfirmation'
|
||||
import Review from '~/routes/open/components/ReviewInformation'
|
||||
import SafeFields, { safeFieldsValidation } from '~/routes/open/components/SafeForm'
|
||||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||
import Link from '~/components/layout/Link'
|
||||
|
||||
const getSteps = () => [
|
||||
'Fill Safe Form', 'Review Information', 'Deploy it',
|
||||
|
@ -28,14 +30,16 @@ const Layout = ({
|
|||
}: Props) => {
|
||||
const steps = getSteps()
|
||||
const initialValues = initialValuesFrom(userAccount)
|
||||
const finishedButton = <Stepper.FinishButton title="VISIT SAFES" component={Link} to={SAFELIST_ADDRESS} />
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{ provider
|
||||
? (
|
||||
<Stepper
|
||||
onSubmit={onCallSafeContractSubmit}
|
||||
finishedButton={finishedButton}
|
||||
finishedTransaction={!!safeAddress}
|
||||
onSubmit={onCallSafeContractSubmit}
|
||||
steps={steps}
|
||||
initialValues={initialValues}
|
||||
>
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
// @flow
|
||||
import TestUtils from 'react-dom/test-utils'
|
||||
import { store } from '~/store'
|
||||
import { FIELD_NAME, FIELD_OWNERS, FIELD_CONFIRMATIONS, getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
||||
import {
|
||||
FIELD_NAME,
|
||||
FIELD_OWNERS,
|
||||
FIELD_CONFIRMATIONS,
|
||||
FIELD_DAILY_LIMIT,
|
||||
getOwnerNameBy,
|
||||
getOwnerAddressBy,
|
||||
} from '~/routes/open/components/fields'
|
||||
import { DEPLOYED_COMPONENT_ID } from '~/routes/open/components/FormConfirmation'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { getProviderInfo } from '~/wallets/getWeb3'
|
||||
|
@ -26,6 +33,9 @@ describe('React DOM TESTS > Create Safe form', () => {
|
|||
const fieldConfirmations = inputs[2]
|
||||
expect(fieldConfirmations.name).toEqual(FIELD_CONFIRMATIONS)
|
||||
|
||||
const dailyLimitConfirmations = inputs[3]
|
||||
expect(dailyLimitConfirmations.name).toEqual(FIELD_DAILY_LIMIT)
|
||||
|
||||
TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } })
|
||||
const inputsExpanded = TestUtils.scryRenderedDOMComponentsWithTag(open, 'input')
|
||||
|
||||
|
@ -39,6 +49,7 @@ describe('React DOM TESTS > Create Safe form', () => {
|
|||
TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } })
|
||||
TestUtils.Simulate.change(fieldConfirmations, { target: { value: '1' } })
|
||||
TestUtils.Simulate.change(ownerName, { target: { value: 'Adolfo Eth Account' } })
|
||||
TestUtils.Simulate.change(dailyLimitConfirmations, { target: { value: '10' } })
|
||||
|
||||
const form = TestUtils.findRenderedDOMComponentWithTag(open, 'form')
|
||||
// One submit per step when creating a safe
|
||||
|
@ -48,16 +59,16 @@ describe('React DOM TESTS > Create Safe form', () => {
|
|||
|
||||
// giving some time to the component for updating its state with safe
|
||||
// before destroying its context
|
||||
await sleep(1500)
|
||||
await sleep(6000)
|
||||
|
||||
// THEN
|
||||
const deployed = TestUtils.findRenderedDOMComponentWithClass(open, DEPLOYED_COMPONENT_ID)
|
||||
|
||||
if (deployed) {
|
||||
const transactionHash = JSON.parse(deployed.getElementsByTagName('pre')[0].innerHTML)
|
||||
delete transactionHash.logsBloom
|
||||
const transaction = JSON.parse(deployed.getElementsByTagName('pre')[0].innerHTML)
|
||||
delete transaction.receipt.logsBloom
|
||||
// eslint-disable-next-line
|
||||
console.log(transactionHash)
|
||||
// console.log(transaction)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ import Col from '~/components/layout/Col'
|
|||
import Heading from '~/components/layout/Heading'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import { FIELD_NAME, FIELD_CONFIRMATIONS, FIELD_DAILY_LIMIT } from '../fields'
|
||||
|
||||
type FormProps = {
|
||||
values: Object,
|
||||
|
@ -20,10 +21,13 @@ const ReviewInformation = () => ({ values }: FormProps) => {
|
|||
<Block>
|
||||
<Heading tag="h2">Review the Safe information</Heading>
|
||||
<Paragraph>
|
||||
<Bold>Safe Name: </Bold> {values.name}
|
||||
<Bold>Safe Name: </Bold> {values[FIELD_NAME]}
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Bold>Required confirmations: </Bold> {values.confirmations}
|
||||
<Bold>Required confirmations: </Bold> {values[FIELD_CONFIRMATIONS]}
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Bold>Daily limit: </Bold> {values[FIELD_DAILY_LIMIT]} ETH
|
||||
</Paragraph>
|
||||
<Heading tag="h3">Owners</Heading>
|
||||
{ names.map((name, index) => (
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as React from 'react'
|
||||
import { Field } from 'react-final-form'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, minValue, mustBeNumber, required } from '~/components/forms/validator'
|
||||
import { composeValidators, minValue, mustBeInteger, required } from '~/components/forms/validator'
|
||||
import Block from '~/components/layout/Block'
|
||||
import { FIELD_CONFIRMATIONS } from '~/routes/open/components/fields'
|
||||
|
||||
|
@ -14,7 +14,7 @@ const Confirmations = () => (
|
|||
type="text"
|
||||
validate={composeValidators(
|
||||
required,
|
||||
mustBeNumber,
|
||||
mustBeInteger,
|
||||
minValue(1),
|
||||
)}
|
||||
placeholder="Required confirmations*"
|
||||
|
|
|
@ -49,7 +49,7 @@ describe('React DOM TESTS > Create Safe form', () => {
|
|||
|
||||
// THEN
|
||||
const muiFields = TestUtils.scryRenderedComponentsWithType(open, TextField)
|
||||
expect(5).toEqual(muiFields.length)
|
||||
expect(6).toEqual(muiFields.length)
|
||||
const confirmationsField = muiFields[4]
|
||||
|
||||
expect(confirmationsField.props.meta.valid).toBe(false)
|
||||
|
@ -64,7 +64,7 @@ describe('React DOM TESTS > Create Safe form', () => {
|
|||
|
||||
// THEN
|
||||
const muiFields = TestUtils.scryRenderedComponentsWithType(open, TextField)
|
||||
expect(7).toEqual(muiFields.length)
|
||||
expect(8).toEqual(muiFields.length)
|
||||
const confirmationsField = muiFields[6]
|
||||
|
||||
expect(confirmationsField.props.meta.valid).toBe(false)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, mustBeFloat, required, minValue } from '~/components/forms/validator'
|
||||
import Block from '~/components/layout/Block'
|
||||
import { FIELD_DAILY_LIMIT } from '~/routes/open/components/fields'
|
||||
|
||||
const DailyLimit = () => (
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={FIELD_DAILY_LIMIT}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeFloat, minValue(0))}
|
||||
placeholder="Daily Limit*"
|
||||
text="Daily Limit"
|
||||
/>
|
||||
</Block>
|
||||
)
|
||||
|
||||
export default DailyLimit
|
|
@ -6,7 +6,7 @@ import {
|
|||
composeValidators,
|
||||
minValue,
|
||||
maxValue,
|
||||
mustBeNumber,
|
||||
mustBeInteger,
|
||||
mustBeEthereumAddress,
|
||||
required,
|
||||
uniqueAddress,
|
||||
|
@ -45,7 +45,7 @@ const Owners = (props: Props) => {
|
|||
name={FIELD_OWNERS}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeNumber, maxValue(MAX_NUMBER_OWNERS), minValue(1))}
|
||||
validate={composeValidators(required, mustBeInteger, maxValue(MAX_NUMBER_OWNERS), minValue(1))}
|
||||
placeholder="Number of owners*"
|
||||
text="Number of owners"
|
||||
/>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
|||
import Name from './Name'
|
||||
import Owners from './Owners'
|
||||
import Confirmations from './Confirmations'
|
||||
import DailyLimit from './DailyLimit'
|
||||
|
||||
export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners'
|
||||
|
||||
|
@ -25,5 +26,6 @@ export default () => ({ values }: Object) => (
|
|||
<Name />
|
||||
<Owners numOwners={values.owners} otherAccounts={getAccountsFrom(values)} />
|
||||
<Confirmations />
|
||||
<DailyLimit />
|
||||
</Block>
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
export const FIELD_NAME: string = 'name'
|
||||
export const FIELD_CONFIRMATIONS: string = 'confirmations'
|
||||
export const FIELD_OWNERS: string = 'owners'
|
||||
export const FIELD_DAILY_LIMIT: string = 'limit'
|
||||
|
||||
export const getOwnerNameBy = (index: number) => `owner${index}Name`
|
||||
export const getOwnerAddressBy = (index: number) => `owner${index}Address`
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import contract from 'truffle-contract'
|
||||
|
||||
import Page from '~/components/layout/Page'
|
||||
import { getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||
import { getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom, getDailyLimitFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||
import { getWeb3 } from '~/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import Safe from '#/GnosisSafe.json'
|
||||
import { getGnosisSafeContract, deploySafeContract, initContracts } from '~/wallets/safeContracts'
|
||||
import { checkReceiptStatus } from '~/wallets/ethTransactions'
|
||||
import selector from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
import actions, { type Actions, type AddSafe } from './actions'
|
||||
import Layout from '../components/Layout'
|
||||
|
||||
type Props = Actions & {
|
||||
|
@ -21,18 +21,26 @@ type State = {
|
|||
safeTx: string,
|
||||
}
|
||||
|
||||
const createSafe = async (safeContract, values, userAccount, addSafe) => {
|
||||
const createSafe = async (values: Object, userAccount: string, addSafe: AddSafe): Promise<State> => {
|
||||
const accounts = getAccountsFrom(values)
|
||||
const numConfirmations = getThresholdFrom(values)
|
||||
const name = getSafeNameFrom(values)
|
||||
const owners = getNamesFrom(values)
|
||||
const dailyLimit = getDailyLimitFrom(values)
|
||||
|
||||
const web3 = getWeb3()
|
||||
safeContract.setProvider(web3.currentProvider)
|
||||
const GnosisSafe = getGnosisSafeContract(web3)
|
||||
|
||||
const safe = await safeContract.new(accounts, numConfirmations, 0, 0, { from: userAccount, gas: '5000000' })
|
||||
addSafe(name, safe.address, numConfirmations, owners, accounts)
|
||||
return safe
|
||||
await initContracts()
|
||||
const safe = await deploySafeContract(accounts, numConfirmations, dailyLimit, userAccount)
|
||||
checkReceiptStatus(safe.tx)
|
||||
|
||||
const param = safe.logs[1].args.proxy
|
||||
const safeContract = GnosisSafe.at(param)
|
||||
|
||||
addSafe(name, safeContract.address, numConfirmations, dailyLimit, owners, accounts)
|
||||
|
||||
return { safeAddress: safeContract.address, safeTx: safe }
|
||||
}
|
||||
|
||||
class Open extends React.Component<Props, State> {
|
||||
|
@ -43,29 +51,19 @@ class Open extends React.Component<Props, State> {
|
|||
safeAddress: '',
|
||||
safeTx: '',
|
||||
}
|
||||
|
||||
this.safe = contract(Safe)
|
||||
}
|
||||
|
||||
onCallSafeContractSubmit = async (values) => {
|
||||
try {
|
||||
const { userAccount, addSafe } = this.props
|
||||
const web3 = getWeb3()
|
||||
|
||||
const safeInstance = await createSafe(this.safe, values, userAccount, addSafe)
|
||||
const { address, transactionHash } = safeInstance
|
||||
|
||||
const transactionReceipt = await promisify(cb => web3.eth.getTransactionReceipt(transactionHash, cb))
|
||||
|
||||
this.setState({ safeAddress: address, safeTx: transactionReceipt })
|
||||
const safeInstance = await createSafe(values, userAccount, addSafe)
|
||||
this.setState(safeInstance)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error while creating the Safe' + error)
|
||||
}
|
||||
}
|
||||
|
||||
safe: any
|
||||
|
||||
render() {
|
||||
const { safeAddress, safeTx } = this.state
|
||||
const { provider, userAccount } = this.props
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// @flow
|
||||
import addSafe from '~/routes/safe/store/actions/addSafe'
|
||||
|
||||
export type AddSafe = typeof addSafe
|
||||
|
||||
export type Actions = {
|
||||
addSafe: typeof addSafe,
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
// @flow
|
||||
|
||||
/*
|
||||
onAddFunds = async (values: Object) => {
|
||||
const { fundsToAdd } = values
|
||||
const { safeAddress } = this.state
|
||||
try {
|
||||
const web3 = getWeb3()
|
||||
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
const txData = { from: accounts[0], to: safeAddress, value: web3.toWei(fundsToAdd, 'ether') }
|
||||
await promisify(cb => web3.eth.sendTransaction(txData, cb))
|
||||
const funds = await promisify(cb => web3.eth.getBalance(safeAddress, cb))
|
||||
const fundsInEther = funds ? web3.fromWei(funds.toNumber(), 'ether') : 0
|
||||
this.setState({ funds: fundsInEther })
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.log(`Errog adding funds to safe${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<GnoForm onSubmit={onAddFunds} width="500">
|
||||
{(pristine, invalid) => (
|
||||
<Block margin="md">
|
||||
<Heading tag="h2" margin="lg">Add Funds to the safe</Heading>
|
||||
<div style={{ margin: '10px 0px' }}>
|
||||
<label style={{ marginRight: '10px' }}>{safeAddress || 'Not safe detected'}</label>
|
||||
</div>
|
||||
{ safeAddress &&
|
||||
<div>
|
||||
<Field name="fundsToAdd" component={TextField} type="text" placeholder="ETH to add" />
|
||||
<Button type="submit" disabled={!safeAddress || pristine || invalid}>
|
||||
Add funds
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
{ safeAddress &&
|
||||
<div style={{ margin: '15px 0px' }}>
|
||||
Total funds in this safe: { funds || 0 } ETH
|
||||
</div>
|
||||
}
|
||||
</Block>
|
||||
)}
|
||||
</GnoForm>
|
||||
*/
|
|
@ -1,4 +1,6 @@
|
|||
// @flow
|
||||
export const getDailyLimitFrom = (values: Object): number => Number(values.limit)
|
||||
|
||||
export const getAccountsFrom = (values: Object): string[] => {
|
||||
const accounts = Object.keys(values).sort().filter(key => /^owner\d+Address$/.test(key))
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// @flow
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import * as React from 'react'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import styles from '~/components/layout/PageFrame/index.scss'
|
||||
import MultisigForm from './index'
|
||||
|
||||
|
||||
const FrameDecorator = story => (
|
||||
<div className={styles.frame} style={{ textAlign: 'center' }}>
|
||||
{ story() }
|
||||
</div>
|
||||
)
|
||||
|
||||
storiesOf('Components', module)
|
||||
.addDecorator(FrameDecorator)
|
||||
.add('MultisigForm', () => (
|
||||
<Stepper
|
||||
finishedTransaction={false}
|
||||
finishedButton={<Stepper.FinishButton title="SEE TXS" />}
|
||||
onSubmit={() => {}}
|
||||
steps={['Multisig TX Form', 'Review TX']}
|
||||
onReset={() => {}}
|
||||
>
|
||||
<Stepper.Page balance={10}>
|
||||
{ MultisigForm }
|
||||
</Stepper.Page>
|
||||
</Stepper>
|
||||
))
|
|
@ -0,0 +1,67 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, inLimit, mustBeFloat, required, greaterThan, mustBeEthereumAddress } from '~/components/forms/validator'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import { TX_NAME_PARAM, TX_DESTINATION_PARAM, TX_VALUE_PARAM } from '~/routes/safe/component/AddTransaction/createTransactions'
|
||||
|
||||
export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners'
|
||||
|
||||
export const safeFieldsValidation = (values: Object) => {
|
||||
const errors = {}
|
||||
|
||||
if (Number.parseInt(values.owners, 10) < Number.parseInt(values.confirmations, 10)) {
|
||||
errors.confirmations = CONFIRMATIONS_ERROR
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
type Props = {
|
||||
balance: number,
|
||||
}
|
||||
|
||||
const WithdrawnForm = ({ balance }: Props) => () => (
|
||||
<Block margin="md">
|
||||
<Heading tag="h2" margin="lg">
|
||||
Multisig Transaction
|
||||
</Heading>
|
||||
<Heading tag="h4" margin="lg">
|
||||
{`Available balance: ${balance} ETH`}
|
||||
</Heading>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={TX_NAME_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={required}
|
||||
placeholder="Transaction name"
|
||||
text="Transaction name"
|
||||
/>
|
||||
</Block>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={TX_DESTINATION_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeEthereumAddress)}
|
||||
placeholder="Destination*"
|
||||
text="Destination"
|
||||
/>
|
||||
</Block>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={TX_VALUE_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeFloat, greaterThan(0), inLimit(balance, 0, 'available balance'))}
|
||||
placeholder="Amount in ETH*"
|
||||
text="Amount in ETH"
|
||||
/>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
||||
export default WithdrawnForm
|
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { CircularProgress } from 'material-ui/Progress'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import { TX_NAME_PARAM, TX_DESTINATION_PARAM, TX_VALUE_PARAM } from '~/routes/safe/component/AddTransaction/createTransactions'
|
||||
|
||||
type FormProps = {
|
||||
values: Object,
|
||||
submitting: boolean,
|
||||
}
|
||||
|
||||
const spinnerStyle = {
|
||||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const ReviewTx = () => ({ values, submitting }: FormProps) => (
|
||||
<Block>
|
||||
<Heading tag="h2">Review the Multisig Tx</Heading>
|
||||
<Paragraph align="left">
|
||||
<Bold>Transaction Name: </Bold> {values[TX_NAME_PARAM]}
|
||||
</Paragraph>
|
||||
<Paragraph align="left">
|
||||
<Bold>Destination: </Bold> {values[TX_DESTINATION_PARAM]}
|
||||
</Paragraph>
|
||||
<Paragraph align="left">
|
||||
<Bold>Amount to transfer in ETH: </Bold> {values[TX_VALUE_PARAM]}
|
||||
</Paragraph>
|
||||
<Block style={spinnerStyle}>
|
||||
{ submitting && <CircularProgress size={50} /> }
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
||||
export default ReviewTx
|
|
@ -0,0 +1,10 @@
|
|||
// @flow
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
|
||||
export type Actions = {
|
||||
fetchTransactions: typeof fetchTransactions,
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchTransactions,
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { load, TX_KEY } from '~/utils/localStorage'
|
||||
import { type Confirmation, makeConfirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { makeTransaction, type Transaction, type TransactionProps } from '~/routes/safe/store/model/transaction'
|
||||
import { getGnosisSafeContract } from '~/wallets/safeContracts'
|
||||
import { getWeb3 } from '~/wallets/getWeb3'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { sameAddress } from '~/wallets/ethAddresses'
|
||||
import executeTransaction, { checkReceiptStatus } from '~/wallets/ethTransactions'
|
||||
|
||||
export const TX_NAME_PARAM = 'txName'
|
||||
export const TX_DESTINATION_PARAM = 'txDestination'
|
||||
export const TX_VALUE_PARAM = 'txValue'
|
||||
|
||||
export const EXECUTED_CONFIRMATION_HASH = 'EXECUTED'
|
||||
|
||||
// Exported for testing it, should not use it. Use #transactions fnc.
|
||||
export const buildConfirmationsFrom =
|
||||
(owners: List<Owner>, creator: string, confirmationHash: string): List<Confirmation> => {
|
||||
if (!owners) {
|
||||
throw new Error('This safe has no owners')
|
||||
}
|
||||
|
||||
if (!owners.find((owner: Owner) => sameAddress(owner.get('address'), creator))) {
|
||||
throw new Error('The creator of the tx is not an owner')
|
||||
}
|
||||
|
||||
return owners.map((owner: Owner) => makeConfirmation({
|
||||
owner,
|
||||
status: sameAddress(owner.get('address'), creator),
|
||||
hash: sameAddress(owner.get('address'), creator) ? confirmationHash : undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
export const buildExecutedConfirmationFrom = (owners: List<Owner>, creator: string): List<Confirmation> =>
|
||||
buildConfirmationsFrom(owners, creator, EXECUTED_CONFIRMATION_HASH)
|
||||
|
||||
export const storeTransaction = (
|
||||
name: string,
|
||||
nonce: number,
|
||||
destination: string,
|
||||
value: number,
|
||||
creator: string,
|
||||
confirmations: List<Confirmation>,
|
||||
tx: string,
|
||||
safeAddress: string,
|
||||
safeThreshold: number,
|
||||
) => {
|
||||
const notMinedWhenOneOwnerSafe = confirmations.count() === 1 && !tx
|
||||
if (notMinedWhenOneOwnerSafe) {
|
||||
throw new Error('The tx should be mined before storing it in safes with one owner')
|
||||
}
|
||||
|
||||
const transaction: Transaction = makeTransaction({
|
||||
name, nonce, value, confirmations, destination, threshold: safeThreshold, tx,
|
||||
})
|
||||
|
||||
const safeTransactions = load(TX_KEY) || {}
|
||||
const transactions = safeTransactions[safeAddress]
|
||||
const txsRecord = transactions ? List(transactions) : List([])
|
||||
|
||||
if (txsRecord.find((txs: TransactionProps) => txs.nonce === nonce)) {
|
||||
throw new Error(`Transaction with same nonce: ${nonce} already created for safe: ${safeAddress}`)
|
||||
}
|
||||
|
||||
safeTransactions[safeAddress] = txsRecord.push(transaction)
|
||||
|
||||
localStorage.setItem(TX_KEY, JSON.stringify(safeTransactions))
|
||||
}
|
||||
|
||||
const hasOneOwner = (safe: Safe) => {
|
||||
const owners = safe.get('owners')
|
||||
if (!owners) {
|
||||
throw new Error('Received a Safe without owners when creating a tx')
|
||||
}
|
||||
|
||||
return owners.count() === 1
|
||||
}
|
||||
|
||||
export const createTransaction = async (
|
||||
safe: Safe,
|
||||
txName: string,
|
||||
txDestination: string,
|
||||
txValue: number,
|
||||
nonce: number,
|
||||
user: string,
|
||||
) => {
|
||||
const web3 = getWeb3()
|
||||
const GnosisSafe = await getGnosisSafeContract(web3)
|
||||
const safeAddress = safe.get('address')
|
||||
const gnosisSafe = GnosisSafe.at(safeAddress)
|
||||
|
||||
const valueInWei = web3.toWei(txValue, 'ether')
|
||||
const CALL = 0
|
||||
|
||||
const thresholdIsOne = safe.get('confirmations') === 1
|
||||
if (hasOneOwner(safe) || thresholdIsOne) {
|
||||
const txConfirmationData = gnosisSafe.contract.execTransactionIfApproved.getData(txDestination, valueInWei, '0x', CALL, nonce)
|
||||
const txHash = await executeTransaction(txConfirmationData, user, safeAddress)
|
||||
checkReceiptStatus(txHash)
|
||||
|
||||
const executedConfirmations: List<Confirmation> = buildExecutedConfirmationFrom(safe.get('owners'), user)
|
||||
return storeTransaction(txName, nonce, txDestination, txValue, user, executedConfirmations, txHash, safeAddress, safe.get('confirmations'))
|
||||
}
|
||||
|
||||
const txConfirmationData = gnosisSafe.contract.approveTransactionWithParameters.getData(txDestination, valueInWei, '0x', CALL, nonce)
|
||||
const txConfirmationHash = await executeTransaction(txConfirmationData, user, safeAddress)
|
||||
checkReceiptStatus(txConfirmationHash)
|
||||
|
||||
const confirmations: List<Confirmation> = buildConfirmationsFrom(safe.get('owners'), user, txConfirmationHash)
|
||||
|
||||
return storeTransaction(txName, nonce, txDestination, txValue, user, confirmations, '', safeAddress, safe.get('confirmations'))
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import actions, { type Actions } from './actions'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import { createTransaction, TX_NAME_PARAM, TX_DESTINATION_PARAM, TX_VALUE_PARAM } from './createTransactions'
|
||||
import MultisigForm from './MultisigForm'
|
||||
import ReviewTx from './ReviewTx'
|
||||
|
||||
const getSteps = () => [
|
||||
'Fill Mutlisig Tx form', 'Review Tx',
|
||||
]
|
||||
|
||||
type Props = SelectorProps & Actions & {
|
||||
safe: Safe,
|
||||
balance: number,
|
||||
onReset: () => void,
|
||||
}
|
||||
|
||||
type State = {
|
||||
done: boolean,
|
||||
}
|
||||
|
||||
export const SEE_TXS_BUTTON_TEXT = 'VISIT TXS'
|
||||
|
||||
class AddTransaction extends React.Component<Props, State> {
|
||||
state = {
|
||||
done: false,
|
||||
}
|
||||
|
||||
onTransaction = async (values: Object) => {
|
||||
try {
|
||||
const { safe, userAddress } = this.props
|
||||
const nonce = Date.now()
|
||||
const destination = values[TX_DESTINATION_PARAM]
|
||||
const value = values[TX_VALUE_PARAM]
|
||||
const name = values[TX_NAME_PARAM]
|
||||
await createTransaction(safe, name, destination, value, nonce, userAddress)
|
||||
await sleep(1500)
|
||||
this.props.fetchTransactions()
|
||||
this.setState({ done: true })
|
||||
} catch (error) {
|
||||
this.setState({ done: false })
|
||||
// eslint-disable-next-line
|
||||
console.log('Error while creating multisig tx ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
this.setState({ done: false })
|
||||
this.props.onReset() // This is for show the TX list component
|
||||
}
|
||||
|
||||
render() {
|
||||
const { done } = this.state
|
||||
const { balance } = this.props
|
||||
const steps = getSteps()
|
||||
const finishedButton = <Stepper.FinishButton title={SEE_TXS_BUTTON_TEXT} />
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Stepper
|
||||
finishedTransaction={done}
|
||||
finishedButton={finishedButton}
|
||||
onSubmit={this.onTransaction}
|
||||
steps={steps}
|
||||
onReset={this.onReset}
|
||||
>
|
||||
<Stepper.Page balance={balance}>
|
||||
{ MultisigForm }
|
||||
</Stepper.Page>
|
||||
<Stepper.Page>
|
||||
{ ReviewTx }
|
||||
</Stepper.Page>
|
||||
</Stepper>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(AddTransaction)
|
|
@ -0,0 +1,11 @@
|
|||
// @flow
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { userAccountSelector } from '~/wallets/store/selectors/index'
|
||||
|
||||
export type SelectorProps = {
|
||||
userAddress: userAccountSelector,
|
||||
}
|
||||
|
||||
export default createStructuredSelector({
|
||||
userAddress: userAccountSelector,
|
||||
})
|
|
@ -0,0 +1,225 @@
|
|||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { storeTransaction, buildConfirmationsFrom, EXECUTED_CONFIRMATION_HASH, buildExecutedConfirmationFrom } from '~/routes/safe/component/AddTransaction/createTransactions'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { loadSafeTransactions } from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { testSizeOfSafesWith, testSizeOfTransactions, testTransactionFrom } from './transactionsHelper'
|
||||
|
||||
describe('Transactions Suite', () => {
|
||||
let safe: Safe
|
||||
let destination: string
|
||||
let value: number
|
||||
let owners: List<Owner>
|
||||
beforeEach(async () => {
|
||||
localStorage.clear()
|
||||
|
||||
safe = SafeFactory.twoOwnersSafe('foo', 'bar')
|
||||
destination = 'baz'
|
||||
value = 2
|
||||
owners = safe.get('owners')
|
||||
|
||||
const firstOwner = owners.get(0)
|
||||
if (!firstOwner) { throw new Error() }
|
||||
const secondOwner = owners.get(1)
|
||||
if (!secondOwner) { throw new Error() }
|
||||
})
|
||||
|
||||
it('adds first confirmation to stored safe', async () => {
|
||||
// GIVEN
|
||||
const txName = 'Buy butteries for project'
|
||||
const nonce: number = 10
|
||||
const confirmations: List<Confirmation> = buildConfirmationsFrom(owners, 'foo', 'confirmationHash')
|
||||
storeTransaction(txName, nonce, destination, value, 'foo', confirmations, '', safe.get('address'), safe.get('confirmations'))
|
||||
|
||||
// WHEN
|
||||
const transactions: Map<string, List<Transaction>> = loadSafeTransactions()
|
||||
|
||||
// THEN
|
||||
testSizeOfSafesWith(transactions, 1)
|
||||
|
||||
const safeTransactions: List<Transaction> | typeof undefined = transactions.get(safe.get('address'))
|
||||
if (!safeTransactions) { throw new Error() }
|
||||
testSizeOfTransactions(safeTransactions, 1)
|
||||
|
||||
testTransactionFrom(safeTransactions, 0, txName, nonce, value, 2, destination, 'foo', 'confirmationHash', owners.get(0), owners.get(1))
|
||||
})
|
||||
|
||||
it('adds second confirmation to stored safe with one confirmation', async () => {
|
||||
// GIVEN
|
||||
const firstTxName = 'Buy butteries for project'
|
||||
const firstNonce: number = Date.now()
|
||||
const safeAddress = safe.get('address')
|
||||
const creator = 'foo'
|
||||
const confirmations: List<Confirmation> = buildConfirmationsFrom(owners, creator, 'confirmationHash')
|
||||
storeTransaction(firstTxName, firstNonce, destination, value, creator, confirmations, '', safeAddress, safe.get('confirmations'))
|
||||
|
||||
const secondTxName = 'Buy printers for project'
|
||||
const secondNonce: number = firstNonce + 100
|
||||
const secondConfirmations: List<Confirmation> = buildConfirmationsFrom(owners, creator, 'confirmationHash')
|
||||
storeTransaction(secondTxName, secondNonce, destination, value, creator, secondConfirmations, '', safeAddress, safe.get('confirmations'))
|
||||
|
||||
// WHEN
|
||||
const transactions: Map<string, List<Transaction>> = loadSafeTransactions()
|
||||
|
||||
// THEN
|
||||
testSizeOfSafesWith(transactions, 1)
|
||||
|
||||
const safeTxs: List<Transaction> | typeof undefined = transactions.get(safeAddress)
|
||||
if (!safeTxs) { throw new Error() }
|
||||
testSizeOfTransactions(safeTxs, 2)
|
||||
|
||||
testTransactionFrom(safeTxs, 0, firstTxName, firstNonce, value, 2, destination, 'foo', 'confirmationHash', owners.get(0), owners.get(1))
|
||||
testTransactionFrom(safeTxs, 1, secondTxName, secondNonce, value, 2, destination, 'foo', 'confirmationHash', owners.get(0), owners.get(1))
|
||||
})
|
||||
|
||||
it('adds second confirmation to stored safe having two safes with one confirmation each', async () => {
|
||||
const txName = 'Buy batteris for Alplha project'
|
||||
const nonce = 10
|
||||
const safeAddress = safe.address
|
||||
const creator = 'foo'
|
||||
const confirmations: List<Confirmation> = buildConfirmationsFrom(owners, creator, 'confirmationHash')
|
||||
storeTransaction(txName, nonce, destination, value, creator, confirmations, '', safeAddress, safe.get('confirmations'))
|
||||
|
||||
const secondSafe = SafeFactory.dailyLimitSafe(10, 2)
|
||||
const txSecondName = 'Buy batteris for Beta project'
|
||||
const txSecondNonce = 10
|
||||
const secondSafeAddress = secondSafe.address
|
||||
const secondCreator = '0x03db1a8b26d08df23337e9276a36b474510f0023'
|
||||
const secondConfirmations: List<Confirmation> = buildConfirmationsFrom(secondSafe.get('owners'), secondCreator, 'confirmationHash')
|
||||
storeTransaction(
|
||||
txSecondName, txSecondNonce, destination, value, secondCreator,
|
||||
secondConfirmations, '', secondSafeAddress, secondSafe.get('confirmations'),
|
||||
)
|
||||
|
||||
let transactions: Map<string, List<Transaction>> = loadSafeTransactions()
|
||||
testSizeOfSafesWith(transactions, 2)
|
||||
|
||||
const firstSafeTxs: List<Transaction> | typeof undefined = transactions.get(safeAddress)
|
||||
if (!firstSafeTxs) { throw new Error() }
|
||||
testSizeOfTransactions(firstSafeTxs, 1)
|
||||
|
||||
const secondSafeTxs: List<Transaction> | typeof undefined = transactions.get(secondSafeAddress)
|
||||
if (!secondSafeTxs) { throw new Error() }
|
||||
testSizeOfTransactions(secondSafeTxs, 1)
|
||||
|
||||
// WHEN
|
||||
const txFirstName = 'Buy paper for Alplha project'
|
||||
const txFirstNonce = 11
|
||||
const txConfirmations: List<Confirmation> = buildConfirmationsFrom(owners, creator, 'secondConfirmationHash')
|
||||
storeTransaction(
|
||||
txFirstName, txFirstNonce, destination, value, creator,
|
||||
txConfirmations, '', safe.get('address'), safe.get('confirmations'),
|
||||
)
|
||||
|
||||
transactions = loadSafeTransactions()
|
||||
|
||||
// THEN
|
||||
testSizeOfSafesWith(transactions, 2)
|
||||
testSizeOfTransactions(transactions.get(safeAddress), 2)
|
||||
testSizeOfTransactions(transactions.get(secondSafeAddress), 1)
|
||||
|
||||
// Test 2 transactions of first safe
|
||||
testTransactionFrom(
|
||||
transactions.get(safe.address), 0,
|
||||
txName, nonce, value, 2, destination,
|
||||
'foo', 'confirmationHash', owners.get(0), owners.get(1),
|
||||
)
|
||||
testTransactionFrom(
|
||||
transactions.get(safe.address), 1,
|
||||
txFirstName, txFirstNonce, value, 2, destination,
|
||||
'foo', 'secondConfirmationHash', owners.get(0), owners.get(1),
|
||||
)
|
||||
|
||||
// Test one transaction of second safe
|
||||
testTransactionFrom(
|
||||
transactions.get(secondSafe.address), 0,
|
||||
txSecondName, txSecondNonce, value, 2, destination,
|
||||
'0x03db1a8b26d08df23337e9276a36b474510f0023', 'confirmationHash', secondSafe.get('owners').get(0), secondSafe.get('owners').get(1),
|
||||
)
|
||||
})
|
||||
|
||||
it('does not allow to store same transaction twice', async () => {
|
||||
// GIVEN
|
||||
const txName = 'Buy butteries for project'
|
||||
const nonce: number = 10
|
||||
const creator = 'foo'
|
||||
const confirmations: List<Confirmation> = buildConfirmationsFrom(owners, creator, 'confirmationHash')
|
||||
storeTransaction(txName, nonce, destination, value, creator, confirmations, '', safe.get('address'), safe.get('confirmations'))
|
||||
|
||||
// WHEN
|
||||
const createTxFnc = () => storeTransaction(txName, nonce, destination, value, creator, confirmations, '', safe.get('address'), safe.get('confirmations'))
|
||||
expect(createTxFnc).toThrow(/Transaction with same nonce/)
|
||||
})
|
||||
|
||||
it('checks the owner who creates the tx has confirmed it', async () => {
|
||||
// GIVEN
|
||||
const txName = 'Buy butteries for project'
|
||||
const nonce: number = 10
|
||||
const creator = 'foo'
|
||||
const confirmations: List<Confirmation> = buildConfirmationsFrom(owners, creator, 'confirmationHash')
|
||||
storeTransaction(txName, nonce, destination, value, creator, confirmations, '', safe.get('address'), safe.get('confirmations'))
|
||||
|
||||
// WHEN
|
||||
const transactions: Map<string, List<Transaction>> = loadSafeTransactions()
|
||||
|
||||
// THEN
|
||||
testSizeOfSafesWith(transactions, 1)
|
||||
})
|
||||
|
||||
it('checks the owner who creates the tx is an owner', async () => {
|
||||
// GIVEN
|
||||
const ownerName = 'invented'
|
||||
const buildConfirmationsTxFnc = () => buildConfirmationsFrom(owners, ownerName, 'confirmationHash')
|
||||
|
||||
expect(buildConfirmationsTxFnc).toThrow(/The creator of the tx is not an owner/)
|
||||
})
|
||||
|
||||
it('checks if safe has one owner transaction has been executed', async () => {
|
||||
const ownerName = 'foo'
|
||||
const oneOwnerSafe = SafeFactory.oneOwnerSafe(ownerName)
|
||||
const txName = 'Buy butteries for project'
|
||||
const nonce: number = 10
|
||||
const tx = ''
|
||||
const confirmations: List<Confirmation> = buildExecutedConfirmationFrom(oneOwnerSafe.get('owners'), ownerName)
|
||||
const createTxFnc = () => storeTransaction(txName, nonce, destination, value, ownerName, confirmations, tx, oneOwnerSafe.get('address'), oneOwnerSafe.get('confirmations'))
|
||||
|
||||
expect(createTxFnc).toThrow(/The tx should be mined before storing it in safes with one owner/)
|
||||
})
|
||||
|
||||
it('checks if safe has one owner transaction the confirmation list is correctly build', async () => {
|
||||
const ownerName = 'foo'
|
||||
const oneOwnerSafe = SafeFactory.oneOwnerSafe(ownerName)
|
||||
const txName = 'Buy butteries for project'
|
||||
const nonce: number = 10
|
||||
const tx = 'validTxHash'
|
||||
const confirmations: List<Confirmation> = buildExecutedConfirmationFrom(oneOwnerSafe.get('owners'), ownerName)
|
||||
storeTransaction(txName, nonce, destination, value, ownerName, confirmations, tx, oneOwnerSafe.get('address'), oneOwnerSafe.get('confirmations'))
|
||||
|
||||
// WHEN
|
||||
const safeTransactions: Map<string, List<Transaction>> = loadSafeTransactions()
|
||||
|
||||
// THEN
|
||||
expect(safeTransactions.size).toBe(1)
|
||||
|
||||
const transactions: List<Transaction> | typeof undefined = safeTransactions.get(oneOwnerSafe.address)
|
||||
if (!transactions) throw new Error()
|
||||
expect(transactions.count()).toBe(1)
|
||||
|
||||
const batteriesTx: Transaction | typeof undefined = transactions.get(0)
|
||||
if (!batteriesTx) throw new Error()
|
||||
expect(batteriesTx.get('name')).toBe(txName)
|
||||
|
||||
const txConfirmations = batteriesTx.confirmations
|
||||
if (!txConfirmations) throw new Error()
|
||||
expect(txConfirmations.count()).toBe(1)
|
||||
|
||||
const firstConfirmation: Confirmation | typeof undefined = txConfirmations.get(0)
|
||||
if (!firstConfirmation) throw new Error()
|
||||
expect(firstConfirmation.get('hash')).toBe(EXECUTED_CONFIRMATION_HASH)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
|
||||
export const testSizeOfSafesWith = (transactions: Map<string, List<Transaction>>, size: number) => {
|
||||
expect(transactions).not.toBe(undefined)
|
||||
expect(transactions).not.toBe(null)
|
||||
expect(transactions.size).toBe(size)
|
||||
}
|
||||
|
||||
export const testSizeOfTransactions = (safeTxs: List<Transaction> | typeof undefined, size: number) => {
|
||||
if (!safeTxs) { throw new Error() }
|
||||
expect(safeTxs.count()).toBe(size)
|
||||
expect(safeTxs.get(0)).not.toBe(undefined)
|
||||
expect(safeTxs.get(0)).not.toBe(null)
|
||||
}
|
||||
|
||||
export const testTransactionFrom = (
|
||||
safeTxs: List<Transaction> | typeof undefined, pos: number, name: string,
|
||||
nonce: number, value: number, threshold: number, destination: string,
|
||||
creator: string, txHash: string,
|
||||
firstOwner: Owner | typeof undefined, secondOwner: Owner | typeof undefined,
|
||||
) => {
|
||||
if (!safeTxs) { throw new Error() }
|
||||
const tx: Transaction | typeof undefined = safeTxs.get(pos)
|
||||
|
||||
if (!tx) { throw new Error() }
|
||||
expect(tx.get('name')).toBe(name)
|
||||
expect(tx.get('value')).toBe(value)
|
||||
expect(tx.get('threshold')).toBe(threshold)
|
||||
expect(tx.get('destination')).toBe(destination)
|
||||
expect(tx.get('confirmations').count()).toBe(2)
|
||||
expect(tx.get('nonce')).toBe(nonce)
|
||||
|
||||
const confirmations: List<Confirmation> = tx.get('confirmations')
|
||||
const firstConfirmation: Confirmation | typeof undefined = confirmations.get(0)
|
||||
if (!firstConfirmation) { throw new Error() }
|
||||
expect(firstConfirmation.get('owner')).not.toBe(undefined)
|
||||
expect(firstConfirmation.get('owner')).toEqual(firstOwner)
|
||||
expect(firstConfirmation.get('status')).toBe(true)
|
||||
expect(firstConfirmation.get('hash')).toBe(txHash)
|
||||
|
||||
const secondConfirmation: Confirmation | typeof undefined = confirmations.get(1)
|
||||
if (!secondConfirmation) { throw new Error() }
|
||||
expect(secondConfirmation.get('owner')).not.toBe(undefined)
|
||||
expect(secondConfirmation.get('owner')).toEqual(secondOwner)
|
||||
expect(secondConfirmation.get('status')).toBe(false)
|
||||
}
|
|
@ -30,8 +30,20 @@ storiesOf('Routes /safe:address', module)
|
|||
fetchBalance={() => {}}
|
||||
/>
|
||||
))
|
||||
.add('Safe with 2 owners', () => {
|
||||
const safe = SafeFactory.twoOwnersSafe
|
||||
.add('Safe with 2 owners and 10ETH as dailyLimit', () => {
|
||||
const safe = SafeFactory.dailyLimitSafe(10, 1.345)
|
||||
|
||||
return (
|
||||
<Component
|
||||
safe={safe}
|
||||
provider="METAMASK"
|
||||
balance="2"
|
||||
fetchBalance={() => {}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
.add('Safe with dailyLimit reached', () => {
|
||||
const safe = SafeFactory.dailyLimitSafe(10, 10)
|
||||
|
||||
return (
|
||||
<Component
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Link from '~/components/layout/Link'
|
||||
import Col from '~/components/layout/Col'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Paragraph from '~/components/layout/Paragraph/index'
|
||||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||
|
||||
const NoRights = () => (
|
||||
<Row>
|
||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||
<Paragraph size="lg">
|
||||
<Bold>Impossible load Safe, check its address and ownership</Bold>
|
||||
</Paragraph>
|
||||
</Col>
|
||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||
<Button
|
||||
component={Link}
|
||||
to={SAFELIST_ADDRESS}
|
||||
variant="raised"
|
||||
color="primary"
|
||||
>
|
||||
Safe List
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
|
||||
export default NoRights
|
|
@ -0,0 +1,17 @@
|
|||
// @flow
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import * as React from 'react'
|
||||
import styles from '~/components/layout/PageFrame/index.scss'
|
||||
import Component from './index.jsx'
|
||||
|
||||
const FrameDecorator = story => (
|
||||
<div className={styles.frame}>
|
||||
{ story() }
|
||||
</div>
|
||||
)
|
||||
|
||||
storiesOf('Components', module)
|
||||
.addDecorator(FrameDecorator)
|
||||
.add('NoRights', () => (
|
||||
<Component />
|
||||
))
|
|
@ -1,86 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import Col from '~/components/layout/Col'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Table, { TableBody, TableCell, TableHead, TableRow } from '~/components/layout/Table'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
|
||||
type SafeProps = {
|
||||
safe: Safe,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
const GnoSafe = ({ safe, balance }: SafeProps) => (
|
||||
<React.Fragment>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<Paragraph size="lg">
|
||||
<Bold>{safe.name.toUpperCase()}</Bold>
|
||||
</Paragraph>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Paragraph size="lg">
|
||||
<Bold>Balance</Bold>
|
||||
</Paragraph>
|
||||
</Row>
|
||||
<Row>
|
||||
<Block>
|
||||
<Paragraph>
|
||||
{balance} - ETH
|
||||
</Paragraph>
|
||||
</Block>
|
||||
</Row>
|
||||
<Row>
|
||||
<Paragraph size="lg">
|
||||
<Bold>Address</Bold>
|
||||
</Paragraph>
|
||||
</Row>
|
||||
<Row>
|
||||
<Block>
|
||||
<Paragraph>
|
||||
{safe.address}
|
||||
</Paragraph>
|
||||
</Block>
|
||||
</Row>
|
||||
<Row>
|
||||
<Paragraph size="lg">
|
||||
<Bold>Number of required confirmations per transaction</Bold>
|
||||
</Paragraph>
|
||||
</Row>
|
||||
<Row>
|
||||
<Paragraph>
|
||||
{safe.get('confirmations')}
|
||||
</Paragraph>
|
||||
</Row>
|
||||
<Row>
|
||||
<Paragraph size="lg">
|
||||
<Bold>Owners</Bold>
|
||||
</Paragraph>
|
||||
</Row>
|
||||
<Row margin="lg">
|
||||
<Table size={700}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Adress</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{safe.owners.map(owner => (
|
||||
<TableRow key={safe.address}>
|
||||
<TableCell>{owner.name}</TableCell>
|
||||
<TableCell>{owner.address}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div />
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
export default GnoSafe
|
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { ListItem } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import Mail from 'material-ui-icons/Mail'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
|
||||
type Props = {
|
||||
address: string,
|
||||
}
|
||||
|
||||
const Address = ({ address }: Props) => (
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<Mail />
|
||||
</Avatar>
|
||||
<ListItemText primary="Safe Address" secondary={address} cut />
|
||||
</ListItem>
|
||||
)
|
||||
|
||||
export default Address
|
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { ListItem, ListItemText } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import AccountBalance from 'material-ui-icons/AccountBalance'
|
||||
|
||||
type Props = {
|
||||
balance: string,
|
||||
}
|
||||
|
||||
const Balance = ({ balance }: Props) => (
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<AccountBalance />
|
||||
</Avatar>
|
||||
<ListItemText primary="Balance" secondary={`${balance} ETH`} />
|
||||
</ListItem>
|
||||
)
|
||||
|
||||
export default Balance
|
|
@ -0,0 +1,25 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { ListItem } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import DoneAll from 'material-ui-icons/DoneAll'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
|
||||
type Props = {
|
||||
confirmations: number,
|
||||
}
|
||||
|
||||
const Confirmations = ({ confirmations }: Props) => (
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<DoneAll />
|
||||
</Avatar>
|
||||
<ListItemText
|
||||
primary="Confirmations"
|
||||
secondary={`${confirmations} required confirmations per transaction`}
|
||||
cut
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
|
||||
export default Confirmations
|
|
@ -0,0 +1,43 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { ListItem } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import NotificationsPaused from 'material-ui-icons/NotificationsPaused'
|
||||
import Button from '~/components/layout/Button'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
import { type DailyLimit } from '~/routes/safe/store/model/dailyLimit'
|
||||
|
||||
type Props = {
|
||||
dailyLimit: DailyLimit,
|
||||
onWithdrawn: () => void,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
export const WITHDRAWN_BUTTON_TEXT = 'Withdrawn'
|
||||
|
||||
const DailyLimitComponent = ({ dailyLimit, balance, onWithdrawn }: Props) => {
|
||||
const limit = dailyLimit.get('value')
|
||||
const spentToday = dailyLimit.get('spentToday')
|
||||
|
||||
const disabled = spentToday >= limit || Number(balance) === 0
|
||||
const text = `${limit} ETH (spent today: ${spentToday} ETH)`
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<NotificationsPaused />
|
||||
</Avatar>
|
||||
<ListItemText primary="Daily Limit" secondary={text} />
|
||||
<Button
|
||||
variant="raised"
|
||||
color="primary"
|
||||
onClick={onWithdrawn}
|
||||
disabled={disabled}
|
||||
>
|
||||
{WITHDRAWN_BUTTON_TEXT}
|
||||
</Button>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default DailyLimitComponent
|
|
@ -0,0 +1,52 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { ListItem } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import AcoountBalanceWallet from 'material-ui-icons/AccountBalanceWallet'
|
||||
import Button from '~/components/layout/Button'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
|
||||
type Props = {
|
||||
balance: string,
|
||||
onAddTx: () => void,
|
||||
onSeeTxs: () => void,
|
||||
}
|
||||
|
||||
export const ADD_MULTISIG_BUTTON_TEXT = 'Add'
|
||||
export const SEE_MULTISIG_BUTTON_TEXT = 'TXs'
|
||||
|
||||
const addStyle = {
|
||||
marginRight: '10px',
|
||||
}
|
||||
|
||||
const DailyLimitComponent = ({ balance, onAddTx, onSeeTxs }: Props) => {
|
||||
const text = `Available ${balance} ETH`
|
||||
const disabled = Number(balance) <= 0
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<AcoountBalanceWallet />
|
||||
</Avatar>
|
||||
<ListItemText primary="Multisig TXs" secondary={text} />
|
||||
<Button
|
||||
style={addStyle}
|
||||
variant="raised"
|
||||
color="primary"
|
||||
onClick={onAddTx}
|
||||
disabled={disabled}
|
||||
>
|
||||
{ADD_MULTISIG_BUTTON_TEXT}
|
||||
</Button>
|
||||
<Button
|
||||
variant="raised"
|
||||
color="primary"
|
||||
onClick={onSeeTxs}
|
||||
>
|
||||
{SEE_MULTISIG_BUTTON_TEXT}
|
||||
</Button>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default DailyLimitComponent
|
|
@ -0,0 +1,58 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import Collapse from 'material-ui/transitions/Collapse'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
import List, { ListItem, ListItemIcon } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import Group from 'material-ui-icons/Group'
|
||||
import Person from 'material-ui-icons/Person'
|
||||
import ExpandLess from 'material-ui-icons/ExpandLess'
|
||||
import ExpandMore from 'material-ui-icons/ExpandMore'
|
||||
import { type OwnerProps } from '~/routes/safe/store/model/owner'
|
||||
import { type WithStyles } from '~/theme/mui'
|
||||
|
||||
const styles = {
|
||||
nested: {
|
||||
paddingLeft: '40px',
|
||||
},
|
||||
}
|
||||
|
||||
type Props = Open & WithStyles & {
|
||||
owners: List<OwnerProps>,
|
||||
}
|
||||
|
||||
const Owners = openHoc(({
|
||||
open, toggle, owners, classes,
|
||||
}: Props) => (
|
||||
<React.Fragment>
|
||||
<ListItem onClick={toggle}>
|
||||
<Avatar>
|
||||
<Group />
|
||||
</Avatar>
|
||||
<ListItemText primary="Owners" secondary={`${owners.size} owners`} />
|
||||
<ListItemIcon>
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding>
|
||||
{owners.map(owner => (
|
||||
<ListItem key={owner.address} className={classes.nested}>
|
||||
<ListItemIcon>
|
||||
<Person />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
cut
|
||||
primary={owner.name}
|
||||
secondary={owner.address}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
))
|
||||
|
||||
export default withStyles(styles)(Owners)
|
|
@ -0,0 +1,17 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 500 500">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="0%" y1="50.001%" y2="50.001%">
|
||||
<stop offset="0%" stop-color="#00B3CE"/>
|
||||
<stop offset="100%" stop-color="#00C8DD"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="nonzero">
|
||||
<circle cx="250" cy="250" r="250" fill="url(#a)"/>
|
||||
<g fill="#FFF">
|
||||
<path d="M189.93 245.903l-49.95-49.674c-4.508 6.049-7.237 13.563-7.237 21.674 0 19.583 15.972 35.52 35.528 35.52 8.118 0 15.66-2.715 21.66-7.52z"/>
|
||||
<path d="M248.639 71.028c-52.077 0-100.542 21.097-136.063 58.694l-5.708 6.042 143.84 144.48v25.923l-.555.562-37.007-37.326c-17.18 11.423-39.153 14.757-59.903 7.528-34.924-12.653-53-50.882-40.361-85.5 1.826-5.41 4.52-10.23 7.52-14.743l-15.937-15.959-3.014 5.118c-16.555 27.104-25.597 58.403-25.597 90.59-.285 96.042 77.965 174.577 173.965 174.577h29.396V71.056l-30.576-.028zm-118.292 64.729c31.945-31.02 73.743-47.854 118.611-47.854h.285c.5 0 .993 0 1.465.02v168.5l-120.36-120.666z"/>
|
||||
<path d="M248.639 71.028c-52.077 0-100.542 21.097-136.063 58.694l-5.708 6.042 143.84 144.48v25.923l-.555.562-37.007-37.326c-17.18 11.423-39.153 14.757-59.903 7.528-34.924-12.653-53-50.882-40.361-85.5 1.826-5.41 4.52-10.23 7.52-14.743l-15.937-15.959-3.014 5.118c-16.555 27.104-25.597 58.403-25.597 90.59-.285 96.042 77.965 174.577 173.965 174.577h29.396V71.056l-30.576-.028zm-118.292 64.729c31.945-31.02 73.743-47.854 118.611-47.854h.285c.5 0 .993 0 1.465.02v168.5l-120.36-120.666zM423.611 250.41c0-79.618-64.764-144.417-144.389-144.473v17.3c70.104.027 127.125 57.069 127.125 127.166 0 70.11-57.02 127.166-127.125 127.194v17.278c79.625-.035 144.39-64.813 144.39-144.465z"/>
|
||||
<path d="M314.549 250.486l-6.855 42.98h33.577l-6.854-42.98c7.646-3.743 12.944-11.59 12.944-20.708 0-12.702-10.236-23-22.868-23-12.632 0-22.875 10.298-22.875 23-.014 9.132 5.285 16.965 12.93 20.708zM303.25 152.986c9.028 2.333 12.507-11.201 3.48-13.528-9.015-2.312-12.508 11.223-3.48 13.528M342.868 173.549c7.139 5.972 16.111-4.73 8.972-10.702-7.11-6-16.104 4.715-8.972 10.702M368.333 206.944c4.105 8.362 16.646 2.223 12.549-6.138-4.09-8.369-16.646-2.209-12.549 6.138M303.25 347.618c9.028-2.326 12.507 11.23 3.48 13.549-9.015 2.305-12.508-11.23-3.48-13.549M342.868 327.07c7.139-5.98 16.111 4.729 8.972 10.687-7.11 6.014-16.104-4.701-8.972-10.688M368.333 293.66c4.105-8.361 16.646-2.202 12.549 6.166-4.09 8.34-16.646 2.202-12.549-6.166M378.424 250.826c0 9.334 13.958 9.334 13.958 0 0-9.312-13.958-9.312-13.958 0"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,95 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Col from '~/components/layout/Col'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import Img from '~/components/layout/Img'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Row from '~/components/layout/Row'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import List from 'material-ui/List'
|
||||
|
||||
import Withdrawn from '~/routes/safe/component/Withdrawn'
|
||||
import Transactions from '~/routes/safe/component/Transactions'
|
||||
import AddTransaction from '~/routes/safe/component/AddTransaction'
|
||||
|
||||
import Address from './Address'
|
||||
import Balance from './Balance'
|
||||
import Owners from './Owners'
|
||||
import Confirmations from './Confirmations'
|
||||
import DailyLimit from './DailyLimit'
|
||||
import MultisigTx from './MultisigTx'
|
||||
|
||||
const safeIcon = require('./assets/gnosis_safe.svg')
|
||||
|
||||
type SafeProps = {
|
||||
safe: Safe,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
type State = {
|
||||
component: React$Node,
|
||||
}
|
||||
|
||||
const listStyle = {
|
||||
width: '100%',
|
||||
}
|
||||
|
||||
class GnoSafe extends React.PureComponent<SafeProps, State> {
|
||||
state = {
|
||||
component: undefined,
|
||||
}
|
||||
|
||||
onWithdrawn = () => {
|
||||
const { safe } = this.props
|
||||
|
||||
this.setState({ component: <Withdrawn safeAddress={safe.get('address')} dailyLimit={safe.get('dailyLimit')} /> })
|
||||
}
|
||||
|
||||
onAddTx = () => {
|
||||
const { balance, safe } = this.props
|
||||
this.setState({
|
||||
component: <AddTransaction safe={safe} balance={Number(balance)} onReset={this.onListTransactions} />,
|
||||
})
|
||||
}
|
||||
|
||||
onListTransactions = () => {
|
||||
const { safe } = this.props
|
||||
|
||||
this.setState({ component: <Transactions safeName={safe.get('name')} safeAddress={safe.get('address')} onAddTx={this.onAddTx} /> })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { safe, balance } = this.props
|
||||
const { component } = this.state
|
||||
|
||||
return (
|
||||
<Row grow>
|
||||
<Col sm={12} top="xs" md={5} margin="xl" overflow>
|
||||
<List style={listStyle}>
|
||||
<Balance balance={balance} />
|
||||
<Owners owners={safe.owners} />
|
||||
<Confirmations confirmations={safe.get('confirmations')} />
|
||||
<Address address={safe.get('address')} />
|
||||
<DailyLimit balance={balance} dailyLimit={safe.get('dailyLimit')} onWithdrawn={this.onWithdrawn} />
|
||||
<MultisigTx balance={balance} onAddTx={this.onAddTx} onSeeTxs={this.onListTransactions} />
|
||||
</List>
|
||||
</Col>
|
||||
<Col sm={12} center="xs" md={7} margin="xl" layout="column">
|
||||
<Block margin="xl">
|
||||
<Paragraph size="lg" noMargin align="right">
|
||||
<Bold>{safe.name.toUpperCase()}</Bold>
|
||||
</Paragraph>
|
||||
</Block>
|
||||
<Row grow>
|
||||
<Col sm={12} center={component ? undefined : 'sm'} middle={component ? undefined : 'sm'} layout="column">
|
||||
{ component || <Img alt="Safe Icon" src={safeIcon} height={330} /> }
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default GnoSafe
|
|
@ -0,0 +1,76 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import Collapse from 'material-ui/transitions/Collapse'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
import List, { ListItem, ListItemIcon } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import Group from 'material-ui-icons/Group'
|
||||
import Person from 'material-ui-icons/Person'
|
||||
import ExpandLess from 'material-ui-icons/ExpandLess'
|
||||
import ExpandMore from 'material-ui-icons/ExpandMore'
|
||||
import { type WithStyles } from '~/theme/mui'
|
||||
import { type Confirmation, type ConfirmationProps } from '~/routes/safe/store/model/confirmation'
|
||||
|
||||
const styles = {
|
||||
nested: {
|
||||
paddingLeft: '40px',
|
||||
},
|
||||
}
|
||||
|
||||
type Props = Open & WithStyles & {
|
||||
confirmations: List<Confirmation>,
|
||||
threshold: number,
|
||||
}
|
||||
|
||||
const GnoConfirmation = ({ owner, status, hash }: ConfirmationProps) => {
|
||||
const address = owner.get('address')
|
||||
const text = status ? 'Confirmed' : 'Not confirmed'
|
||||
const hashText = status ? `Confirmation hash: ${hash}` : undefined
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListItem key={address}>
|
||||
<ListItemIcon>
|
||||
<Person />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
cut
|
||||
primary={`${owner.get('name')} [${text}]`}
|
||||
secondary={hashText}
|
||||
/>
|
||||
</ListItem>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const Confirmaitons = openHoc(({
|
||||
open, toggle, confirmations, threshold,
|
||||
}: Props) => (
|
||||
<React.Fragment>
|
||||
<ListItem onClick={toggle}>
|
||||
<Avatar>
|
||||
<Group />
|
||||
</Avatar>
|
||||
<ListItemText primary="Threshold" secondary={`${threshold} confirmation${threshold === 1 ? '' : 's'} needed`} />
|
||||
<ListItemIcon>
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding style={{ width: '100%' }}>
|
||||
{confirmations.map(confirmation => (
|
||||
<GnoConfirmation
|
||||
key={confirmation.get('owner').get('address')}
|
||||
owner={confirmation.get('owner')}
|
||||
status={confirmation.get('status')}
|
||||
hash={confirmation.get('hash')}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
))
|
||||
|
||||
export default withStyles(styles)(Confirmaitons)
|
|
@ -0,0 +1,51 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { List as ImmutableList } from 'immutable'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Col from '~/components/layout/Col'
|
||||
import List, { ListItem } from 'material-ui/List'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import Group from 'material-ui-icons/Group'
|
||||
import MailOutline from 'material-ui-icons/MailOutline'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import Confirmations from './Confirmations'
|
||||
|
||||
type Props = {
|
||||
safeName: string,
|
||||
confirmations: ImmutableList<Confirmation>,
|
||||
destination: string,
|
||||
threshold: number,
|
||||
}
|
||||
|
||||
const listStyle = {
|
||||
width: '100%',
|
||||
}
|
||||
|
||||
class Collapsed extends React.PureComponent<Props, {}> {
|
||||
render() {
|
||||
const {
|
||||
confirmations, destination, safeName, threshold,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col sm={12} top="xs" overflow>
|
||||
<List style={listStyle}>
|
||||
<ListItem>
|
||||
<Avatar><Group /></Avatar>
|
||||
<ListItemText primary={safeName} secondary="Safe Name" />
|
||||
</ListItem>
|
||||
<Confirmations confirmations={confirmations} threshold={threshold} />
|
||||
<ListItem>
|
||||
<Avatar><MailOutline /></Avatar>
|
||||
<ListItemText primary="Destination" secondary={destination} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Collapsed
|
|
@ -0,0 +1,32 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Col from '~/components/layout/Col'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Paragraph from '~/components/layout/Paragraph/index'
|
||||
|
||||
type Props = {
|
||||
onAddTx: () => void
|
||||
}
|
||||
|
||||
const NoRights = ({ onAddTx }: Props) => (
|
||||
<Row>
|
||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||
<Paragraph size="lg">
|
||||
<Bold>No transactions found for this safe</Bold>
|
||||
</Paragraph>
|
||||
</Col>
|
||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||
<Button
|
||||
onClick={onAddTx}
|
||||
variant="raised"
|
||||
color="primary"
|
||||
>
|
||||
Add Multisig Transaction
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
|
||||
export default NoRights
|
|
@ -0,0 +1,106 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { List } from 'immutable'
|
||||
import { connect } from 'react-redux'
|
||||
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
|
||||
import ExpandLess from 'material-ui-icons/ExpandLess'
|
||||
import ExpandMore from 'material-ui-icons/ExpandMore'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
import Row from '~/components/layout/Row'
|
||||
import { ListItem, ListItemIcon } from 'material-ui/List'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import AttachMoney from 'material-ui-icons/AttachMoney'
|
||||
import Atm from 'material-ui-icons/LocalAtm'
|
||||
import DoneAll from 'material-ui-icons/DoneAll'
|
||||
import CompareArrows from 'material-ui-icons/CompareArrows'
|
||||
import Collapsed from '~/routes/safe/component/Transactions/Collapsed'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import Hairline from '~/components/layout/Hairline/index'
|
||||
import Button from '~/components/layout/Button'
|
||||
import { sameAddress } from '~/wallets/ethAddresses'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
|
||||
type Props = Open & SelectorProps & {
|
||||
transaction: Transaction,
|
||||
safeName: string,
|
||||
onProcessTx: (tx: Transaction, alreadyConfirmed: number) => void,
|
||||
}
|
||||
|
||||
export const PROCESS_TXS = 'PROCESS TRANSACTION'
|
||||
|
||||
class GnoTransaction extends React.PureComponent<Props, {}> {
|
||||
onProccesClick = () => this.props.onProcessTx(this.props.transaction, this.props.confirmed)
|
||||
|
||||
hasConfirmed = (userAddress: string, confirmations: List<Confirmation>): boolean =>
|
||||
confirmations.filter((conf: Confirmation) => sameAddress(userAddress, conf.get('owner').get('address')) && conf.get('status')).count() > 0
|
||||
|
||||
render() {
|
||||
const {
|
||||
open, toggle, transaction, confirmed, safeName, userAddress,
|
||||
} = this.props
|
||||
|
||||
const txHash = transaction.get('tx')
|
||||
const confirmationText = txHash ? 'Already executed' : `${confirmed} of the ${transaction.get('threshold')} confirmations needed`
|
||||
const userConfirmed = this.hasConfirmed(userAddress, transaction.get('confirmations'))
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Row>
|
||||
<ListItem onClick={toggle}>
|
||||
<Avatar>
|
||||
<Atm />
|
||||
</Avatar>
|
||||
<ListItemText primary="Tx Name" secondary={transaction.get('name')} />
|
||||
<Avatar>
|
||||
<AttachMoney />
|
||||
</Avatar>
|
||||
<ListItemText primary="Value" secondary={`${transaction.get('value')} ETH`} />
|
||||
<Avatar>
|
||||
<DoneAll />
|
||||
</Avatar>
|
||||
<ListItemText primary="Status" secondary={confirmationText} />
|
||||
<ListItemIcon>
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
</Row>
|
||||
<Row>
|
||||
<ListItem>
|
||||
{ txHash &&
|
||||
<React.Fragment>
|
||||
<Avatar><CompareArrows /></Avatar>
|
||||
<ListItemText cut primary="Transaction Hash" secondary={txHash} />
|
||||
</React.Fragment>
|
||||
}
|
||||
{ !txHash && userConfirmed &&
|
||||
<React.Fragment>
|
||||
<Avatar><CompareArrows /></Avatar>
|
||||
<ListItemText cut primary="Confirmed" secondary="Waiting for the rest of confirmations" />
|
||||
</React.Fragment>
|
||||
}
|
||||
{ !txHash && !userConfirmed &&
|
||||
<Button
|
||||
variant="raised"
|
||||
color="primary"
|
||||
onClick={this.onProccesClick}
|
||||
>
|
||||
{PROCESS_TXS}
|
||||
</Button>
|
||||
}
|
||||
</ListItem>
|
||||
</Row>
|
||||
{ open &&
|
||||
<Collapsed
|
||||
safeName={safeName}
|
||||
confirmations={transaction.get('confirmations')}
|
||||
destination={transaction.get('destination')}
|
||||
threshold={transaction.get('threshold')}
|
||||
/> }
|
||||
<Hairline />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector)(openHoc(GnoTransaction))
|
|
@ -0,0 +1,14 @@
|
|||
// @flow
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { confirmationsTransactionSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { userAccountSelector } from '~/wallets/store/selectors/index'
|
||||
|
||||
export type SelectorProps = {
|
||||
confirmed: confirmationsTransactionSelector,
|
||||
userAddress: userAccountSelector,
|
||||
}
|
||||
|
||||
export default createStructuredSelector({
|
||||
confirmed: confirmationsTransactionSelector,
|
||||
userAddress: userAccountSelector,
|
||||
})
|
|
@ -0,0 +1,10 @@
|
|||
// @flow
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
|
||||
export type Actions = {
|
||||
fetchTransactions: typeof fetchTransactions,
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchTransactions,
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import NoTransactions from '~/routes/safe/component/Transactions/NoTransactions'
|
||||
import GnoTransaction from '~/routes/safe/component/Transactions/Transaction'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { processTransaction } from './processTransactions'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
|
||||
type Props = SelectorProps & Actions & {
|
||||
onAddTx: () => void,
|
||||
safeName: string,
|
||||
safeAddress: string,
|
||||
|
||||
}
|
||||
class Transactions extends React.Component<Props, {}> {
|
||||
onProcessTx = async (tx: Transaction, alreadyConfirmed: number) => {
|
||||
const { fetchTransactions, safeAddress, userAddress } = this.props
|
||||
await processTransaction(safeAddress, tx, alreadyConfirmed, userAddress)
|
||||
await sleep(1200)
|
||||
fetchTransactions()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { transactions, onAddTx, safeName } = this.props
|
||||
const hasTransactions = transactions.count() > 0
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{ hasTransactions
|
||||
? transactions.map((tx: Transaction) => <GnoTransaction key={tx.get('nonce')} safeName={safeName} onProcessTx={this.onProcessTx} transaction={tx} />)
|
||||
: <NoTransactions onAddTx={onAddTx} />
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(Transactions)
|
|
@ -0,0 +1,134 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { load, TX_KEY } from '~/utils/localStorage'
|
||||
import { type Confirmation, makeConfirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { makeTransaction, type Transaction, type TransactionProps } from '~/routes/safe/store/model/transaction'
|
||||
import { getGnosisSafeContract } from '~/wallets/safeContracts'
|
||||
import { getWeb3 } from '~/wallets/getWeb3'
|
||||
import { sameAddress } from '~/wallets/ethAddresses'
|
||||
import { EXECUTED_CONFIRMATION_HASH } from '~/routes/safe/component/AddTransaction/createTransactions'
|
||||
import executeTransaction, { checkReceiptStatus } from '~/wallets/ethTransactions'
|
||||
|
||||
export const updateTransaction = (
|
||||
name: string,
|
||||
nonce: number,
|
||||
destination: string,
|
||||
value: number,
|
||||
creator: string,
|
||||
confirmations: List<Confirmation>,
|
||||
tx: string,
|
||||
safeAddress: string,
|
||||
safeThreshold: number,
|
||||
) => {
|
||||
const transaction: Transaction = makeTransaction({
|
||||
name, nonce, value, confirmations, destination, threshold: safeThreshold, tx,
|
||||
})
|
||||
|
||||
const safeTransactions = load(TX_KEY) || {}
|
||||
const transactions = safeTransactions[safeAddress]
|
||||
const txsRecord = transactions ? List(transactions) : List([])
|
||||
|
||||
const index = txsRecord.findIndex((trans: TransactionProps) => trans.nonce === nonce)
|
||||
|
||||
safeTransactions[safeAddress] = txsRecord.remove(index).push(transaction)
|
||||
|
||||
localStorage.setItem(TX_KEY, JSON.stringify(safeTransactions))
|
||||
}
|
||||
|
||||
const getData = () => '0x'
|
||||
const getOperation = () => 0
|
||||
|
||||
const execTransaction = async (
|
||||
gnosisSafe: any,
|
||||
destination: string,
|
||||
txValue: number,
|
||||
nonce: number,
|
||||
executor: string,
|
||||
) => {
|
||||
const data = getData()
|
||||
const CALL = getOperation()
|
||||
const web3 = getWeb3()
|
||||
const valueInWei = web3.toWei(txValue, 'ether')
|
||||
const txData = await gnosisSafe.contract.execTransactionIfApproved.getData(destination, valueInWei, data, CALL, nonce)
|
||||
|
||||
return executeTransaction(txData, executor, gnosisSafe.address)
|
||||
}
|
||||
|
||||
const execConfirmation = async (
|
||||
gnosisSafe: any,
|
||||
txDestination: string,
|
||||
txValue: number,
|
||||
nonce: number,
|
||||
executor: string,
|
||||
) => {
|
||||
const data = getData()
|
||||
const CALL = getOperation()
|
||||
const web3 = getWeb3()
|
||||
const valueInWei = web3.toWei(txValue, 'ether')
|
||||
const txConfirmationData =
|
||||
await gnosisSafe.contract.approveTransactionWithParameters.getData(txDestination, valueInWei, data, CALL, nonce)
|
||||
|
||||
return executeTransaction(txConfirmationData, executor, gnosisSafe.address)
|
||||
}
|
||||
|
||||
const updateConfirmations = (confirmations: List<Confirmation>, userAddress: string, txHash: string) =>
|
||||
confirmations.map((confirmation: Confirmation) => {
|
||||
const owner: Owner = confirmation.get('owner')
|
||||
const samePerson = sameAddress(owner.get('address'), userAddress)
|
||||
const status: boolean = samePerson ? true : confirmation.get('status')
|
||||
const hash: string = samePerson ? txHash : confirmation.get('hash')
|
||||
|
||||
return makeConfirmation({ owner, status, hash })
|
||||
})
|
||||
|
||||
export const processTransaction = async (
|
||||
safeAddress: string,
|
||||
tx: Transaction,
|
||||
alreadyConfirmed: number,
|
||||
userAddress: string,
|
||||
) => {
|
||||
const web3 = getWeb3()
|
||||
const GnosisSafe = await getGnosisSafeContract(web3)
|
||||
const gnosisSafe = GnosisSafe.at(safeAddress)
|
||||
|
||||
const confirmations = tx.get('confirmations')
|
||||
const userHasAlreadyConfirmed = confirmations.filter((confirmation: Confirmation) => {
|
||||
const ownerAddress = confirmation.get('owner').get('address')
|
||||
const samePerson = sameAddress(ownerAddress, userAddress)
|
||||
|
||||
return samePerson && confirmation.get('status')
|
||||
}).count() > 0
|
||||
|
||||
if (userHasAlreadyConfirmed) {
|
||||
throw new Error('Owner has already confirmed this transaction')
|
||||
}
|
||||
|
||||
const threshold = tx.get('threshold')
|
||||
const thresholdReached = threshold === alreadyConfirmed + 1
|
||||
const nonce = tx.get('nonce')
|
||||
const txName = tx.get('name')
|
||||
const txValue = tx.get('value')
|
||||
const txDestination = tx.get('destination')
|
||||
|
||||
const txHash = thresholdReached
|
||||
? await execTransaction(gnosisSafe, txDestination, txValue, nonce, userAddress)
|
||||
: await execConfirmation(gnosisSafe, txDestination, txValue, nonce, userAddress)
|
||||
|
||||
checkReceiptStatus(txHash)
|
||||
|
||||
const confirmationHash = thresholdReached ? EXECUTED_CONFIRMATION_HASH : txHash
|
||||
const executedConfirmations: List<Confirmation> = updateConfirmations(tx.get('confirmations'), userAddress, confirmationHash)
|
||||
|
||||
return updateTransaction(
|
||||
txName,
|
||||
nonce,
|
||||
txDestination,
|
||||
txValue,
|
||||
userAddress,
|
||||
executedConfirmations,
|
||||
thresholdReached ? txHash : '',
|
||||
safeAddress,
|
||||
threshold,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { userAccountSelector } from '~/wallets/store/selectors/index'
|
||||
|
||||
export type SelectorProps = {
|
||||
transactions: List<Transaction>,
|
||||
userAddress: userAccountSelector,
|
||||
}
|
||||
|
||||
export default createStructuredSelector({
|
||||
transactions: safeTransactionsSelector,
|
||||
userAddress: userAccountSelector,
|
||||
})
|
|
@ -0,0 +1,34 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { CircularProgress } from 'material-ui/Progress'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import { DESTINATION_PARAM, VALUE_PARAM } from '~/routes/safe/component/Withdrawn/withdrawn'
|
||||
|
||||
type FormProps = {
|
||||
values: Object,
|
||||
submitting: boolean,
|
||||
}
|
||||
|
||||
const spinnerStyle = {
|
||||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const Review = () => ({ values, submitting }: FormProps) => (
|
||||
<Block>
|
||||
<Heading tag="h2">Review the Withdrawn Operation</Heading>
|
||||
<Paragraph align="left">
|
||||
<Bold>Destination: </Bold> {values[DESTINATION_PARAM]}
|
||||
</Paragraph>
|
||||
<Paragraph align="left">
|
||||
<Bold>Value in ETH: </Bold> {values[VALUE_PARAM]}
|
||||
</Paragraph>
|
||||
<Block style={spinnerStyle}>
|
||||
{ submitting && <CircularProgress size={50} /> }
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
||||
export default Review
|
|
@ -0,0 +1,29 @@
|
|||
// @flow
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import * as React from 'react'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import styles from '~/components/layout/PageFrame/index.scss'
|
||||
import WithdrawnForm from './index'
|
||||
|
||||
|
||||
const FrameDecorator = story => (
|
||||
<div className={styles.frame} style={{ textAlign: 'center' }}>
|
||||
{ story() }
|
||||
</div>
|
||||
)
|
||||
|
||||
storiesOf('Components', module)
|
||||
.addDecorator(FrameDecorator)
|
||||
.add('WithdrawnForm', () => (
|
||||
<Stepper
|
||||
finishedTransaction={false}
|
||||
finishedButton={<Stepper.FinishButton title="RESET" />}
|
||||
onSubmit={() => {}}
|
||||
steps={['Fill Withdrawn Form', 'Review Withdrawn']}
|
||||
onReset={() => {}}
|
||||
>
|
||||
<Stepper.Page dailyLimit={10} spentToday={7}>
|
||||
{ WithdrawnForm }
|
||||
</Stepper.Page>
|
||||
</Stepper>
|
||||
))
|
|
@ -0,0 +1,58 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, inLimit, mustBeFloat, required, greaterThan, mustBeEthereumAddress } from '~/components/forms/validator'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import { DESTINATION_PARAM, VALUE_PARAM } from '~/routes/safe/component/Withdrawn/withdrawn'
|
||||
|
||||
export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners'
|
||||
|
||||
export const safeFieldsValidation = (values: Object) => {
|
||||
const errors = {}
|
||||
|
||||
if (Number.parseInt(values.owners, 10) < Number.parseInt(values.confirmations, 10)) {
|
||||
errors.confirmations = CONFIRMATIONS_ERROR
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
type Props = {
|
||||
limit: number,
|
||||
spentToday: number,
|
||||
}
|
||||
|
||||
const WithdrawnForm = ({ limit, spentToday }: Props) => () => (
|
||||
<Block margin="md">
|
||||
<Heading tag="h2" margin="lg">
|
||||
Withdrawn Funds
|
||||
</Heading>
|
||||
<Heading tag="h4" margin="lg">
|
||||
{`Daily limit ${limit} ETH (spent today: ${spentToday} ETH)`}
|
||||
</Heading>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={VALUE_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeFloat, greaterThan(0), inLimit(limit, spentToday, 'daily limit'))}
|
||||
placeholder="Amount in ETH*"
|
||||
text="Amount in ETH"
|
||||
/>
|
||||
</Block>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={DESTINATION_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeEthereumAddress)}
|
||||
placeholder="Destination*"
|
||||
text="Destination"
|
||||
/>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
||||
export default WithdrawnForm
|
|
@ -0,0 +1,10 @@
|
|||
// @flow
|
||||
import fetchDailyLimit from '~/routes/safe/store/actions/fetchDailyLimit'
|
||||
|
||||
export type Actions = {
|
||||
fetchDailyLimit: typeof fetchDailyLimit,
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchDailyLimit,
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { type DailyLimit } from '~/routes/safe/store/model/dailyLimit'
|
||||
import actions, { type Actions } from './actions'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import withdrawn from './withdrawn'
|
||||
import WithdrawnForm from './WithdrawnForm'
|
||||
import Review from './Review'
|
||||
|
||||
const getSteps = () => [
|
||||
'Fill Withdrawn Form', 'Review Withdrawn',
|
||||
]
|
||||
|
||||
type Props = SelectorProps & Actions & {
|
||||
safeAddress: string,
|
||||
dailyLimit: DailyLimit,
|
||||
}
|
||||
|
||||
type State = {
|
||||
done: boolean,
|
||||
}
|
||||
|
||||
export const SEE_TXS_BUTTON_TEXT = 'RESET'
|
||||
|
||||
class Withdrawn extends React.Component<Props, State> {
|
||||
state = {
|
||||
done: false,
|
||||
}
|
||||
|
||||
onWithdrawn = async (values: Object) => {
|
||||
try {
|
||||
const { safeAddress, userAddress } = this.props
|
||||
await withdrawn(values, safeAddress, userAddress)
|
||||
await sleep(3500)
|
||||
this.props.fetchDailyLimit(safeAddress)
|
||||
this.setState({ done: true })
|
||||
} catch (error) {
|
||||
this.setState({ done: false })
|
||||
// eslint-disable-next-line
|
||||
console.log('Error while withdrawing funds ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
this.setState({ done: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dailyLimit } = this.props
|
||||
const { done } = this.state
|
||||
const steps = getSteps()
|
||||
const finishedButton = <Stepper.FinishButton title={SEE_TXS_BUTTON_TEXT} />
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Stepper
|
||||
finishedTransaction={done}
|
||||
finishedButton={finishedButton}
|
||||
onSubmit={this.onWithdrawn}
|
||||
steps={steps}
|
||||
onReset={this.onReset}
|
||||
>
|
||||
<Stepper.Page limit={dailyLimit.get('value')} spentToday={dailyLimit.get('spentToday')}>
|
||||
{ WithdrawnForm }
|
||||
</Stepper.Page>
|
||||
<Stepper.Page>
|
||||
{ Review }
|
||||
</Stepper.Page>
|
||||
</Stepper>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(Withdrawn)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// @flow
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { userAccountSelector } from '~/wallets/store/selectors/index'
|
||||
|
||||
export type SelectorProps = {
|
||||
userAddress: userAccountSelector,
|
||||
}
|
||||
|
||||
export default createStructuredSelector({
|
||||
userAddress: userAccountSelector,
|
||||
})
|
|
@ -0,0 +1,52 @@
|
|||
// @flow
|
||||
import { getWeb3 } from '~/wallets/getWeb3'
|
||||
import { getGnosisSafeContract, getCreateDailyLimitExtensionContract } from '~/wallets/safeContracts'
|
||||
import { type DailyLimitProps } from '~/routes/safe/store/model/dailyLimit'
|
||||
import executeTransaction, { checkReceiptStatus } from '~/wallets/ethTransactions'
|
||||
|
||||
export const LIMIT_POSITION = 0
|
||||
export const SPENT_TODAY_POS = 1
|
||||
export const DESTINATION_PARAM = 'destination'
|
||||
export const VALUE_PARAM = 'ether'
|
||||
|
||||
const getDailyLimitModuleFrom = async (safeAddress) => {
|
||||
const web3 = getWeb3()
|
||||
const gnosisSafe = getGnosisSafeContract(web3).at(safeAddress)
|
||||
|
||||
const modules = await gnosisSafe.getModules()
|
||||
const dailyAddress = modules[0]
|
||||
|
||||
const dailyLimitModule = getCreateDailyLimitExtensionContract(web3).at(dailyAddress)
|
||||
if (await dailyLimitModule.manager.call() !== gnosisSafe.address) {
|
||||
throw new Error('Using an extension of different safe')
|
||||
}
|
||||
|
||||
return dailyLimitModule
|
||||
}
|
||||
|
||||
export const getDailyLimitFrom = async (safeAddress: string, tokenAddress: number): Promise<DailyLimitProps> => {
|
||||
const web3 = getWeb3()
|
||||
const dailyLimitModule = await getDailyLimitModuleFrom(safeAddress)
|
||||
|
||||
const dailyLimitEth = await dailyLimitModule.dailyLimits(tokenAddress)
|
||||
|
||||
const limit = web3.fromWei(dailyLimitEth[LIMIT_POSITION].valueOf(), 'ether').toString()
|
||||
const spentToday = web3.fromWei(dailyLimitEth[SPENT_TODAY_POS].valueOf(), 'ether').toString()
|
||||
|
||||
return { value: Number(limit), spentToday: Number(spentToday) }
|
||||
}
|
||||
|
||||
const withdrawn = async (values: Object, safeAddress: string, userAccount: string): Promise<void> => {
|
||||
const web3 = getWeb3()
|
||||
const dailyLimitModule = await getDailyLimitModuleFrom(safeAddress)
|
||||
|
||||
const destination = values[DESTINATION_PARAM]
|
||||
const value = web3.toWei(values[VALUE_PARAM], 'ether')
|
||||
|
||||
const dailyLimitData = dailyLimitModule.contract.executeDailyLimit.getData(0, destination, value)
|
||||
|
||||
const txHash = await executeTransaction(dailyLimitData, userAccount, dailyLimitModule.address)
|
||||
checkReceiptStatus(txHash)
|
||||
}
|
||||
|
||||
export default withdrawn
|
|
@ -0,0 +1,26 @@
|
|||
// @flow
|
||||
import { aNewStore } from '~/store'
|
||||
import { addEtherTo } from '~/test/addEtherTo'
|
||||
import { aDeployedSafe, executeWithdrawnOn } from '~/routes/safe/store/test/builder/deployedSafe.builder'
|
||||
|
||||
describe('Safe Blockchain Test', () => {
|
||||
let store
|
||||
beforeEach(async () => {
|
||||
store = aNewStore()
|
||||
})
|
||||
|
||||
it('wihdrawn should return revert error if exceeded dailyLimit', async () => {
|
||||
// GIVEN
|
||||
const dailyLimitValue = 0.30
|
||||
const safeAddress = await aDeployedSafe(store, dailyLimitValue)
|
||||
await addEtherTo(safeAddress, '0.7')
|
||||
const value = 0.15
|
||||
|
||||
// WHEN
|
||||
await executeWithdrawnOn(safeAddress, value)
|
||||
await executeWithdrawnOn(safeAddress, value)
|
||||
|
||||
// THEN
|
||||
expect(executeWithdrawnOn(safeAddress, value)).rejects.toThrow('VM Exception while processing transaction: revert')
|
||||
})
|
||||
})
|
|
@ -1,10 +1,13 @@
|
|||
// @flow
|
||||
import fetchBalance from '~/routes/safe/store/actions/fetchBalance'
|
||||
import fetchDailyLimit from '~/routes/safe/store/actions/fetchDailyLimit'
|
||||
|
||||
export type Actions = {
|
||||
fetchBalance: typeof fetchBalance,
|
||||
fetchDailyLimit: typeof fetchDailyLimit,
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchBalance,
|
||||
fetchDailyLimit,
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@ import * as React from 'react'
|
|||
import { connect } from 'react-redux'
|
||||
import Page from '~/components/layout/Page'
|
||||
import Layout from '~/routes/safe/component/Layout'
|
||||
import NoRights from '~/routes/safe/component/NoRights'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
|
||||
type Props = Actions & SelectorProps
|
||||
type Props = Actions & SelectorProps & {
|
||||
granted: boolean,
|
||||
}
|
||||
|
||||
class SafeView extends React.PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
|
@ -17,6 +20,11 @@ class SafeView extends React.PureComponent<Props> {
|
|||
const safeAddress: string = safe.get('address')
|
||||
fetchBalance(safeAddress)
|
||||
}, 1500)
|
||||
|
||||
const { fetchDailyLimit, safe } = this.props
|
||||
if (safe) {
|
||||
fetchDailyLimit(safe.get('address'))
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -26,15 +34,16 @@ class SafeView extends React.PureComponent<Props> {
|
|||
intervalId: IntervalID
|
||||
|
||||
render() {
|
||||
const { safe, provider, balance } = this.props
|
||||
const {
|
||||
safe, provider, balance, granted,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Layout
|
||||
balance={balance}
|
||||
provider={provider}
|
||||
safe={safe}
|
||||
/>
|
||||
{ granted
|
||||
? <Layout balance={balance} provider={provider} safe={safe} />
|
||||
: <NoRights />
|
||||
}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
// @flow
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { balanceSelector, safeSelector, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
||||
import { providerNameSelector } from '~/wallets/store/selectors/index'
|
||||
import { List } from 'immutable'
|
||||
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
||||
import { balanceSelector, safeSelector, type RouterProps, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
||||
import { providerNameSelector, userAccountSelector } from '~/wallets/store/selectors/index'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { sameAddress } from '~/wallets/ethAddresses'
|
||||
|
||||
export type SelectorProps = {
|
||||
safe: SafeSelectorProps,
|
||||
|
@ -9,8 +14,30 @@ export type SelectorProps = {
|
|||
balance: string,
|
||||
}
|
||||
|
||||
export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = createSelector(
|
||||
userAccountSelector,
|
||||
safeSelector,
|
||||
(userAccount: string, safe: Safe | typeof undefined): boolean => {
|
||||
if (!safe) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!userAccount) {
|
||||
return false
|
||||
}
|
||||
|
||||
const owners: List<Owner> = safe.get('owners')
|
||||
if (!owners) {
|
||||
return false
|
||||
}
|
||||
|
||||
return owners.find((owner: Owner) => sameAddress(owner.get('address'), userAccount)) !== undefined
|
||||
},
|
||||
)
|
||||
|
||||
export default createStructuredSelector({
|
||||
safe: safeSelector,
|
||||
provider: providerNameSelector,
|
||||
balance: balanceSelector,
|
||||
granted: grantedSelector,
|
||||
})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue