Merge pull request #21 from gnosis/feature/WA-238-withdraw-eth

WA-238 - Withdraw ETH
This commit is contained in:
Adolfo Panizo 2018-06-06 10:50:42 +02:00 committed by GitHub
commit 5db96a8de5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 77809 additions and 429 deletions

View File

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

4
.gitignore vendored
View File

@ -1,8 +1,4 @@
node_modules/
build/
build_webpack/
build_storybook/
build/contracts/
truffle-config.js
gnosis-safe-contracts/
.DS_Store

View File

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

View File

@ -0,0 +1,2 @@
// @flow
jest.setTimeout(30000)

View File

@ -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'),

View File

@ -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: [

View File

@ -5,7 +5,7 @@
"baseUrl": "./",
"paths": {
"~/*":["src/*"],
"@/*":["gnosis-safe-contracts/build/contracts"]
"@/*":["safe-contracts/build/contracts"]
}
},
"exclude": [

View File

@ -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"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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})`
}

View File

@ -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 }) },
)

View File

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

View File

@ -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;
}

View File

@ -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,
)

View File

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

View File

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

View File

@ -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 />)

View File

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

View File

@ -24,7 +24,15 @@
}
.center {
text-align: center;
text-align: center;
}
.left {
text-align: left;
}
.right {
text-align: right;
}
.sm {

View File

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

View File

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

View File

@ -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)
}
})
})

View File

@ -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) => (

View File

@ -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*"

View File

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

View File

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

View File

@ -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"
/>

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
// @flow
import addSafe from '~/routes/safe/store/actions/addSafe'
export type AddSafe = typeof addSafe
export type Actions = {
addSafe: typeof addSafe,
}

View File

@ -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>
*/

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,10 @@
// @flow
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
export type Actions = {
fetchTransactions: typeof fetchTransactions,
}
export default {
fetchTransactions,
}

View File

@ -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'))
}

View File

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

View File

@ -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,
})

View File

@ -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)
})
})

View File

@ -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)
}

View File

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

View File

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

View File

@ -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 />
))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
})

View File

@ -0,0 +1,10 @@
// @flow
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
export type Actions = {
fetchTransactions: typeof fetchTransactions,
}
export default {
fetchTransactions,
}

View File

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

View File

@ -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,
)
}

View File

@ -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,
})

View File

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

View File

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

View File

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

View File

@ -0,0 +1,10 @@
// @flow
import fetchDailyLimit from '~/routes/safe/store/actions/fetchDailyLimit'
export type Actions = {
fetchDailyLimit: typeof fetchDailyLimit,
}
export default {
fetchDailyLimit,
}

View File

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

View File

@ -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,
})

View File

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

View File

@ -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')
})
})

View File

@ -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,
}

View File

@ -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>
)
}

View File

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