Merge pull request #47 from gnosis/feature/WA-232-custom-tokens

WA-232 Display & Transfer custom tokens
This commit is contained in:
Adolfo Panizo 2018-07-27 13:27:32 +02:00 committed by GitHub
commit 84ffda916c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
149 changed files with 47492 additions and 18157 deletions

View File

@ -1,7 +1,7 @@
import 'babel-polyfill'
import { addDecorator, configure } from '@storybook/react'
import { withKnobs } from '@storybook/addon-knobs'
import { MuiThemeProvider } from 'material-ui/styles'
import { MuiThemeProvider } from '@material-ui/core/styles'
import * as React from 'react'
import { Provider } from 'react-redux'
import StoryRouter from 'storybook-router'

View File

@ -101,6 +101,7 @@
"webpack-manifest-plugin": "^2.0.0-rc.2"
},
"dependencies": {
"@gnosis.pm/util-contracts": "^0.2.14",
"@material-ui/core": "^1.2.1",
"@material-ui/icons": "^1.1.0",
"final-form": "^4.2.1",

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -52,8 +52,8 @@
],
"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",
"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:13;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:13;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:13;;;;;;;;;;;;;;;;;;;;;;;1302:118;;8:9:-1;5:2;;;30:1;27;20:12;5:2;1302:118:13;;;;;;;;;;;;;;;;;;;;;;;;;;;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": {
@ -82,7 +82,7 @@
"id": 2,
"nodeType": "ImportDirective",
"scope": 24,
"sourceUnit": 1612,
"sourceUnit": 2989,
"src": "24:21:0",
"symbolAliases": [],
"unitAlias": ""
@ -96,10 +96,10 @@
"id": 3,
"name": "Proxy",
"nodeType": "UserDefinedTypeName",
"referencedDeclaration": 1611,
"referencedDeclaration": 2988,
"src": "392:5:0",
"typeDescriptions": {
"typeIdentifier": "t_contract$_Proxy_$1611",
"typeIdentifier": "t_contract$_Proxy_$2988",
"typeString": "contract Proxy"
}
},
@ -109,7 +109,7 @@
}
],
"contractDependencies": [
1611
2988
],
"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>",
@ -117,7 +117,7 @@
"id": 23,
"linearizedBaseContracts": [
23,
1611
2988
],
"name": "DelegateConstructorProxy",
"nodeType": "ContractDefinition",
@ -264,10 +264,10 @@
"name": "Proxy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1611,
"referencedDeclaration": 2988,
"src": "662:5:0",
"typeDescriptions": {
"typeIdentifier": "t_type$_t_contract$_Proxy_$1611_$",
"typeIdentifier": "t_type$_t_contract$_Proxy_$2988_$",
"typeString": "type(contract Proxy)"
}
},
@ -382,7 +382,7 @@
"id": 2,
"nodeType": "ImportDirective",
"scope": 24,
"sourceUnit": 1612,
"sourceUnit": 2989,
"src": "24:21:0",
"symbolAliases": [],
"unitAlias": ""
@ -396,10 +396,10 @@
"id": 3,
"name": "Proxy",
"nodeType": "UserDefinedTypeName",
"referencedDeclaration": 1611,
"referencedDeclaration": 2988,
"src": "392:5:0",
"typeDescriptions": {
"typeIdentifier": "t_contract$_Proxy_$1611",
"typeIdentifier": "t_contract$_Proxy_$2988",
"typeString": "contract Proxy"
}
},
@ -409,7 +409,7 @@
}
],
"contractDependencies": [
1611
2988
],
"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>",
@ -417,7 +417,7 @@
"id": 23,
"linearizedBaseContracts": [
23,
1611
2988
],
"name": "DelegateConstructorProxy",
"nodeType": "ContractDefinition",
@ -564,10 +564,10 @@
"name": "Proxy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1611,
"referencedDeclaration": 2988,
"src": "662:5:0",
"typeDescriptions": {
"typeIdentifier": "t_type$_t_contract$_Proxy_$1611_$",
"typeIdentifier": "t_type$_t_contract$_Proxy_$2988_$",
"typeString": "type(contract Proxy)"
}
},
@ -662,5 +662,5 @@
},
"networks": {},
"schemaVersion": "2.0.0",
"updatedAt": "2018-06-18T14:44:09.445Z"
"updatedAt": "2018-06-29T09:01:22.054Z"
}

File diff suppressed because it is too large Load Diff

View File

@ -3,29 +3,29 @@
"abi": [],
"bytecode": "0x6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820641ab8b295edfaa2b1c8a8e0ae7d17ea2f4c8b95ea27e45d8947ed9a4799ca1f0029",
"deployedBytecode": "0x6080604052600080fd00a165627a7a72305820641ab8b295edfaa2b1c8a8e0ae7d17ea2f4c8b95ea27e45d8947ed9a4799ca1f0029",
"sourceMap": "115:95:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;115:95:0;;;;;;;",
"deployedSourceMap": "115:95:0:-;;;;;",
"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": [
6
30
]
},
"id": 7,
"id": 31,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 1,
"id": 25,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:0"
"src": "0:23:1"
},
{
"baseContracts": [],
@ -33,66 +33,66 @@
"contractKind": "contract",
"documentation": "@title Enum - Collection of enums\n @author Richard Meissner - <richard@gnosis.pm>",
"fullyImplemented": true,
"id": 6,
"id": 30,
"linearizedBaseContracts": [
6
30
],
"name": "Enum",
"nodeType": "ContractDefinition",
"nodes": [
{
"canonicalName": "Enum.Operation",
"id": 5,
"id": 29,
"members": [
{
"id": 2,
"id": 26,
"name": "Call",
"nodeType": "EnumValue",
"src": "160:4:0"
"src": "160:4:1"
},
{
"id": 3,
"id": 27,
"name": "DelegateCall",
"nodeType": "EnumValue",
"src": "174:12:0"
"src": "174:12:1"
},
{
"id": 4,
"id": 28,
"name": "Create",
"nodeType": "EnumValue",
"src": "196:6:0"
"src": "196:6:1"
}
],
"name": "Operation",
"nodeType": "EnumDefinition",
"src": "135:73:0"
"src": "135:73:1"
}
],
"scope": 7,
"src": "115:95:0"
"scope": 31,
"src": "115:95:1"
}
],
"src": "0:211:0"
"src": "0:211:1"
},
"legacyAST": {
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/Enum.sol",
"exportedSymbols": {
"Enum": [
6
30
]
},
"id": 7,
"id": 31,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 1,
"id": 25,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:0"
"src": "0:23:1"
},
{
"baseContracts": [],
@ -100,46 +100,46 @@
"contractKind": "contract",
"documentation": "@title Enum - Collection of enums\n @author Richard Meissner - <richard@gnosis.pm>",
"fullyImplemented": true,
"id": 6,
"id": 30,
"linearizedBaseContracts": [
6
30
],
"name": "Enum",
"nodeType": "ContractDefinition",
"nodes": [
{
"canonicalName": "Enum.Operation",
"id": 5,
"id": 29,
"members": [
{
"id": 2,
"id": 26,
"name": "Call",
"nodeType": "EnumValue",
"src": "160:4:0"
"src": "160:4:1"
},
{
"id": 3,
"id": 27,
"name": "DelegateCall",
"nodeType": "EnumValue",
"src": "174:12:0"
"src": "174:12:1"
},
{
"id": 4,
"id": 28,
"name": "Create",
"nodeType": "EnumValue",
"src": "196:6:0"
"src": "196:6:1"
}
],
"name": "Operation",
"nodeType": "EnumDefinition",
"src": "135:73:0"
"src": "135:73:1"
}
],
"scope": 7,
"src": "115:95:0"
"scope": 31,
"src": "115:95:1"
}
],
"src": "0:211:0"
"src": "0:211:1"
},
"compiler": {
"name": "solc",
@ -147,5 +147,5 @@
},
"networks": {},
"schemaVersion": "2.0.0",
"updatedAt": "2018-06-20T07:57:26.985Z"
"updatedAt": "2018-06-29T09:01:22.054Z"
}

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

@ -18,38 +18,38 @@
],
"bytecode": "0x608060405234801561001057600080fd5b50610276806100206000396000f300608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637de7edef14610046575b600080fd5b34801561005257600080fd5b50610087600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610089565b005b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610152576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001807f4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207481526020017f68697320636f6e7472616374000000000000000000000000000000000000000081525060400191505060405180910390fd5b60008173ffffffffffffffffffffffffffffffffffffffff1614151515610207576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505600a165627a7a72305820243ca7a44eb0464a47c14309cc3a29e407df6e966674981a787df22c0d9280220029",
"deployedBytecode": "0x608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637de7edef14610046575b600080fd5b34801561005257600080fd5b50610087600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610089565b005b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610152576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001807f4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207481526020017f68697320636f6e7472616374000000000000000000000000000000000000000081525060400191505060405180910390fd5b60008173ffffffffffffffffffffffffffffffffffffffff1614151515610207576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505600a165627a7a72305820243ca7a44eb0464a47c14309cc3a29e407df6e966674981a787df22c0d9280220029",
"sourceMap": "203:673:4:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;203:673:4;;;;;;;",
"deployedSourceMap": "203:673:4:-;;;;;;;;;;;;;;;;;;;;;;;;626:248;;8:9:-1;5:2;;;30:1;27;20:12;5:2;626:248:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;244:4:8;222:27;;:10;:27;;;214:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;791:1:4;776:11;:16;;;;768:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;856:11;843:10;;:24;;;;;;;;;;;;;;;;;;626:248;:::o",
"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:16;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": [
608
632
]
},
"id": 609,
"id": 633,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 583,
"id": 607,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:4"
"src": "0:23:5"
},
{
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
"file": "./SelfAuthorized.sol",
"id": 584,
"id": 608,
"nodeType": "ImportDirective",
"scope": 609,
"sourceUnit": 1450,
"src": "24:30:4",
"scope": 633,
"sourceUnit": 3066,
"src": "24:30:5",
"symbolAliases": [],
"unitAlias": ""
},
@ -59,42 +59,42 @@
"arguments": null,
"baseName": {
"contractScope": null,
"id": 585,
"id": 609,
"name": "SelfAuthorized",
"nodeType": "UserDefinedTypeName",
"referencedDeclaration": 1449,
"src": "226:14:4",
"referencedDeclaration": 3065,
"src": "226:14:5",
"typeDescriptions": {
"typeIdentifier": "t_contract$_SelfAuthorized_$1449",
"typeIdentifier": "t_contract$_SelfAuthorized_$3065",
"typeString": "contract SelfAuthorized"
}
},
"id": 586,
"id": 610,
"nodeType": "InheritanceSpecifier",
"src": "226:14:4"
"src": "226:14:5"
}
],
"contractDependencies": [
1449
3065
],
"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": 608,
"id": 632,
"linearizedBaseContracts": [
608,
1449
632,
3065
],
"name": "MasterCopy",
"nodeType": "ContractDefinition",
"nodes": [
{
"constant": false,
"id": 588,
"id": 612,
"name": "masterCopy",
"nodeType": "VariableDeclaration",
"scope": 608,
"src": "465:18:4",
"scope": 632,
"src": "465:18:5",
"stateVariable": true,
"storageLocation": "default",
"typeDescriptions": {
@ -102,10 +102,10 @@
"typeString": "address"
},
"typeName": {
"id": 587,
"id": 611,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "465:7:4",
"src": "465:7:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -116,9 +116,9 @@
},
{
"body": {
"id": 606,
"id": 630,
"nodeType": "Block",
"src": "711:163:4",
"src": "711:163:5",
"statements": [
{
"expression": {
@ -130,19 +130,19 @@
"typeIdentifier": "t_address",
"typeString": "address"
},
"id": 598,
"id": 622,
"isConstant": false,
"isLValue": false,
"isPure": false,
"lValueRequested": false,
"leftExpression": {
"argumentTypes": null,
"id": 596,
"id": 620,
"name": "_masterCopy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 590,
"src": "776:11:4",
"referencedDeclaration": 614,
"src": "776:11:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -153,14 +153,14 @@
"rightExpression": {
"argumentTypes": null,
"hexValue": "30",
"id": 597,
"id": 621,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "number",
"lValueRequested": false,
"nodeType": "Literal",
"src": "791:1:4",
"src": "791:1:5",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_rational_0_by_1",
@ -168,7 +168,7 @@
},
"value": "0"
},
"src": "776:16:4",
"src": "776:16:5",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
@ -177,14 +177,14 @@
{
"argumentTypes": null,
"hexValue": "496e76616c6964206d617374657220636f707920616464726573732070726f7669646564",
"id": 599,
"id": 623,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "string",
"lValueRequested": false,
"nodeType": "Literal",
"src": "794:38:4",
"src": "794:38:5",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87",
@ -204,21 +204,21 @@
"typeString": "literal_string \"Invalid master copy address provided\""
}
],
"id": 595,
"id": 619,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
1776,
1777
4039,
4040
],
"referencedDeclaration": 1777,
"src": "768:7:4",
"referencedDeclaration": 4040,
"src": "768:7:5",
"typeDescriptions": {
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
"typeString": "function (bool,string memory) pure"
}
},
"id": 600,
"id": 624,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -226,32 +226,32 @@
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "768:65:4",
"src": "768:65:5",
"typeDescriptions": {
"typeIdentifier": "t_tuple$__$",
"typeString": "tuple()"
}
},
"id": 601,
"id": 625,
"nodeType": "ExpressionStatement",
"src": "768:65:4"
"src": "768:65:5"
},
{
"expression": {
"argumentTypes": null,
"id": 604,
"id": 628,
"isConstant": false,
"isLValue": false,
"isPure": false,
"lValueRequested": false,
"leftHandSide": {
"argumentTypes": null,
"id": 602,
"id": 626,
"name": "masterCopy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 588,
"src": "843:10:4",
"referencedDeclaration": 612,
"src": "843:10:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -261,68 +261,68 @@
"operator": "=",
"rightHandSide": {
"argumentTypes": null,
"id": 603,
"id": 627,
"name": "_masterCopy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 590,
"src": "856:11:4",
"referencedDeclaration": 614,
"src": "856:11:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"src": "843:24:4",
"src": "843:24:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"id": 605,
"id": 629,
"nodeType": "ExpressionStatement",
"src": "843:24:4"
"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": 607,
"id": 631,
"implemented": true,
"isConstructor": false,
"isDeclaredConst": false,
"modifiers": [
{
"arguments": null,
"id": 593,
"id": 617,
"modifierName": {
"argumentTypes": null,
"id": 592,
"id": 616,
"name": "authorized",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1448,
"src": "696:10:4",
"referencedDeclaration": 3064,
"src": "696:10:5",
"typeDescriptions": {
"typeIdentifier": "t_modifier$__$",
"typeString": "modifier ()"
}
},
"nodeType": "ModifierInvocation",
"src": "696:10:4"
"src": "696:10:5"
}
],
"name": "changeMasterCopy",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 591,
"id": 615,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 590,
"id": 614,
"name": "_masterCopy",
"nodeType": "VariableDeclaration",
"scope": 607,
"src": "652:19:4",
"scope": 631,
"src": "652:19:5",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
@ -330,10 +330,10 @@
"typeString": "address"
},
"typeName": {
"id": 589,
"id": 613,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "652:7:4",
"src": "652:7:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -343,56 +343,56 @@
"visibility": "internal"
}
],
"src": "651:21:4"
"src": "651:21:5"
},
"payable": false,
"returnParameters": {
"id": 594,
"id": 618,
"nodeType": "ParameterList",
"parameters": [],
"src": "711:0:4"
"src": "711:0:5"
},
"scope": 608,
"src": "626:248:4",
"scope": 632,
"src": "626:248:5",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "public"
}
],
"scope": 609,
"src": "203:673:4"
"scope": 633,
"src": "203:673:5"
}
],
"src": "0:877:4"
"src": "0:877:5"
},
"legacyAST": {
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/MasterCopy.sol",
"exportedSymbols": {
"MasterCopy": [
608
632
]
},
"id": 609,
"id": 633,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 583,
"id": 607,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:4"
"src": "0:23:5"
},
{
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
"file": "./SelfAuthorized.sol",
"id": 584,
"id": 608,
"nodeType": "ImportDirective",
"scope": 609,
"sourceUnit": 1450,
"src": "24:30:4",
"scope": 633,
"sourceUnit": 3066,
"src": "24:30:5",
"symbolAliases": [],
"unitAlias": ""
},
@ -402,42 +402,42 @@
"arguments": null,
"baseName": {
"contractScope": null,
"id": 585,
"id": 609,
"name": "SelfAuthorized",
"nodeType": "UserDefinedTypeName",
"referencedDeclaration": 1449,
"src": "226:14:4",
"referencedDeclaration": 3065,
"src": "226:14:5",
"typeDescriptions": {
"typeIdentifier": "t_contract$_SelfAuthorized_$1449",
"typeIdentifier": "t_contract$_SelfAuthorized_$3065",
"typeString": "contract SelfAuthorized"
}
},
"id": 586,
"id": 610,
"nodeType": "InheritanceSpecifier",
"src": "226:14:4"
"src": "226:14:5"
}
],
"contractDependencies": [
1449
3065
],
"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": 608,
"id": 632,
"linearizedBaseContracts": [
608,
1449
632,
3065
],
"name": "MasterCopy",
"nodeType": "ContractDefinition",
"nodes": [
{
"constant": false,
"id": 588,
"id": 612,
"name": "masterCopy",
"nodeType": "VariableDeclaration",
"scope": 608,
"src": "465:18:4",
"scope": 632,
"src": "465:18:5",
"stateVariable": true,
"storageLocation": "default",
"typeDescriptions": {
@ -445,10 +445,10 @@
"typeString": "address"
},
"typeName": {
"id": 587,
"id": 611,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "465:7:4",
"src": "465:7:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -459,9 +459,9 @@
},
{
"body": {
"id": 606,
"id": 630,
"nodeType": "Block",
"src": "711:163:4",
"src": "711:163:5",
"statements": [
{
"expression": {
@ -473,19 +473,19 @@
"typeIdentifier": "t_address",
"typeString": "address"
},
"id": 598,
"id": 622,
"isConstant": false,
"isLValue": false,
"isPure": false,
"lValueRequested": false,
"leftExpression": {
"argumentTypes": null,
"id": 596,
"id": 620,
"name": "_masterCopy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 590,
"src": "776:11:4",
"referencedDeclaration": 614,
"src": "776:11:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -496,14 +496,14 @@
"rightExpression": {
"argumentTypes": null,
"hexValue": "30",
"id": 597,
"id": 621,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "number",
"lValueRequested": false,
"nodeType": "Literal",
"src": "791:1:4",
"src": "791:1:5",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_rational_0_by_1",
@ -511,7 +511,7 @@
},
"value": "0"
},
"src": "776:16:4",
"src": "776:16:5",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
@ -520,14 +520,14 @@
{
"argumentTypes": null,
"hexValue": "496e76616c6964206d617374657220636f707920616464726573732070726f7669646564",
"id": 599,
"id": 623,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "string",
"lValueRequested": false,
"nodeType": "Literal",
"src": "794:38:4",
"src": "794:38:5",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87",
@ -547,21 +547,21 @@
"typeString": "literal_string \"Invalid master copy address provided\""
}
],
"id": 595,
"id": 619,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
1776,
1777
4039,
4040
],
"referencedDeclaration": 1777,
"src": "768:7:4",
"referencedDeclaration": 4040,
"src": "768:7:5",
"typeDescriptions": {
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
"typeString": "function (bool,string memory) pure"
}
},
"id": 600,
"id": 624,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -569,32 +569,32 @@
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "768:65:4",
"src": "768:65:5",
"typeDescriptions": {
"typeIdentifier": "t_tuple$__$",
"typeString": "tuple()"
}
},
"id": 601,
"id": 625,
"nodeType": "ExpressionStatement",
"src": "768:65:4"
"src": "768:65:5"
},
{
"expression": {
"argumentTypes": null,
"id": 604,
"id": 628,
"isConstant": false,
"isLValue": false,
"isPure": false,
"lValueRequested": false,
"leftHandSide": {
"argumentTypes": null,
"id": 602,
"id": 626,
"name": "masterCopy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 588,
"src": "843:10:4",
"referencedDeclaration": 612,
"src": "843:10:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -604,68 +604,68 @@
"operator": "=",
"rightHandSide": {
"argumentTypes": null,
"id": 603,
"id": 627,
"name": "_masterCopy",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 590,
"src": "856:11:4",
"referencedDeclaration": 614,
"src": "856:11:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"src": "843:24:4",
"src": "843:24:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"id": 605,
"id": 629,
"nodeType": "ExpressionStatement",
"src": "843:24:4"
"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": 607,
"id": 631,
"implemented": true,
"isConstructor": false,
"isDeclaredConst": false,
"modifiers": [
{
"arguments": null,
"id": 593,
"id": 617,
"modifierName": {
"argumentTypes": null,
"id": 592,
"id": 616,
"name": "authorized",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1448,
"src": "696:10:4",
"referencedDeclaration": 3064,
"src": "696:10:5",
"typeDescriptions": {
"typeIdentifier": "t_modifier$__$",
"typeString": "modifier ()"
}
},
"nodeType": "ModifierInvocation",
"src": "696:10:4"
"src": "696:10:5"
}
],
"name": "changeMasterCopy",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 591,
"id": 615,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 590,
"id": 614,
"name": "_masterCopy",
"nodeType": "VariableDeclaration",
"scope": 607,
"src": "652:19:4",
"scope": 631,
"src": "652:19:5",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
@ -673,10 +673,10 @@
"typeString": "address"
},
"typeName": {
"id": 589,
"id": 613,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "652:7:4",
"src": "652:7:5",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -686,27 +686,27 @@
"visibility": "internal"
}
],
"src": "651:21:4"
"src": "651:21:5"
},
"payable": false,
"returnParameters": {
"id": 594,
"id": 618,
"nodeType": "ParameterList",
"parameters": [],
"src": "711:0:4"
"src": "711:0:5"
},
"scope": 608,
"src": "626:248:4",
"scope": 632,
"src": "626:248:5",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "public"
}
],
"scope": 609,
"src": "203:673:4"
"scope": 633,
"src": "203:673:5"
}
],
"src": "0:877:4"
"src": "0:877:5"
},
"compiler": {
"name": "solc",
@ -714,5 +714,5 @@
},
"networks": {},
"schemaVersion": "2.0.0",
"updatedAt": "2018-06-20T07:57:27.004Z"
"updatedAt": "2018-06-29T09:01:22.057Z"
}

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

@ -18,29 +18,29 @@
],
"bytecode": "0x608060405234801561001057600080fd5b50610169806100206000396000f300608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638d80ff0a14610046575b600080fd5b34801561005257600080fd5b506100ad600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506100af565b005b805160205b81811015610138578083015160208201840151604083018501516080840186015160a085018701600085600081146100f357600181146101035761010e565b6000808585888a5af1915061010e565b6000808585895af491505b50600081141561011d57600080fd5b602080601f8501040260a001870196505050505050506100b4565b5050505600a165627a7a72305820c1abf4988401674d6d6a5ceca56de123817c61305360ceffe26ebab59a828b7d0029",
"deployedBytecode": "0x608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638d80ff0a14610046575b600080fd5b34801561005257600080fd5b506100ad600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506100af565b005b805160205b81811015610138578083015160208201840151604083018501516080840186015160a085018701600085600081146100f357600181146101035761010e565b6000808585888a5af1915061010e565b6000808585895af491505b50600081141561011d57600080fd5b602080601f8501040260a001870196505050505050506100b4565b5050505600a165627a7a72305820c1abf4988401674d6d6a5ceca56de123817c61305360ceffe26ebab59a828b7d0029",
"sourceMap": "253:1424:17:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;253:1424:17;;;;;;;",
"deployedSourceMap": "253:1424:17:-;;;;;;;;;;;;;;;;;;;;;;;;695:980;;8:9:-1;5:2;;;30:1;27;20:12;5:2;695:980:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;870:12;864:19;905:4;922:737;936:6;933:1;930:13;922:737;;;1007:1;993:12;989:20;983:27;1068:4;1065:1;1061:12;1047;1043:31;1037:38;1136:4;1133:1;1129:12;1115;1111:31;1105:38;1209:4;1206:1;1202:12;1188;1184:31;1178:38;1270:4;1267:1;1263:12;1249;1245:31;1308:1;1333:9;1365:1;1360:66;;;;1448:1;1443:67;;;;1326:184;;1360:66;1422:1;1419;1407:10;1401:4;1394:5;1390:2;1385:3;1380:44;1369:55;;1360:66;;1443:67;1506:1;1503;1491:10;1485:4;1481:2;1476:3;1463:45;1452:56;;1326:184;;1542:1;1533:7;1530:14;1527:2;;;1557:1;1554;1547:12;1527:2;1638:4;1631;1624;1612:10;1608:21;1604:32;1600:43;1594:4;1590:54;1587:1;1583:62;1578:67;;948:711;;;;;;922:737;;;836:833;;;:::o",
"sourceMap": "253:1424:19:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;253:1424:19;;;;;;;",
"deployedSourceMap": "253:1424:19:-;;;;;;;;;;;;;;;;;;;;;;;;695:980;;8:9:-1;5:2;;;30:1;27;20:12;5:2;695:980:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;870:12;864:19;905:4;922:737;936:6;933:1;930:13;922:737;;;1007:1;993:12;989:20;983:27;1068:4;1065:1;1061:12;1047;1043:31;1037:38;1136:4;1133:1;1129:12;1115;1111:31;1105:38;1209:4;1206:1;1202:12;1188;1184:31;1178:38;1270:4;1267:1;1263:12;1249;1245:31;1308:1;1333:9;1365:1;1360:66;;;;1448:1;1443:67;;;;1326:184;;1360:66;1422:1;1419;1407:10;1401:4;1394:5;1390:2;1385:3;1380:44;1369:55;;1360:66;;1443:67;1506:1;1503;1491:10;1485:4;1481:2;1476:3;1463:45;1452:56;;1326:184;;1542:1;1533:7;1530:14;1527:2;;;1557:1;1554;1547:12;1527:2;1638:4;1631;1624;1612:10;1608:21;1604:32;1600:43;1594:4;1590:54;1587:1;1583:62;1578:67;;948:711;;;;;;922:737;;;836:833;;;:::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 a \n /// tuple(operation,address,uint256,bytes), where operation \n /// can be 0 for a call or 1 for a delegatecall. The bytes \n /// of all 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 operation := mload(add(transactions, i))\n let to := mload(add(transactions, add(i, 0x20)))\n let value := mload(add(transactions, add(i, 0x40)))\n let dataLength := mload(add(transactions, add(i, 0x80)))\n let data := add(transactions, add(i, 0xa0))\n let success := 0\n switch operation \n case 0 { success := call(gas, to, value, data, dataLength, 0, 0) }\n case 1 { success := delegatecall(gas, to, data, dataLength, 0, 0) }\n if eq(success, 0) { revert(0, 0) }\n i := add(i, add(0xa0, 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": [
1840
3176
]
},
"id": 1841,
"id": 3177,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 1832,
"id": 3168,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:17"
"src": "0:23:19"
},
{
"baseContracts": [],
@ -48,85 +48,85 @@
"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": 1840,
"id": 3176,
"linearizedBaseContracts": [
1840
3176
],
"name": "MultiSend",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 1838,
"id": 3174,
"nodeType": "Block",
"src": "753:922:17",
"src": "753:922:19",
"statements": [
{
"externalReferences": [
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "870:12:17",
"src": "870:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "993:12:17",
"src": "993:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1047:12:17",
"src": "1047:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1188:12:17",
"src": "1188:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1115:12:17",
"src": "1115:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1249:12:17",
"src": "1249:12:19",
"valueSize": 1
}
}
],
"id": 1837,
"id": 3173,
"nodeType": "InlineAssembly",
"operations": "{\n let length := mload(transactions)\n let i := 0x20\n for {\n }\n lt(i, length)\n {\n }\n {\n let operation := mload(add(transactions, i))\n let to := mload(add(transactions, add(i, 0x20)))\n let value := mload(add(transactions, add(i, 0x40)))\n let dataLength := mload(add(transactions, add(i, 0x80)))\n let data := add(transactions, add(i, 0xa0))\n let success := 0\n switch operation\n case 0 {\n success := call(gas(), to, value, data, dataLength, 0, 0)\n }\n case 1 {\n success := delegatecall(gas(), to, data, dataLength, 0, 0)\n }\n if eq(success, 0)\n {\n revert(0, 0)\n }\n i := add(i, add(0xa0, mul(div(add(dataLength, 0x1f), 0x20), 0x20)))\n }\n}",
"src": "827:848:17"
"src": "827:848:19"
}
]
},
"documentation": "@dev Sends multiple transactions and reverts all if one fails.\n @param transactions Encoded transactions. Each transaction is encoded as a \n tuple(operation,address,uint256,bytes), where operation \n can be 0 for a call or 1 for a delegatecall. The bytes \n of all encoded transactions are concatenated to form the input.",
"id": 1839,
"id": 3175,
"implemented": true,
"isConstructor": false,
"isDeclaredConst": false,
@ -134,16 +134,16 @@
"name": "multiSend",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 1835,
"id": 3171,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 1834,
"id": 3170,
"name": "transactions",
"nodeType": "VariableDeclaration",
"scope": 1839,
"src": "714:18:17",
"scope": 3175,
"src": "714:18:19",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
@ -151,10 +151,10 @@
"typeString": "bytes"
},
"typeName": {
"id": 1833,
"id": 3169,
"name": "bytes",
"nodeType": "ElementaryTypeName",
"src": "714:5:17",
"src": "714:5:19",
"typeDescriptions": {
"typeIdentifier": "t_bytes_storage_ptr",
"typeString": "bytes"
@ -164,47 +164,47 @@
"visibility": "internal"
}
],
"src": "713:20:17"
"src": "713:20:19"
},
"payable": false,
"returnParameters": {
"id": 1836,
"id": 3172,
"nodeType": "ParameterList",
"parameters": [],
"src": "753:0:17"
"src": "753:0:19"
},
"scope": 1840,
"src": "695:980:17",
"scope": 3176,
"src": "695:980:19",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "public"
}
],
"scope": 1841,
"src": "253:1424:17"
"scope": 3177,
"src": "253:1424:19"
}
],
"src": "0:1678:17"
"src": "0:1678:19"
},
"legacyAST": {
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/libraries/MultiSend.sol",
"exportedSymbols": {
"MultiSend": [
1840
3176
]
},
"id": 1841,
"id": 3177,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 1832,
"id": 3168,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:17"
"src": "0:23:19"
},
{
"baseContracts": [],
@ -212,85 +212,85 @@
"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": 1840,
"id": 3176,
"linearizedBaseContracts": [
1840
3176
],
"name": "MultiSend",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 1838,
"id": 3174,
"nodeType": "Block",
"src": "753:922:17",
"src": "753:922:19",
"statements": [
{
"externalReferences": [
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "870:12:17",
"src": "870:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "993:12:17",
"src": "993:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1047:12:17",
"src": "1047:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1188:12:17",
"src": "1188:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1115:12:17",
"src": "1115:12:19",
"valueSize": 1
}
},
{
"transactions": {
"declaration": 1834,
"declaration": 3170,
"isOffset": false,
"isSlot": false,
"src": "1249:12:17",
"src": "1249:12:19",
"valueSize": 1
}
}
],
"id": 1837,
"id": 3173,
"nodeType": "InlineAssembly",
"operations": "{\n let length := mload(transactions)\n let i := 0x20\n for {\n }\n lt(i, length)\n {\n }\n {\n let operation := mload(add(transactions, i))\n let to := mload(add(transactions, add(i, 0x20)))\n let value := mload(add(transactions, add(i, 0x40)))\n let dataLength := mload(add(transactions, add(i, 0x80)))\n let data := add(transactions, add(i, 0xa0))\n let success := 0\n switch operation\n case 0 {\n success := call(gas(), to, value, data, dataLength, 0, 0)\n }\n case 1 {\n success := delegatecall(gas(), to, data, dataLength, 0, 0)\n }\n if eq(success, 0)\n {\n revert(0, 0)\n }\n i := add(i, add(0xa0, mul(div(add(dataLength, 0x1f), 0x20), 0x20)))\n }\n}",
"src": "827:848:17"
"src": "827:848:19"
}
]
},
"documentation": "@dev Sends multiple transactions and reverts all if one fails.\n @param transactions Encoded transactions. Each transaction is encoded as a \n tuple(operation,address,uint256,bytes), where operation \n can be 0 for a call or 1 for a delegatecall. The bytes \n of all encoded transactions are concatenated to form the input.",
"id": 1839,
"id": 3175,
"implemented": true,
"isConstructor": false,
"isDeclaredConst": false,
@ -298,16 +298,16 @@
"name": "multiSend",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 1835,
"id": 3171,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 1834,
"id": 3170,
"name": "transactions",
"nodeType": "VariableDeclaration",
"scope": 1839,
"src": "714:18:17",
"scope": 3175,
"src": "714:18:19",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
@ -315,10 +315,10 @@
"typeString": "bytes"
},
"typeName": {
"id": 1833,
"id": 3169,
"name": "bytes",
"nodeType": "ElementaryTypeName",
"src": "714:5:17",
"src": "714:5:19",
"typeDescriptions": {
"typeIdentifier": "t_bytes_storage_ptr",
"typeString": "bytes"
@ -328,27 +328,27 @@
"visibility": "internal"
}
],
"src": "713:20:17"
"src": "713:20:19"
},
"payable": false,
"returnParameters": {
"id": 1836,
"id": 3172,
"nodeType": "ParameterList",
"parameters": [],
"src": "753:0:17"
"src": "753:0:19"
},
"scope": 1840,
"src": "695:980:17",
"scope": 3176,
"src": "695:980:19",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "public"
}
],
"scope": 1841,
"src": "253:1424:17"
"scope": 3177,
"src": "253:1424:19"
}
],
"src": "0:1678:17"
"src": "0:1678:19"
},
"compiler": {
"name": "solc",
@ -358,28 +358,28 @@
"4": {
"events": {},
"links": {},
"address": "0xe94c33a523bf201e412f601eff906eec0b1d21f5",
"transactionHash": "0x1f04811d6fb97329f6e96a579587fe5a3abe9fe2f202d4b2e27adb78d3de1c48"
"address": "0x7115893bc1477bc22f127bc2d82fcb4dd99e5838",
"transactionHash": "0x1f321cf880f5ec5b189cb1ac1c1c1b71ef8d854fc059c345fea6e6129bb19645"
},
"1529327661534": {
"1530013596495": {
"events": {},
"links": {},
"address": "0xa2a1eb294e1191c1e374f9015a37db35c49e9f26",
"transactionHash": "0x5cd2551fce05a8b864a085616ee54449fdddd56dcdebf1019bebd0839c69ef5f"
"address": "0x18a8eaa498a58752858e5a912c8ff136b0bc6c69",
"transactionHash": "0x7da393438ee13b991e6d94278addb64e86b0c13101efb8a0f0f00d9f86ba51d0"
},
"1529333415702": {
"1530525742205": {
"events": {},
"links": {},
"address": "0xaba1a0da223a2a5e04158bd80b2af7671e27e2c6",
"transactionHash": "0x6e7c0c3947a3a851738677ae63915781ce79fdfb0453030fc63bbcd346f2cf89"
"address": "0x0a25374d1738169d8653e6ee01f464bf4cdc8dea",
"transactionHash": "0x2d7f72a94ced345c372040faa1f51a51f3e8da27a02dd04b9eefef487de3ba05"
},
"1529652204341": {
"1530611935189": {
"events": {},
"links": {},
"address": "0x2013688de17569d52fc4cd4fe5f18821d8b32b2a",
"address": "0x0b98f9d8fbd164d8d4884a997a3ef8e7275d5e0e",
"transactionHash": "0x2d7f72a94ced345c372040faa1f51a51f3e8da27a02dd04b9eefef487de3ba05"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-06-22T07:24:16.274Z"
"updatedAt": "2018-07-03T09:59:18.515Z"
}

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,783 @@
{
"contractName": "SecuredTokenTransfer",
"abi": [],
"bytecode": "0x6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a723058203704d691dd103c6cda573e69dcd48a2de4d0edf672da44096a50180687c79a770029",
"deployedBytecode": "0x6080604052600080fd00a165627a7a723058203704d691dd103c6cda573e69dcd48a2de4d0edf672da44096a50180687c79a770029",
"sourceMap": "133:1051:15:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;133:1051:15;;;;;;;",
"deployedSourceMap": "133:1051:15:-;;;;;",
"source": "pragma solidity 0.4.24;\n\n\n/// @title SecuredTokenTransfer - Secure token transfer\n/// @author Richard Meissner - <richard@gnosis.pm>\ncontract SecuredTokenTransfer {\n\n /// @dev Transfers a token and returns if it was a success\n /// @param token Token that should be transferred\n /// @param receiver Receiver to whom the token should be transferred\n /// @param amount The amount of tokens that should be transferred\n function transferToken (\n address token, \n address receiver,\n uint256 amount\n )\n internal\n returns (bool transferred)\n {\n bytes memory data = abi.encodeWithSignature(\"transfer(address,uint256)\", receiver, amount);\n // solium-disable-next-line security/no-inline-assembly\n assembly {\n let success := call(sub(gas, 10000), token, 0, add(data, 0x20), mload(data), 0, 0)\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize)\n switch returndatasize \n case 0 { transferred := success }\n case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(ptr)))) }\n default { transferred := 0 }\n }\n }\n}\n",
"sourcePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SecuredTokenTransfer.sol",
"ast": {
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SecuredTokenTransfer.sol",
"exportedSymbols": {
"SecuredTokenTransfer": [
3048
]
},
"id": 3049,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 3025,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:15"
},
{
"baseContracts": [],
"contractDependencies": [],
"contractKind": "contract",
"documentation": "@title SecuredTokenTransfer - Secure token transfer\n @author Richard Meissner - <richard@gnosis.pm>",
"fullyImplemented": true,
"id": 3048,
"linearizedBaseContracts": [
3048
],
"name": "SecuredTokenTransfer",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 3046,
"nodeType": "Block",
"src": "590:592:15",
"statements": [
{
"assignments": [
3037
],
"declarations": [
{
"constant": false,
"id": 3037,
"name": "data",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "600:17:15",
"stateVariable": false,
"storageLocation": "memory",
"typeDescriptions": {
"typeIdentifier": "t_bytes_memory_ptr",
"typeString": "bytes"
},
"typeName": {
"id": 3036,
"name": "bytes",
"nodeType": "ElementaryTypeName",
"src": "600:5:15",
"typeDescriptions": {
"typeIdentifier": "t_bytes_storage_ptr",
"typeString": "bytes"
}
},
"value": null,
"visibility": "internal"
}
],
"id": 3044,
"initialValue": {
"argumentTypes": null,
"arguments": [
{
"argumentTypes": null,
"hexValue": "7472616e7366657228616464726573732c75696e7432353629",
"id": 3040,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "string",
"lValueRequested": false,
"nodeType": "Literal",
"src": "644:27:15",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_stringliteral_a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b",
"typeString": "literal_string \"transfer(address,uint256)\""
},
"value": "transfer(address,uint256)"
},
{
"argumentTypes": null,
"id": 3041,
"name": "receiver",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 3029,
"src": "673:8:15",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
{
"argumentTypes": null,
"id": 3042,
"name": "amount",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 3031,
"src": "683:6:15",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
}
],
"expression": {
"argumentTypes": [
{
"typeIdentifier": "t_stringliteral_a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b",
"typeString": "literal_string \"transfer(address,uint256)\""
},
{
"typeIdentifier": "t_address",
"typeString": "address"
},
{
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
],
"expression": {
"argumentTypes": null,
"id": 3038,
"name": "abi",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 4023,
"src": "620:3:15",
"typeDescriptions": {
"typeIdentifier": "t_magic_abi",
"typeString": "abi"
}
},
"id": 3039,
"isConstant": false,
"isLValue": false,
"isPure": true,
"lValueRequested": false,
"memberName": "encodeWithSignature",
"nodeType": "MemberAccess",
"referencedDeclaration": null,
"src": "620:23:15",
"typeDescriptions": {
"typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$",
"typeString": "function (string memory) pure returns (bytes memory)"
}
},
"id": 3043,
"isConstant": false,
"isLValue": false,
"isPure": false,
"kind": "functionCall",
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "620:70:15",
"typeDescriptions": {
"typeIdentifier": "t_bytes_memory_ptr",
"typeString": "bytes memory"
}
},
"nodeType": "VariableDeclarationStatement",
"src": "600:90:15"
},
{
"externalReferences": [
{
"transferred": {
"declaration": 3034,
"isOffset": false,
"isSlot": false,
"src": "1061:11:15",
"valueSize": 1
}
},
{
"data": {
"declaration": 3037,
"isOffset": false,
"isSlot": false,
"src": "857:4:15",
"valueSize": 1
}
},
{
"token": {
"declaration": 3027,
"isOffset": false,
"isSlot": false,
"src": "824:5:15",
"valueSize": 1
}
},
{
"data": {
"declaration": 3037,
"isOffset": false,
"isSlot": false,
"src": "838:4:15",
"valueSize": 1
}
},
{
"transferred": {
"declaration": 3034,
"isOffset": false,
"isSlot": false,
"src": "1012:11:15",
"valueSize": 1
}
},
{
"transferred": {
"declaration": 3034,
"isOffset": false,
"isSlot": false,
"src": "1148:11:15",
"valueSize": 1
}
}
],
"id": 3045,
"nodeType": "InlineAssembly",
"operations": "{\n let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0)\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n switch returndatasize()\n case 0 {\n transferred := success\n }\n case 0x20 {\n transferred := iszero(or(iszero(success), iszero(mload(ptr))))\n }\n default {\n transferred := 0\n }\n}",
"src": "764:418:15"
}
]
},
"documentation": "@dev Transfers a token and returns if it was a success\n @param token Token that should be transferred\n @param receiver Receiver to whom the token should be transferred\n @param amount The amount of tokens that should be transferred",
"id": 3047,
"implemented": true,
"isConstructor": false,
"isDeclaredConst": false,
"modifiers": [],
"name": "transferToken",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 3032,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 3027,
"name": "token",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "463:13:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
},
"typeName": {
"id": 3026,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "463:7:15",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"value": null,
"visibility": "internal"
},
{
"constant": false,
"id": 3029,
"name": "receiver",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "487:16:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
},
"typeName": {
"id": 3028,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "487:7:15",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"value": null,
"visibility": "internal"
},
{
"constant": false,
"id": 3031,
"name": "amount",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "513:14:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
},
"typeName": {
"id": 3030,
"name": "uint256",
"nodeType": "ElementaryTypeName",
"src": "513:7:15",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"value": null,
"visibility": "internal"
}
],
"src": "453:80:15"
},
"payable": false,
"returnParameters": {
"id": 3035,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 3034,
"name": "transferred",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "568:16:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
},
"typeName": {
"id": 3033,
"name": "bool",
"nodeType": "ElementaryTypeName",
"src": "568:4:15",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
}
},
"value": null,
"visibility": "internal"
}
],
"src": "567:18:15"
},
"scope": 3048,
"src": "430:752:15",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "internal"
}
],
"scope": 3049,
"src": "133:1051:15"
}
],
"src": "0:1185:15"
},
"legacyAST": {
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SecuredTokenTransfer.sol",
"exportedSymbols": {
"SecuredTokenTransfer": [
3048
]
},
"id": 3049,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 3025,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:15"
},
{
"baseContracts": [],
"contractDependencies": [],
"contractKind": "contract",
"documentation": "@title SecuredTokenTransfer - Secure token transfer\n @author Richard Meissner - <richard@gnosis.pm>",
"fullyImplemented": true,
"id": 3048,
"linearizedBaseContracts": [
3048
],
"name": "SecuredTokenTransfer",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 3046,
"nodeType": "Block",
"src": "590:592:15",
"statements": [
{
"assignments": [
3037
],
"declarations": [
{
"constant": false,
"id": 3037,
"name": "data",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "600:17:15",
"stateVariable": false,
"storageLocation": "memory",
"typeDescriptions": {
"typeIdentifier": "t_bytes_memory_ptr",
"typeString": "bytes"
},
"typeName": {
"id": 3036,
"name": "bytes",
"nodeType": "ElementaryTypeName",
"src": "600:5:15",
"typeDescriptions": {
"typeIdentifier": "t_bytes_storage_ptr",
"typeString": "bytes"
}
},
"value": null,
"visibility": "internal"
}
],
"id": 3044,
"initialValue": {
"argumentTypes": null,
"arguments": [
{
"argumentTypes": null,
"hexValue": "7472616e7366657228616464726573732c75696e7432353629",
"id": 3040,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "string",
"lValueRequested": false,
"nodeType": "Literal",
"src": "644:27:15",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_stringliteral_a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b",
"typeString": "literal_string \"transfer(address,uint256)\""
},
"value": "transfer(address,uint256)"
},
{
"argumentTypes": null,
"id": 3041,
"name": "receiver",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 3029,
"src": "673:8:15",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
{
"argumentTypes": null,
"id": 3042,
"name": "amount",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 3031,
"src": "683:6:15",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
}
],
"expression": {
"argumentTypes": [
{
"typeIdentifier": "t_stringliteral_a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b",
"typeString": "literal_string \"transfer(address,uint256)\""
},
{
"typeIdentifier": "t_address",
"typeString": "address"
},
{
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
],
"expression": {
"argumentTypes": null,
"id": 3038,
"name": "abi",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 4023,
"src": "620:3:15",
"typeDescriptions": {
"typeIdentifier": "t_magic_abi",
"typeString": "abi"
}
},
"id": 3039,
"isConstant": false,
"isLValue": false,
"isPure": true,
"lValueRequested": false,
"memberName": "encodeWithSignature",
"nodeType": "MemberAccess",
"referencedDeclaration": null,
"src": "620:23:15",
"typeDescriptions": {
"typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$",
"typeString": "function (string memory) pure returns (bytes memory)"
}
},
"id": 3043,
"isConstant": false,
"isLValue": false,
"isPure": false,
"kind": "functionCall",
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "620:70:15",
"typeDescriptions": {
"typeIdentifier": "t_bytes_memory_ptr",
"typeString": "bytes memory"
}
},
"nodeType": "VariableDeclarationStatement",
"src": "600:90:15"
},
{
"externalReferences": [
{
"transferred": {
"declaration": 3034,
"isOffset": false,
"isSlot": false,
"src": "1061:11:15",
"valueSize": 1
}
},
{
"data": {
"declaration": 3037,
"isOffset": false,
"isSlot": false,
"src": "857:4:15",
"valueSize": 1
}
},
{
"token": {
"declaration": 3027,
"isOffset": false,
"isSlot": false,
"src": "824:5:15",
"valueSize": 1
}
},
{
"data": {
"declaration": 3037,
"isOffset": false,
"isSlot": false,
"src": "838:4:15",
"valueSize": 1
}
},
{
"transferred": {
"declaration": 3034,
"isOffset": false,
"isSlot": false,
"src": "1012:11:15",
"valueSize": 1
}
},
{
"transferred": {
"declaration": 3034,
"isOffset": false,
"isSlot": false,
"src": "1148:11:15",
"valueSize": 1
}
}
],
"id": 3045,
"nodeType": "InlineAssembly",
"operations": "{\n let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0)\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n switch returndatasize()\n case 0 {\n transferred := success\n }\n case 0x20 {\n transferred := iszero(or(iszero(success), iszero(mload(ptr))))\n }\n default {\n transferred := 0\n }\n}",
"src": "764:418:15"
}
]
},
"documentation": "@dev Transfers a token and returns if it was a success\n @param token Token that should be transferred\n @param receiver Receiver to whom the token should be transferred\n @param amount The amount of tokens that should be transferred",
"id": 3047,
"implemented": true,
"isConstructor": false,
"isDeclaredConst": false,
"modifiers": [],
"name": "transferToken",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 3032,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 3027,
"name": "token",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "463:13:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
},
"typeName": {
"id": 3026,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "463:7:15",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"value": null,
"visibility": "internal"
},
{
"constant": false,
"id": 3029,
"name": "receiver",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "487:16:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
},
"typeName": {
"id": 3028,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "487:7:15",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"value": null,
"visibility": "internal"
},
{
"constant": false,
"id": 3031,
"name": "amount",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "513:14:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
},
"typeName": {
"id": 3030,
"name": "uint256",
"nodeType": "ElementaryTypeName",
"src": "513:7:15",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"value": null,
"visibility": "internal"
}
],
"src": "453:80:15"
},
"payable": false,
"returnParameters": {
"id": 3035,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 3034,
"name": "transferred",
"nodeType": "VariableDeclaration",
"scope": 3047,
"src": "568:16:15",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
},
"typeName": {
"id": 3033,
"name": "bool",
"nodeType": "ElementaryTypeName",
"src": "568:4:15",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
}
},
"value": null,
"visibility": "internal"
}
],
"src": "567:18:15"
},
"scope": 3048,
"src": "430:752:15",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "internal"
}
],
"scope": 3049,
"src": "133:1051:15"
}
],
"src": "0:1185:15"
},
"compiler": {
"name": "solc",
"version": "0.4.24+commit.e67f0147.Emscripten.clang"
},
"networks": {},
"schemaVersion": "2.0.0",
"updatedAt": "2018-06-29T09:01:22.084Z"
}

View File

@ -3,29 +3,29 @@
"abi": [],
"bytecode": "0x6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820ec80f1b4520aa5197e4181778f1e2e4fc460002d4a40e2e8e6709c8986067c220029",
"deployedBytecode": "0x6080604052600080fd00a165627a7a72305820ec80f1b4520aa5197e4181778f1e2e4fc460002d4a40e2e8e6709c8986067c220029",
"sourceMap": "152:166:8:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;152:166:8;;;;;;;",
"deployedSourceMap": "152:166:8:-;;;;;",
"sourceMap": "152:166:16:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;152:166:16;;;;;;;",
"deployedSourceMap": "152:166:16:-;;;;;",
"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": [
1449
3065
]
},
"id": 1450,
"id": 3066,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 1434,
"id": 3050,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:8"
"src": "0:23:16"
},
{
"baseContracts": [],
@ -33,18 +33,18 @@
"contractKind": "contract",
"documentation": "@title SelfAuthorized - authorizes current contract to perform actions\n @author Richard Meissner - <richard@gnosis.pm>",
"fullyImplemented": true,
"id": 1449,
"id": 3065,
"linearizedBaseContracts": [
1449
3065
],
"name": "SelfAuthorized",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 1447,
"id": 3063,
"nodeType": "Block",
"src": "204:112:8",
"src": "204:112:16",
"statements": [
{
"expression": {
@ -56,7 +56,7 @@
"typeIdentifier": "t_address",
"typeString": "address"
},
"id": 1442,
"id": 3058,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -65,18 +65,18 @@
"argumentTypes": null,
"expression": {
"argumentTypes": null,
"id": 1437,
"id": 3053,
"name": "msg",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1773,
"src": "222:3:8",
"referencedDeclaration": 4036,
"src": "222:3:16",
"typeDescriptions": {
"typeIdentifier": "t_magic_message",
"typeString": "msg"
}
},
"id": 1438,
"id": 3054,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -84,7 +84,7 @@
"memberName": "sender",
"nodeType": "MemberAccess",
"referencedDeclaration": null,
"src": "222:10:8",
"src": "222:10:16",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -97,14 +97,14 @@
"arguments": [
{
"argumentTypes": null,
"id": 1440,
"id": 3056,
"name": "this",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1788,
"src": "244:4:8",
"referencedDeclaration": 4055,
"src": "244:4:16",
"typeDescriptions": {
"typeIdentifier": "t_contract$_SelfAuthorized_$1449",
"typeIdentifier": "t_contract$_SelfAuthorized_$3065",
"typeString": "contract SelfAuthorized"
}
}
@ -112,24 +112,24 @@
"expression": {
"argumentTypes": [
{
"typeIdentifier": "t_contract$_SelfAuthorized_$1449",
"typeIdentifier": "t_contract$_SelfAuthorized_$3065",
"typeString": "contract SelfAuthorized"
}
],
"id": 1439,
"id": 3055,
"isConstant": false,
"isLValue": false,
"isPure": true,
"lValueRequested": false,
"nodeType": "ElementaryTypeNameExpression",
"src": "236:7:8",
"src": "236:7:16",
"typeDescriptions": {
"typeIdentifier": "t_type$_t_address_$",
"typeString": "type(address)"
},
"typeName": "address"
},
"id": 1441,
"id": 3057,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -137,13 +137,13 @@
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "236:13:8",
"src": "236:13:16",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"src": "222:27:8",
"src": "222:27:16",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
@ -152,14 +152,14 @@
{
"argumentTypes": null,
"hexValue": "4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207468697320636f6e7472616374",
"id": 1443,
"id": 3059,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "string",
"lValueRequested": false,
"nodeType": "Literal",
"src": "251:46:8",
"src": "251:46:16",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733",
@ -179,21 +179,21 @@
"typeString": "literal_string \"Method can only be called from this contract\""
}
],
"id": 1436,
"id": 3052,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
1776,
1777
4039,
4040
],
"referencedDeclaration": 1777,
"src": "214:7:8",
"referencedDeclaration": 4040,
"src": "214:7:16",
"typeDescriptions": {
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
"typeString": "function (bool,string memory) pure"
}
},
"id": 1444,
"id": 3060,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -201,62 +201,62 @@
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "214:84:8",
"src": "214:84:16",
"typeDescriptions": {
"typeIdentifier": "t_tuple$__$",
"typeString": "tuple()"
}
},
"id": 1445,
"id": 3061,
"nodeType": "ExpressionStatement",
"src": "214:84:8"
"src": "214:84:16"
},
{
"id": 1446,
"id": 3062,
"nodeType": "PlaceholderStatement",
"src": "308:1:8"
"src": "308:1:16"
}
]
},
"documentation": null,
"id": 1448,
"id": 3064,
"name": "authorized",
"nodeType": "ModifierDefinition",
"parameters": {
"id": 1435,
"id": 3051,
"nodeType": "ParameterList",
"parameters": [],
"src": "201:2:8"
"src": "201:2:16"
},
"src": "182:134:8",
"src": "182:134:16",
"visibility": "internal"
}
],
"scope": 1450,
"src": "152:166:8"
"scope": 3066,
"src": "152:166:16"
}
],
"src": "0:319:8"
"src": "0:319:16"
},
"legacyAST": {
"absolutePath": "/Users/apanizo/git/gnosis/safe-contracts/contracts/SelfAuthorized.sol",
"exportedSymbols": {
"SelfAuthorized": [
1449
3065
]
},
"id": 1450,
"id": 3066,
"nodeType": "SourceUnit",
"nodes": [
{
"id": 1434,
"id": 3050,
"literals": [
"solidity",
"0.4",
".24"
],
"nodeType": "PragmaDirective",
"src": "0:23:8"
"src": "0:23:16"
},
{
"baseContracts": [],
@ -264,18 +264,18 @@
"contractKind": "contract",
"documentation": "@title SelfAuthorized - authorizes current contract to perform actions\n @author Richard Meissner - <richard@gnosis.pm>",
"fullyImplemented": true,
"id": 1449,
"id": 3065,
"linearizedBaseContracts": [
1449
3065
],
"name": "SelfAuthorized",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 1447,
"id": 3063,
"nodeType": "Block",
"src": "204:112:8",
"src": "204:112:16",
"statements": [
{
"expression": {
@ -287,7 +287,7 @@
"typeIdentifier": "t_address",
"typeString": "address"
},
"id": 1442,
"id": 3058,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -296,18 +296,18 @@
"argumentTypes": null,
"expression": {
"argumentTypes": null,
"id": 1437,
"id": 3053,
"name": "msg",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1773,
"src": "222:3:8",
"referencedDeclaration": 4036,
"src": "222:3:16",
"typeDescriptions": {
"typeIdentifier": "t_magic_message",
"typeString": "msg"
}
},
"id": 1438,
"id": 3054,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -315,7 +315,7 @@
"memberName": "sender",
"nodeType": "MemberAccess",
"referencedDeclaration": null,
"src": "222:10:8",
"src": "222:10:16",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
@ -328,14 +328,14 @@
"arguments": [
{
"argumentTypes": null,
"id": 1440,
"id": 3056,
"name": "this",
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": 1788,
"src": "244:4:8",
"referencedDeclaration": 4055,
"src": "244:4:16",
"typeDescriptions": {
"typeIdentifier": "t_contract$_SelfAuthorized_$1449",
"typeIdentifier": "t_contract$_SelfAuthorized_$3065",
"typeString": "contract SelfAuthorized"
}
}
@ -343,24 +343,24 @@
"expression": {
"argumentTypes": [
{
"typeIdentifier": "t_contract$_SelfAuthorized_$1449",
"typeIdentifier": "t_contract$_SelfAuthorized_$3065",
"typeString": "contract SelfAuthorized"
}
],
"id": 1439,
"id": 3055,
"isConstant": false,
"isLValue": false,
"isPure": true,
"lValueRequested": false,
"nodeType": "ElementaryTypeNameExpression",
"src": "236:7:8",
"src": "236:7:16",
"typeDescriptions": {
"typeIdentifier": "t_type$_t_address_$",
"typeString": "type(address)"
},
"typeName": "address"
},
"id": 1441,
"id": 3057,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -368,13 +368,13 @@
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "236:13:8",
"src": "236:13:16",
"typeDescriptions": {
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"src": "222:27:8",
"src": "222:27:16",
"typeDescriptions": {
"typeIdentifier": "t_bool",
"typeString": "bool"
@ -383,14 +383,14 @@
{
"argumentTypes": null,
"hexValue": "4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d207468697320636f6e7472616374",
"id": 1443,
"id": 3059,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "string",
"lValueRequested": false,
"nodeType": "Literal",
"src": "251:46:8",
"src": "251:46:16",
"subdenomination": null,
"typeDescriptions": {
"typeIdentifier": "t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733",
@ -410,21 +410,21 @@
"typeString": "literal_string \"Method can only be called from this contract\""
}
],
"id": 1436,
"id": 3052,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
1776,
1777
4039,
4040
],
"referencedDeclaration": 1777,
"src": "214:7:8",
"referencedDeclaration": 4040,
"src": "214:7:16",
"typeDescriptions": {
"typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$",
"typeString": "function (bool,string memory) pure"
}
},
"id": 1444,
"id": 3060,
"isConstant": false,
"isLValue": false,
"isPure": false,
@ -432,42 +432,42 @@
"lValueRequested": false,
"names": [],
"nodeType": "FunctionCall",
"src": "214:84:8",
"src": "214:84:16",
"typeDescriptions": {
"typeIdentifier": "t_tuple$__$",
"typeString": "tuple()"
}
},
"id": 1445,
"id": 3061,
"nodeType": "ExpressionStatement",
"src": "214:84:8"
"src": "214:84:16"
},
{
"id": 1446,
"id": 3062,
"nodeType": "PlaceholderStatement",
"src": "308:1:8"
"src": "308:1:16"
}
]
},
"documentation": null,
"id": 1448,
"id": 3064,
"name": "authorized",
"nodeType": "ModifierDefinition",
"parameters": {
"id": 1435,
"id": 3051,
"nodeType": "ParameterList",
"parameters": [],
"src": "201:2:8"
"src": "201:2:16"
},
"src": "182:134:8",
"src": "182:134:16",
"visibility": "internal"
}
],
"scope": 1450,
"src": "152:166:8"
"scope": 3066,
"src": "152:166:16"
}
],
"src": "0:319:8"
"src": "0:319:16"
},
"compiler": {
"name": "solc",
@ -475,5 +475,5 @@
},
"networks": {},
"schemaVersion": "2.0.0",
"updatedAt": "2018-06-20T07:57:27.027Z"
"updatedAt": "2018-06-29T09:01:22.084Z"
}

File diff suppressed because it is too large Load Diff

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

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,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_7" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<g id="ZWC4wa.tif">
<g>
<g>
<path d="M13.5,26c3.7-6.1,7.2-12,11.1-18.4C28.3,14,31.9,19.9,35.6,26c-3.7,2.2-7.4,4.4-11.1,6.6C20.9,30.4,17.2,28.2,13.5,26z
M25.1,20.8c2.7,1.2,5.4,2.5,8.5,3.9c-2.9-4.8-5.6-9.3-8.5-14.1C25.1,14.2,25.1,17.4,25.1,20.8z M15.5,24.7
c3.1-1.4,5.8-2.7,8.4-3.8c0-3.4,0-6.6,0-10.2C21,15.4,18.4,19.9,15.5,24.7z M33.8,25.9c-3-1.4-5.8-2.6-8.7-3.9c0,3.1,0,6,0,9.1
C28,29.3,30.8,27.7,33.8,25.9z M23.9,22c-3,1.4-5.7,2.6-8.6,3.9c3,1.8,5.8,3.4,8.6,5.1C23.9,27.9,23.9,25,23.9,22z"/>
<path d="M24.5,33.3c3.5-2,6.9-4,12.1-7.1c-4.9,6.9-8.4,11.8-12.1,17.1C20.8,38,17.2,33,12.4,26.2C17.8,29.4,21.2,31.4,24.5,33.3z
M25.1,40.7c2.7-3.8,5.1-7.3,7.9-11.2c-3,1.8-5.5,3.3-7.9,4.7C25.1,36.4,25.1,38.4,25.1,40.7z M23.9,34.2
c-2.4-1.4-4.9-2.9-7.9-4.7c2.8,4,5.2,7.4,7.9,11.1C23.9,38.3,23.9,36.4,23.9,34.2z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -6,16 +6,16 @@ type ControlProps = {
next: string,
onPrevious: () => void,
firstPage: boolean,
submitting: boolean,
disabled: boolean,
}
const ControlButtons = ({
next, firstPage, onPrevious, submitting,
next, firstPage, onPrevious, disabled,
}: ControlProps) => (
<React.Fragment>
<Button
type="button"
disabled={firstPage || submitting}
disabled={firstPage || disabled}
onClick={onPrevious}
>
Back
@ -24,7 +24,7 @@ const ControlButtons = ({
variant="raised"
color="primary"
type="submit"
disabled={submitting}
disabled={disabled}
>
{next}
</Button>
@ -37,16 +37,16 @@ type Props = {
onPrevious: () => void,
firstPage: boolean,
lastPage: boolean,
submitting: boolean,
disabled: boolean,
}
const Controls = ({
finishedTx, finishedButton, onPrevious, firstPage, lastPage, submitting,
finishedTx, finishedButton, onPrevious, firstPage, lastPage, disabled,
}: Props) => (
finishedTx
? <React.Fragment>{finishedButton}</React.Fragment>
: <ControlButtons
submitting={submitting}
disabled={disabled}
next={lastPage ? 'Finish' : 'Next'}
firstPage={firstPage}
onPrevious={onPrevious}

View File

@ -4,7 +4,6 @@ import FormStep from '@material-ui/core/Step'
import StepLabel from '@material-ui/core/StepLabel'
import { withStyles } from '@material-ui/core/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'
@ -14,6 +13,7 @@ import Controls from './Controls'
export { default as Step } from './Step'
type Props = {
disabledWhenValidating?: boolean,
classes: Object,
steps: string[],
finishedTransaction: boolean,
@ -21,7 +21,7 @@ type Props = {
initialValues?: Object,
children: React$Node,
onReset?: () => void,
onSubmit: (values: Object, form: FormApi, callback: ?(errors: ?Object) => void) => ?Object | Promise<?Object> | void,
onSubmit: (values: Object) => Promise<void>,
}
type State = {
@ -31,6 +31,7 @@ type State = {
type PageProps = {
children: Function,
prepareNextInitialProps: (values: Object) => {},
}
class GnoStepper extends React.PureComponent<Props, State> {
@ -63,8 +64,10 @@ class GnoStepper extends React.PureComponent<Props, State> {
}))
}
getPageProps = (pages: React$Node): PageProps => React.Children.toArray(pages)[this.state.page].props
getActivePageFrom = (pages: React$Node) => {
const activePageProps = React.Children.toArray(pages)[this.state.page].props
const activePageProps = this.getPageProps(pages)
const { children, ...props } = activePageProps
return children(props)
@ -77,18 +80,28 @@ class GnoStepper extends React.PureComponent<Props, State> {
return activePage.props.validate ? activePage.props.validate(values) : {}
}
next = (values: Object) =>
next = async (values: Object) => {
const activePageProps = this.getPageProps(this.props.children)
const { prepareNextInitialProps } = activePageProps
let pageInitialProps
if (prepareNextInitialProps) {
pageInitialProps = await prepareNextInitialProps(values)
}
const finalValues = { ...values, ...pageInitialProps }
this.setState(state => ({
page: Math.min(state.page + 1, React.Children.count(this.props.children) - 1),
values,
values: finalValues,
}))
}
previous = () =>
this.setState(state => ({
page: Math.max(state.page - 1, 0),
}))
handleSubmit = (values: Object) => {
handleSubmit = async (values: Object) => {
const { children, onSubmit } = this.props
const { page } = this.state
const isLastPage = page === React.Children.count(children) - 1
@ -101,7 +114,7 @@ class GnoStepper extends React.PureComponent<Props, State> {
render() {
const {
steps, children, finishedTransaction, finishedButton, classes,
steps, children, finishedTransaction, finishedButton, classes, disabledWhenValidating = false,
} = this.props
const { page, values } = this.state
const activePage = this.getActivePageFrom(children)
@ -124,20 +137,24 @@ class GnoStepper extends React.PureComponent<Props, State> {
validation={this.validate}
render={activePage}
>
{(submitting: boolean) => (
<Row align="end" margin="lg" grow>
<Col xs={12} center="xs">
<Controls
submitting={submitting}
finishedTx={finishedTransaction}
finishedButton={finished}
onPrevious={this.previous}
firstPage={page === 0}
lastPage={isLastPage}
/>
</Col>
</Row>
)}
{(submitting: boolean, validating: boolean) => {
const disabled = disabledWhenValidating ? submitting || validating : submitting
return (
<Row align="end" margin="lg" grow>
<Col xs={12} center="xs">
<Controls
disabled={disabled}
finishedTx={finishedTransaction}
finishedButton={finished}
onPrevious={this.previous}
firstPage={page === 0}
lastPage={isLastPage}
/>
</Col>
</Row>
)
}}
</GnoForm>
</React.Fragment>
)

View File

@ -1,4 +1,8 @@
// @flow
import * as React from 'react'
import { Field } from 'react-final-form'
export default Field
// $FlowFixMe
const GnoField = ({ ...props }): React.Element<*> => <Field {...props} />
export default GnoField

View File

@ -1,10 +1,16 @@
// @flow
import * as React from 'react'
import { type FormApi } from 'final-form'
import { Form } from 'react-final-form'
import type { FormApi } from 'react-final-form'
export type OnSubmit = (
values: Object,
form: FormApi,
callback: ?(errors: ?Object) => ?Object
) => ?Object | Promise<?Object> | void
type Props = {
onSubmit: (values: Object, form: FormApi, callback: ?(errors: ?Object) => void) => ?Object | Promise<?Object> | void,
onSubmit: OnSubmit,
children: Function,
padding: number,
validation?: (values: Object) => Object | Promise<Object>,
@ -29,7 +35,7 @@ const GnoForm = ({
render={({ handleSubmit, ...rest }) => (
<form onSubmit={handleSubmit} style={stylesBasedOn(padding)}>
{render(rest)}
{children(rest.submitting, rest.submitSucceeded)}
{children(rest.submitting, rest.validating)}
</form>
)}
/>

View File

@ -1,7 +1,8 @@
// @flow
import { getWeb3 } from '~/wallets/getWeb3'
import { type FieldValidator } from 'final-form'
type Field = boolean | string
type Field = boolean | string | null | typeof undefined
export const required = (value: Field) => (value ? undefined : 'Required')
@ -19,6 +20,16 @@ export const greaterThan = (min: number) => (value: string) => {
return `Should be greater than ${min}`
}
const regexQuery = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
const url = new RegExp(regexQuery)
export const mustBeUrl = (value: string) => {
if (url.test(value)) {
return undefined
}
return 'Please, provide a valid url'
}
export const minValue = (min: number) => (value: string) => {
if (Number.isNaN(Number(value)) || Number.parseFloat(value) >= Number(min)) {
return undefined
@ -48,15 +59,15 @@ export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
export const uniqueAddress = (addresses: string[]) => (value: string) =>
(addresses.includes(value) ? ADDRESS_REPEATED_ERROR : undefined)
export const composeValidators = (...validators: Function[]) => (value: Field) =>
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) =>
validators.reduce((error, validator) => error || validator(value), undefined)
export const inLimit = (limit: number, base: number, baseText: string) => (value: string) => {
export const inLimit = (limit: number, base: number, baseText: string, symbol: string = 'ETH') => (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})`
return `Should not exceed ${max} ${symbol} (amount to reach ${baseText})`
}

View File

@ -4,13 +4,18 @@ import Loadable from 'react-loadable'
import { Switch, Redirect, Route } from 'react-router-dom'
import Loader from '~/components/Loader'
import Welcome from './welcome/container'
import { SAFELIST_ADDRESS, OPEN_ADDRESS, SAFE_PARAM_ADDRESS, WELCOME_ADDRESS } from './routes'
import { SAFELIST_ADDRESS, OPEN_ADDRESS, SAFE_PARAM_ADDRESS, WELCOME_ADDRESS, SETTINS_ADDRESS } from './routes'
const Safe = Loadable({
loader: () => import('./safe/container'),
loading: Loader,
})
const Settings = Loadable({
loader: () => import('./tokens/container'),
loading: Loader,
})
const SafeList = Loadable({
loader: () => import('./safeList/container'),
loading: Loader,
@ -22,6 +27,9 @@ const Open = Loadable({
})
const SAFE_ADDRESS = `${SAFELIST_ADDRESS}/:${SAFE_PARAM_ADDRESS}`
const SAFE_SETTINGS = `${SAFE_ADDRESS}${SETTINS_ADDRESS}`
export const settingsUrlFrom = (safeAddress: string) => `${SAFELIST_ADDRESS}/${safeAddress}/settings`
const Routes = () => (
<Switch>
@ -30,6 +38,7 @@ const Routes = () => (
<Route exact path={OPEN_ADDRESS} component={Open} />
<Route exact path={SAFELIST_ADDRESS} component={SafeList} />
<Route exact path={SAFE_ADDRESS} component={Safe} />
<Route exact path={SAFE_SETTINGS} component={Settings} />
</Switch>
)

View File

@ -1,6 +1,5 @@
// @flow
import * as React from 'react'
import type { FormApi } from 'react-final-form'
import Stepper from '~/components/Stepper'
import Confirmation from '~/routes/open/components/FormConfirmation'
import Review from '~/routes/open/components/ReviewInformation'
@ -21,8 +20,7 @@ type Props = {
userAccount: string,
safeAddress: string,
safeTx: string,
onCallSafeContractSubmit: (values: Object, form: FormApi, callback: ?(errors: ?Object) => void)
=> ?Object | Promise<?Object> | void,
onCallSafeContractSubmit: (values: Object) => Promise<void>,
}
const Layout = ({

View File

@ -26,7 +26,7 @@ storiesOf('Routes /open', module)
getProviderInfo()
const provider = 'METAMASK'
const userAccount = '0x03db1a8b26d08df23337e9276a36b474510f0023'
const onCallSafeContractSubmit = async (values: Object) => {
const onCallSafeContractSubmit = async (values: Object): Promise<void> => {
const accounts = getAccountsFrom(values)
const numConfirmations = getThresholdFrom(values)
const data = {

View File

@ -1,6 +1,6 @@
// @flow
import * as React from 'react'
import { Field } from 'react-final-form'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import { composeValidators, minValue, mustBeInteger, required } from '~/components/forms/validator'
import Block from '~/components/layout/Block'

View File

@ -8,7 +8,7 @@ import { getProviderInfo } from '~/wallets/getWeb3'
import Wrapper from '~/test/utils/Wrapper'
import { CONFIRMATIONS_ERROR } from '~/routes/open/components/SafeForm'
const obSubmitMock = () => {}
const onSubmitMock = async (): Promise<void> => {}
describe('React DOM TESTS > Create Safe form', () => {
let open
@ -25,7 +25,7 @@ describe('React DOM TESTS > Create Safe form', () => {
userAccount="foo"
safeAddress=""
safeTx=""
onCallSafeContractSubmit={obSubmitMock}
onCallSafeContractSubmit={onSubmitMock}
/>
</Wrapper>
))

View File

@ -16,12 +16,12 @@ type Props = Actions & {
userAccount: string,
}
type State = {
export type OpenState = {
safeAddress: string,
safeTx: string,
}
const createSafe = async (values: Object, userAccount: string, addSafe: AddSafe): Promise<State> => {
export const createSafe = async (values: Object, userAccount: string, addSafe: AddSafe): Promise<OpenState> => {
const accounts = getAccountsFrom(values)
const numConfirmations = getThresholdFrom(values)
const name = getSafeNameFrom(values)
@ -43,7 +43,7 @@ const createSafe = async (values: Object, userAccount: string, addSafe: AddSafe)
return { safeAddress: safeContract.address, safeTx: safe }
}
class Open extends React.Component<Props, State> {
class Open extends React.Component<Props, OpenState> {
constructor() {
super()

View File

@ -3,3 +3,4 @@ export const SAFE_PARAM_ADDRESS = 'address'
export const SAFELIST_ADDRESS = '/safes'
export const OPEN_ADDRESS = '/open'
export const WELCOME_ADDRESS = '/welcome'
export const SETTINS_ADDRESS = '/settings'

View File

@ -5,7 +5,7 @@ import Stepper from '~/components/Stepper'
import { connect } from 'react-redux'
import { type Safe } from '~/routes/safe/store/model/safe'
import { type Owner, makeOwner } from '~/routes/safe/store/model/owner'
import { getSafeEthereumInstance, createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions'
import { getSafeEthereumInstance, createTransaction } from '~/wallets/createTransactions'
import { setOwners } from '~/utils/localStorage'
import AddOwnerForm, { NAME_PARAM, OWNER_ADDRESS_PARAM, INCREASE_PARAM } from './AddOwnerForm'
import Review from './Review'
@ -35,6 +35,18 @@ const getOwnerAddressesFrom = (owners: List<Owner>) => {
return owners.map((owner: Owner) => owner.get('address'))
}
export const addOwner = async (values: Object, safe: Safe, threshold: number, executor: string) => {
const nonce = Date.now()
const newThreshold = values[INCREASE_PARAM] ? threshold + 1 : threshold
const newOwnerAddress = values[OWNER_ADDRESS_PARAM]
const newOwnerName = values[NAME_PARAM]
const safeAddress = safe.get('address')
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const data = gnosisSafe.contract.addOwnerWithThreshold.getData(newOwnerAddress, newThreshold)
await createTransaction(safe, `Add Owner ${newOwnerName}`, safeAddress, 0, nonce, executor, data)
setOwners(safeAddress, safe.get('owners').push(makeOwner({ name: newOwnerName, address: newOwnerAddress })))
}
class AddOwner extends React.Component<Props, State> {
state = {
done: false,
@ -45,15 +57,7 @@ class AddOwner extends React.Component<Props, State> {
const {
safe, threshold, userAddress, fetchTransactions,
} = this.props
const nonce = Date.now()
const newThreshold = values[INCREASE_PARAM] ? threshold + 1 : threshold
const newOwnerAddress = values[OWNER_ADDRESS_PARAM]
const newOwnerName = values[NAME_PARAM]
const safeAddress = safe.get('address')
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const data = gnosisSafe.contract.addOwnerWithThreshold.getData(newOwnerAddress, newThreshold)
await createTransaction(safe, `Add Owner ${newOwnerName}`, safeAddress, 0, nonce, userAddress, data)
setOwners(safeAddress, safe.get('owners').push(makeOwner({ name: newOwnerName, address: newOwnerAddress })))
await addOwner(values, safe, threshold, userAddress)
fetchTransactions()
this.setState({ done: true })
} catch (error) {

View File

@ -1,29 +0,0 @@
// @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

@ -1,84 +0,0 @@
// @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

@ -2,7 +2,7 @@
import * as React from 'react'
import Stepper from '~/components/Stepper'
import { connect } from 'react-redux'
import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions'
import { createTransaction } from '~/wallets/createTransactions'
import { getEditDailyLimitData, getDailyLimitAddress } from '~/routes/safe/component/Withdraw/withdraw'
import { type Safe } from '~/routes/safe/store/model/safe'
import EditDailyLimitForm, { EDIT_DAILY_LIMIT_PARAM } from './EditDailyLimitForm'

View File

@ -7,11 +7,11 @@ import GnoSafe from './Safe'
type Props = SelectorProps
const Layout = ({
safe, balance, provider, userAddress,
safe, activeTokens, provider, userAddress,
}: Props) => (
<React.Fragment>
{ safe
? <GnoSafe safe={safe} balance={balance} userAddress={userAddress} />
? <GnoSafe safe={safe} tokens={activeTokens} userAddress={userAddress} />
: <NoSafe provider={provider} text="Not found safe" />
}
</React.Fragment>

View File

@ -1,8 +1,10 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import { List } from 'immutable'
import styles from '~/components/layout/PageFrame/index.scss'
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
import { makeToken } from '~/routes/tokens/store/model/token'
import Component from './Layout'
@ -12,6 +14,15 @@ const FrameDecorator = story => (
</div>
)
const ethBalance = makeToken({
address: '0',
name: 'Ether',
symbol: 'ETH',
decimals: 18,
logoUrl: 'assets/icons/icon_etherTokens.svg',
funds: '2',
})
storiesOf('Routes /safe:address', module)
.addDecorator(FrameDecorator)
.add('Safe undefined being connected', () => (
@ -19,7 +30,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={undefined}
provider="METAMASK"
balance="0"
activeTokens={List([])}
fetchBalance={() => {}}
/>
))
@ -28,7 +39,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={undefined}
provider=""
balance="0"
activeTokens={List([])}
fetchBalance={() => {}}
/>
))
@ -40,7 +51,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={safe}
provider="METAMASK"
balance="2"
activeTokens={List([]).push(ethBalance)}
fetchBalance={() => {}}
/>
)
@ -53,7 +64,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={safe}
provider="METAMASK"
balance="2"
activeTokens={List([]).push(ethBalance)}
fetchBalance={() => {}}
/>
)

View File

@ -3,7 +3,7 @@ import * as React from 'react'
import Stepper from '~/components/Stepper'
import { connect } from 'react-redux'
import { type Safe } from '~/routes/safe/store/model/safe'
import { getSafeEthereumInstance, createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions'
import { getSafeEthereumInstance, createTransaction } from '~/wallets/createTransactions'
import RemoveOwnerForm, { DECREASE_PARAM } from './RemoveOwnerForm'
import Review from './Review'
import selector, { type SelectorProps } from './selector'
@ -27,11 +27,32 @@ type State = {
const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001'
export const REMOVE_OWNER_RESET_BUTTON_TEXT = 'RESET'
const initialValuesFrom = (decreaseMandatory: boolean = false) => ({
export const initialValuesFrom = (decreaseMandatory: boolean = false) => ({
[DECREASE_PARAM]: decreaseMandatory,
})
const shouldDecrease = (numOwners: number, threshold: number) => threshold === numOwners
export const shouldDecrease = (numOwners: number, threshold: number) => threshold === numOwners
export const removeOwner = async (
values: Object,
safe: Safe,
threshold: number,
userToRemove: string,
name: string,
executor: string,
) => {
const nonce = Date.now()
const newThreshold = values[DECREASE_PARAM] ? threshold - 1 : threshold
const safeAddress = safe.get('address')
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const storedOwners = await gnosisSafe.getOwners()
const index = storedOwners.findIndex(ownerAddress => ownerAddress === userToRemove)
const prevAddress = index === 0 ? SENTINEL_ADDRESS : storedOwners[index - 1]
const data = gnosisSafe.contract.removeOwner.getData(prevAddress, userToRemove, newThreshold)
const text = name || userToRemove
return createTransaction(safe, `Remove Owner ${text}`, safeAddress, 0, nonce, executor, data)
}
class RemoveOwner extends React.Component<Props, State> {
state = {
@ -43,19 +64,7 @@ class RemoveOwner extends React.Component<Props, State> {
const {
safe, threshold, executor, fetchTransactions, userToRemove, name,
} = this.props
const nonce = Date.now()
const newThreshold = values[DECREASE_PARAM] ? threshold - 1 : threshold
const safeAddress = safe.get('address')
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const storedOwners = await gnosisSafe.getOwners()
const index = storedOwners.findIndex(ownerAddress => ownerAddress === userToRemove)
const prevAddress = index === 0 ? SENTINEL_ADDRESS : storedOwners[index - 1]
const data = gnosisSafe.contract.removeOwner.getData(prevAddress, userToRemove, newThreshold)
const text = name || userToRemove
await createTransaction(safe, `Remove Owner ${text}`, safeAddress, 0, nonce, executor, data)
await removeOwner(values, safe, threshold, userToRemove, name, executor)
fetchTransactions()
this.setState({ done: true })
} catch (error) {

View File

@ -1,21 +0,0 @@
// @flow
import * as React from 'react'
import ListItem from '@material-ui/core/ListItem'
import ListItemText from '@material-ui/core/ListItemText'
import Avatar from '@material-ui/core/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,90 @@
// @flow
import * as React from 'react'
import classNames from 'classnames'
import Link from '~/components/layout/Link'
import AccountBalance from '@material-ui/icons/AccountBalance'
import Settings from '@material-ui/icons/Settings'
import Avatar from '@material-ui/core/Avatar'
import Collapse from '@material-ui/core/Collapse'
import IconButton from '@material-ui/core/IconButton'
import List from '@material-ui/core/List'
import Img from '~/components/layout/Img'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import { withStyles } from '@material-ui/core/styles'
import ExpandLess from '@material-ui/icons/ExpandLess'
import ExpandMore from '@material-ui/icons/ExpandMore'
import { Map } from 'immutable'
import Button from '~/components/layout/Button'
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
import { type WithStyles } from '~/theme/mui'
import { type Token } from '~/routes/tokens/store/model/token'
import { settingsUrlFrom } from '~/routes'
type Props = Open & WithStyles & {
safeAddress: string,
tokens: Map<string, Token>,
onMoveFunds: (token: Token) => void,
}
const styles = {
nested: {
paddingLeft: '40px',
},
}
export const MOVE_FUNDS_BUTTON_TEXT = 'Move'
const BalanceComponent = openHoc(({
open, toggle, tokens, classes, onMoveFunds, safeAddress,
}: Props) => {
const hasBalances = tokens.count() > 0
const settingsUrl = settingsUrlFrom(safeAddress)
return (
<React.Fragment>
<ListItem onClick={hasBalances ? toggle : undefined}>
<Avatar>
<AccountBalance />
</Avatar>
<ListItemText primary="Balance" secondary="List of different token balances" />
<ListItemIcon>
<IconButton to={settingsUrl} disabled={!hasBalances} component={Link} className={classes.button}>
<Settings />
</IconButton>
</ListItemIcon>
<ListItemIcon>
{open
? <IconButton disableRipple><ExpandLess /></IconButton>
: <IconButton disabled={!hasBalances} disableRipple><ExpandMore /></IconButton>
}
</ListItemIcon>
</ListItem>
<Collapse in={open} timeout="auto">
<List component="div" disablePadding>
{tokens.valueSeq().map((token: Token) => {
const symbol = token.get('symbol')
const name = token.get('name')
const disabled = Number(token.get('funds')) === 0
const onMoveFundsClick = () => onMoveFunds(token)
return (
<ListItem key={symbol} className={classNames(classes.nested, symbol)}>
<ListItemIcon>
<Img src={token.get('logoUrl')} height={30} alt={name} />
</ListItemIcon>
<ListItemText primary={name} secondary={`${token.get('funds')} ${symbol}`} />
<Button variant="raised" color="primary" onClick={onMoveFundsClick} disabled={disabled}>
{MOVE_FUNDS_BUTTON_TEXT}
</Button>
</ListItem>
)
})}
</List>
</Collapse>
</React.Fragment>
)
})
export default withStyles(styles)(BalanceComponent)

View File

@ -11,7 +11,7 @@ type Props = {
dailyLimit: DailyLimit,
onWithdraw: () => void,
onEditDailyLimit: () => void,
balance: string,
balance: number,
}
export const EDIT_WITHDRAW = 'Edit'
export const WITHDRAW_BUTTON_TEXT = 'Withdraw'
@ -26,7 +26,7 @@ const DailyLimitComponent = ({
const limit = dailyLimit.get('value')
const spentToday = dailyLimit.get('spentToday')
const disabled = spentToday >= limit || Number(balance) === 0
const disabled = spentToday >= limit || balance === 0
const text = `${limit} ETH (spent today: ${spentToday} ETH)`
return (

View File

@ -7,37 +7,20 @@ 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
const DailyLimitComponent = ({ onSeeTxs }: Props) => {
const text = 'See multisig txs executed on this Safe'
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>
<ListItemText primary="Safe's Multisig Transaction" secondary={text} />
<Button
variant="raised"
color="primary"

View File

@ -1,5 +1,7 @@
// @flow
import ListComponent from '@material-ui/core/List'
import * as React from 'react'
import { List } from 'immutable'
import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col'
import Bold from '~/components/layout/Bold'
@ -7,18 +9,18 @@ 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/core/List'
import { type Token } from '~/routes/tokens/store/model/token'
import Withdraw from '~/routes/safe/component/Withdraw'
import Transactions from '~/routes/safe/component/Transactions'
import AddTransaction from '~/routes/safe/component/AddTransaction'
import Threshold from '~/routes/safe/component/Threshold'
import AddOwner from '~/routes/safe/component/AddOwner'
import RemoveOwner from '~/routes/safe/component/RemoveOwner'
import EditDailyLimit from '~/routes/safe/component/EditDailyLimit'
import SendToken from '~/routes/safe/component/SendToken'
import Address from './Address'
import Balance from './Balance'
import BalanceInfo from './BalanceInfo'
import Owners from './Owners'
import Confirmations from './Confirmations'
import DailyLimit from './DailyLimit'
@ -28,7 +30,7 @@ const safeIcon = require('./assets/gnosis_safe.svg')
type SafeProps = {
safe: Safe,
balance: string,
tokens: List<Token>,
userAddress: string,
}
@ -40,6 +42,20 @@ const listStyle = {
width: '100%',
}
const getEthBalanceFrom = (tokens: List<Token>) => {
const filteredTokens = tokens.filter(token => token.get('symbol') === 'ETH')
if (filteredTokens.count() === 0) {
return 0
}
const ethToken = filteredTokens.get(0)
if (!ethToken) {
return 0
}
return Number(ethToken.get('funds'))
}
class GnoSafe extends React.PureComponent<SafeProps, State> {
state = {
component: undefined,
@ -58,17 +74,10 @@ class GnoSafe extends React.PureComponent<SafeProps, State> {
this.setState({ component: <Withdraw safe={safe} 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} /> })
this.setState({ component: <Transactions safeName={safe.get('name')} safeAddress={safe.get('address')} /> })
}
onEditThreshold = () => {
@ -89,15 +98,30 @@ class GnoSafe extends React.PureComponent<SafeProps, State> {
this.setState({ component: <RemoveOwner safeAddress={safe.get('address')} threshold={safe.get('threshold')} safe={safe} name={name} userToRemove={address} /> })
}
onMoveTokens = (ercToken: Token) => {
const { safe } = this.props
this.setState({
component: <SendToken
safe={safe}
token={ercToken}
key={ercToken.get('address')}
onReset={this.onListTransactions}
/>,
})
}
render() {
const { safe, balance, userAddress } = this.props
const { safe, tokens, userAddress } = this.props
const { component } = this.state
const ethBalance = getEthBalanceFrom(tokens)
const address = safe.get('address')
return (
<Row grow>
<Col sm={12} top="xs" md={5} margin="xl" overflow>
<List style={listStyle}>
<Balance balance={balance} />
<ListComponent style={listStyle}>
<BalanceInfo tokens={tokens} onMoveFunds={this.onMoveTokens} safeAddress={address} />
<Owners
owners={safe.owners}
onAddOwner={this.onAddOwner}
@ -105,10 +129,10 @@ class GnoSafe extends React.PureComponent<SafeProps, State> {
onRemoveOwner={this.onRemoveOwner}
/>
<Confirmations confirmations={safe.get('threshold')} onEditThreshold={this.onEditThreshold} />
<Address address={safe.get('address')} />
<DailyLimit balance={balance} dailyLimit={safe.get('dailyLimit')} onWithdraw={this.onWithdraw} onEditDailyLimit={this.onEditDailyLimit} />
<MultisigTx balance={balance} onAddTx={this.onAddTx} onSeeTxs={this.onListTransactions} />
</List>
<Address address={address} />
<DailyLimit balance={ethBalance} dailyLimit={safe.get('dailyLimit')} onWithdraw={this.onWithdraw} onEditDailyLimit={this.onEditDailyLimit} />
<MultisigTx onSeeTxs={this.onListTransactions} />
</ListComponent>
</Col>
<Col sm={12} center="xs" md={7} margin="xl" layout="column">
<Block margin="xl">

View File

@ -5,28 +5,29 @@ 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'
import { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from '~/routes/safe/component/SendToken/SendTokenForm/index'
type FormProps = {
values: Object,
submitting: boolean,
}
type Props = {
symbol: string
}
const spinnerStyle = {
minHeight: '50px',
}
const ReviewTx = () => ({ values, submitting }: FormProps) => (
const ReviewTx = ({ symbol }: Props) => ({ values, submitting }: FormProps) => (
<Block>
<Heading tag="h2">Review the Multisig Tx</Heading>
<Heading tag="h2">Review the move token funds</Heading>
<Paragraph align="left">
<Bold>Transaction Name: </Bold> {values[TX_NAME_PARAM]}
<Bold>Destination: </Bold> {values[TKN_DESTINATION_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]}
<Bold>{`Amount to transfer: ${values[TKN_VALUE_PARAM]} ${symbol}`}</Bold>
</Paragraph>
<Block style={spinnerStyle}>
{ submitting && <CircularProgress size={50} /> }

View File

@ -5,45 +5,28 @@ 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
}
export const TKN_DESTINATION_PARAM = 'tknDestination'
export const TKN_VALUE_PARAM = 'tknValue'
type Props = {
balance: number,
funds: string,
symbol: string,
}
const WithdrawForm = ({ balance }: Props) => () => (
const SendTokenForm = ({ funds, symbol }: Props) => () => (
<Block margin="md">
<Heading tag="h2" margin="lg">
Multisig Transaction
Send tokens Transaction
</Heading>
<Heading tag="h4" margin="lg">
{`Available balance: ${balance} ETH`}
{`Available tokens: ${funds} ${symbol}`}
</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}
name={TKN_DESTINATION_PARAM}
component={TextField}
type="text"
validate={composeValidators(required, mustBeEthereumAddress)}
@ -53,15 +36,15 @@ const WithdrawForm = ({ balance }: Props) => () => (
</Block>
<Block margin="md">
<Field
name={TX_VALUE_PARAM}
name={TKN_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"
validate={composeValidators(required, mustBeFloat, greaterThan(0), inLimit(Number(funds), 0, 'available balance', symbol))}
placeholder="Amount of tokens*"
text="Amount of Tokens"
/>
</Block>
</Block>
)
export default WithdrawForm
export default SendTokenForm

View File

@ -0,0 +1,112 @@
// @flow
import * as React from 'react'
import { BigNumber } from 'bignumber.js'
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 { getStandardTokenContract } from '~/routes/tokens/store/actions/fetchTokens'
import { type Token } from '~/routes/tokens/store/model/token'
import { createTransaction } from '~/wallets/createTransactions'
import { EMPTY_DATA } from '~/wallets/ethTransactions'
import { toNative } from '~/wallets/tokens'
import { isEther } from '~/utils/tokens'
import actions, { type Actions } from './actions'
import selector, { type SelectorProps } from './selector'
import SendTokenForm, { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from './SendTokenForm'
import ReviewTx from './ReviewTx'
const getSteps = () => [
'Fill Move Token form', 'Review Move Token form',
]
type Props = SelectorProps & Actions & {
safe: Safe,
token: Token,
onReset: () => void,
}
type State = {
done: boolean,
}
export const SEE_TXS_BUTTON_TEXT = 'VISIT TXS'
const getTransferData = async (tokenAddress: string, to: string, amount: BigNumber) => {
const StandardToken = await getStandardTokenContract()
const myToken = await StandardToken.at(tokenAddress)
return myToken.contract.transfer.getData(to, amount)
}
const processTokenTransfer = async (safe: Safe, token: Token, to: string, amount: number, userAddress: string) => {
const symbol = token.get('symbol')
const nonce = Date.now()
const name = `Send ${amount} ${symbol} to ${to}`
const value = isEther(symbol) ? amount : 0
const tokenAddress = token.get('address')
const destination = isEther(symbol) ? to : tokenAddress
const data = isEther(symbol)
? EMPTY_DATA
: await getTransferData(tokenAddress, to, await toNative(amount, token.get('decimals')))
return createTransaction(safe, name, destination, value, nonce, userAddress, data)
}
class SendToken extends React.Component<Props, State> {
state = {
done: false,
}
onTransaction = async (values: Object) => {
try {
const { safe, token, userAddress } = this.props
const amount = values[TKN_VALUE_PARAM]
const destination = values[TKN_DESTINATION_PARAM]
await processTokenTransfer(safe, token, destination, amount, 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 moving ERC20 token funds ' + error)
}
}
onReset = () => {
this.setState({ done: false })
this.props.onReset() // This is for show the TX list component
}
render() {
const { done } = this.state
const { token } = this.props
const steps = getSteps()
const finishedButton = <Stepper.FinishButton title={SEE_TXS_BUTTON_TEXT} />
const symbol = token.get('symbol')
return (
<React.Fragment>
<Stepper
finishedTransaction={done}
finishedButton={finishedButton}
onSubmit={this.onTransaction}
steps={steps}
onReset={this.onReset}
>
<Stepper.Page funds={token.get('funds')} symbol={symbol}>
{ SendTokenForm }
</Stepper.Page>
<Stepper.Page symbol={symbol}>
{ ReviewTx }
</Stepper.Page>
</Stepper>
</React.Fragment>
)
}
}
export default connect(selector, actions)(SendToken)

View File

@ -2,7 +2,7 @@
import * as React from 'react'
import Stepper from '~/components/Stepper'
import { connect } from 'react-redux'
import { getSafeEthereumInstance, createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions'
import { getSafeEthereumInstance, createTransaction } from '~/wallets/createTransactions'
import { type Safe } from '~/routes/safe/store/model/safe'
import ThresholdForm, { THRESHOLD_PARAM } from './ThresholdForm'
import selector, { type SelectorProps } from './selector'

View File

@ -1,31 +1,17 @@
// @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) => (
const NoRights = () => (
<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>
)

View File

@ -9,7 +9,6 @@ import selector, { type SelectorProps } from './selector'
import actions, { type Actions } from './actions'
type Props = SelectorProps & Actions & {
onAddTx: () => void,
safeName: string,
safeAddress: string,
@ -25,14 +24,14 @@ class Transactions extends React.Component<Props, {}> {
}
render() {
const { transactions, onAddTx, safeName } = this.props
const { transactions, 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} />
: <NoTransactions />
}
</React.Fragment>
)

View File

@ -7,7 +7,7 @@ import { makeTransaction, type Transaction, type TransactionProps } from '~/rout
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 { EXECUTED_CONFIRMATION_HASH } from '~/wallets/createTransactions'
import { checkReceiptStatus, calculateGasOf, calculateGasPrice } from '~/wallets/ethTransactions'
export const updateTransaction = (

View File

@ -1,10 +1,12 @@
// @flow
import { List } from 'immutable'
import { getWeb3 } from '~/wallets/getWeb3'
import { getGnosisSafeContract, getCreateDailyLimitExtensionContract } from '~/wallets/safeContracts'
import { type DailyLimitProps } from '~/routes/safe/store/model/dailyLimit'
import { checkReceiptStatus, calculateGasOf, calculateGasPrice } from '~/wallets/ethTransactions'
import { checkReceiptStatus, calculateGasOf, calculateGasPrice, EMPTY_DATA } from '~/wallets/ethTransactions'
import { type Safe } from '~/routes/safe/store/model/safe'
import { buildExecutedConfirmationFrom, storeTransaction } from '~/routes/safe/component/AddTransaction/createTransactions'
import { buildExecutedConfirmationFrom, storeTransaction } from '~/wallets/createTransactions'
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
export const LIMIT_POSITION = 0
export const SPENT_TODAY_POS = 1
@ -44,7 +46,7 @@ export const getDailyLimitAddress = async (safeAddress: string) => {
return dailyLimitModule.address
}
export const getEditDailyLimitData = async (safeAddress: string, token: string, dailyLimit: string) => {
export const getEditDailyLimitData = async (safeAddress: string, token: number, dailyLimit: number) => {
const web3 = getWeb3()
const dailyLimitModule = await getDailyLimitModuleFrom(safeAddress)
const dailyLimitInWei = web3.toWei(dailyLimit, 'ether')
@ -70,7 +72,7 @@ const withdraw = async (values: Object, safe: Safe, userAccount: string): Promis
const nonce = Date.now()
const executedConfirmations: List<Confirmation> = buildExecutedConfirmationFrom(safe.get('owners'), userAccount)
return storeTransaction(`Withdraw movement of ${valueInEth}`, nonce, destination, valueInEth, userAccount, executedConfirmations, txHash.tx, safeAddress, safe.get('threshold'), '0x')
return storeTransaction(`Withdraw movement of ${valueInEth}`, nonce, destination, valueInEth, userAccount, executedConfirmations, txHash.tx, safeAddress, safe.get('threshold'), EMPTY_DATA)
}
export default withdraw

View File

@ -1,9 +1,10 @@
// @flow
import { aNewStore } from '~/store'
import { addEtherTo } from '~/test/utils/etherMovements'
import { addEtherTo } from '~/test/utils/tokenMovements'
import { aDeployedSafe, executeWithdrawOn } from '~/routes/safe/store/test/builder/deployedSafe.builder'
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
import { safeSelector } from '~/routes/safe/store/selectors/index'
import { type Match } from 'react-router-dom'
describe('Safe Blockchain Test', () => {
let store
@ -21,10 +22,12 @@ describe('Safe Blockchain Test', () => {
// WHEN
const match: Match = buildMathPropsFrom(safeAddress)
const safe = safeSelector(store.getState(), { match })
if (!safe) throw new Error()
await executeWithdrawOn(safe, value)
await executeWithdrawOn(safe, value)
// THEN
expect(executeWithdrawOn(safeAddress, value)).rejects.toThrow('VM Exception while processing transaction: revert')
expect(executeWithdrawOn(safe, value)).rejects.toThrow('VM Exception while processing transaction: revert')
})
})

View File

@ -1,13 +1,13 @@
// @flow
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
import fetchBalance from '~/routes/safe/store/actions/fetchBalance'
import { fetchTokens } from '~/routes/tokens/store/actions/fetchTokens'
export type Actions = {
fetchSafe: typeof fetchSafe,
fetchBalance: typeof fetchBalance,
fetchTokens: typeof fetchTokens,
}
export default {
fetchSafe,
fetchBalance,
fetchTokens,
}

View File

@ -11,17 +11,32 @@ type Props = Actions & SelectorProps & {
granted: boolean,
}
const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 15000
class SafeView extends React.PureComponent<Props> {
componentDidMount() {
this.intervalId = setInterval(() => {
const { safe, fetchSafe, fetchBalance } = this.props
if (!safe) { return }
const safeAddress: string = safe.get('address')
fetchBalance(safeAddress)
if (safe) {
fetchSafe(safe)
const {
safe, fetchTokens, fetchSafe,
} = this.props
if (!safe) {
return
}
}, 1500)
const safeAddress = safe.get('address')
fetchTokens(safeAddress)
fetchSafe(safe)
}, TIMEOUT)
}
componentDidUpdate(prevProps) {
if (prevProps.safe) {
return
}
if (this.props.safe) {
const safeAddress = this.props.safe.get('address')
this.props.fetchTokens(safeAddress)
}
}
componentWillUnmount() {
@ -32,13 +47,13 @@ class SafeView extends React.PureComponent<Props> {
render() {
const {
safe, provider, balance, granted, userAddress,
safe, provider, activeTokens, granted, userAddress,
} = this.props
return (
<Page>
{ granted
? <Layout balance={balance} provider={provider} safe={safe} userAddress={userAddress} />
? <Layout activeTokens={activeTokens} provider={provider} safe={safe} userAddress={userAddress} />
: <NoRights />
}
</Page>

View File

@ -1,17 +1,19 @@
// @flow
import { List } from 'immutable'
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
import { balanceSelector, safeSelector, type RouterProps, type SafeSelectorProps } from '~/routes/safe/store/selectors'
import { 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'
import { activeTokensSelector } from '~/routes/tokens/store/selectors'
import { type Token } from '~/routes/tokens/store/model/token'
export type SelectorProps = {
safe: SafeSelectorProps,
provider: string,
balance: string,
activeTokens: List<Token>,
userAddress: string,
}
@ -39,7 +41,7 @@ export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = crea
export default createStructuredSelector({
safe: safeSelector,
provider: providerNameSelector,
balance: balanceSelector,
activeTokens: activeTokensSelector,
granted: grantedSelector,
userAddress: userAccountSelector,
})

View File

@ -1,19 +0,0 @@
// @flow
import { createAction } from 'redux-actions'
export const ADD_BALANCE = 'ADD_BALANCE'
type BalanceProps = {
safeAddress: string,
funds: string,
}
const addBalance = createAction(
ADD_BALANCE,
(safeAddress: string, funds: string): BalanceProps => ({
safeAddress,
funds,
}),
)
export default addBalance

View File

@ -1,11 +0,0 @@
// @flow
import type { Dispatch as ReduxDispatch } from 'redux'
import { getBalanceInEtherOf } from '~/wallets/getWeb3'
import { type GlobalState } from '~/store/index'
import addBalance from './addBalance'
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
const balance = await getBalanceInEtherOf(safeAddress)
return dispatch(addBalance(safeAddress, balance))
}

View File

@ -37,7 +37,14 @@ export const buildSafe = async (storedSafe: Object) => {
}
export default (safe: Safe) => async (dispatch: ReduxDispatch<GlobalState>) => {
const safeRecord = await buildSafe(safe.toJSON())
try {
const safeRecord = await buildSafe(safe.toJSON())
return dispatch(updateSafe(safeRecord))
return dispatch(updateSafe(safeRecord))
} catch (err) {
// eslint-disable-next-line
console.log("Error while updating safe information")
return Promise.resolve()
}
}

View File

@ -11,15 +11,23 @@ const buildSafesFrom = async (loadedSafes: Object): Promise<Map<string, Safe>> =
const safes = Map()
const keys = Object.keys(loadedSafes)
const safeRecords = await Promise.all(keys.map((address: string) => buildSafe(loadedSafes[address])))
try {
const safeRecords = await Promise.all(keys.map((address: string) => buildSafe(loadedSafes[address])))
return safes.withMutations(async (map) => {
safeRecords.forEach((safe: Safe) => map.set(safe.get('address'), safe))
})
return safes.withMutations(async (map) => {
safeRecords.forEach((safe: Safe) => map.set(safe.get('address'), safe))
})
} catch (err) {
// eslint-disable-next-line
console.log("Error while fetching safes information")
return Map()
}
}
export default () => async (dispatch: ReduxDispatch<GlobalState>) => {
const storedSafes = load(SAFES_KEY)
const safes = storedSafes ? await buildSafesFrom(storedSafes) : Map()
return dispatch(updateSafes(safes))

View File

@ -1,13 +0,0 @@
// @flow
import { Map } from 'immutable'
import { handleActions, type ActionType } from 'redux-actions'
import addBalance, { ADD_BALANCE } from '~/routes/safe/store/actions/addBalance'
export const BALANCE_REDUCER_ID = 'balances'
export type State = Map<string, string>
export default handleActions({
[ADD_BALANCE]: (state: State, action: ActionType<typeof addBalance>): State =>
state.set(action.payload.safeAddress, action.payload.funds),
}, Map())

View File

@ -6,7 +6,6 @@ import { type GlobalState } from '~/store/index'
import { SAFE_PARAM_ADDRESS } from '~/routes/routes'
import { type Safe } from '~/routes/safe/store/model/safe'
import { safesMapSelector } from '~/routes/safeList/store/selectors'
import { BALANCE_REDUCER_ID } from '~/routes/safe/store/reducer/balances'
import { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions'
import { type Transaction } from '~/routes/safe/store/model/transaction'
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
@ -25,14 +24,12 @@ type TransactionProps = {
const safePropAddressSelector = (state: GlobalState, props: SafeProps) => props.safeAddress
const safeParamAddressSelector = (state: GlobalState, props: RouterProps) => props.match.params[SAFE_PARAM_ADDRESS] || ''
const balancesSelector = (state: GlobalState) => state[BALANCE_REDUCER_ID]
const transactionsSelector = (state: GlobalState): TransactionsState => state[TRANSACTIONS_REDUCER_ID]
const oneTransactionSelector = (state: GlobalState, props: TransactionProps) => props.transaction
export const safeParamAddressSelector = (state: GlobalState, props: RouterProps) => props.match.params[SAFE_PARAM_ADDRESS] || ''
export const safeTransactionsSelector: Selector<GlobalState, SafeProps, List<Transaction>> = createSelector(
transactionsSelector,
safePropAddressSelector,
@ -81,18 +78,6 @@ export const safeSelector: Selector<GlobalState, RouterProps, SafeSelectorProps>
},
)
export const balanceSelector: Selector<GlobalState, RouterProps, string> = createSelector(
balancesSelector,
safeParamAddressSelector,
(balances: Map<string, string>, address: string) => {
if (!address) {
return '0'
}
return balances.get(address) || '0'
},
)
export default createStructuredSelector({
safe: safeSelector,
})

View File

@ -1,44 +0,0 @@
// @flow
import { BALANCE_REDUCER_ID } from '~/routes/safe/store/reducer/balances'
import fetchBalance from '~/routes/safe/store/actions/fetchBalance'
import { aNewStore } from '~/store'
import { addEtherTo } from '~/test/utils/etherMovements'
import { aDeployedSafe } from './builder/deployedSafe.builder'
const balanceReducerTests = () => {
describe('Safe Actions[fetchBalance]', () => {
let store
beforeEach(async () => {
store = aNewStore()
})
it('reducer should return 0 to just deployed safe', async () => {
// GIVEN
const address = await aDeployedSafe(store)
// WHEN
await store.dispatch(fetchBalance(address))
// THEN
const balances = store.getState()[BALANCE_REDUCER_ID]
expect(balances).not.toBe(undefined)
expect(balances.get(address)).toBe('0')
})
it('reducer should return 1.3456 ETH as funds to safe with 1.3456 ETH', async () => {
// GIVEN
const address = await aDeployedSafe(store)
// WHEN
await addEtherTo(address, '1.3456')
await store.dispatch(fetchBalance(address))
// THEN
const balances = store.getState()[BALANCE_REDUCER_ID]
expect(balances).not.toBe(undefined)
expect(balances.get(address)).toBe('1.3456')
})
})
}
export default balanceReducerTests

View File

@ -1,5 +1,6 @@
// @flow
import addBalance from '~/routes/safe/store/actions/addBalance'
/*
import addBalances from '~/routes/safe/store/actions/addBalances'
import { aNewStore } from '~/store'
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
import { balanceSelector } from '../selectors'
@ -26,7 +27,7 @@ const balanceSelectorTests = () => {
const store = aNewStore()
// WHEN
await store.dispatch(addBalance('bar', '1'))
await store.dispatch(addBalances('bar', '1'))
const balance = balanceSelector(store.getState(), { match })
// THEN
@ -40,7 +41,7 @@ const balanceSelectorTests = () => {
const store = aNewStore()
// WHEN
await store.dispatch(addBalance(safeAddress, '1.3456'))
await store.dispatch(addBalances(safeAddress, '1.3456'))
const balance = balanceSelector(store.getState(), { match })
// THEN
@ -50,3 +51,4 @@ const balanceSelectorTests = () => {
}
export default balanceSelectorTests
*/

View File

@ -29,7 +29,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
safes: Map(),
providers: makeProvider(),
balances: Map(),
tokens: Map(),
transactions: Map(),
}
@ -67,7 +67,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
safes: Map(),
providers: makeProvider(),
balances: Map(),
tokens: Map(),
transactions: Map(),
}
@ -82,7 +82,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
safes: Map(),
providers: makeProvider(),
balances: Map(),
tokens: Map(),
transactions: Map(),
}

View File

@ -26,7 +26,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: map,
providers: makeProvider(provider),
balances: undefined,
tokens: undefined,
transactions: undefined,
}
@ -47,7 +47,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: map,
providers: makeProvider(provider),
balances: undefined,
tokens: undefined,
transactions: undefined,
}
@ -68,7 +68,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: map,
providers: makeProvider(provider),
balances: undefined,
tokens: undefined,
transactions: undefined,
}

View File

@ -14,7 +14,7 @@ const safeSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: Map(),
providers: undefined,
balances: undefined,
tokens: undefined,
transactions: undefined,
}
const match: Match = buildMathPropsFrom('fooAddress')
@ -38,7 +38,7 @@ const safeSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: map,
providers: undefined,
balances: undefined,
tokens: undefined,
transactions: undefined,
}

View File

@ -1,7 +1,6 @@
// @flow
import balanceReducerTests from './balance.reducer'
import safeReducerTests from './safe.reducer'
import balanceSelectorTests from './balance.selector'
// import balanceSelectorTests from './balance.selector'
import safeSelectorTests from './safe.selector'
import grantedSelectorTests from './granted.selector'
import confirmationsSelectorTests from './confirmations.selector'
@ -10,13 +9,12 @@ import transactionsSelectorTests from './transactions.selector'
describe('Safe Test suite', () => {
// ACTIONS AND REDUCERS
safeReducerTests()
balanceReducerTests()
// SAFE SELECTOR
safeSelectorTests()
// BALANCE SELECTOR
balanceSelectorTests()
// balanceSelectorTests()
// GRANTED SELECTOR
grantedSelectorTests()

View File

@ -14,7 +14,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: Map(),
providers: makeProvider(),
balances: undefined,
tokens: undefined,
transactions: Map(),
}
@ -46,7 +46,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: Map(),
providers: makeProvider(),
balances: undefined,
tokens: undefined,
transactions: Map({ fooAddress: List([transaction]) }),
}
@ -81,7 +81,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: Map(),
providers: makeProvider(),
balances: undefined,
tokens: undefined,
transactions: Map({ fooAddress: List([transaction]) }),
}
@ -113,7 +113,7 @@ const grantedSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: Map(),
providers: makeProvider(),
balances: undefined,
tokens: undefined,
transactions: Map({ fooAddress: List([transaction]) }),
}

View File

@ -1,103 +0,0 @@
// @flow
import * as React from 'react'
import TestUtils from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'react-router-redux'
import Button from '~/components/layout/Button'
import { aNewStore, history } from '~/store'
import { addEtherTo } from '~/test/utils/etherMovements'
import { aDeployedSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder'
import { SAFELIST_ADDRESS } from '~/routes/routes'
import SafeView from '~/routes/safe/component/Safe'
import AppRoutes from '~/routes'
import AddTransactionComponent, { SEE_TXS_BUTTON_TEXT } from '~/routes/safe/component/AddTransaction'
import TransactionsComponent from '~/routes/safe/component/Transactions'
import TransactionComponent from '~/routes/safe/component/Transactions/Transaction'
import { getBalanceInEtherOf } from '~/wallets/getWeb3'
import { sleep } from '~/utils/timer'
import { ADD_MULTISIG_BUTTON_TEXT } from '~/routes/safe/component/Safe/MultisigTx'
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
import { MOVE_FUNDS_INDEX } from '~/test/builder/safe.dom.utils'
describe('React DOM TESTS > Withdraw funds from safe', () => {
let SafeDom
let store
let address
beforeEach(async () => {
// create store
store = aNewStore()
// deploy safe updating store
address = await aDeployedSafe(store)
// navigate to SAFE route
history.push(`${SAFELIST_ADDRESS}/${address}`)
SafeDom = TestUtils.renderIntoDocument((
<Provider store={store}>
<ConnectedRouter history={history}>
<AppRoutes />
</ConnectedRouter>
</Provider>
))
})
it('should execute one transaction if safe has only one owner', async () => {
// add funds to safe
await addEtherTo(address, '0.1')
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(Safe, 'button')
const addTxButton = buttons[MOVE_FUNDS_INDEX]
expect(addTxButton.getElementsByTagName('span')[0].innerHTML).toEqual(ADD_MULTISIG_BUTTON_TEXT)
await sleep(1800) // Give time to enable Add button
TestUtils.Simulate.click(addTxButton)
await sleep(1800) // Give time to render the form
const AddTransaction = TestUtils.findRenderedComponentWithType(SafeDom, AddTransactionComponent)
// $FlowFixMe
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(AddTransaction, 'input')
const name = inputs[0]
const destination = inputs[1]
const amountInEth = inputs[2]
TestUtils.Simulate.change(name, { target: { value: 'Buying betteries' } })
TestUtils.Simulate.change(amountInEth, { target: { value: '0.01' } })
TestUtils.Simulate.change(destination, { target: { value: store.getState().providers.account } })
// $FlowFixMe
const form = TestUtils.findRenderedDOMComponentWithTag(AddTransaction, 'form')
TestUtils.Simulate.submit(form) // fill the form
TestUtils.Simulate.submit(form) // confirming data
await sleep(4000)
const safeBalance = await getBalanceInEtherOf(address)
expect(safeBalance).toBe('0.09')
// $FlowFixMe
const addTransactionButtons = TestUtils.scryRenderedComponentsWithType(AddTransaction, Button)
expect(addTransactionButtons.length).toBe(1)
const visitTxsButton = addTransactionButtons[0]
expect(visitTxsButton.props.children).toEqual(SEE_TXS_BUTTON_TEXT)
// NOW it is time to check the just executed transaction
TestUtils.Simulate.click(TestUtils.scryRenderedDOMComponentsWithTag(visitTxsButton, 'button')[0])
const Transactions = TestUtils.findRenderedComponentWithType(SafeDom, TransactionsComponent)
if (!Transactions) throw new Error()
const Transaction = TestUtils.findRenderedComponentWithType(Transactions, TransactionComponent)
if (!Transaction) throw new Error()
const paragraphs = TestUtils.scryRenderedDOMComponentsWithTag(Transaction, 'p')
expect(paragraphs[2].innerHTML).toBe('Already executed')
TestUtils.Simulate.click(paragraphs[2]) // expanded
await sleep(1000) // Time to expand
const paragraphsExpanded = TestUtils.scryRenderedDOMComponentsWithTag(Transaction, 'p')
const txHashParagraph = paragraphsExpanded[3]
const transactions = safeTransactionsSelector(store.getState(), { safeAddress: address })
const batteryTx = transactions.get(0)
if (!batteryTx) throw new Error()
expect(txHashParagraph.innerHTML).toBe(batteryTx.get('tx'))
})
})

View File

@ -1,62 +0,0 @@
// @flow
import * as React from 'react'
import TestUtils from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'react-router-redux'
import { aNewStore, history } from '~/store'
import { aDeployedSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder'
import { SAFELIST_ADDRESS } from '~/routes/routes'
import AppRoutes from '~/routes'
import AddTransactionComponent from '~/routes/safe/component/AddTransaction'
import { createMultisigTxFilling, addFundsTo, checkBalanceOf, listTxsOf, getListItemsFrom, expandTransactionOf, getTransactionFromReduxStore, confirmOwners } from '~/routes/safe/test/testMultisig'
import { sleep } from '~/utils/timer'
const renderSafe = localStore => (
TestUtils.renderIntoDocument((
<Provider store={localStore}>
<ConnectedRouter history={history}>
<AppRoutes />
</ConnectedRouter>
</Provider>
))
)
describe('React DOM TESTS > Multisig transactions from safe [3 owners & 1 threshold] ', () => {
let SafeDom
let store
let address
beforeEach(async () => {
// create store
store = aNewStore()
// deploy safe updating store
address = await aDeployedSafe(store, 10, 1, 3)
// navigate to SAFE route
history.push(`${SAFELIST_ADDRESS}/${address}`)
SafeDom = renderSafe(store)
})
it('should execute transaction straight away', async () => {
await addFundsTo(SafeDom, address)
await checkBalanceOf(address, '0.1')
await createMultisigTxFilling(SafeDom, AddTransactionComponent, store)
await checkBalanceOf(address, '0.09')
await listTxsOf(SafeDom)
await sleep(2500)
await expandTransactionOf(SafeDom, 3, 1)
await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Not confirmed]', 'Adolfo 3 Eth Account [Not confirmed]')
const listItems = getListItemsFrom(SafeDom)
const status = listItems[2].props.secondary
expect(status).toBe('Already executed')
const confirmed = listItems[3].props.secondary
const tx = getTransactionFromReduxStore(store, address)
if (!tx) throw new Error()
expect(confirmed).toBe(tx.get('tx'))
const ownerTx = listItems[6].props.secondary
expect(ownerTx).toBe('Confirmation hash: EXECUTED')
})
})

View File

@ -1,104 +0,0 @@
// @flow
import * as React from 'react'
import TestUtils from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'react-router-redux'
import { aNewStore, history } from '~/store'
import { aDeployedSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder'
import { SAFELIST_ADDRESS } from '~/routes/routes'
import AppRoutes from '~/routes'
import { getWeb3 } from '~/wallets/getWeb3'
import { sleep } from '~/utils/timer'
import { promisify } from '~/utils/promisify'
import AddTransactionComponent from '~/routes/safe/component/AddTransaction'
import { processTransaction } from '~/routes/safe/component/Transactions/processTransactions'
import { confirmationsTransactionSelector } from '~/routes/safe/store/selectors/index'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import { createMultisigTxFilling, addFundsTo, checkBalanceOf, listTxsOf, getListItemsFrom, expandTransactionOf, getTransactionFromReduxStore, confirmOwners } from '~/routes/safe/test/testMultisig'
const renderSafe = localStore => (
TestUtils.renderIntoDocument((
<Provider store={localStore}>
<ConnectedRouter history={history}>
<AppRoutes />
</ConnectedRouter>
</Provider>
))
)
describe('React DOM TESTS > Multisig transactions from safe [3 owners & 3 threshold] ', () => {
let SafeDom
let store
let address
let accounts
beforeEach(async () => {
// create store
store = aNewStore()
// deploy safe updating store
address = await aDeployedSafe(store, 10, 3, 3)
// navigate to SAFE route
history.push(`${SAFELIST_ADDRESS}/${address}`)
SafeDom = renderSafe(store)
accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
})
const getAlreadyConfirmed = () => {
const tx = getTransactionFromReduxStore(store, address)
if (!tx) throw new Error()
const confirmed = confirmationsTransactionSelector(store.getState(), { transaction: tx })
return confirmed
}
const makeConfirmation = async (executor) => {
const alreadyConfirmed = getAlreadyConfirmed()
const tx = getTransactionFromReduxStore(store, address)
if (!tx) throw new Error()
await processTransaction(address, tx, alreadyConfirmed, executor)
await sleep(800)
store.dispatch(fetchTransactions())
sleep(1800)
SafeDom = renderSafe(store)
sleep(1800)
await listTxsOf(SafeDom)
sleep(800)
await expandTransactionOf(SafeDom, 3, 3)
sleep(800)
}
it('should execute transaction after 2 owners have confirmed and the last one executed correctly', async () => {
await addFundsTo(SafeDom, address)
await createMultisigTxFilling(SafeDom, AddTransactionComponent, store)
await checkBalanceOf(address, '0.1')
await listTxsOf(SafeDom)
sleep(1400)
const listItems = getListItemsFrom(SafeDom)
const status = listItems[2].props.secondary
expect(status).toBe('1 of the 3 confirmations needed')
const confirmed = listItems[3].props.secondary
expect(confirmed).toBe('Waiting for the rest of confirmations')
await expandTransactionOf(SafeDom, 3, 3)
await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Not confirmed]', 'Adolfo 3 Eth Account [Not confirmed]')
await makeConfirmation(accounts[1])
await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Confirmed]', 'Adolfo 3 Eth Account [Not confirmed]')
await makeConfirmation(accounts[2])
await confirmOwners(SafeDom, 'Adolfo 1 Eth Account [Confirmed]', 'Adolfo 2 Eth Account [Confirmed]', 'Adolfo 3 Eth Account [Confirmed]')
const listItemsExecuted = getListItemsFrom(SafeDom)
const statusExecuted = listItemsExecuted[2].props.secondary
expect(statusExecuted).toBe('Already executed')
const confirmedExecuted = listItemsExecuted[3].props.secondary
const tx = getTransactionFromReduxStore(store, address)
if (!tx) throw new Error()
expect(confirmedExecuted).toBe(tx.get('tx'))
})
})

View File

@ -1,187 +0,0 @@
// @flow
import { aNewStore } from '~/store'
import { aDeployedSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder'
import { getWeb3 } from '~/wallets/getWeb3'
import { sleep } from '~/utils/timer'
import { type Match } from 'react-router-dom'
import { promisify } from '~/utils/promisify'
import { processTransaction } from '~/routes/safe/component/Transactions/processTransactions'
import { confirmationsTransactionSelector, safeSelector, safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
import { getTransactionFromReduxStore } from '~/routes/safe/test/testMultisig'
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import { type GlobalState } from '~/store/index'
import { type Safe } from '~/routes/safe/store/model/safe'
import { type Transaction } from '~/routes/safe/store/model/transaction'
import { getGnosisSafeInstanceAt } from '~/wallets/safeContracts'
const getSafeFrom = (state: GlobalState, safeAddress: string): Safe => {
const match: Match = buildMathPropsFrom(safeAddress)
const safe = safeSelector(state, { match })
if (!safe) throw new Error()
return safe
}
describe('React DOM TESTS > Add and remove owners', () => {
const assureExecuted = (transaction: Transaction) => {
expect(transaction.get('tx')).not.toBe(null)
expect(transaction.get('tx')).not.toBe(undefined)
expect(transaction.get('tx')).not.toBe('')
}
const assureThresholdIs = async (gnosisSafe, threshold: number) => {
const safeThreshold = await gnosisSafe.getThreshold()
expect(Number(safeThreshold)).toEqual(threshold)
}
const assureOwnersAre = async (gnosisSafe, ...owners) => {
const safeOwners = await gnosisSafe.getOwners()
expect(safeOwners.length).toEqual(owners.length)
for (let i = 0; i < owners.length; i += 1) {
expect(safeOwners[i]).toBe(owners[i])
}
}
it('adds owner without increasing the threshold', async () => {
// GIVEN
const numOwners = 2
const threshold = 1
const store = aNewStore()
const address = await aDeployedSafe(store, 10, threshold, numOwners)
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
const safe = getSafeFrom(store.getState(), address)
const gnosisSafe = await getGnosisSafeInstanceAt(address)
// WHEN
await assureThresholdIs(gnosisSafe, 1)
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
const nonce = Date.now()
const accountIndex = 5
const data = gnosisSafe.contract.addOwnerWithThreshold.getData(accounts[accountIndex], 1)
await createTransaction(safe, `Add Owner with index ${accountIndex}`, address, 0, nonce, accounts[0], data)
await sleep(1500)
await store.dispatch(fetchTransactions())
// THEN
const transactions = safeTransactionsSelector(store.getState(), { safeAddress: address })
expect(transactions.count()).toBe(1)
const tx = transactions.get(0)
if (!tx) throw new Error()
assureExecuted(tx)
await assureOwnersAre(gnosisSafe, accounts[5], accounts[0], accounts[1])
await assureThresholdIs(gnosisSafe, 1)
})
it('adds owner increasing the threshold', async () => {
// GIVEN
const numOwners = 2
const threshold = 1
const store = aNewStore()
const address = await aDeployedSafe(store, 10, threshold, numOwners)
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
const safe = getSafeFrom(store.getState(), address)
const gnosisSafe = await getGnosisSafeInstanceAt(address)
// WHEN
await assureThresholdIs(gnosisSafe, 1)
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
const nonce = Date.now()
const accountIndex = 5
const data = gnosisSafe.contract.addOwnerWithThreshold.getData(accounts[accountIndex], 2)
await createTransaction(safe, `Add Owner with index ${accountIndex}`, address, 0, nonce, accounts[0], data)
await sleep(1500)
await store.dispatch(fetchTransactions())
// THEN
const transactions = safeTransactionsSelector(store.getState(), { safeAddress: address })
expect(transactions.count()).toBe(1)
const tx = transactions.get(0)
if (!tx) throw new Error()
assureExecuted(tx)
await assureOwnersAre(gnosisSafe, accounts[accountIndex], accounts[0], accounts[1])
await assureThresholdIs(gnosisSafe, 2)
})
const processOwnerModification = async (store, safeAddress, executor) => {
const tx = getTransactionFromReduxStore(store, safeAddress)
if (!tx) throw new Error()
const confirmed = confirmationsTransactionSelector(store.getState(), { transaction: tx })
const data = tx.get('data')
expect(data).not.toBe(null)
expect(data).not.toBe(undefined)
expect(data).not.toBe('')
await processTransaction(safeAddress, tx, confirmed, executor)
await sleep(1800)
}
it('remove owner without decreasing the threshold', async () => {
// GIVEN
const numOwners = 3
const threshold = 2
const store = aNewStore()
const address = await aDeployedSafe(store, 10, threshold, numOwners)
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
const safe = getSafeFrom(store.getState(), address)
const gnosisSafe = await getGnosisSafeInstanceAt(address)
// WHEN
await assureThresholdIs(gnosisSafe, 2)
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1], accounts[2])
const nonce = Date.now()
const accountIndex = 2
const data = gnosisSafe.contract.removeOwner.getData(accounts[accountIndex - 1], accounts[accountIndex], 2)
await createTransaction(safe, `Remove owner Address 3 ${nonce}`, address, 0, nonce, accounts[0], data)
await sleep(1500)
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1], accounts[2])
await store.dispatch(fetchTransactions())
processOwnerModification(store, address, accounts[1])
await sleep(3000)
await store.dispatch(fetchTransactions())
await sleep(3000)
const tx = getTransactionFromReduxStore(store, address)
if (!tx) throw new Error()
const txHash = tx.get('tx')
expect(txHash).not.toBe('')
await assureThresholdIs(gnosisSafe, 2)
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
})
it('remove owner decreasing the threshold', async () => {
// GIVEN
const numOwners = 2
const threshold = 2
const store = aNewStore()
const address = await aDeployedSafe(store, 10, threshold, numOwners)
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
const safe = getSafeFrom(store.getState(), address)
const gnosisSafe = await getGnosisSafeInstanceAt(address)
// WHEN
await assureThresholdIs(gnosisSafe, 2)
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
const nonce = Date.now()
const accountIndex = 1
const data = gnosisSafe.contract.removeOwner.getData(accounts[accountIndex - 1], accounts[accountIndex], 1)
await createTransaction(safe, `Remove owner Address 2 ${nonce}`, address, 0, nonce, accounts[0], data)
await sleep(1500)
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
await store.dispatch(fetchTransactions())
processOwnerModification(store, address, accounts[1])
await sleep(3000)
await store.dispatch(fetchTransactions())
await sleep(3000)
const tx = getTransactionFromReduxStore(store, address)
if (!tx) throw new Error()
const txHash = tx.get('tx')
expect(txHash).not.toBe('')
await assureThresholdIs(gnosisSafe, 1)
await assureOwnersAre(gnosisSafe, accounts[0])
})
})

View File

@ -9,7 +9,7 @@ import { processTransaction } from '~/routes/safe/component/Transactions/process
import { confirmationsTransactionSelector, safeSelector, safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
import { getTransactionFromReduxStore } from '~/routes/safe/test/testMultisig'
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
import { createTransaction } from '~/routes/safe/component/AddTransaction/createTransactions'
import { createTransaction } from '~/wallets/createTransactions'
import { getGnosisSafeContract } from '~/wallets/safeContracts'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'

View File

@ -1,125 +0,0 @@
// @flow
import * as React from 'react'
import TestUtils from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'react-router-redux'
import Button from '~/components/layout/Button'
import { aNewStore, history } from '~/store'
import { addEtherTo } from '~/test/utils/etherMovements'
import { aDeployedSafe, executeWithdrawOn } from '~/routes/safe/store/test/builder/deployedSafe.builder'
import { SAFELIST_ADDRESS } from '~/routes/routes'
import SafeView from '~/routes/safe/component/Safe'
import AppRoutes from '~/routes'
import { WITHDRAW_BUTTON_TEXT } from '~/routes/safe/component/Safe/DailyLimit'
import WithdrawComponent, { SEE_TXS_BUTTON_TEXT } from '~/routes/safe/component/Withdraw'
import { getBalanceInEtherOf } from '~/wallets/getWeb3'
import { sleep } from '~/utils/timer'
import { getDailyLimitFrom } from '~/routes/safe/component/Withdraw/withdraw'
import { type DailyLimitProps } from '~/routes/safe/store/model/dailyLimit'
import { ADD_MULTISIG_BUTTON_TEXT } from '~/routes/safe/component/Safe/MultisigTx'
import { WITHDRAW_INDEX, MOVE_FUNDS_INDEX } from '~/test/builder/safe.dom.utils'
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
import { safeSelector } from '~/routes/safe/store/selectors/index'
describe('React DOM TESTS > Withdraw funds from safe', () => {
let SafeDom
let store
let address
beforeEach(async () => {
// create store
store = aNewStore()
// deploy safe updating store
address = await aDeployedSafe(store)
// navigate to SAFE route
history.push(`${SAFELIST_ADDRESS}/${address}`)
SafeDom = TestUtils.renderIntoDocument((
<Provider store={store}>
<ConnectedRouter history={history}>
<AppRoutes />
</ConnectedRouter>
</Provider>
))
})
it('should withdraw funds under dailyLimit without needing confirmations', async () => {
// add funds to safe
await addEtherTo(address, '0.1')
await sleep(3000)
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(Safe, 'button')
const addWithdrawButton = buttons[WITHDRAW_INDEX]
expect(addWithdrawButton.getElementsByTagName('span')[0].innerHTML).toEqual(WITHDRAW_BUTTON_TEXT)
TestUtils.Simulate.click(addWithdrawButton)
await sleep(4000)
const Withdraw = TestUtils.findRenderedComponentWithType(SafeDom, WithdrawComponent)
// $FlowFixMe
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(Withdraw, 'input')
const amountInEth = inputs[0]
const toAddress = inputs[1]
TestUtils.Simulate.change(amountInEth, { target: { value: '0.01' } })
TestUtils.Simulate.change(toAddress, { target: { value: store.getState().providers.account } })
// $FlowFixMe
const form = TestUtils.findRenderedDOMComponentWithTag(Withdraw, 'form')
TestUtils.Simulate.submit(form) // fill the form
TestUtils.Simulate.submit(form) // confirming data
await sleep(6000)
const safeBalance = await getBalanceInEtherOf(address)
expect(safeBalance).toBe('0.09')
// $FlowFixMe
const withdrawButtons = TestUtils.scryRenderedComponentsWithType(Withdraw, Button)
const visitTxsButton = withdrawButtons[0]
expect(visitTxsButton.props.children).toEqual(SEE_TXS_BUTTON_TEXT)
})
it('spentToday dailyLimitModule property is updated correctly', async () => {
// add funds to safe
await addEtherTo(address, '0.1')
const match: Match = buildMathPropsFrom(address)
const safe = safeSelector(store.getState(), { match })
await executeWithdrawOn(safe, 0.01)
await executeWithdrawOn(safe, 0.01)
const ethAddress = 0
const dailyLimit: DailyLimitProps = await getDailyLimitFrom(address, ethAddress)
// THEN
expect(dailyLimit.value).toBe(0.5)
expect(dailyLimit.spentToday).toBe(0.02)
})
it('add multisig txs button disabled when balance is 0', async () => {
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(Safe, 'button')
const addTxButton = buttons[MOVE_FUNDS_INDEX]
expect(addTxButton.getElementsByTagName('span')[0].innerHTML).toEqual(ADD_MULTISIG_BUTTON_TEXT)
expect(addTxButton.hasAttribute('disabled')).toBe(true)
await addEtherTo(address, '0.1')
await sleep(1800)
expect(addTxButton.hasAttribute('disabled')).toBe(false)
})
it('Withdraw button disabled when balance is 0', async () => {
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(Safe, 'button')
const addWithdrawButton = buttons[WITHDRAW_INDEX]
expect(addWithdrawButton.getElementsByTagName('span')[0].innerHTML).toEqual(WITHDRAW_BUTTON_TEXT)
expect(addWithdrawButton.hasAttribute('disabled')).toBe(true)
await addEtherTo(address, '0.1')
await sleep(1800)
expect(addWithdrawButton.hasAttribute('disabled')).toBe(false)
})
})

View File

@ -1,114 +1,9 @@
// @flow
import TestUtils from 'react-dom/test-utils'
import { sleep } from '~/utils/timer'
import { getBalanceInEtherOf } from '~/wallets/getWeb3'
import { ADD_MULTISIG_BUTTON_TEXT, SEE_MULTISIG_BUTTON_TEXT } from '~/routes/safe/component/Safe/MultisigTx'
import { addEtherTo } from '~/test/utils/etherMovements'
import SafeView from '~/routes/safe/component/Safe'
import TransactionsComponent from '~/routes/safe/component/Transactions'
import TransactionComponent from '~/routes/safe/component/Transactions/Transaction'
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
import { type GlobalState } from '~/store/index'
import ListItemText from '~/components/List/ListItemText'
import { MOVE_FUNDS_INDEX, LIST_TXS_INDEX } from '~/test/builder/safe.dom.utils'
export const createMultisigTxFilling = async (
SafeDom: React$Component<any, any>,
AddTransactionComponent: React$ElementType,
store: Store<GlobalState>,
) => {
// Get AddTransaction form component
const AddTransaction = TestUtils.findRenderedComponentWithType(SafeDom, AddTransactionComponent)
// $FlowFixMe
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(AddTransaction, 'input')
const name = inputs[0]
const destination = inputs[1]
const amountInEth = inputs[2]
TestUtils.Simulate.change(name, { target: { value: 'Buying betteries' } })
TestUtils.Simulate.change(amountInEth, { target: { value: '0.01' } })
TestUtils.Simulate.change(destination, { target: { value: store.getState().providers.account } })
// $FlowFixMe
const form = TestUtils.findRenderedDOMComponentWithTag(AddTransaction, 'form')
TestUtils.Simulate.submit(form) // fill the form
TestUtils.Simulate.submit(form) // confirming data
return sleep(4000)
}
export const checkBalanceOf = async (addressToTest: string, value: string) => {
const safeBalance = await getBalanceInEtherOf(addressToTest)
expect(safeBalance).toBe(value)
}
export const addFundsTo = async (SafeDom: React$Component<any, any>, destination: string) => {
// add funds to safe
await addEtherTo(destination, '0.1')
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(Safe, 'button')
const addTxButton = buttons[MOVE_FUNDS_INDEX]
expect(addTxButton.getElementsByTagName('span')[0].innerHTML).toEqual(ADD_MULTISIG_BUTTON_TEXT)
await sleep(1800) // Give time to enable Add button
TestUtils.Simulate.click(addTxButton)
}
export const listTxsOf = (SafeDom: React$Component<any, any>) => {
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(Safe, 'button')
const seeTx = buttons[LIST_TXS_INDEX]
expect(seeTx.getElementsByTagName('span')[0].innerHTML).toEqual(SEE_MULTISIG_BUTTON_TEXT)
TestUtils.Simulate.click(seeTx)
}
export const getListItemsFrom = (SafeDom: React$Component<any, any>) => {
const Transactions = TestUtils.findRenderedComponentWithType(SafeDom, TransactionsComponent)
if (!Transactions) throw new Error()
const Transaction = TestUtils.findRenderedComponentWithType(Transactions, TransactionComponent)
if (!Transaction) throw new Error()
return TestUtils.scryRenderedComponentsWithType(Transaction, ListItemText)
}
export const expandTransactionOf = async (
SafeDom: React$Component<any, any>,
numOwners: number,
safeThreshold: number,
) => {
const listItems = getListItemsFrom(SafeDom)
TestUtils.Simulate.click(TestUtils.scryRenderedDOMComponentsWithTag(listItems[2], 'p')[0]) // expanded
await sleep(2500) // Time to expand
const listItemsExpanded = getListItemsFrom(SafeDom)
const threshold = listItemsExpanded[5]
expect(threshold.props.secondary).toContain(`confirmation${safeThreshold === 1 ? '' : 's'} needed`)
TestUtils.Simulate.click(TestUtils.scryRenderedDOMComponentsWithTag(threshold, 'p')[0]) // expanded
await sleep(2500) // Time to expand
expect(listItemsExpanded.length).toBe(listItems.length + numOwners)
}
export const getTransactionFromReduxStore = (store: Store<GlobalState>, address: string, index: number = 0) => {
const transactions = safeTransactionsSelector(store.getState(), { safeAddress: address })
return transactions.get(index)
}
export const confirmOwners = async (SafeDom: React$Component<any, any>, ...statusses: string[]) => {
const Transactions = TestUtils.findRenderedComponentWithType(SafeDom, TransactionsComponent)
if (!Transactions) throw new Error()
const Transaction = TestUtils.findRenderedComponentWithType(Transactions, TransactionComponent)
if (!Transaction) throw new Error()
const listItems = TestUtils.scryRenderedComponentsWithType(Transaction, ListItemText)
for (let i = 0; i < statusses.length; i += 1) {
const ownerIndex = i + 6
const ownerParagraph = listItems[ownerIndex].props.primary
expect(statusses[i]).toEqual(ownerParagraph)
}
}

View File

@ -21,7 +21,7 @@ const safesListSelectorTests = () => {
const reduxStore = {
[PROVIDER_REDUCER_ID]: walletRecord,
[SAFE_REDUCER_ID]: Map(),
balances: undefined,
tokens: undefined,
transactions: undefined,
}
const emptyList = List([])
@ -42,7 +42,7 @@ const safesListSelectorTests = () => {
const reduxStore = {
[PROVIDER_REDUCER_ID]: walletRecord,
[SAFE_REDUCER_ID]: map,
balances: undefined,
tokens: undefined,
transactions: undefined,
}
@ -62,7 +62,7 @@ const safesListSelectorTests = () => {
const reduxStore = {
[PROVIDER_REDUCER_ID]: walletRecord,
[SAFE_REDUCER_ID]: map,
balances: undefined,
tokens: undefined,
transactions: undefined,
}
@ -83,7 +83,7 @@ const safesListSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: map,
[PROVIDER_REDUCER_ID]: walletRecord,
balances: undefined,
tokens: undefined,
transactions: undefined,
}
@ -105,7 +105,7 @@ const safesListSelectorTests = () => {
const reduxStore = {
[SAFE_REDUCER_ID]: map,
[PROVIDER_REDUCER_ID]: walletRecord,
balances: undefined,
tokens: undefined,
transactions: undefined,
}

View File

@ -0,0 +1,18 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import { List } from 'immutable'
import styles from '~/components/layout/PageFrame/index.scss'
import AddTokenForm from './index'
const FrameDecorator = story => (
<div className={styles.frame} style={{ textAlign: 'center' }}>
{ story() }
</div>
)
storiesOf('Components', module)
.addDecorator(FrameDecorator)
.add('AddTokenForm', () => (
// $FlowFixMe
<AddTokenForm tokens={List([]).toArray()} safeAddress="" />
))

View File

@ -0,0 +1,56 @@
// @flow
import * as React from 'react'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import { composeValidators, required, mustBeEthereumAddress, uniqueAddress } from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Heading from '~/components/layout/Heading'
import { promisify } from '~/utils/promisify'
import { getWeb3 } from '~/wallets/getWeb3'
import { EMPTY_DATA } from '~/wallets/ethTransactions'
import { getStandardTokenContract } from '~/routes/tokens/store/actions/fetchTokens'
type Props = {
addresses: string[],
}
export const TOKEN_ADRESS_PARAM = 'tokenAddress'
export const token = async (tokenAddress: string) => {
const code = await promisify(cb => getWeb3().eth.getCode(tokenAddress, cb))
const isDeployed = code !== EMPTY_DATA
if (!isDeployed) {
return 'Specified address is not deployed on the current network'
}
const erc20Token = await getStandardTokenContract()
const instance = await erc20Token.at(tokenAddress)
const supply = await instance.totalSupply()
if (Number(supply) === 0) {
return 'Specified address is not a valid standard token'
}
return undefined
}
const FirstPage = ({ addresses }: Props) => () => (
<Block margin="md">
<Heading tag="h2" margin="lg">
Add Custom ERC20 Token
</Heading>
<Block margin="md">
<Field
name={TOKEN_ADRESS_PARAM}
component={TextField}
type="text"
validate={composeValidators(required, mustBeEthereumAddress, uniqueAddress(addresses), token)}
placeholder="ERC20 Token Address*"
text="ERC20 Token Address"
/>
</Block>
</Block>
)
export default FirstPage

View File

@ -0,0 +1,45 @@
// @flow
import * as React from 'react'
import CircularProgress from '@material-ui/core/CircularProgress'
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 { TOKEN_ADRESS_PARAM } from '~/routes/tokens/component/AddToken/FirstPage'
import { TOKEN_LOGO_URL_PARAM, TOKEN_NAME_PARAM, TOKEN_SYMBOL_PARAM, TOKEN_DECIMALS_PARAM } from '~/routes/tokens/component/AddToken/SecondPage'
type FormProps = {
values: Object,
submitting: boolean,
}
const spinnerStyle = {
minHeight: '50px',
}
const Review = () => ({ values, submitting }: FormProps) => (
<Block>
<Heading tag="h2">Review ERC20 Token operation</Heading>
<Paragraph align="left">
<Bold>Token address: </Bold> {values[TOKEN_ADRESS_PARAM]}
</Paragraph>
<Paragraph align="left">
<Bold>Token name: </Bold> {values[TOKEN_NAME_PARAM]}
</Paragraph>
<Paragraph align="left">
<Bold>Token symbol: </Bold> {values[TOKEN_SYMBOL_PARAM]}
</Paragraph>
<Paragraph align="left">
<Bold>Token decimals: </Bold> {values[TOKEN_DECIMALS_PARAM]}
</Paragraph>
<Paragraph align="left">
<Bold>Token logo: </Bold> {values[TOKEN_LOGO_URL_PARAM]}
</Paragraph>
<Block style={spinnerStyle}>
{ submitting && <CircularProgress size={50} /> }
</Block>
</Block>
)
export default Review

View File

@ -0,0 +1,62 @@
// @flow
import * as React from 'react'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import { composeValidators, required, mustBeInteger, mustBeUrl } from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Heading from '~/components/layout/Heading'
export const TOKEN_NAME_PARAM = 'tokenName'
export const TOKEN_SYMBOL_PARAM = 'tokenSymbol'
export const TOKEN_DECIMALS_PARAM = 'tokenDecimals'
export const TOKEN_LOGO_URL_PARAM = 'tokenLogo'
const SecondPage = () => () => (
<Block margin="md">
<Heading tag="h2" margin="lg">
Complete Custom Token information
</Heading>
<Block margin="md">
<Field
name={TOKEN_NAME_PARAM}
component={TextField}
type="text"
validate={required}
placeholder="ERC20 Token Name*"
text="ERC20 Token Name"
/>
</Block>
<Block margin="md">
<Field
name={TOKEN_SYMBOL_PARAM}
component={TextField}
type="text"
validate={required}
placeholder="ERC20 Token Symbol*"
text="ERC20 Token Symbol"
/>
</Block>
<Block margin="md">
<Field
name={TOKEN_DECIMALS_PARAM}
component={TextField}
type="text"
validate={composeValidators(required, mustBeInteger)}
placeholder="ERC20 Token Decimals*"
text="ERC20 Token Decimals"
/>
</Block>
<Block margin="md">
<Field
name={TOKEN_LOGO_URL_PARAM}
component={TextField}
type="text"
validate={composeValidators(required, mustBeUrl)}
placeholder="ERC20 Token Logo url*"
text="ERC20 Token Logo"
/>
</Block>
</Block>
)
export default SecondPage

View File

@ -0,0 +1,128 @@
// @flow
import * as React from 'react'
import Stepper from '~/components/Stepper'
import { getHumanFriendlyToken } from '~/routes/tokens/store/actions/fetchTokens'
import FirstPage, { TOKEN_ADRESS_PARAM } from '~/routes/tokens/component/AddToken/FirstPage'
import SecondPage, { TOKEN_SYMBOL_PARAM, TOKEN_DECIMALS_PARAM, TOKEN_LOGO_URL_PARAM, TOKEN_NAME_PARAM } from '~/routes/tokens/component/AddToken/SecondPage'
import { makeToken, type Token } from '~/routes/tokens/store/model/token'
import addTokenAction from '~/routes/tokens/store/actions/addToken'
import { getWeb3 } from '~/wallets/getWeb3'
import { promisify } from '~/utils/promisify'
import { EMPTY_DATA } from '~/wallets/ethTransactions'
import Review from './Review'
export const getSteps = () => [
'Fill Add Token Form', 'Check optional attributes', 'Review Information',
]
type Props = {
tokens: string[],
safeAddress: string,
addToken: typeof addTokenAction,
}
type State = {
done: boolean,
}
export const ADD_TOKEN_RESET_BUTTON_TEXT = 'RESET'
export const addTokenFnc = async (values: Object, addToken: typeof addTokenAction, safeAddress: string) => {
const address = values[TOKEN_ADRESS_PARAM]
const name = values[TOKEN_NAME_PARAM]
const symbol = values[TOKEN_SYMBOL_PARAM]
const decimals = values[TOKEN_DECIMALS_PARAM]
const logo = values[TOKEN_LOGO_URL_PARAM]
const token: Token = makeToken({
address,
name,
symbol,
decimals: Number(decimals),
logoUrl: logo,
status: true,
removable: true,
})
return addToken(safeAddress, token)
}
class AddToken extends React.Component<Props, State> {
state = {
done: false,
}
onAddToken = async (values: Object) => {
const { addToken, safeAddress } = this.props
const result = addTokenFnc(values, addToken, safeAddress)
this.setState({ done: true })
return result
}
onReset = () => {
this.setState({ done: false })
}
fetchInitialPropsSecondPage = async (values: Object) => {
const tokenAddress = values[TOKEN_ADRESS_PARAM]
const erc20Token = await getHumanFriendlyToken()
const instance = await erc20Token.at(tokenAddress)
const dataName = await instance.contract.name.getData()
const nameResult = await promisify(cb => getWeb3().eth.call({ to: tokenAddress, data: dataName }, cb))
const hasName = nameResult !== EMPTY_DATA
const dataSymbol = await instance.contract.symbol.getData()
const symbolResult = await promisify(cb => getWeb3().eth.call({ to: tokenAddress, data: dataSymbol }, cb))
const hasSymbol = symbolResult !== EMPTY_DATA
const dataDecimals = await instance.contract.decimals.getData()
const decimalsResult = await promisify(cb => getWeb3().eth.call({ to: tokenAddress, data: dataDecimals }, cb))
const hasDecimals = decimalsResult !== EMPTY_DATA
const name = hasName ? await instance.name() : undefined
const symbol = hasSymbol ? await instance.symbol() : undefined
const decimals = hasDecimals ? `${await instance.decimals()}` : undefined
return ({
[TOKEN_SYMBOL_PARAM]: symbol,
[TOKEN_DECIMALS_PARAM]: decimals,
[TOKEN_NAME_PARAM]: name,
})
}
render() {
const { tokens, safeAddress } = this.props
const { done } = this.state
const steps = getSteps()
const finishedButton = <Stepper.FinishButton title={ADD_TOKEN_RESET_BUTTON_TEXT} />
return (
<React.Fragment>
<Stepper
finishedTransaction={done}
finishedButton={finishedButton}
onSubmit={this.onAddToken}
steps={steps}
onReset={this.onReset}
disabledWhenValidating
>
<Stepper.Page addresses={tokens} prepareNextInitialProps={this.fetchInitialPropsSecondPage}>
{ FirstPage }
</Stepper.Page>
<Stepper.Page safeAddress={safeAddress}>
{ SecondPage }
</Stepper.Page>
<Stepper.Page>
{ Review }
</Stepper.Page>
</Stepper>
</React.Fragment>
)
}
}
export default AddToken

View File

@ -0,0 +1,126 @@
// @flow
import MuiList from '@material-ui/core/List'
import * as React from 'react'
import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col'
import AccountBalanceWallet from '@material-ui/icons/AccountBalanceWallet'
import AddCircle from '@material-ui/icons/AddCircle'
import Link from '~/components/layout/Link'
import Bold from '~/components/layout/Bold'
import Img from '~/components/layout/Img'
import IconButton from '@material-ui/core/IconButton'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import { type Token } from '~/routes/tokens/store/model/token'
import { type SelectorProps } from '~/routes/tokens/container/selector'
import { type Actions } from '~/routes/tokens/container/actions'
import { SAFELIST_ADDRESS } from '~/routes/routes'
import AddToken from '~/routes/tokens/component/AddToken'
import RemoveToken from '~/routes/tokens/component/RemoveToken'
import TokenComponent from './Token'
const safeIcon = require('~/routes/safe/component/Safe/assets/gnosis_safe.svg')
type TokenProps = SelectorProps & Actions
type State = {
component: React$Node,
}
const listStyle = {
width: '100%',
paddingBottom: 0,
}
class TokenLayout extends React.PureComponent<TokenProps, State> {
state = {
component: undefined,
}
onAddToken = () => {
const { addresses, safeAddress, addToken } = this.props
this.setState({
component: <AddToken
addToken={addToken}
tokens={addresses.toArray()}
safeAddress={safeAddress}
/>,
})
}
onReset = () => {
this.setState({ component: undefined })
}
onRemoveToken = (token: Token) => {
const { safeAddress, removeToken } = this.props
this.setState({
component: <RemoveToken
token={token}
safeAddress={safeAddress}
removeTokenAction={removeToken}
onReset={this.onReset}
/>,
})
}
onEnableToken = (token: Token) => {
const { enableToken, safe } = this.props
const safeAddress = safe.get('address')
enableToken(safeAddress, token)
}
onDisableToken = (token: Token) => {
const { disableToken, safe } = this.props
const safeAddress = safe.get('address')
disableToken(safeAddress, token)
}
render() {
const { safe, safeAddress, tokens } = this.props
const { component } = this.state
const name = safe ? safe.get('name') : ''
return (
<Row grow>
<Col sm={12} top="xs" md={5} margin="xl" overflow>
<MuiList style={listStyle}>
{tokens.map((token: Token) => (
<TokenComponent
key={token.get('address')}
token={token}
onDisableToken={this.onDisableToken}
onEnableToken={this.onEnableToken}
onRemove={this.onRemoveToken}
/>
))}
</MuiList>
</Col>
<Col sm={12} center="xs" md={7} margin="xl" layout="column">
<Block margin="xl">
<Paragraph size="lg" noMargin align="right">
<IconButton to={`${SAFELIST_ADDRESS}/${safeAddress}`} component={Link}>
<AccountBalanceWallet />
</IconButton>
<IconButton onClick={this.onAddToken}>
<AddCircle />
</IconButton>
<Bold>{name}</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 TokenLayout

View File

@ -0,0 +1,38 @@
// @flow
import * as React from 'react'
import CircularProgress from '@material-ui/core/CircularProgress'
import Block from '~/components/layout/Block'
import Bold from '~/components/layout/Bold'
import Heading from '~/components/layout/Heading'
import Paragraph from '~/components/layout/Paragraph'
type Props = {
name: string,
funds: string,
symbol: string,
}
type FormProps = {
submitting: boolean,
}
const spinnerStyle = {
minHeight: '50px',
}
const Review = ({ name, funds, symbol }: Props) => ({ submitting }: FormProps) => (
<Block>
<Heading tag="h2">Remove CUSTOM ERC 20 Token</Heading>
<Paragraph align="left">
<Bold>You are about to remove the custom token: </Bold> {name}
</Paragraph>
<Paragraph align="left">
<Bold>{`You have ${funds} ${symbol} in your wallet`}</Bold>
</Paragraph>
<Block style={spinnerStyle}>
{ submitting && <CircularProgress size={50} /> }
</Block>
</Block>
)
export default Review

View File

@ -0,0 +1,73 @@
// @flow
import * as React from 'react'
import { type Token } from '~/routes/tokens/store/model/token'
import Stepper from '~/components/Stepper'
import RemoveTokenAction from '~/routes/tokens/store/actions/removeToken'
import Review from '~/routes/tokens/component/RemoveToken/Review'
const getSteps = () => [
'Review remove token operation',
]
type Props = {
token: Token,
safeAddress: string,
removeTokenAction: typeof RemoveTokenAction,
onReset: () => void,
}
type State = {
done: boolean,
}
export const REMOVE_TOKEN_RESET_BUTTON_TEXT = 'RESET'
export const removeToken = async (safeAddress: string, token: Token, removeTokenAction: typeof RemoveTokenAction) =>
removeTokenAction(safeAddress, token)
class RemoveToken extends React.PureComponent<Props, State> {
state = {
done: false,
}
onRemoveReset = () => {
this.setState({ done: false }, this.props.onReset())
}
executeRemoveOperation = async () => {
try {
const { token, safeAddress, removeTokenAction } = this.props
await removeToken(safeAddress, token, removeTokenAction)
this.setState({ done: true })
} catch (error) {
this.setState({ done: false })
// eslint-disable-next-line
console.log('Error while removing owner ' + error)
}
}
render() {
const { done } = this.state
const { token } = this.props
const finishedButton = <Stepper.FinishButton title={REMOVE_TOKEN_RESET_BUTTON_TEXT} />
const steps = getSteps()
return (
<React.Fragment>
<Stepper
finishedTransaction={done}
finishedButton={finishedButton}
onSubmit={this.executeRemoveOperation}
steps={steps}
onReset={this.onRemoveReset}
>
<Stepper.Page name={token.get('name')} symbol={token.get('symbol')} funds={token.get('funds')}>
{ Review }
</Stepper.Page>
</Stepper>
</React.Fragment>
)
}
}
export default RemoveToken

View File

@ -0,0 +1,96 @@
// @flow
import * as React from 'react'
import { type Token } from '~/routes/tokens/store/model/token'
import { withStyles } from '@material-ui/core/styles'
import Block from '~/components/layout/Block'
import Checkbox from '@material-ui/core/Checkbox'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardMedia from '@material-ui/core/CardMedia'
import Typography from '@material-ui/core/Typography'
import { isEther } from '~/utils/tokens'
import Delete from '@material-ui/icons/Delete'
import IconButton from '@material-ui/core/IconButton'
import { type WithStyles } from '~/theme/mui'
type Props = WithStyles & {
token: Token,
onRemove: (token: Token)=> void,
onEnableToken: (token: Token) => void,
onDisableToken: (token: Token) => void,
}
type State = {
checked: boolean,
}
const styles = () => ({
card: {
display: 'flex',
},
details: {
display: 'flex',
flexDirection: 'column',
},
content: {
flex: '1 0 auto',
},
cover: {
width: 150,
margin: 10,
backgroundSize: '50%',
},
})
class TokenComponent extends React.PureComponent<Props, State> {
state = {
checked: this.props.token.get('status'),
}
onRemoveClick = () => this.props.onRemove(this.props.token)
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const { checked } = e.target
const callback = checked ? this.props.onEnableToken : this.props.onDisableToken
this.setState(() => ({ checked }), () => callback(this.props.token))
}
render() {
const { classes, token } = this.props
const name = token.get('name')
const symbol = token.get('symbol')
const disabled = isEther(symbol)
return (
<Card className={classes.card}>
<Block className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="headline">{name}</Typography>
<Typography variant="subheading" color="textSecondary">
<Checkbox
disabled={disabled}
checked={!!this.state.checked}
onChange={this.handleChange}
color="primary"
/>
{symbol}
{ token.get('removable') &&
<IconButton aria-label="Delete" onClick={this.onRemoveClick}>
<Delete />
</IconButton>
}
</Typography>
</CardContent>
</Block>
<CardMedia
className={classes.cover}
image={token.get('logoUrl')}
title={name}
/>
</Card>
)
}
}
export default withStyles(styles, { withTheme: true })(TokenComponent)

View File

@ -0,0 +1,21 @@
// @flow
import addToken from '~/routes/tokens/store/actions/addToken'
import removeToken from '~/routes/tokens/store/actions/removeToken'
import enableToken from '~/routes/tokens/store/actions/enableToken'
import disableToken from '~/routes/tokens/store/actions/disableToken'
import { fetchTokens } from '~/routes/tokens/store/actions/fetchTokens'
export type Actions = {
enableToken: typeof enableToken,
disableToken: typeof disableToken,
addToken: typeof addToken,
removeToken: typeof removeToken,
}
export default {
addToken,
removeToken,
enableToken,
disableToken,
fetchTokens,
}

View File

@ -0,0 +1,45 @@
// @flow
import * as React from 'react'
import { connect } from 'react-redux'
import Page from '~/components/layout/Page'
import Layout from '~/routes/tokens/component/Layout'
import { fetchTokens } from '~/routes/tokens/store/actions/fetchTokens'
import selector, { type SelectorProps } from './selector'
import actions, { type Actions } from './actions'
type Props = Actions & SelectorProps & {
fetchTokens: typeof fetchTokens,
}
class TokensView extends React.PureComponent<Props> {
componentDidUpdate() {
const { safeAddress } = this.props
if (this.props.tokens.count() === 0) {
this.props.fetchTokens(safeAddress)
}
}
render() {
const {
tokens, addresses, safe, safeAddress, disableToken, enableToken, addToken, removeToken,
} = this.props
return (
<Page>
<Layout
tokens={tokens}
addresses={addresses}
safe={safe}
safeAddress={safeAddress}
disableToken={disableToken}
enableToken={enableToken}
addToken={addToken}
removeToken={removeToken}
/>
</Page>
)
}
}
export default connect(selector, actions)(TokensView)

View File

@ -0,0 +1,21 @@
// @flow
import { List } from 'immutable'
import { createStructuredSelector } from 'reselect'
import { tokenListSelector, tokenAddressesSelector } from '~/routes/tokens/store/selectors'
import { type Safe } from '~/routes/safe/store/model/safe'
import { safeSelector, safeParamAddressSelector } from '~/routes/safe/store/selectors'
import { type Token } from '~/routes/tokens/store/model/token'
export type SelectorProps = {
tokens: List<Token>,
addresses: List<string>,
safe: Safe,
safeAddress: string,
}
export default createStructuredSelector({
safe: safeSelector,
safeAddress: safeParamAddressSelector,
tokens: tokenListSelector,
addresses: tokenAddressesSelector,
})

Some files were not shown because too many files have changed in this diff Show More