├── .gitattributes
├── .gitignore
├── README.md
├── gas-relayer
├── .babelrc
├── .eslintrc
├── abi
│ ├── ERC20Token.json
│ ├── IdentityFactory.json
│ ├── IdentityGasRelay.json
│ └── SNTController.json
├── config
│ ├── config.js
│ └── config.testnet.js
├── launch-geth-testnet.sh
├── package-lock.json
├── package.json
├── plugins
│ └── token-utils.js
├── src
│ ├── account-parser.js
│ ├── contract-settings.js
│ ├── message-processor.js
│ ├── service.js
│ └── strategy
│ │ ├── AvailabilityStrategy.js
│ │ ├── BaseStrategy.js
│ │ ├── IdentityStrategy.js
│ │ └── SNTStrategy.js
└── test
│ ├── sampleContracts.sol
│ ├── sendmsg.html
│ ├── sendmsg.js
│ └── web3.min.js
├── installation-development.md
├── installation-testnet-mainnet.md
├── javascript-library.md
├── relayer-protocol.md
└── test-dapp
├── .babelrc
├── .eslintrc
├── .gitignore
├── app
├── components
│ ├── approveandcallgasrelayed.js
│ ├── body-identity.js
│ ├── body-sntcontroller.js
│ ├── callgasrelayed.js
│ ├── execute.js
│ ├── header.js
│ ├── snackbar.js
│ ├── status-identity.js
│ ├── status-sntcontroller.js
│ └── transfersnt.js
├── config.js
├── css
│ ├── .gitkeep
│ └── dapp.css
├── identity.html
├── identity.js
├── images
│ └── .gitkeep
├── index.html
├── js
│ ├── .gitkeep
│ └── index.js
├── sntcontroller.html
├── sntcontroller.js
└── status-gas-relayer.js
├── config
├── blockchain.js
├── communication.js
├── contracts.js
├── namesystem.js
├── privatenet
│ ├── genesis.json
│ └── password
├── storage.js
├── testnet
│ └── password
└── webserver.js
├── contracts
├── .gitkeep
├── common
│ ├── Controlled.sol
│ ├── MessageSigned.sol
│ ├── Owned.sol
│ └── SafeMath.sol
├── deploy
│ ├── DelayedUpdatableInstance.sol
│ ├── DelayedUpdatableInstanceStorage.sol
│ ├── DelegatedCall.sol
│ ├── Factory.sol
│ ├── Instance.sol
│ ├── InstanceStorage.sol
│ └── UpdatableInstance.sol
├── identity
│ ├── ERC725.sol
│ ├── ERC735.sol
│ ├── Identity.sol
│ ├── IdentityFactory.sol
│ ├── IdentityGasRelay.sol
│ └── IdentityKernel.sol
├── status
│ └── SNTController.sol
├── tests
│ ├── TestContract.sol
│ ├── TestMiniMeToken.sol
│ └── UpdatedIdentityKernel.sol
└── token
│ ├── ApproveAndCallFallBack.sol
│ ├── ERC20Receiver.sol
│ ├── ERC20Token.sol
│ ├── MiniMeToken.sol
│ ├── MiniMeTokenFactory.sol
│ ├── MiniMeTokenInterface.sol
│ ├── StandardToken.sol
│ ├── TestToken.sol
│ └── TokenController.sol
├── embark.json
├── launch-geth-testnet.sh
├── package-lock.json
├── package.json
├── setup_dev_env.sh
├── test
├── contract_spec.js
├── erc20token.js
├── factory.js
├── identity.js
├── identityExtended.js
├── identityfactory.js
└── testtoken.js
└── utils
├── identityUtils.js
└── testUtils.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # embark
7 | .embark/
8 | chains.json
9 | config/production/password
10 | config/livenet/password
11 |
12 | # egg-related
13 | viper.egg-info/
14 | build/
15 | dist/
16 | .eggs/
17 |
18 | # pyenv
19 | .python-version
20 |
21 | # dotenv
22 | .env
23 |
24 | # virtualenv
25 | .venv/
26 | venv/
27 | ENV/
28 |
29 | # Coverage tests
30 | .coverage
31 | .cache/
32 | coverage/
33 | coverageEnv/
34 | coverage.json
35 |
36 | # node
37 | node_modules/
38 | npm-debug.log
39 |
40 | # other
41 | .vs/
42 | bin/
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # token-gas-relayer
2 | Gas relayer mplementation for economic abstraction. This project consists of two elements:
3 | - `gas-relayer`: nodejs service that listens to whisper on a symmetric key, with specific topics, and processes any transaction.
4 | - `test-dapp`: DApp created for testing purposes. It allows the easy creation of the messages expected by the service.
5 |
6 |
7 | ## Documentation
8 | 1. [Installation - testnet/mainnet](installation-testnet-mainnet.md)
9 | 2. [Installation - development environment](installation-development.md)
10 | 3. [Gas relayer protocol](relayer-protocol.md)
11 | 4. [Javascript library](javascript-library.md)
12 | 5. Status Extensions (TODO)
13 |
14 |
15 | ## Deployment Details
16 | | Contract | Ropsten Address | Mainnet Address |
17 | | ---------------------------|------------------------------------------- | ------------------------------------------ |
18 | | status/SNTController | 0x1f42B87b375b8ac6C77A8CAd8E78319c18695E75 | - |
19 | | identity/IdentityFactory | 0xCf3473C2A50F7A94D3D7Dcc2BeBbeE989dAA014E | - |
20 |
--------------------------------------------------------------------------------
/gas-relayer/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-object-rest-spread"
4 | ],
5 | "presets": [
6 | "stage-2"
7 | ],
8 | "ignore": [
9 | "config/",
10 | "node_modules"
11 | ]
12 | }
--------------------------------------------------------------------------------
/gas-relayer/.eslintrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "parser": "babel-eslint",
4 | "plugins": [ ],
5 | "parserOptions": {
6 | "ecmaVersion": 2017,
7 | "sourceType": "module",
8 | "ecmaFeatures": {
9 | "jsx": true
10 | }
11 | },
12 | "env": {
13 | "es6": true,
14 | "browser": true,
15 | "node": true,
16 | "mocha": true
17 | },
18 | "extends": [
19 | "eslint:recommended"
20 | ],
21 | "rules": {
22 | "accessor-pairs": "error",
23 | "array-bracket-newline": "error",
24 | "array-bracket-spacing": [
25 | "error",
26 | "never"
27 | ],
28 | "array-callback-return": "off",
29 | "array-element-newline": "off",
30 | "arrow-body-style": "off",
31 | "arrow-parens": "off",
32 | "arrow-spacing": [
33 | "error",
34 | {
35 | "after": true,
36 | "before": true
37 | }
38 | ],
39 | "block-scoped-var": "error",
40 | "block-spacing": "error",
41 | "brace-style": "off",
42 | "callback-return": "off",
43 | "camelcase": "off",
44 | "capitalized-comments": "off",
45 | "class-methods-use-this": "off",
46 | "comma-dangle": "error",
47 | "comma-spacing": "off",
48 | "comma-style": [
49 | "error",
50 | "last"
51 | ],
52 | "complexity": "error",
53 | "computed-property-spacing": [
54 | "error",
55 | "never"
56 | ],
57 | "consistent-return": "off",
58 | "consistent-this": "off",
59 | "curly": "off",
60 | "default-case": "error",
61 | "dot-location": [
62 | "error",
63 | "property"
64 | ],
65 | "dot-notation": "off",
66 | "eol-last": "error",
67 | "eqeqeq": "off",
68 | "for-direction": "error",
69 | "func-call-spacing": "error",
70 | "func-name-matching": "error",
71 | "func-names": "off",
72 | "func-style": "off",
73 | "function-paren-newline": "off",
74 | "generator-star-spacing": "error",
75 | "getter-return": "error",
76 | "global-require": "off",
77 | "guard-for-in": "off",
78 | "handle-callback-err": "off",
79 | "id-blacklist": "error",
80 | "id-length": "off",
81 | "id-match": "error",
82 | "indent": "off",
83 | "indent-legacy": "off",
84 | "init-declarations": "off",
85 | "jsx-quotes": "error",
86 | "key-spacing": "off",
87 | "keyword-spacing": "off",
88 | "line-comment-position": "off",
89 | "linebreak-style": [
90 | "error",
91 | "unix"
92 | ],
93 | "lines-around-comment": "error",
94 | "lines-around-directive": "error",
95 | "max-depth": "error",
96 | "max-len": "off",
97 | "max-lines": "off",
98 | "max-nested-callbacks": "error",
99 | "max-params": "off",
100 | "max-statements": "off",
101 | "max-statements-per-line": "off",
102 | "multiline-ternary": [
103 | "error",
104 | "never"
105 | ],
106 | "new-parens": "off",
107 | "newline-after-var": "off",
108 | "newline-before-return": "off",
109 | "newline-per-chained-call": "off",
110 | "no-alert": "error",
111 | "no-array-constructor": "error",
112 | "no-await-in-loop": "error",
113 | "no-bitwise": "error",
114 | "no-buffer-constructor": "error",
115 | "no-caller": "error",
116 | "no-catch-shadow": "error",
117 | "no-confusing-arrow": "error",
118 | "no-console": "off",
119 | "no-continue": "off",
120 | "no-div-regex": "error",
121 | "no-duplicate-imports": "error",
122 | "no-else-return": "off",
123 | "no-empty-function": "off",
124 | "no-eq-null": "error",
125 | "no-eval": "off",
126 | "no-extend-native": "error",
127 | "no-extra-bind": "error",
128 | "no-extra-label": "error",
129 | "no-extra-parens": "off",
130 | "no-floating-decimal": "error",
131 | "no-implicit-coercion": "error",
132 | "no-implicit-globals": "error",
133 | "no-implied-eval": "error",
134 | "no-inline-comments": "off",
135 | "no-inner-declarations": [
136 | "error",
137 | "functions"
138 | ],
139 | "no-invalid-this": "off",
140 | "no-iterator": "error",
141 | "no-label-var": "error",
142 | "no-labels": "error",
143 | "no-lone-blocks": "error",
144 | "no-lonely-if": "off",
145 | "no-loop-func": "off",
146 | "no-magic-numbers": "off",
147 | "no-mixed-operators": "error",
148 | "no-mixed-requires": "error",
149 | "no-multi-assign": "error",
150 | "no-multi-spaces": "off",
151 | "no-multi-str": "error",
152 | "no-multiple-empty-lines": "error",
153 | "no-native-reassign": "error",
154 | "no-negated-condition": "off",
155 | "no-negated-in-lhs": "error",
156 | "no-nested-ternary": "error",
157 | "no-new": "error",
158 | "no-new-func": "error",
159 | "no-new-object": "error",
160 | "no-new-require": "error",
161 | "no-new-wrappers": "error",
162 | "no-octal-escape": "error",
163 | "no-param-reassign": "off",
164 | "no-path-concat": "error",
165 | "no-plusplus": "off",
166 | "no-process-env": "off",
167 | "no-process-exit": "off",
168 | "no-proto": "error",
169 | "no-prototype-builtins": "off",
170 | "no-restricted-globals": "error",
171 | "no-restricted-imports": "error",
172 | "no-restricted-modules": "error",
173 | "no-restricted-properties": "error",
174 | "no-restricted-syntax": "error",
175 | "no-return-assign": "error",
176 | "no-return-await": "error",
177 | "no-script-url": "error",
178 | "no-self-compare": "error",
179 | "no-sequences": "error",
180 | "no-shadow": "off",
181 | "no-shadow-restricted-names": "error",
182 | "no-spaced-func": "error",
183 | "no-sync": "off",
184 | "no-tabs": "error",
185 | "no-template-curly-in-string": "error",
186 | "no-ternary": "off",
187 | "no-throw-literal": "error",
188 | "no-trailing-spaces": "off",
189 | "no-undef-init": "error",
190 | "no-undefined": "off",
191 | "no-underscore-dangle": "off",
192 | "no-unmodified-loop-condition": "error",
193 | "no-unneeded-ternary": "error",
194 | "no-unused-expressions": "error",
195 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
196 | "no-use-before-define": "off",
197 | "no-useless-call": "off",
198 | "no-useless-computed-key": "error",
199 | "no-useless-concat": "error",
200 | "no-useless-constructor": "error",
201 | "no-useless-escape": "off",
202 | "no-useless-rename": "error",
203 | "no-useless-return": "off",
204 | "no-var": "off",
205 | "no-void": "error",
206 | "no-warning-comments": "off",
207 | "no-whitespace-before-property": "error",
208 | "no-with": "error",
209 | "nonblock-statement-body-position": "error",
210 | "object-curly-newline": "off",
211 | "object-curly-spacing": [
212 | "error",
213 | "never"
214 | ],
215 | "object-property-newline": "off",
216 | "object-shorthand": "off",
217 | "one-var": "off",
218 | "one-var-declaration-per-line": "off",
219 | "operator-assignment": "off",
220 | "operator-linebreak": "error",
221 | "padded-blocks": "off",
222 | "padding-line-between-statements": "error",
223 | "prefer-arrow-callback": "off",
224 | "prefer-const": "off",
225 | "prefer-destructuring": "off",
226 | "prefer-numeric-literals": "error",
227 | "prefer-promise-reject-errors": "error",
228 | "prefer-reflect": "off",
229 | "prefer-rest-params": "off",
230 | "prefer-spread": "off",
231 | "prefer-template": "off",
232 | "quote-props": "off",
233 | "quotes": "off",
234 | "radix": "error",
235 | "require-await": "error",
236 | "require-jsdoc": "off",
237 | "rest-spread-spacing": "error",
238 | "semi": "error",
239 | "semi-spacing": [
240 | "error",
241 | {
242 | "after": true,
243 | "before": false
244 | }
245 | ],
246 | "semi-style": [
247 | "error",
248 | "last"
249 | ],
250 | "sort-imports": [2, {
251 | "ignoreCase": false,
252 | "ignoreMemberSort": false,
253 | "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
254 | }],
255 | "sort-keys": "off",
256 | "sort-vars": "off",
257 | "space-before-blocks": "off",
258 | "space-before-function-paren": "off",
259 | "space-in-parens": [
260 | "error",
261 | "never"
262 | ],
263 | "space-infix-ops": "off",
264 | "space-unary-ops": "error",
265 | "spaced-comment": "off",
266 | "strict": "error",
267 | "switch-colon-spacing": "error",
268 | "symbol-description": "error",
269 | "template-curly-spacing": [
270 | "error",
271 | "never"
272 | ],
273 | "template-tag-spacing": "error",
274 | "unicode-bom": [
275 | "error",
276 | "never"
277 | ],
278 | "valid-jsdoc": "off",
279 | "vars-on-top": "off",
280 | "wrap-iife": "error",
281 | "wrap-regex": "error",
282 | "yield-star-spacing": "error",
283 | "yoda": [
284 | "error",
285 | "never"
286 | ]
287 | }
288 | }
--------------------------------------------------------------------------------
/gas-relayer/abi/ERC20Token.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "ERC20Token",
3 | "code": "",
4 | "runtime_bytecode": "",
5 | "real_runtime_bytecode": "",
6 | "swarm_hash": "",
7 | "gas_estimates": null,
8 | "function_hashes": {
9 | "allowance(address,address)": "dd62ed3e",
10 | "approve(address,uint256)": "095ea7b3",
11 | "balanceOf(address)": "70a08231",
12 | "decimals()": "313ce567",
13 | "totalSupply()": "18160ddd",
14 | "transfer(address,uint256)": "a9059cbb",
15 | "transferFrom(address,address,uint256)": "23b872dd"
16 | },
17 | "abi": [
18 | {
19 | "constant": false,
20 | "inputs": [
21 | {
22 | "name": "_spender",
23 | "type": "address"
24 | },
25 | {
26 | "name": "_value",
27 | "type": "uint256"
28 | }
29 | ],
30 | "name": "approve",
31 | "outputs": [
32 | {
33 | "name": "success",
34 | "type": "bool"
35 | }
36 | ],
37 | "payable": false,
38 | "stateMutability": "nonpayable",
39 | "type": "function"
40 | },
41 | {
42 | "constant": true,
43 | "inputs": [],
44 | "name": "totalSupply",
45 | "outputs": [
46 | {
47 | "name": "",
48 | "type": "uint256"
49 | }
50 | ],
51 | "payable": false,
52 | "stateMutability": "view",
53 | "type": "function"
54 | },
55 | {
56 | "constant": false,
57 | "inputs": [
58 | {
59 | "name": "_from",
60 | "type": "address"
61 | },
62 | {
63 | "name": "_to",
64 | "type": "address"
65 | },
66 | {
67 | "name": "_value",
68 | "type": "uint256"
69 | }
70 | ],
71 | "name": "transferFrom",
72 | "outputs": [
73 | {
74 | "name": "success",
75 | "type": "bool"
76 | }
77 | ],
78 | "payable": false,
79 | "stateMutability": "nonpayable",
80 | "type": "function"
81 | },
82 | {
83 | "constant": true,
84 | "inputs": [
85 | {
86 | "name": "_owner",
87 | "type": "address"
88 | }
89 | ],
90 | "name": "balanceOf",
91 | "outputs": [
92 | {
93 | "name": "balance",
94 | "type": "uint256"
95 | }
96 | ],
97 | "payable": false,
98 | "stateMutability": "view",
99 | "type": "function"
100 | },
101 | {
102 | "constant": true,
103 | "inputs": [],
104 | "name": "decimals",
105 | "outputs": [
106 | {
107 | "name": "",
108 | "type": "uint8"
109 | }
110 | ],
111 | "payable": false,
112 | "stateMutability": "view",
113 | "type": "function"
114 | },
115 | {
116 | "constant": false,
117 | "inputs": [
118 | {
119 | "name": "_to",
120 | "type": "address"
121 | },
122 | {
123 | "name": "_value",
124 | "type": "uint256"
125 | }
126 | ],
127 | "name": "transfer",
128 | "outputs": [
129 | {
130 | "name": "success",
131 | "type": "bool"
132 | }
133 | ],
134 | "payable": false,
135 | "stateMutability": "nonpayable",
136 | "type": "function"
137 | },
138 | {
139 | "constant": true,
140 | "inputs": [
141 | {
142 | "name": "_owner",
143 | "type": "address"
144 | },
145 | {
146 | "name": "_spender",
147 | "type": "address"
148 | }
149 | ],
150 | "name": "allowance",
151 | "outputs": [
152 | {
153 | "name": "remaining",
154 | "type": "uint256"
155 | }
156 | ],
157 | "payable": false,
158 | "stateMutability": "view",
159 | "type": "function"
160 | },
161 | {
162 | "anonymous": false,
163 | "inputs": [
164 | {
165 | "indexed": true,
166 | "name": "_from",
167 | "type": "address"
168 | },
169 | {
170 | "indexed": true,
171 | "name": "_to",
172 | "type": "address"
173 | },
174 | {
175 | "indexed": false,
176 | "name": "_value",
177 | "type": "uint256"
178 | }
179 | ],
180 | "name": "Transfer",
181 | "type": "event"
182 | },
183 | {
184 | "anonymous": false,
185 | "inputs": [
186 | {
187 | "indexed": true,
188 | "name": "_owner",
189 | "type": "address"
190 | },
191 | {
192 | "indexed": true,
193 | "name": "_spender",
194 | "type": "address"
195 | },
196 | {
197 | "indexed": false,
198 | "name": "_value",
199 | "type": "uint256"
200 | }
201 | ],
202 | "name": "Approval",
203 | "type": "event"
204 | }
205 | ]
206 | }
207 |
--------------------------------------------------------------------------------
/gas-relayer/config/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "node": {
3 | "local":{
4 | "protocol": "ws",
5 | "host": "localhost",
6 | "port": 8546
7 | },
8 | "ganache": {
9 | "protocol": "http",
10 | "host": "localhost",
11 | "port": 8545
12 | },
13 | "blockchain": {
14 | // DO NOT USE THIS ACCOUNT ON MAINNET - IT IS ONLY FOR DEV PURPOSES
15 | // For dev chain, address: 0x5b9b5db9cde96fda2e2c88e83f1b833f189e01f4 has this privKey
16 | privateKey: "b2ab40d549e67ba67f278781fec03b3a90515ad4d0c898a6326dd958de1e46fa" //
17 |
18 |
19 | // privateKey: "your_private_key",
20 |
21 |
22 | // privateKeyFile: "path/to/file"
23 |
24 | // mnemonic: "12 word mnemonic",
25 | // addressIndex: "0", // Optionnal. The index to start getting the address
26 | // hdpath: "m/44'/60'/0'/0/" // Optionnal. HD derivation path
27 | },
28 | "whisper": {
29 | "symKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b",
30 | "ttl": 1000,
31 | "minPow": 0.2,
32 | "powTime": 1000
33 | }
34 | },
35 |
36 | "tokens": {
37 | "0x0000000000000000000000000000000000000000": {
38 | "name": "Ethereum",
39 | "symbol": "ETH",
40 | "minAcceptedRate": 1,
41 | "refreshPricePeriod": 60000
42 | },
43 | "%STTAddress%": {
44 | "name": "Status Test Token",
45 | "symbol": "SNT",
46 | "minAcceptedRate": 150000000000000,
47 | "refreshPricePeriod": 60000,
48 | "pricePlugin": "../plugins/token-utils.js"
49 | }
50 | },
51 |
52 | "contracts":{
53 | "IdentityGasRelay": {
54 | "abiFile": "../abi/IdentityGasRelay.json",
55 | "isIdentity": true,
56 | "factoryAddress": "%IdentityFactoryAddress%",
57 | "kernelVerification": "isKernel(bytes32)",
58 | "allowedFunctions": [
59 | {
60 | "function": "approveAndCallGasRelayed(address,address,uint256,bytes,uint256,uint256,uint256,address,bytes)",
61 | "isToken": true
62 | },
63 | {
64 | "function": "callGasRelayed(address,uint256,bytes,uint256,uint256,uint256,address,bytes)",
65 | "isToken": false
66 | }
67 | ],
68 | "strategy": "../src/strategy/IdentityStrategy.js"
69 | },
70 | "SNTController": {
71 | "abiFile": "../abi/SNTController.json",
72 | "isIdentity": false,
73 | "address": "%SNTController%",
74 | "allowedFunctions": [
75 | {
76 | "function":"transferSNT(address,uint256,uint256,uint256,bytes)"
77 | },
78 | {
79 | "function":"executeGasRelayed(address,bytes,uint256,uint256,uint256,bytes)"
80 | }
81 | ],
82 | "strategy": "../src/strategy/SNTStrategy.js"
83 | }
84 | },
85 | "gasPrice": {
86 | "modifier": 50000, // Added/removed to current network gas price
87 | "maxPrice": 20000000000 // 20 gwei
88 | }
89 | };
90 |
--------------------------------------------------------------------------------
/gas-relayer/config/config.testnet.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "node": {
3 | "local":{
4 | "protocol": "ws",
5 | "host": "localhost",
6 | "port": 8546
7 | },
8 | "ganache": {
9 | "protocol": "http",
10 | "host": "localhost",
11 | "port": 8545
12 | },
13 | "blockchain": {
14 | privateKey: "0x......" //
15 | },
16 | "whisper": {
17 | "symKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b",
18 | "ttl": 10,
19 | "minPow": 0.002,
20 | "powTime": 1
21 | }
22 | },
23 |
24 | "tokens": {
25 | "0x0000000000000000000000000000000000000000": {
26 | "name": "Ethereum",
27 | "symbol": "ETH",
28 | "minAcceptedRate": 1,
29 | "refreshPricePeriod": 60000
30 | },
31 | "0x139724523662E54447B70d043b711b2A00c5EF49": {
32 | "name": "Status Test Token",
33 | "symbol": "SNT",
34 | "minAcceptedRate": 150000000000000,
35 | "refreshPricePeriod": 60000,
36 | "pricePlugin": "../plugins/token-utils.js"
37 | }
38 | },
39 |
40 | "contracts":{
41 | "IdentityGasRelay": {
42 | "abiFile": "../abi/IdentityGasRelay.json",
43 | "isIdentity": true,
44 | "factoryAddress": "0xCf3473C2A50F7A94D3D7Dcc2BeBbeE989dAA014E",
45 | "kernelVerification": "isKernel(bytes32)",
46 | "allowedFunctions": [
47 | {
48 | "function": "approveAndCallGasRelayed(address,address,uint256,bytes,uint256,uint256,uint256,address,bytes)",
49 | "isToken": true
50 | },
51 | {
52 | "function": "callGasRelayed(address,uint256,bytes,uint256,uint256,uint256,address,bytes)",
53 | "isToken": false
54 | }
55 | ],
56 | "strategy": "../src/strategy/IdentityStrategy.js"
57 | },
58 | "SNTController": {
59 | "abiFile": "../abi/SNTController.json",
60 | "isIdentity": false,
61 | "address": "0x1f42B87b375b8ac6C77A8CAd8E78319c18695E75",
62 | "allowedFunctions": [
63 | {
64 | "function":"transferSNT(address,uint256,uint256,uint256,bytes)"
65 | },
66 | {
67 | "function":"executeGasRelayed(address,bytes,uint256,uint256,uint256,bytes)"
68 | }
69 | ],
70 | "strategy": "../src/strategy/SNTStrategy.js"
71 | }
72 | },
73 | "gasPrice": {
74 | "modifier": 50000, // Added/removed to current network gas price
75 | "maxPrice": 20000000000 // 20 gwei
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/gas-relayer/launch-geth-testnet.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #!/bin/bash
3 | geth --testnet --syncmode=light --port=30303 --ws --wsport=8546 --wsaddr=localhost --wsorigins=http://localhost:8000,embark,gas-relayer --maxpeers=25 --shh --shh.pow=0.002 --wsapi=eth,web3,net,shh
4 |
5 |
--------------------------------------------------------------------------------
/gas-relayer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gas-relayer",
3 | "version": "0.0.1",
4 | "description": "Gas relayer to avoid having to hold ether to perform transactions when you already have a token",
5 | "main": "src/service.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node src/service.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-eslint": "^8.2.6",
14 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
15 | "babel-preset-stage-2": "^6.24.1",
16 | "eslint": "^4.19.1",
17 | "eslint-config-standard": "^11.0.0",
18 | "eslint-plugin-import": "^2.13.0",
19 | "eslint-plugin-node": "^7.0.1",
20 | "eslint-plugin-promise": "^3.8.0",
21 | "eslint-plugin-standard": "^3.1.0"
22 | },
23 | "dependencies": {
24 | "axios": "^0.18.0",
25 | "bip39": "^2.5.0",
26 | "consola": "^1.4.3",
27 | "daemonize2": "^0.4.2",
28 | "ethereumjs-wallet": "^0.6.2",
29 | "ganache-cli": "^6.1.0",
30 | "jsum": "^0.1.4",
31 | "memory-cache": "^0.2.0",
32 | "typedarray-to-buffer": "^3.1.5",
33 | "web3": "^1.0.0-beta.33",
34 | "winston": "^3.1.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/gas-relayer/plugins/token-utils.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 |
3 | class TokenUtils {
4 | constructor(tokenConfig, gasPrice, web3){
5 | this.gasPrice = gasPrice;
6 | this.name = tokenConfig.name || "";
7 | this.symbol = tokenConfig.symbol || "";
8 | this.minRelayFactor = tokenConfig.minRelayFactor || 1;
9 | this.web3 = web3;
10 | }
11 |
12 | async getRate(){
13 | // Using cryptocompare API
14 | const {toBN, toWei} = this.web3.utils;
15 |
16 | const doc = await axios.get('https://min-api.cryptocompare.com/data/price?fsym=' + this.symbol + '&tsyms=ETH');
17 | return toBN(toWei(doc.data.ETH.toString(), "ether"));
18 | }
19 | }
20 |
21 |
22 | module.exports = TokenUtils;
23 |
--------------------------------------------------------------------------------
/gas-relayer/src/account-parser.js:
--------------------------------------------------------------------------------
1 | const bip39 = require("bip39");
2 | const hdkey = require('ethereumjs-wallet/hdkey');
3 | const ethereumjsWallet = require('ethereumjs-wallet');
4 | const path = require('path');
5 | const fs = require('fs');
6 |
7 |
8 | class AccountParser {
9 | static get(accountConfig, web3) {
10 | if (accountConfig.privateKey) {
11 | if (!accountConfig.privateKey.startsWith('0x')) {
12 | accountConfig.privateKey = '0x' + accountConfig.privateKey;
13 | }
14 | if (!web3.utils.isHexStrict(accountConfig.privateKey)) {
15 | console.error(`Private key ending with ${accountConfig.privateKey.substr(accountConfig.privateKey.length - 5)} is not a HEX string`);
16 | return null;
17 | }
18 | return web3.eth.accounts.privateKeyToAccount(accountConfig.privateKey);
19 | }
20 |
21 |
22 | if (accountConfig.privateKeyFile) {
23 | let privateKeyFile = path.resolve(accountConfig.privateKeyFile);
24 | let fileContent = fs.readFileSync(privateKeyFile).toString();
25 | if (accountConfig.password) {
26 | try {
27 | fileContent = JSON.parse(fileContent);
28 | if (!ethereumjsWallet['fromV' + fileContent.version]) {
29 | console.error(`Key file ${accountConfig.privateKeyFile} is not a valid keystore file`);
30 | return null;
31 | }
32 | const wallet = ethereumjsWallet['fromV' + fileContent.version](fileContent, accountConfig.password);
33 | return web3.eth.accounts.privateKeyToAccount('0x' + wallet.getPrivateKey().toString('hex'));
34 | } catch (e) {
35 | console.error('Private key file is not a keystore JSON file but a password was provided');
36 | console.error(e.message || e);
37 | return null;
38 | }
39 | }
40 |
41 | fileContent = fileContent.trim().split(/[,;]/);
42 | return fileContent.map((key, index) => {
43 | if (!key.startsWith('0x')) {
44 | key = '0x' + key;
45 | }
46 | if (!web3.utils.isHexStrict(key)) {
47 | console.error(`Private key is not a HEX string in file ${accountConfig.privateKeyFile} at index ${index}`);
48 | return null;
49 | }
50 | return web3.eth.accounts.privateKeyToAccount(key);
51 | });
52 | }
53 |
54 | if (accountConfig.mnemonic) {
55 | const hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(accountConfig.mnemonic.trim()));
56 | const addressIndex = accountConfig.addressIndex || 0;
57 | const wallet_hdpath = accountConfig.hdpath || "m/44'/60'/0'/0/";
58 | const wallet = hdwallet.derivePath(wallet_hdpath + addressIndex).getWallet();
59 | return web3.eth.accounts.privateKeyToAccount('0x' + wallet.getPrivateKey().toString('hex'));
60 | }
61 |
62 | console.error('Unsupported account configuration: ' + JSON.stringify(accountConfig));
63 | console.error('Try using one of those: ' +
64 | '{ "privateKey": "your-private-key", "privateKeyFile": "path/to/file/containing/key", "mnemonic": "12 word mnemonic" }');
65 |
66 | return null;
67 | }
68 | }
69 |
70 | module.exports = AccountParser;
71 |
--------------------------------------------------------------------------------
/gas-relayer/src/contract-settings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Configuration Settings related to contracts
3 | */
4 | class ContractSettings {
5 |
6 | /**
7 | * @param {object} config - Configuration object obtained from `./config/config.js`
8 | * @param {object} web3 - Web3 object already configured
9 | * @param {object} eventEmitter - Event Emitter
10 | */
11 | constructor(config, web3, eventEmitter, logger){
12 | this.tokens = config.tokens;
13 | this.topics = [];
14 | this.contracts = config.contracts;
15 | this.config = config;
16 | this.logger = logger;
17 |
18 | this.web3 = web3;
19 | this.events = eventEmitter;
20 |
21 | this.pendingToLoad = 0;
22 | }
23 |
24 | /**
25 | * Process configuration file
26 | */
27 | process(){
28 | this._setTokenPricePlugin();
29 | this._processContracts();
30 | }
31 |
32 | /**
33 | * Set price plugin for token
34 | */
35 | _setTokenPricePlugin(){
36 | for(let token in this.tokens){
37 | if(this.tokens[token].pricePlugin !== undefined){
38 | let PricePlugin = require(this.tokens[token].pricePlugin);
39 | this.tokens[token].pricePlugin = new PricePlugin(this.tokens[token], this.config.gasPrice, this.web3);
40 | }
41 | }
42 | }
43 |
44 | /**
45 | * Get allowed tokens
46 | * @return {object} - Dictionary with allowed tokens (address as key)
47 | */
48 | getTokens(){
49 | return this.tokens;
50 | }
51 |
52 | /**
53 | * Get token by address
54 | * @param {string} - Token address
55 | * @return {object} - Token details
56 | */
57 | getToken(token){
58 | const tokenObj = this.tokens[token];
59 | tokenObj.address = token;
60 | return tokenObj;
61 | }
62 |
63 | /**
64 | * Get token by symbol
65 | * @param {string} - Token symbol
66 | * @return {object} - Token details
67 | */
68 | getTokenBySymbol(symbol){
69 | for(let token in this.tokens){
70 | if(this.tokens[token].symbol == symbol){
71 | const tokenObj = this.tokens[token];
72 | tokenObj.address = token;
73 | return tokenObj;
74 | }
75 | }
76 | }
77 |
78 | /**
79 | * Get contract by topicName
80 | * @param {string} topicName - Topic name that represents a contract
81 | * @return {object} - Contract details
82 | */
83 | getContractByTopic(topicName){
84 | return this.contracts[topicName];
85 | }
86 |
87 | /**
88 | * Calculate the topic based on the contract's name
89 | * @param {string} contractName - Name of the contract as it appears in the configuration
90 | * @return {string} - Topic
91 | */
92 | getTopicName(contractName){
93 | return this.web3.utils.toHex(contractName).slice(0, 10);
94 | }
95 |
96 | /**
97 | * Set contract's bytecode in the configuration
98 | * @param {string} topicName - Topic name that represents a contract
99 | */
100 | async _obtainContractBytecode(topicName){
101 | if(this.contracts[topicName].isIdentity) return;
102 |
103 | this.pendingToLoad++;
104 |
105 | try {
106 | const code = await this.web3.eth.getCode(this.contracts[topicName].address);
107 | this.contracts[topicName].code = this.web3.utils.soliditySha3(code);
108 | this.pendingToLoad--;
109 | if(this.pendingToLoad == 0) this.events.emit("setup:complete", this);
110 | } catch(err) {
111 | this.logger.error("Invalid contract for " + topicName);
112 | this.logger.error(err);
113 | process.exit();
114 | }
115 | }
116 |
117 | /**
118 | * Extract function details based on topicName
119 | * @param {string} topicName - Topic name that represents a contract
120 | */
121 | _extractFunctions(topicName){
122 | const contract = this.getContractByTopic(topicName);
123 |
124 | for(let i = 0; i < contract.allowedFunctions.length; i++){
125 | contract.allowedFunctions[i].functionName = contract.allowedFunctions[i].function.slice(0, contract.allowedFunctions[i].function.indexOf('('));
126 |
127 | // Extracting input
128 | contract.allowedFunctions[i].inputs = contract.abi.filter(x => x.name == contract.allowedFunctions[i].functionName && x.type == "function")[0].inputs;
129 |
130 | // Obtaining function signatures
131 | let functionSignature = this.web3.utils.sha3(contract.allowedFunctions[i].function).slice(0, 10);
132 | contract.allowedFunctions[functionSignature] = contract.allowedFunctions[i];
133 | delete this.contracts[topicName].allowedFunctions[i];
134 | }
135 |
136 | contract.functionSignatures = Object.keys(contract.allowedFunctions);
137 | this.contracts[topicName] = contract;
138 | }
139 |
140 | /**
141 | * Process contracts and setup the settings object
142 | */
143 | _processContracts(){
144 | for(let contractName in this.contracts){
145 | // Obtaining the abis
146 | this.contracts[contractName].abi = require(this.contracts[contractName].abiFile).abi;
147 |
148 | const topicName = this.getTopicName(contractName);
149 |
150 | // Extracting topic
151 | this.topics.push(topicName);
152 | this.contracts[topicName] = this.contracts[contractName];
153 | this.contracts[topicName].name = contractName;
154 | delete this.contracts[contractName];
155 |
156 | // Obtaining strategy
157 | if(this.contracts[topicName].strategy){
158 | this.contracts[topicName].strategy = this.buildStrategy(this.contracts[topicName].strategy, topicName);
159 | }
160 |
161 | this._obtainContractBytecode(topicName);
162 |
163 | this._extractFunctions(topicName);
164 | }
165 | }
166 |
167 | /**
168 | * Create strategy object based on source code and topicName
169 | * @param {string} strategyFile - Souce code path of strategy to build
170 | * @param {string} topicName - Hex string that represents a contract's topic
171 | */
172 | buildStrategy(strategyFile, topicName){
173 | const strategy = require(strategyFile);
174 | return new strategy(this.web3, this.config, this, this.contracts[topicName]);
175 | }
176 | }
177 |
178 |
179 | module.exports = ContractSettings;
180 |
--------------------------------------------------------------------------------
/gas-relayer/src/message-processor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Message Processor to analyze and execute strategies based on input objects
3 | */
4 | class MessageProcessor {
5 |
6 | /**
7 | * @param {object} config - Configuration object obtained from `./config/config.js`
8 | * @param {object} settings - Settings obtained from parsing the configuration object
9 | * @param {object} web3 - Web3 object already configured
10 | * @param {object} events - Event emitter
11 | */
12 | constructor(config, settings, web3, events, logger, cache){
13 | this.config = config;
14 | this.settings = settings;
15 | this.web3 = web3;
16 | this.events = events;
17 | this.logger = logger;
18 | this.cache = cache;
19 | }
20 |
21 | /**
22 | * Validate input message content
23 | * @param {object} contract - Object obtained from the settings based on the message topic
24 | * @param {object} input - Object obtained from a message.
25 | * @returns {object} State of validation
26 | */
27 | async _validateInput(contract, input){
28 | this.logger.info("Processing '" + input.action + "' request to contract: " + input.contract);
29 |
30 | if(contract == undefined){
31 | return {success: false, message: 'Unknown contract'};
32 | }
33 |
34 | if(input.functionName && !contract.functionSignatures.includes(input.functionName)){
35 | return {success: false, message: 'Function not allowed'};
36 | }
37 |
38 | // Get code from contract and compare it against the contract code
39 | if(!contract.isIdentity){
40 | const code = this.web3.utils.soliditySha3(await this.web3.eth.getCode(input.contract));
41 | if(code != contract.code){
42 | return {success: false, message: 'Invalid contract code'};
43 | }
44 | } else {
45 | if(!(/^0x[0-9a-f]{40}$/i).test(input.contract)){
46 | return {success: false, message: 'Invalid contract address'};
47 | }
48 | }
49 |
50 | if(input.address && !(/^0x[0-9a-f]{40}$/i).test(input.address)){
51 | return {success: false, message: 'Invalid address'};
52 | }
53 |
54 | return {success: true};
55 | }
56 |
57 | /**
58 | * Process strategy and return validation result
59 | * @param {object} contract - Object obtained from the settings based on the message topic
60 | * @param {object} input - Object obtained from a message.
61 | * @param {function} reply - Function to reply a message
62 | * @param {object} strategy - Strategy to apply. If undefined, it will use a strategy based on the contract
63 | * @returns {object} State of validation
64 | */
65 | async processStrategy(contract, input, reply, strategy){
66 | const inputValidation = await this._validateInput(contract, input);
67 | if(!inputValidation.success){
68 | // TODO Log?
69 | return inputValidation;
70 | }
71 |
72 | if(strategy || contract.strategy){
73 | let validationResult;
74 | if(strategy){
75 | validationResult = await strategy.execute(input, this.cache);
76 | } else {
77 | validationResult = await contract.strategy.execute(input, this.cache);
78 | }
79 |
80 | if(!validationResult.success){
81 | reply(validationResult.message);
82 | }
83 |
84 | return validationResult;
85 | }
86 | }
87 |
88 | /**
89 | * Process strategy and based on its result, send a transaction to the blockchain
90 | * @param {object} contract - Object obtained from the settings based on the message topic
91 | * @param {object} input - Object obtained from a message.
92 | * @param {function} reply - function to reply a message
93 | * @returns {undefined}
94 | */
95 | async processTransaction(contract, input, reply, account, cb){
96 | const validationResult = await this.processStrategy(contract, input, reply);
97 |
98 | const {toHex} = this.web3.utils;
99 |
100 | if(!validationResult.success) return;
101 |
102 | const {toBN} = this.web3.utils;
103 |
104 | const gasPrice = toBN(await this.web3.eth.getGasPrice()).add(toBN(this.config.gasPrice.modifier)).toString();
105 |
106 | if(!validationResult.estimatedGas){
107 | validationResult.estimatedGas = await this.web3.eth.estimateGas(p);
108 | }
109 |
110 | const estimatedGas = parseInt(validationResult.estimatedGas, 10);
111 |
112 | let p = {
113 | from: this.config.node.blockchain.account.address,
114 | to: input.contract,
115 | value: "0x00",
116 | data: input.payload,
117 | gasPrice: parseInt(gasPrice, 10),
118 | gas: estimatedGas + 1000 // Tune this,
119 | };
120 |
121 | const nodeBalance = await this.web3.eth.getBalance(this.config.node.blockchain.account.address);
122 |
123 | if(nodeBalance < p.gas){
124 | reply("Relayer unavailable");
125 | this.logger.error("Relayer doesn't have enough gas to process trx: " + nodeBalance + ", required " + p.gas);
126 | this.events.emit('exit');
127 | } else {
128 | try {
129 | this.web3.eth.sendTransaction(p)
130 | .on('transactionHash', function(hash){
131 | reply("Transaction broadcasted: " + hash);
132 | cb();
133 | })
134 | .on('receipt', function(receipt){
135 | reply("Transaction mined", receipt);
136 | });
137 |
138 | } catch(err){
139 | reply("Couldn't mine transaction: " + err.message);
140 | // TODO log this?
141 | this.logger.error(err);
142 | }
143 | }
144 | }
145 | }
146 |
147 | module.exports = MessageProcessor;
148 |
--------------------------------------------------------------------------------
/gas-relayer/src/service.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require('events');
2 | const Web3 = require('web3');
3 | const config = require('../config/config.js');
4 | const ContractSettings = require('./contract-settings');
5 | const MessageProcessor = require('./message-processor');
6 | const JSum = require('jsum');
7 | const logger = require('consola');
8 | const winston = require('winston');
9 | const cache = require('memory-cache');
10 | const accountParser = require('./account-parser');
11 |
12 | var pubKey;
13 |
14 | // Setting up logging
15 | const wLogger = winston.createLogger({
16 | level: 'info',
17 | format: winston.format.simple(),
18 | transports: [
19 | new winston.transports.Console(),
20 | new winston.transports.File({filename: 'gas-relayer.log'})
21 | ]
22 | });
23 | logger.clear().add(new logger.WinstonReporter(wLogger));
24 |
25 |
26 | // Service Init
27 | logger.info("Starting...");
28 | const events = new EventEmitter();
29 |
30 | // Web3 Connection
31 | const connectionURL = `${config.node.local.protocol}://${config.node.local.host}:${config.node.local.port}`;
32 | const wsProvider = new Web3.providers.WebsocketProvider(connectionURL, {headers: {Origin: "gas-relayer"}});
33 | const web3 = new Web3(wsProvider);
34 | let account;
35 |
36 |
37 | web3.eth.net.isListening()
38 | .then(() => events.emit('web3:connected', connectionURL))
39 | .catch(error => {
40 | logger.error(error);
41 | process.exit();
42 | });
43 |
44 |
45 | events.on('web3:connected', connURL => {
46 | logger.info("Connected to '" + connURL + "'");
47 |
48 |
49 | account = accountParser.get(config.node.blockchain, web3);
50 | web3.eth.accounts.wallet.add(account);
51 |
52 |
53 | if(!account) {
54 | process.exit(1);
55 | } else {
56 | config.node.blockchain.account = account;
57 | }
58 |
59 | let settings = new ContractSettings(config, web3, events, logger);
60 | settings.process();
61 | });
62 |
63 | // Setting up Whisper options
64 | const shhOptions = {
65 | ttl: config.node.whisper.ttl,
66 | minPow: config.node.whisper.minPow
67 | };
68 |
69 | const verifyBalance = async (exitSubs) => {
70 | const nodeBalance = await web3.eth.getBalance(config.node.blockchain.account.address);
71 | if(web3.utils.toBN(nodeBalance).lte(web3.utils.toBN(100000))){ // TODO: tune minimum amount required for transactions
72 | logger.info("Not enough balance available for processing transactions");
73 | logger.info("> Account: " + config.node.blockchain.account.address);
74 | logger.info("> Balance: " + nodeBalance);
75 |
76 | if(exitSubs){
77 | web3.shh.clearSubscriptions();
78 | }
79 |
80 | process.exit(0);
81 | }
82 | };
83 |
84 | events.on('exit', () => {
85 | web3.shh.clearSubscriptions();
86 | logger.info("Closing service...");
87 | process.exit(0);
88 | });
89 |
90 | events.on('setup:complete', async (settings) => {
91 | // Verifying relayer balance
92 | await verifyBalance();
93 |
94 | shhOptions.kId = await web3.shh.newKeyPair();
95 |
96 | const symKeyID = await web3.shh.addSymKey(config.node.whisper.symKey);
97 | pubKey = await web3.shh.getPublicKey(shhOptions.kId);
98 |
99 | // Listening to whisper
100 | // Individual subscriptions due to https://github.com/ethereum/web3.js/issues/1361
101 | // once this is fixed, we'll be able to use an array of topics and a single subs for symkey and a single subs for privKey
102 | logger.info(`Sym Key: ${config.node.whisper.symKey}`);
103 | logger.info(`Relayer Public Key: ${pubKey}`);
104 | logger.info("Topics Available:");
105 | for(let contract in settings.contracts) {
106 | logger.info("- " + settings.getContractByTopic(contract).name + ": " + contract + " [" + (Object.keys(settings.getContractByTopic(contract).allowedFunctions).join(', ')) + "]");
107 | shhOptions.topics = [contract];
108 |
109 | // Listen to public channel - Used for reporting availability
110 | events.emit('server:listen', Object.assign({symKeyID}, shhOptions), settings);
111 |
112 | // Listen to private channel - Individual transactions
113 | events.emit('server:listen', Object.assign({privateKeyID: shhOptions.kId}, shhOptions), settings);
114 | }
115 | });
116 |
117 | const replyFunction = (message) => (text, receipt) => {
118 | if(message.sig !== undefined){
119 |
120 | let payloadContent;
121 | if(typeof text === 'object'){
122 | payloadContent = {...text, receipt};
123 | } else {
124 | payloadContent = {text, receipt};
125 | }
126 |
127 | web3.shh.post({
128 | pubKey: message.sig,
129 | sig: shhOptions.kId,
130 | ttl: config.node.whisper.ttl,
131 | powTarget:config.node.whisper.minPow,
132 | powTime: config.node.whisper.powTime,
133 | topic: message.topic,
134 | payload: web3.utils.fromAscii(JSON.stringify(payloadContent, null, " "))
135 | }).catch(console.error);
136 | }
137 | };
138 |
139 | const extractInput = (message) => {
140 | let obj = {
141 | contract: null,
142 | address: null,
143 | action: null
144 | };
145 |
146 | try {
147 | const msg = web3.utils.toAscii(message.payload);
148 | let parsedObj = JSON.parse(msg);
149 | obj.contract = parsedObj.contract;
150 | obj.address = parsedObj.address;
151 | obj.action = parsedObj.action;
152 | if(obj.action == 'transaction'){
153 | obj.functionName = parsedObj.encodedFunctionCall.slice(0, 10);
154 | obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10);
155 | obj.payload = parsedObj.encodedFunctionCall;
156 | } else if(obj.action == 'availability') {
157 | obj.gasToken = parsedObj.gasToken;
158 | obj.gasPrice = parsedObj.gasPrice;
159 | }
160 | } catch(err){
161 | logger.error("Couldn't parse " + message);
162 | }
163 |
164 | return obj;
165 | };
166 |
167 | events.on('server:listen', (shhOptions, settings) => {
168 | let processor = new MessageProcessor(config, settings, web3, events, logger, cache);
169 | web3.shh.subscribe('messages', shhOptions, async (error, message) => {
170 | if(error){
171 | logger.error(error);
172 | return;
173 | }
174 |
175 | verifyBalance(true);
176 |
177 | const input = extractInput(message);
178 | const inputCheckSum = JSum.digest({input}, 'SHA256', 'hex');
179 |
180 | const reply = replyFunction(message, inputCheckSum);
181 |
182 | if(cache.get(inputCheckSum) && input.action != 'availability'){
183 | reply("Duplicated message received");
184 | } else {
185 | let validationResult;
186 | switch(input.action){
187 | case 'transaction':
188 | if(message.recipientPublicKey === pubKey){
189 | processor.processTransaction(settings.getContractByTopic(message.topic),
190 | input,
191 | reply,
192 | account,
193 | () => {
194 | cache.put(inputCheckSum, (new Date().getTime()), 3600000);
195 | }
196 | );
197 | }
198 | break;
199 | case 'availability':
200 | validationResult = await processor.processStrategy(settings.getContractByTopic(message.topic),
201 | input,
202 | reply,
203 | settings.buildStrategy("./strategy/AvailabilityStrategy", message.topic)
204 | );
205 | if(validationResult.success && validationResult.message) {
206 | reply(validationResult.message);
207 | }
208 | break;
209 | default:
210 | reply("unknown-action");
211 | }
212 | }
213 | });
214 | });
215 |
216 |
217 | // Daemon helper functions
218 |
219 | process.on("uncaughtException", function(err) {
220 | // TODO
221 | logger.error(err);
222 | });
223 |
224 | process.once("SIGTERM", function() {
225 | logger.info("Stopping...");
226 | });
227 |
--------------------------------------------------------------------------------
/gas-relayer/src/strategy/AvailabilityStrategy.js:
--------------------------------------------------------------------------------
1 | const Strategy = require('./BaseStrategy');
2 |
3 | /**
4 | * Class representing a strategy to validate an 'availability' request.
5 | * @extends Strategy
6 | */
7 | class AvailabilityStrategy extends Strategy {
8 |
9 | /**
10 | * Process availability strategy
11 | * @param {object} input - Object obtained from an 'availability' request. It expects an object with this structure `{contract, address, action, gasToken, gasPrice}`
12 | * @returns {object} Status of validation, and minimum price
13 | */
14 | async execute(input, cache){
15 |
16 | if(this.contract.isIdentity){
17 | let validInstance = await this._validateInstance(input);
18 | if(!validInstance){
19 | return {success: false, message: "Invalid identity instance"};
20 | }
21 | }
22 |
23 | // Verifying if token is allowed
24 | const token = this.settings.getToken(input.gasToken);
25 | if(token == undefined) return {success: false, message: "Token not allowed"};
26 |
27 | let tokenRate = await this.getTokenRate(token, cache);
28 | if(!tokenRate){
29 | return {
30 | success: false,
31 | message: "Token price unavailable"
32 | };
33 | }
34 |
35 | const {toBN} = this.web3.utils;
36 |
37 | const gasPrices = await this.getGasPrices(token, tokenRate);
38 | if(tokenRate.gte(token.minAcceptedRate) && gasPrices.inEther.lte(toBN(this.config.gasPrice.maxPrice))){
39 | return {
40 | success: true,
41 | message: {
42 | message: "Available",
43 | address: this.config.node.blockchain.account.address,
44 | minGasPrice: gasPrices.inTokens.toString(),
45 | gasPriceETH: gasPrices.inEther.add(toBN(this.config.gasPrice.modifier)).toString()
46 | }
47 | };
48 | }
49 |
50 | return {success: true};
51 | }
52 |
53 | }
54 |
55 | module.exports = AvailabilityStrategy;
56 |
--------------------------------------------------------------------------------
/gas-relayer/src/strategy/BaseStrategy.js:
--------------------------------------------------------------------------------
1 | const ganache = require("ganache-cli");
2 | const Web3 = require('web3');
3 | const erc20ABI = require('../../abi/ERC20Token.json');
4 |
5 | /**
6 | * Abstract class used for validation strategies
7 | */
8 | class BaseStrategy {
9 |
10 | /**
11 | * Validates if the contract being invoked represents an instance created via factory
12 | * @param {object} input - Object obtained from a `transaction` request.
13 | * @returns {bool} Valid instance or not
14 | */
15 | async _validateInstance(input){
16 | const instanceCodeHash = this.web3.utils.soliditySha3(await this.web3.eth.getCode(input.contract));
17 | const kernelVerifSignature = this.web3.utils.soliditySha3(this.contract.kernelVerification).slice(0, 10);
18 |
19 | if(instanceCodeHash === null) return false;
20 |
21 | let verificationResult = await this.web3.eth.call({
22 | to: this.contract.factoryAddress,
23 | data: kernelVerifSignature + instanceCodeHash.slice(2)});
24 |
25 | return this.web3.eth.abi.decodeParameter('bool', verificationResult);
26 | }
27 |
28 |
29 | /**
30 | * @param {object} web3 - Web3 object already configured
31 | * @param {object} config - Configuration object obtained from `./config/config.js`
32 | * @param {object} settings - Settings obtained from parsing the configuration object
33 | * @param {object} contract - Object obtained from the settings based on the message topic
34 | */
35 | constructor(web3, config, settings, contract){
36 | this.web3 = web3;
37 | this.settings = settings;
38 | this.contract = contract;
39 | this.config = config;
40 | }
41 |
42 | /**
43 | * Obtain the balance in tokens or ETH from an address
44 | * @param {string} address - ETH address to obtain the balance from
45 | * @param {object} token - Obtained from `settings.getToken(tokenSymbol)`
46 | * @returns {web3.utils.BN} Balance
47 | */
48 | async getBalance(address, token){
49 | // Determining balances of token used
50 | if(token.symbol == "ETH"){
51 | return new this.web3.utils.BN(await this.web3.eth.getBalance(address));
52 | } else {
53 | const Token = new this.web3.eth.Contract(erc20ABI.abi);
54 | Token.options.address = token.address;
55 | return new this.web3.utils.BN(await Token.methods.balanceOf(address).call());
56 | }
57 | }
58 |
59 | async getTokenRate(token, cache){
60 | // Get Price
61 | let tokenRate = cache.get(token.address);
62 | if(tokenRate === null){
63 | try {
64 | tokenRate = await token.pricePlugin.getRate();
65 | cache.put(token.address, tokenRate, token.refreshPricePeriod);
66 | return tokenRate;
67 | } catch (err) {
68 | console.error(err);
69 | }
70 | } else {
71 | return tokenRate;
72 | }
73 | }
74 |
75 | async getGasPrices(token, tokenRate){
76 | const {toBN} = this.web3.utils;
77 | const Token = new this.web3.eth.Contract(erc20ABI.abi);
78 | Token.options.address = token.address;
79 | const tokenDecimals = await Token.methods.decimals().call();
80 | const multiplier = toBN(Math.pow(10, tokenDecimals));
81 | const currentGasPrice = toBN(await this.web3.eth.getGasPrice());
82 | const currentGasConvertedToTokens = currentGasPrice.mul(multiplier).div(tokenRate);
83 |
84 | return {inEther: currentGasPrice, inTokens: currentGasConvertedToTokens};
85 | }
86 |
87 | /**
88 | * Build Parameters Function
89 | * @param {object} input - Object obtained from an `transaction` request.
90 | * @returns {function} Function that simplifies accessing contract functions' parameters
91 | */
92 | _obtainParametersFunc(input){
93 | const parameterList = this.web3.eth.abi.decodeParameters(this.contract.allowedFunctions[input.functionName].inputs, input.functionParameters);
94 | return function(parameterName){
95 | return parameterList[parameterName];
96 | };
97 | }
98 |
99 | /**
100 | * Estimate gas using web3
101 | * @param {object} input - Object obtained from an `transaction` request.
102 | * @returns {web3.utils.toBN} Estimated gas fees
103 | */
104 | async _estimateGas(input){
105 | let p = {
106 | from: this.config.node.blockchain.account.address,
107 | to: input.contract,
108 | data: input.payload
109 | };
110 | const estimatedGas = await this.web3.eth.estimateGas(p);
111 | return this.web3.utils.toBN(estimatedGas);
112 | }
113 |
114 | /**
115 | * Simulate transaction using ganache. Useful for obtaining events
116 | * @param {object} input - Object obtained from an `transaction` request.
117 | * @returns {object} Simulated transaction receipt
118 | */
119 | async _simulateTransaction(input){
120 | let web3Sim = new Web3(ganache.provider({
121 | fork: `${this.config.node.ganache.protocol}://${this.config.node.ganache.host}:${this.config.node.ganache.port}`,
122 | locked: false,
123 | gasLimit: 10000000
124 | }));
125 |
126 | let simAccounts = await web3Sim.eth.getAccounts();
127 |
128 | let simulatedReceipt = await web3Sim.eth.sendTransaction({
129 | from: simAccounts[0],
130 | to: input.contract,
131 | value: 0,
132 | data: input.payload,
133 | gasLimit: 9500000 // 95% of current chain latest gas block limit
134 |
135 | });
136 | return simulatedReceipt;
137 | }
138 |
139 | /*
140 | async execute(input){
141 | return {
142 | success: true,
143 | message: "Valid transaction"
144 | };
145 | }
146 | */
147 | }
148 |
149 | module.exports = BaseStrategy;
150 |
--------------------------------------------------------------------------------
/gas-relayer/src/strategy/IdentityStrategy.js:
--------------------------------------------------------------------------------
1 | const Strategy = require('./BaseStrategy');
2 | const erc20ABI = require('../../abi/ERC20Token.json');
3 |
4 | const CallGasRelayed = "0xfd0dded5";
5 | const ApproveAndCallGasRelayed = "0x59f4ac61";
6 |
7 | /**
8 | * Class representing a strategy to validate a `transaction` request when the topic is related to Identities.
9 | * @extends Strategy
10 | */
11 | class IdentityStrategy extends Strategy {
12 |
13 |
14 | /**
15 | * Process Identity strategy
16 | * @param {object} input - Object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}`
17 | * @returns {object} Status of validation and estimated gas
18 | */
19 | async execute(input, cache){
20 | if(this.contract.isIdentity){
21 | let validInstance = await this._validateInstance(input);
22 | if(!validInstance){
23 | return {success: false, message: "Invalid identity instance"};
24 | }
25 | }
26 |
27 | const params = this._obtainParametersFunc(input);
28 |
29 | // Verifying if token is allowed
30 | const token = this.settings.getToken(params('_gasToken'));
31 | if(token == undefined) return {success: false, message: "Token not allowed"};
32 |
33 | // Determine if enough balance for baseToken
34 | const gasPrice = this.web3.utils.toBN(params('_gasPrice'));
35 | const gasMinimal = this.web3.utils.toBN(params('_gasMinimal'));
36 | if(this.contract.allowedFunctions[input.functionName].isToken){
37 | const Token = new this.web3.eth.Contract(erc20ABI.abi);
38 | Token.options.address = params('_baseToken');
39 | const tokenBalance = new this.web3.utils.BN(await Token.methods.balanceOf(input.contract).call());
40 | if(tokenBalance.lt(this.web3.utils.toBN(params('_value')))){
41 | return {success: false, message: "Identity has not enough balance for specified value"};
42 | }
43 | }
44 |
45 | // gasPrice * limit calculation
46 | const balance = await this.getBalance(input.contract, token);
47 | if(balance.lt(this.web3.utils.toBN(gasPrice.mul(gasMinimal)))) {
48 | return {success: false, message: "Identity has not enough tokens for gasPrice*gasMinimal"};
49 | }
50 |
51 |
52 | let estimatedGas = 0;
53 | try {
54 | if(input.functionName == CallGasRelayed){
55 | estimatedGas = await this._estimateGas(input);
56 | } else {
57 | const tmp = Math.floor(parseInt((await this._estimateGas(input)).toString(10), 10) * 1.05);
58 | estimatedGas = this.web3.utils.toBN(tmp);
59 | }
60 |
61 | // TODO: executing functions with gas minimal causes relayers to incur in a loss.
62 | // TODO: maybe this can be fixed by increasing the gas price for this kind of operations
63 | if(gasMinimal.add(this.web3.utils.toBN(75000)).lt(estimatedGas)) {
64 | return {success: false, message: "Gas limit below estimated gas (" + estimatedGas + ")"};
65 | } else {
66 | estimatedGas = estimatedGas.add(this.web3.utils.toBN(75000));
67 | }
68 | } catch(exc){
69 | if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"};
70 | else {
71 | console.error(exc);
72 | return {success: false, message: "Transaction will fail"};
73 | }
74 | }
75 |
76 | // Get Price
77 | let tokenRate = await this.getTokenRate(token, cache);
78 | if(!tokenRate){
79 | return {
80 | success: false,
81 | message: "Token price unavailable"
82 | };
83 | }
84 |
85 | const gasPrices = await this.getGasPrices(token, tokenRate);
86 | if(tokenRate.lt(token.minAcceptedRate)){
87 | return {success: false, message: "Not accepting " + token.symbol + " at current rate. (Min rate: " + token.minAcceptedRate+ ")"};
88 | }
89 |
90 | if(gasPrice.lt(gasPrices.inTokens)){
91 | return {success: false, message: "Gas price is less than the required amount (" + gasPrices.inTokens.toString(10) + ")"};
92 | }
93 |
94 | return {
95 | success: true,
96 | message: "Valid transaction",
97 | estimatedGas
98 | };
99 | }
100 |
101 | }
102 |
103 | module.exports = IdentityStrategy;
104 |
--------------------------------------------------------------------------------
/gas-relayer/src/strategy/SNTStrategy.js:
--------------------------------------------------------------------------------
1 | const Strategy = require('./BaseStrategy');
2 |
3 | const TransferSNT = "0x916b6511";
4 | const ExecuteGasRelayed = "0x754e6ab0";
5 |
6 | /**
7 | * Class representing a strategy to validate a `transaction` request when the topic is related to SNTController.
8 | * @extends Strategy
9 | */
10 | class SNTStrategy extends Strategy {
11 |
12 | /**
13 | * Process SNTController strategy
14 | * @param {object} input - Object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}`
15 | * @returns {object} Status of validation and estimated gas
16 | */
17 | async execute(input, cache){
18 | const params = this._obtainParametersFunc(input);
19 |
20 | // Verifying if token is allowed
21 | const token = this.settings.getTokenBySymbol("SNT");
22 | if(token == undefined) return {success: false, message: "Token not allowed"};
23 |
24 | const balance = await this.getBalance(input.address, token);
25 |
26 | let tokenRate = await this.getTokenRate(token, cache);
27 | if(!tokenRate){
28 | return {
29 | success: false,
30 | message: "Token price unavailable"
31 | };
32 | }
33 |
34 | const gasPrices = await this.getGasPrices(token, tokenRate);
35 | if(tokenRate.lt(token.minAcceptedRate)){
36 | return {success: false, message: "Not accepting " + token.symbol + " at current rate. (Min rate: " + token.minAcceptedRate+ ")"};
37 | }
38 |
39 | const gasPrice = this.web3.utils.toBN(params('_gasPrice'));
40 | if(gasPrice.lt(gasPrices.inTokens)){
41 | return {success: false, message: "Gas price is less than the required amount (" + gasPrices.inTokens.toString(10) + ")"};
42 | }
43 |
44 |
45 | let estimatedGas;
46 | try {
47 | estimatedGas = await this.web3.eth.estimateGas({
48 | data: input.payload,
49 | from: this.config.node.blockchain.account.address,
50 | to: input.contract
51 | });
52 | } catch(exc){
53 | if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"};
54 | else {
55 | console.error(exc);
56 | return {success: false, message: "Transaction will fail"};
57 | }
58 | }
59 |
60 |
61 | if(input.functionName == TransferSNT){
62 | const gas = this.web3.utils.toBN(estimatedGas);
63 | const value = this.web3.utils.toBN(params('_amount'));
64 | const requiredGas = value.add(gas);
65 |
66 | if(balance.lt(requiredGas)){
67 | return {success: false, message: "Address has not enough balance to transfer specified value + fees (" + requiredGas.toString() + ")"};
68 | }
69 | } else if(input.functionName == ExecuteGasRelayed){
70 | const latestBlock = await this.web3.eth.getBlock("latest");
71 | let estimatedGas = 0;
72 | try {
73 | const simulatedReceipt = await this._simulateTransaction(input, latestBlock.gasLimit);
74 | estimatedGas = this.web3.utils.toBN(simulatedReceipt.gasUsed);
75 | } catch(exc){
76 | if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"};
77 | }
78 |
79 | if(balance.lt(estimatedGas)){
80 | return {success: false, message: "Address has not enough balance to execute the transaction (" + estimatedGas.toString() + ")"};
81 | }
82 |
83 | const gasMinimal = this.web3.utils.toBN(params('_gasMinimal'));
84 | if(gasMinimal.lt(estimatedGas)){
85 | return {success: false, message: "Gas minimal is less than estimated gas (" + estimatedGas.toString() + ")"};
86 | }
87 |
88 | if(balance.lt(gasMinimal)){
89 | return {success: false, message: "Address has not enough balance for the specified _gasMinimal"};
90 | }
91 | }
92 |
93 | return {
94 | success: true,
95 | message: "Valid transaction",
96 | estimatedGas
97 | };
98 | }
99 |
100 | }
101 |
102 | module.exports = SNTStrategy;
103 |
--------------------------------------------------------------------------------
/gas-relayer/test/sampleContracts.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.21;
2 |
3 | contract TestIdentityGasRelay {
4 | event Debug();
5 |
6 | function approveAndCallGasRelayed(
7 | address _baseToken,
8 | address _to,
9 | uint256 _value,
10 | bytes _data,
11 | uint _nonce,
12 | uint _gasPrice,
13 | uint _gasLimit,
14 | address _gasToken,
15 | bytes _messageSignatures
16 | ) external {
17 | emit Debug();
18 | }
19 |
20 | function callGasRelayed(
21 | address _to,
22 | uint256 _value,
23 | bytes _data,
24 | uint _nonce,
25 | uint _gasPrice,
26 | uint _gasLimit,
27 | address _gasToken,
28 | bytes _messageSignatures
29 | ) external {
30 | emit Debug();
31 | }
32 |
33 | function() payable {
34 |
35 | }
36 | }
37 |
38 | contract TestIdentityFactory {
39 | address public latestKernel;
40 | function TestIdentityFactory(){
41 | latestKernel = address(new TestIdentityGasRelay());
42 | }
43 | }
44 |
45 | contract TestSNTController {
46 | event Debug();
47 | function transferSNT(address a,uint256 b,uint256 c,uint256 d, bytes f){
48 | emit Debug();
49 | }
50 | function executeGasRelayed(address a,bytes b,uint256 c,uint256 d,uint256 e,bytes f){
51 | emit Debug();
52 | }
53 | }
--------------------------------------------------------------------------------
/gas-relayer/test/sendmsg.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Send whisper message to node
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Send whisper message ws://localhost:8546
20 |
web3
is available in your browser's console: Tools > Developer Tools
21 |
Keys:
22 |
23 | - Public:
24 | - Private
25 |
26 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/gas-relayer/test/sendmsg.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 |
3 | const connUrl = "ws://localhost:8546";
4 | let web3 = new Web3(connUrl);
5 | window.web3 = web3;
6 |
7 | let keyPair;
8 |
9 | web3.shh.newKeyPair().then(async function(kid){
10 | $('.pub').text(await web3.shh.getPublicKey(kid));
11 | $('.priv').text(await web3.shh.getPrivateKey(kid));
12 | keyPair = kid;
13 | window.signature = kid;
14 |
15 | web3.shh.subscribe('messages', {
16 | "privateKeyID": signature,
17 | "ttl": 20,
18 | "minPow": 0.8,
19 | "powTime": 1000
20 | }, function(error, message, subscription){
21 | console.log(web3.utils.hexToAscii(message.payload));
22 | $('#messageArea').text(web3.utils.hexToAscii(message.payload));
23 | });
24 |
25 |
26 | });
27 |
28 | console.log("Connected to: %c%s", 'font-weight: bold', connUrl);
29 |
30 | const add0x = function(elem){
31 | if(elem.val().slice(0, 2) != '0x'){
32 | return '0x' + elem.val();
33 | } else {
34 | let val = elem.val();
35 | elem.val(elem.val().slice(2));
36 | return val;
37 | }
38 | }
39 |
40 | $('button').on('click', async function(e){
41 | e.preventDefault();
42 |
43 | $('p.result, #messageArea').text('');
44 |
45 | let sKey = add0x($("#sKey"));
46 | let msgTopic = add0x($('#topic'));
47 | let msgPayload = add0x($('#payload'));
48 | let timeToLive = $('#ttl').val();
49 | let powTarget = $('#powTarget').val();
50 | let powTime = $('#powTime').val();
51 |
52 | $('.invalid-feedback').hide();
53 | $('.is-invalid').removeClass('is-invalid');
54 |
55 | if(!/^0x[0-9a-f]{64}$/i.test(sKey)){
56 | $('#sKey').addClass('is-invalid');
57 | $('.invalid-feedback.sKey').show();
58 | }
59 |
60 | if(!/^0x[0-9a-f]{8}$/i.test(msgTopic)){
61 | $('#topic').addClass('is-invalid');
62 | $('.invalid-feedback.topic').show();
63 | }
64 |
65 | if(!/^0x[0-9a-f]+$/i.test(msgPayload) || msgPayload.length%2 > 0){
66 | $('#payload').addClass('is-invalid');
67 | $('.invalid-feedback.payload').show();
68 | }
69 |
70 | if(!/^[0-9]+$/i.test(timeToLive)){
71 | $('#ttl').addClass('is-invalid');
72 | $('.invalid-feedback.ttl').show();
73 | }
74 |
75 | if(!/^[+-]?([0-9]*[.])?[0-9]+$/.test(powTarget)){
76 | $('#powTarget').addClass('is-invalid');
77 | $('.invalid-feedback.powTarget').show();
78 | }
79 |
80 | if(!/^[+-]?([0-9]*[.])?[0-9]+$/.test(powTime)){
81 | $('#powTime').addClass('is-invalid');
82 | $('.invalid-feedback.powTime').show();
83 | }
84 |
85 |
86 | if($('.is-invalid').length > 0) return;
87 |
88 | console.log(`%c await web3.shh.post({symKeyID: "${sKey}", sig: signature, ttl: ${timeToLive}, powTarget: ${powTarget}, powTime: ${powTime}, topic: "${msgTopic}", payload: "${msgPayload}"})`, 'font-weight: bold');
89 |
90 | let identity;
91 |
92 |
93 | let _symKeyId = await web3.shh.addSymKey(sKey);
94 |
95 |
96 | web3.shh.post({
97 | symKeyID: _symKeyId,
98 | sig: keyPair,
99 | ttl: parseInt(timeToLive),
100 | powTarget: parseFloat(powTarget),
101 | powTime: parseFloat(powTime),
102 | topic: msgTopic,
103 | payload: msgPayload})
104 | .then(result => {
105 | console.log(result);
106 | $('p.result').html("Response: " + result);
107 | });
108 | });
109 | });
--------------------------------------------------------------------------------
/installation-development.md:
--------------------------------------------------------------------------------
1 | # Installation - Development Environment
2 |
3 | - Install the latest develop version of embark: `npm install -g https://github.com/embark-framework/embark.git`
4 |
5 | - Install `geth`
6 |
7 | - Clone the repository
8 | `git clone https://github.com/status-im/snt-gas-relay.git`
9 |
10 | - Execute the following commands
11 | ```
12 | cd snt-gas-relay/test-dapp
13 | chmod +x setup_dev_env.sh
14 | npm install
15 | embark reset
16 | embark blockchain
17 | ```
18 |
19 | - When Embark finishes loading, execute `embark run` to deploy the contracts.
20 |
21 | - After the contracts are deployed and the test dapp is available, execute `./setup_dev_env.sh` to create the test account
22 |
23 | ## Test DApp
24 | To run the test dapp, use `embark run` and then browse `http://localhost:8000/index.html`.
25 |
26 | The gas relayer service needs to be running, and configured correctly to process the transactions. Things to take in account are: the account used in embark, and the contract addresses.
27 |
28 | You may use the test dapp to generate SNT and fund the relayer account before running the gas relayer, as it requires ether to start. You may fund the relayer with `web3.eth.sendTransaction` or configure embark so it funds an account when it starts the chain.
29 |
30 | ## Relayer Node
31 |
32 | - `cd snt-gas-relay/gas-relayer`
33 |
34 | - This program is configured with the default values on `config/config.json` for a embark installation run from 0. To execute the gas-relayer, you may use any of the following three commands.
35 |
36 | ```
37 | npm start
38 | node src/service.js
39 | nodemon src/service.js
40 | ```
41 |
--------------------------------------------------------------------------------
/installation-testnet-mainnet.md:
--------------------------------------------------------------------------------
1 | # Installation - Testnet
2 |
3 | ### Notes
4 | 1. This installation assumes you're using Ubuntu or similar
5 | 2. You need a non-root user that belongs to the sudo group.
6 |
7 | ### Required software install procedure
8 |
9 | 1. Install Ethereum
10 | ```
11 | sudo apt install software-properties-common
12 | sudo add-apt-repository -y ppa:ethereum/ethereum
13 | sudo apt install ethereum
14 | ```
15 |
16 | 2. Install NodeJS using NVM (Check NVM repo for updated procedure)
17 | ```
18 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
19 | source .bashrc
20 | nvm install --lts
21 | ```
22 |
23 | 3. Install Python and other software
24 | ```
25 | sudo apt install python build-essential
26 | ```
27 |
28 | ### Clone the repo
29 | ```
30 | git clone https://github.com/status-im/snt-gas-relay.git
31 | cd snt-gas-relay/gas-relayer
32 | npm install
33 | ```
34 |
35 | ### Setup geth for Whisper
36 |
37 | 1. Verify `geth` light mode starts successfully. Exit geth when you see everything is ok
38 | ```
39 | geth --testnet --syncmode=light console
40 | > exit
41 | ```
42 |
43 | 2. There aren't enough geth peers with Whisper enabled to guarantee that messages will arrive from one node to other. We need to create a `static-nodes.json` file in `~/.ethereum/testnet/geth/`.
44 |
45 | This file needs to contain the following array:
46 | ```
47 | [
48 | "enode://436cc6f674928fdc9a9f7990f2944002b685d1c37f025c1be425185b5b1f0900feaf1ccc2a6130268f9901be4a7d252f37302c8335a2c1a62736e9232691cc3a@174.138.105.243:30404",
49 | "enode://5395aab7833f1ecb671b59bf0521cf20224fe8162fc3d2675de4ee4d5636a75ec32d13268fc184df8d1ddfa803943906882da62a4df42d4fccf6d17808156a87@206.189.243.57:30404",
50 | "enode://7427dfe38bd4cf7c58bb96417806fab25782ec3e6046a8053370022cbaa281536e8d64ecd1b02e1f8f72768e295d06258ba43d88304db068e6f2417ae8bcb9a6@104.154.88.123:30404",
51 | "enode://ebefab39b69bbbe64d8cd86be765b3be356d8c4b24660f65d493143a0c44f38c85a257300178f7845592a1b0332811542e9a58281c835babdd7535babb64efc1@35.202.99.224:30404",
52 | "enode://a6a2a9b3a7cbb0a15da74301537ebba549c990e3325ae78e1272a19a3ace150d03c184b8ac86cc33f1f2f63691e467d49308f02d613277754c4dccd6773b95e8@206.189.243.176:30304",
53 | "enode://207e53d9bf66be7441e3daba36f53bfbda0b6099dba9a865afc6260a2d253fb8a56a72a48598a4f7ba271792c2e4a8e1a43aaef7f34857f520c8c820f63b44c8@35.224.15.65:30304",
54 | "enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:30504",
55 | "enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@206.189.243.169:30504",
56 | "enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@206.189.243.168:30504",
57 | "enode://7de99e4cb1b3523bd26ca212369540646607c721ad4f3e5c821ed9148150ce6ce2e72631723002210fac1fd52dfa8bbdf3555e05379af79515e1179da37cc3db@35.188.19.210:30504",
58 | "enode://015e22f6cd2b44c8a51bd7a23555e271e0759c7d7f52432719665a74966f2da456d28e154e836bee6092b4d686fe67e331655586c57b718be3997c1629d24167@35.226.21.19:30504",
59 | "enode://531e252ec966b7e83f5538c19bf1cde7381cc7949026a6e499b6e998e695751aadf26d4c98d5a4eabfb7cefd31c3c88d600a775f14ed5781520a88ecd25da3c6@35.225.227.79:30504"
60 | ]
61 | ```
62 | These enodes were extracted from https://github.com/status-im/status-go/blob/develop/params/cluster.go.
63 |
64 | ### Setup the gas relayer
65 |
66 | Before executing this program, `config/config.json` must be setup and `npm install` needs to be executed. Important values to verify are related to the node configuration, just like:
67 | - Host, port and protocol to connect to the geth node
68 | - Host, port and protocol Ganache will use when forking the blockchain for gas estimations and other operations
69 | - Account used for processing the transactions
70 | - Symmetric key used to receive the Whisper messages
71 | - Accepted tokens information
72 | - Contract configuration
73 |
74 | 1. For testnet, a config file is provided with the required configuration, you just need to set the account information
75 | ```
76 | cd config
77 | rm config.js
78 | mv config.testnet.js config.js
79 | ```
80 |
81 | 2. A node that wants to act as a relayer only needs to have a geth node with whisper enabled, and an account with ether to process the transactions. This account needs to be configured in `./config/config.js`. Edit this file and set the account:
82 | ```
83 | "blockchain": {
84 | // Use one of these options:
85 |
86 | // Option1 ===========================
87 | // privateKey: "your_private_key",
88 |
89 | // Option2 ===========================
90 | // privateKeyFile: "path/to/file"
91 |
92 | // Option3 ===========================
93 | // mnemonic: "12 word mnemonic",
94 | // addressIndex: "0", // Optionnal. The index to start getting the address
95 | // hdpath: "m/44'/60'/0'/0/" // Optionnal. HD derivation path
96 | },
97 | ```
98 |
99 |
100 | ### Launching the relayer
101 | A `launch-geth-testnet.sh` script is provided in the `snt-gas-relayer/gas-relayer` folder:
102 |
103 | ```
104 | chmod +x ./launch-geth-testnet.sh
105 | ./launch-geth-testnet.sh
106 | ```
107 |
108 | you may use any of the following three commands to launch the relayer.
109 |
110 | ```
111 | npm start
112 | node src/service.js
113 | nodemon src/service.js
114 | ```
115 |
116 |
117 | ### Using the testdapp with testnet
118 | The test dapp may be used for testnet from your computer. It requires a node that allows websockets. You may use this command to launch a geth instance pointing to testnet:
119 |
120 | ```
121 | chmod +x ./launch-geth-testnet.sh
122 | ./launch-geth-testnet.sh
123 | ```
124 |
125 | 1. Execute `embark run testnet` to launch the dapp connected to testnet
126 | 2. Navigate in your browser to http://localhost:8000. Use metamask to connect to your local node.
127 | 3. You're now able to use the dapp normally. The status for the relayer that can be seen in the footer of the dapp won't reflect accurate information, since the relayers account are not deterministic anymore since you're not in a development environment
128 |
129 | #### NOTE
130 | Work is in progress for using the test-dapp inside status.
131 |
--------------------------------------------------------------------------------
/javascript-library.md:
--------------------------------------------------------------------------------
1 |
2 | # Javascript Library
3 |
4 | To simplify the process of building the whisper messages, a js file `status-gas-relayer.js` was created in the test-dapp. It only requires to setup connection to geth, and required keypairs and symkeys. This file might be expanded upon in the future and converted to a npm package.
5 |
6 | ## Use
7 |
8 | ```
9 | import StatusGasRelayer, {Contracts, Functions, Messages} from 'status-gas-relayer';
10 |
11 | // Connecting to web3
12 | const web3 = new Web3('ws://localhost:8546');
13 | const kid = await web3js.shh.newKeyPair()
14 | const skid = await web3.shh.addSymKey("0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b");
15 | ```
16 |
17 | #### Subscribing to messages
18 | General message subscription. Special handling is needed for handling relayer availability messages. The `sig` property is the relayer's public key that needs to be sent when sending a transaction message. More than 1 relayer can reply, so it's recommended to save these keys in a list/array.
19 |
20 | ```
21 | StatusGasRelayer.subscribe(web3js, (error, msgObj) => {
22 | if(error) {
23 | console.error(error);
24 | return;
25 | }
26 |
27 | if(msgObj.message == Messages.available){
28 | // Relayer availability message
29 | console.log("Relayer available: " + msgObj.sig);
30 | } else {
31 | // Normal message
32 | console.log(msgObj);
33 | }
34 | }, {
35 | privateKeyID: kid
36 | });
37 | ```
38 |
39 | #### Polling for relayers
40 | ```
41 | const identityAddress = this.props.identityAddress; // Identity contract
42 | const accountAddress = web3.eth.defaultAccount;
43 | const gasToken = SNT.options.address;
44 | const gasPrice = 1000000000000; // In wei equivalent to the used token
45 |
46 | const s = new StatusGasRelayer.AvailableRelayers(Contracts.Identity, identityAddress, accountAddress)
47 | .setRelayersSymKeyID(skid)
48 | .setAsymmetricKeyID(kid)
49 | .setGasToken(gasToken)
50 | .setGas(gasPrice);
51 | await s.post(web3);
52 | ```
53 |
54 | #### Signing a message
55 | Signing a message is similar to invoking a function. Both use mostly the same functions. The difference is that when you invoke a function, you need to specify the relayer and asymmetric key Id.
56 |
57 | ```
58 | try {
59 | const s = new StatusGasRelayer.Identity(identityAddress, accountAddress)
60 | .setContractFunction(Functions.Identity.call)
61 | .setTransaction(to, value, data)
62 | .setGas(gasToken, gasPrice, gasLimit);
63 |
64 | const signature = await s.sign(web3);
65 | } catch(error){
66 | console.log(error);
67 | }
68 |
69 | ```
70 |
71 | #### Using Identity contract `call` function
72 | This functionality is used when a Identity will invoke a contract function or transfer ether without paying fees
73 |
74 | ```
75 | try {
76 | const s = new StatusGasRelayer.Identity(identityAddress, accountAddress)
77 | .setContractFunction(Functions.Identity.call)
78 | .setTransaction(to, value, data) // 'value' is in wei, and 'data' must be a hex string
79 | .setGasToken(gasToken)
80 | .setGas(gasPrice, gasMinimal)
81 | .setRelayer(relayer)
82 | .setAsymmetricKeyID(kid);
83 |
84 | await s.post(signature, web3);
85 | } catch(error){
86 | console.log(error);
87 | }
88 | ```
89 |
90 | #### Using Identity contract `approveAndCall` function
91 | This functionality is used when a Identity will invoke a contract function that requires a transfer of Tokens
92 |
93 | ```
94 | try {
95 | const s = new StatusGasRelayer.Identity(identityAddress, accountAddress)
96 | .setContractFunction(Functions.Identity.approveAndCall)
97 | .setTransaction(to, value, data)
98 | .setBaseToken(baseToken)
99 | .setGas(gasPrice, gasMinimal)
100 | .setGasToken(SNT_Address)
101 | .setRelayer(relayer)
102 | .setAsymmetricKeyID(kid);
103 |
104 | await s.post(signature, web3);
105 | } catch(error){
106 | console.log(error);
107 | }
108 | ```
109 |
110 | #### Using SNTController `transferSNT` function
111 | This functionality is used for simple wallets to perform SNT transfers without paying ETH fees
112 | ```
113 | try {
114 | const accounts = await web3.eth.getAccounts();
115 |
116 | const s = new StatusGasRelayer.SNTController(SNTController.options.address, accounts[2])
117 | .transferSNT(to, amount)
118 | .setGas(gasPrice)
119 | .setRelayer(relayer)
120 | .setAsymmetricKeyID(kid);
121 |
122 | await s.post(signature, web3);
123 | } catch(error){
124 | console.log(error);
125 | }
126 | ```
127 |
128 | #### Using SNTController `execute` function
129 | ```
130 | try {
131 | const accounts = await web3.eth.getAccounts();
132 |
133 | const s = new StatusGasRelayer.SNTController(SNTController.options.address, accounts[2])
134 | .execute(allowedContract, data)
135 | .setGas(gasPrice, gasMinimal)
136 | .setRelayer(relayer)
137 | .setAsymmetricKeyID(kid);
138 |
139 | await s.post(signature, web3);
140 | } catch(error){
141 | console.log(error);
142 | }
143 | ```
144 |
--------------------------------------------------------------------------------
/relayer-protocol.md:
--------------------------------------------------------------------------------
1 | # Gas relayer protocol
2 |
3 | Current implementation of gas relayers use whisper as a communication medium to broadcast availability, and transaction details. As such, the messages require specific settings and format to be interpreted correctly by the relayer network.
4 |
5 | #### Sending a message to the gas relayer network (all accounts and private keys should be replaced by your own)
6 | ```
7 | shh.post({
8 | symKeyID: SYM_KEY, // If requesting availability
9 | pubKey: PUBLIC_KEY_ID, // If sending a transaction
10 | sig: WHISPER_KEY_ID,
11 | ttl: 10,
12 | powTarget: 0.002,
13 | powTime: 1,
14 | topic: TOPIC_NAME,
15 | payload: PAYLOAD_BYTES
16 | }).then(......)
17 | ```
18 |
19 | - `symKeyID: SYM_KEY` must contain the whisper symmetric key used. It is shown on the console when running the service with `node`. With the provided configuration you can use the symmetric key `0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b`. Only used when asking for relayer availability.
20 | - `pubKey: PUBLIC_KEY_ID`. After asking for availability, once the user decides on a relayer, it needs to set the `pubKey` attribute with the relayer public key (received in the availability reply in the `sig` attribute of the message).
21 | - `WHISPER_KEY_ID` represents a keypair registered on your node, that will be used to sign the message. Can be generated with `web3W.shh.newKeyPair()`
22 | - `TOPIC_NAME` must contain one of the topic names generated based on converting the contract name to hex, and taking the first 8 bytes. For the provided configuration the following topics are available:
23 | - - IdentityGasRelay: `0x4964656e`
24 | - - SNTController: `0x534e5443`
25 | - `PAYLOAD_BYTES` a hex string that contains details on the operation to perform.
26 |
27 |
28 | #### Polling for gas relayers
29 | The first step is asking the relayers for their availability. The message payload needs to be the hex string representation of a JSON object with a specific structure:
30 |
31 | ```
32 | const payload = web3.utils.toHex({
33 | 'contract': "0xContractToInvoke",
34 | 'address': web3.eth.defaultAccount,
35 | 'action': 'availability',
36 | 'token': "0xGasTokenAddress",
37 | 'gasPrice': 1234
38 | });
39 | ```
40 |
41 | - `contract` is the address of the contract that will perform the operation, in this case it can be an Identity, or the SNTController.
42 | - `address` The address that will sign the transactions. Normally it's `web3.eth.defaultAccount`
43 | - `gasToken`: token used for paying the gas cost
44 | - `gasPrice`: The gas price used for the transaction
45 |
46 | This is a example code of how to send an 'availability' message:
47 |
48 | ```
49 | const whisperKeyPairID = await web3W.shh.newKeyPair();
50 |
51 | const msgObj = {
52 | symKeyId: "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b",
53 | sig: whisperKeyPairID,
54 | ttl: 10,
55 | powTarget: 0.002,
56 | powTime: 1,
57 | topic: "0x4964656e",
58 | payload: web3.utils.toHex({
59 | 'contract': "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
60 | 'address': web3.eth.defaultAccount
61 | 'action': 'availability',
62 | 'gasToken': "0x744d70fdbe2ba4cf95131626614a1763df805b9e",
63 | 'gasPrice': 40000000000 // 40 gwei equivalent in SNT
64 | })
65 | };
66 |
67 |
68 | web3.shh.post(msgObj)
69 | .then((err, result) => {
70 | console.log(result);
71 | console.log(err);
72 | });
73 | ```
74 |
75 | When it replies, you need to extract the `sig` attribute to obtain the relayer's public key
76 |
77 | #### Sending transaction details
78 |
79 | Sending a transaction is similar to the previous operation, except that we send the message to an specific node, we use the action `transaction`, and also we send a `encodedFunctionCall` with the details of the transaction to execute.
80 |
81 | From the list of relayers received via whisper messages, you need to extract the `message.sig` to obtain the `pubKey`. This value is used to send the transaction to that specific relayer.
82 |
83 | `encodedFunCall` is the hex data used obtained from `web3.eth.abi.encodeFunctionCall` for the specific function we want to invoke.
84 |
85 | If we were to execute `callGasRelayed(address,uint256,bytes,uint256,uint256,uint256,address,bytes)` (part of the IdentityGasRelay) in contract `0x692a70d2e424a56d2c6c27aa97d1a86395877b3a`, with these values: `"0x11223344556677889900998877665544332211",100,"0x00",1,10,20,"0x1122334455112233445511223344551122334455"`, "0x1122334455", `PAYLOAD_BYTES` can be prepared as follows:
86 |
87 | ```
88 | // The following values are created obtained when polling for relayers
89 | const whisperKeyPairID = await web3W.shh.newKeyPair();
90 | const relayerPubKey = "0xRELAYER_PUBLIC_KEY_HERE";
91 | // ...
92 | // ...
93 | const jsonAbi = ABIOfIdentityGasRelay.find(x => x.name == "callGasRelayed");
94 |
95 | const funCall = web3.eth.abi.encodeFunctionCall(jsonAbi,
96 | [
97 | "0x11223344556677889900998877665544332211",
98 | 100,
99 | "0x00",
100 | 1,
101 | 10,
102 | 20,
103 | "0x1122334455112233445511223344551122334455",
104 | "0x1122334455"
105 | ]);
106 |
107 | const msgObj = {
108 | pubKey: relayerPubKey,
109 | sig: whisperKeyPairID,
110 | ttl: 10,
111 | powTarget: 0.002,
112 | powTime: 1,
113 | topic: "0x4964656e",
114 | payload: web3.utils.toHex({
115 | 'contract': "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
116 | 'address': web3.eth.defaultAccount
117 | 'action': 'transaction',
118 | 'encodedFunctionCall': funCall,
119 | })
120 | };
121 |
122 |
123 | web3.shh.post(msgObj)
124 | .then((err, result) => {
125 | console.log(result);
126 | console.log(err);
127 | });
128 |
129 | ```
130 |
131 | Notice that sending messages use specific TTL, PoW Targets nad Time. These values specified here are those accepted by the Status.im cluster of nodes. The configuration for dev environment use greater values, since it is not expected to communicate with other nodes.
--------------------------------------------------------------------------------
/test-dapp/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "@babel/plugin-proposal-object-rest-spread",
4 | "@babel/plugin-syntax-dynamic-import",
5 | "@babel/plugin-syntax-import-meta",
6 | "@babel/plugin-proposal-class-properties",
7 | "@babel/plugin-proposal-json-strings",
8 | [
9 | "@babel/plugin-proposal-decorators",
10 | {
11 | "legacy": true
12 | }
13 | ],
14 | "@babel/plugin-proposal-function-sent",
15 | "@babel/plugin-proposal-export-namespace-from",
16 | "@babel/plugin-proposal-numeric-separator",
17 | "@babel/plugin-proposal-throw-expressions"
18 | ],
19 | "presets": [],
20 | "ignore": [
21 | "config/",
22 | "node_modules"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/test-dapp/.gitignore:
--------------------------------------------------------------------------------
1 | .embark/
2 | node_modules/
3 | dist/
4 | config/production/password
5 | config/livenet/password
6 |
--------------------------------------------------------------------------------
/test-dapp/app/components/body-identity.js:
--------------------------------------------------------------------------------
1 | import React, {Component, Fragment} from 'react';
2 | import StatusGasRelayer, {Messages} from '../status-gas-relayer';
3 | import ApproveAndCallGasRelayed from './approveandcallgasrelayed';
4 | import CallGasRelayed from './callgasrelayed';
5 | import Divider from '@material-ui/core/Divider';
6 | import EmbarkJS from 'Embark/EmbarkJS';
7 | import IdentityFactory from 'Embark/contracts/IdentityFactory';
8 | import IdentityGasRelay from 'Embark/contracts/IdentityGasRelay';
9 | import PropTypes from 'prop-types';
10 | import Status from './status-identity';
11 | import Tab from '@material-ui/core/Tab';
12 | import Tabs from '@material-ui/core/Tabs';
13 | import Typography from '@material-ui/core/Typography';
14 | import Web3 from 'web3';
15 | import config from '../config';
16 | import {withStyles} from '@material-ui/core/styles';
17 |
18 | window.IdentityFactory = IdentityFactory;
19 |
20 | const styles = {};
21 |
22 | class Body extends Component {
23 |
24 | constructor(props){
25 | super(props);
26 | this.state = {
27 | tab: 0,
28 | identityAddress: "0x0000000000000000000000000000000000000000",
29 | nonce: '0',
30 | kid: null,
31 | skid: null,
32 | message: '',
33 | relayerAddress: '0x0000000000000000000000000000000000000000',
34 | relayers: {}
35 | };
36 | }
37 |
38 | componentDidMount(){
39 | EmbarkJS.onReady(err => {
40 | if(err) {
41 | console.error(err);
42 | return;
43 | }
44 |
45 | const web3js = new Web3('ws://localhost:8546');
46 |
47 | web3js.shh.setMinPoW(0.002).then(res => {
48 | console.log("Min PoW set: " + res);
49 | })
50 |
51 | // Default for devenv
52 | web3js.eth.net.getId().then(netId => {
53 | if(netId != 1 && netId != 3){
54 | this.setState({relayerAddress: config.relayAccount});
55 | }
56 | });
57 |
58 | web3js.shh.newKeyPair()
59 | .then((kid) => {
60 | web3js.shh.addSymKey(config.relaySymKey)
61 | .then((skid) => {
62 | this.setState({kid, skid});
63 |
64 | StatusGasRelayer.subscribe(web3js, (error, msgObj) => {
65 | if(error) {
66 | console.error(error);
67 | return;
68 | }
69 |
70 | if(msgObj.message == Messages.available){
71 | // found a relayer
72 | console.log("Relayer available: " + msgObj.sig);
73 |
74 | let relayers = this.state.relayers;
75 | relayers[msgObj.sig] = msgObj.address;
76 |
77 | if(this.state.relayerAddress == '0x0000000000000000000000000000000000000000'){
78 | this.setState({relayerAddress: msgObj.address});
79 | }
80 |
81 | this.setState({relayers});
82 | }
83 |
84 | this.setState({message: JSON.stringify(msgObj, null, 2)});
85 | }, {
86 | privateKeyID: kid
87 | });
88 |
89 | return true;
90 | });
91 | });
92 |
93 | this.setState({
94 | web3js
95 | });
96 | });
97 | }
98 |
99 | handleChange = (event, tab) => {
100 | this.setState({tab});
101 | };
102 |
103 | updateRelayer = (relayer) => {
104 | this.setState({relayerAddress: this.state.relayers[relayer]});
105 | }
106 |
107 | updateNonce = (newNonce) => {
108 | this.setState({nonce: newNonce});
109 | }
110 |
111 | clearMessages = () => {
112 | this.setState({message: ''});
113 | }
114 |
115 | newIdentity = (cb) => {
116 | let toSend = IdentityFactory.methods['createIdentity()']();
117 | toSend.estimateGas()
118 | .then(estimatedGas => {
119 | console.log("Estimated Gas: " + estimatedGas);
120 | return toSend.send({gas: estimatedGas + 1000000});
121 | })
122 | .then((receipt) => {
123 | console.log(receipt);
124 | const instance = receipt.events.IdentityCreated.returnValues.instance;
125 | this.setState({identityAddress: instance});
126 | cb();
127 | });
128 | }
129 |
130 | render(){
131 | const {tab, identityAddress, nonce, web3js, message, kid, skid, relayers, relayerAddress} = this.state;
132 |
133 | return
134 |
135 |
136 |
137 |
138 |
139 | {tab === 0 && }
140 | {tab === 1 && }
141 | {tab === 2 && Item Three}
142 |
143 |
144 |
145 |
146 | ;
147 | }
148 | }
149 |
150 | function Container(props) {
151 | return
152 | {props.children}
153 | ;
154 | }
155 |
156 | Container.propTypes = {
157 | children: PropTypes.node.isRequired
158 | };
159 |
160 | Body.propTypes = {
161 | classes: PropTypes.object.isRequired
162 | };
163 |
164 | export default withStyles(styles)(Body);
165 |
--------------------------------------------------------------------------------
/test-dapp/app/components/body-sntcontroller.js:
--------------------------------------------------------------------------------
1 | import React, {Component, Fragment} from 'react';
2 | import StatusGasRelayer, {Messages} from '../status-gas-relayer';
3 | import Divider from '@material-ui/core/Divider';
4 | import EmbarkJS from 'Embark/EmbarkJS';
5 | import Execute from './execute';
6 | import PropTypes from 'prop-types';
7 | import STT from 'Embark/contracts/STT';
8 | import Status from './status-sntcontroller';
9 | import Tab from '@material-ui/core/Tab';
10 | import Tabs from '@material-ui/core/Tabs';
11 | import TransferSNT from './transfersnt';
12 | import Typography from '@material-ui/core/Typography';
13 | import Web3 from 'web3';
14 | import config from '../config';
15 | import web3 from 'Embark/web3';
16 | import {withStyles} from '@material-ui/core/styles';
17 |
18 | window.STT = STT;
19 |
20 | const styles = {};
21 |
22 | class Body extends Component {
23 |
24 | constructor(props){
25 | super(props);
26 | this.state = {
27 | tab: 0,
28 | walletAddress: null,
29 | nonce: '0',
30 | kid: null,
31 | skid: null,
32 | message: '',
33 | relayerAddress: '0x0000000000000000000000000000000000000000',
34 | relayers: {}
35 | };
36 | }
37 |
38 | componentDidMount(){
39 | EmbarkJS.onReady(err => {
40 | if(err) {
41 | console.error(err);
42 | return;
43 | }
44 |
45 | const web3js = new Web3('ws://localhost:8546');
46 |
47 | web3js.shh.setMinPoW(0.002).then(res => {
48 | console.log("Min PoW set: " + res);
49 | })
50 |
51 |
52 | // Default for devenv
53 | web3js.eth.net.getId().then(netId => {
54 | if(netId != 1 && netId != 3){
55 | this.setState({relayerAddress: config.relayAccount});
56 | }
57 | });
58 |
59 | web3js.shh.newKeyPair()
60 | .then((kid) => {
61 | web3js.shh.addSymKey(config.relaySymKey)
62 | .then((skid) => {
63 | this.setState({kid, skid});
64 |
65 | StatusGasRelayer.subscribe(web3js, (error, msgObj) => {
66 | if(error) {
67 | console.error(error);
68 | return;
69 | }
70 |
71 | if(msgObj.message == Messages.available){
72 | // found a relayer
73 | console.log("Relayer available: " + msgObj.sig);
74 |
75 | let relayers = this.state.relayers;
76 | relayers[msgObj.sig] = msgObj.address;
77 |
78 | if(this.state.relayerAddress == '0x0000000000000000000000000000000000000000'){
79 | this.setState({relayerAddress: msgObj.address});
80 | }
81 |
82 | this.setState({relayers});
83 | }
84 |
85 | this.setState({message: JSON.stringify(msgObj, null, 2)});
86 | }, {
87 | privateKeyID: kid
88 | });
89 |
90 | return true;
91 | });
92 | });
93 |
94 | this.setState({
95 | web3js
96 | });
97 |
98 | web3.eth.getAccounts()
99 | .then(accounts => {
100 | this.setState({walletAddress: accounts[0]});
101 | });
102 |
103 | });
104 | }
105 |
106 | handleChange = (event, tab) => {
107 | this.setState({tab});
108 | };
109 |
110 | updateRelayer = (relayer) => {
111 | this.setState({relayerAddress: this.state.relayers[relayer]});
112 | }
113 |
114 | updateNonce = (newNonce) => {
115 | this.setState({nonce: newNonce});
116 | }
117 |
118 | clearMessages = () => {
119 | this.setState({message: ''});
120 | }
121 |
122 | render(){
123 | const {tab, walletAddress, nonce, web3js, message, kid, skid, relayers, relayerAddress} = this.state;
124 |
125 | return
126 |
127 |
128 |
129 |
130 | {tab === 0 && }
131 | {tab === 1 && }
132 |
133 |
134 |
135 |
136 | ;
137 | }
138 | }
139 |
140 | function Container(props) {
141 | return
142 | {props.children}
143 | ;
144 | }
145 |
146 | Container.propTypes = {
147 | children: PropTypes.node.isRequired
148 | };
149 |
150 | Body.propTypes = {
151 | classes: PropTypes.object.isRequired
152 | };
153 |
154 | export default withStyles(styles)(Body);
155 |
--------------------------------------------------------------------------------
/test-dapp/app/components/header.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import AppBar from '@material-ui/core/AppBar';
3 | import PropTypes from 'prop-types';
4 | import Toolbar from '@material-ui/core/Toolbar';
5 | import {Typography} from '@material-ui/core';
6 | import {withStyles} from '@material-ui/core/styles';
7 |
8 |
9 | const styles = {
10 | root: {
11 | flexGrow: 1
12 | },
13 | flex: {
14 | flexGrow: 1
15 | },
16 | toolBarTyp: {
17 | color: '#fff'
18 | }
19 | };
20 |
21 | class Header extends Component {
22 |
23 | render(){
24 | const {classes} = this.props;
25 | return (
26 |
27 |
28 |
29 | Gas Relayer Demo
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | Header.propTypes = {
38 | classes: PropTypes.object.isRequired
39 | };
40 |
41 | export default withStyles(styles)(Header);
42 |
--------------------------------------------------------------------------------
/test-dapp/app/components/snackbar.js:
--------------------------------------------------------------------------------
1 | import ErrorIcon from '@material-ui/icons/Error';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import SnackbarContent from '@material-ui/core/SnackbarContent';
5 | import classNames from 'classnames';
6 | import {withStyles} from '@material-ui/core/styles';
7 |
8 | const variantIcon = {
9 | error: ErrorIcon
10 | };
11 |
12 | const styles1 = theme => ({
13 | error: {
14 | backgroundColor: theme.palette.error.dark
15 | },
16 | icon: {
17 | fontSize: 20
18 | },
19 | iconVariant: {
20 | opacity: 0.9,
21 | marginRight: theme.spacing.unit
22 | },
23 | message: {
24 | display: 'flex',
25 | alignItems: 'center'
26 | }
27 | });
28 |
29 | function MySnackbarContent(props) {
30 | const {classes, className, message, variant, ...other} = props;
31 | const Icon = variantIcon[variant];
32 |
33 | return (
34 |
39 |
40 | {message}
41 |
42 | }
43 | {...other}
44 | />
45 | );
46 | }
47 |
48 | MySnackbarContent.propTypes = {
49 | classes: PropTypes.object.isRequired,
50 | className: PropTypes.string,
51 | message: PropTypes.node,
52 | onClose: PropTypes.func,
53 | variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired
54 | };
55 |
56 | export default withStyles(styles1)(MySnackbarContent);
57 |
--------------------------------------------------------------------------------
/test-dapp/app/config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | "relayAccount": "0x5b9b5db9cde96fda2e2c88e83f1b833f189e01f4",
3 | "relaySymKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b"
4 | };
5 |
6 | export default config;
7 |
--------------------------------------------------------------------------------
/test-dapp/app/css/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/status-im/snt-gas-relay/4d563f44c8bf0d53fe8165183ef705e6bfd55c8e/test-dapp/app/css/.gitkeep
--------------------------------------------------------------------------------
/test-dapp/app/css/dapp.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | background: #eeeeee;
4 | }
--------------------------------------------------------------------------------
/test-dapp/app/identity.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test-dapp/app/identity.js:
--------------------------------------------------------------------------------
1 | import React, {Component, Fragment} from 'react';
2 | import Body from './components/body-identity';
3 | import Header from './components/header';
4 | import ReactDOM from 'react-dom';
5 |
6 |
7 | class App extends Component {
8 | render(){
9 | return
10 |
11 |
12 | ;
13 | }
14 | }
15 |
16 | ReactDOM.render(, document.getElementById('app'));
17 |
--------------------------------------------------------------------------------
/test-dapp/app/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/status-im/snt-gas-relay/4d563f44c8bf0d53fe8165183ef705e6bfd55c8e/test-dapp/app/images/.gitkeep
--------------------------------------------------------------------------------
/test-dapp/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Gas Relayer Demos
13 | The following demos are available:
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test-dapp/app/js/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/status-im/snt-gas-relay/4d563f44c8bf0d53fe8165183ef705e6bfd55c8e/test-dapp/app/js/.gitkeep
--------------------------------------------------------------------------------
/test-dapp/app/js/index.js:
--------------------------------------------------------------------------------
1 | import EmbarkJS from 'Embark/EmbarkJS';
2 |
3 | // import your contracts
4 | // e.g if you have a contract named SimpleStorage:
5 | //import SimpleStorage from 'Embark/contracts/SimpleStorage';
6 |
7 |
--------------------------------------------------------------------------------
/test-dapp/app/sntcontroller.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test-dapp/app/sntcontroller.js:
--------------------------------------------------------------------------------
1 | import React, {Component, Fragment} from 'react';
2 | import BodySNTController from './components/body-sntcontroller';
3 | import Header from './components/header';
4 | import ReactDOM from 'react-dom';
5 |
6 |
7 | class App extends Component {
8 | render(){
9 | return
10 |
11 |
12 | ;
13 | }
14 | }
15 |
16 | ReactDOM.render(, document.getElementById('app'));
17 |
--------------------------------------------------------------------------------
/test-dapp/config/blockchain.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // applies to all environments
3 | default: {
4 | enabled: true,
5 | rpcHost: "localhost", // HTTP-RPC server listening interface (default: "localhost")
6 | rpcPort: 8545, // HTTP-RPC server listening port (default: 8545)
7 | rpcCorsDomain: "auto", // Comma separated list of domains from which to accept cross origin requests (browser enforced)
8 | // When set to "auto", Embark will automatically set the cors to the address of the webserver
9 | wsRPC: true, // Enable the WS-RPC server
10 | wsOrigins: "http://localhost:8000,gas-relayer", // Origins from which to accept websockets requests
11 | // When set to "auto", Embark will automatically set the cors to the address of the webserver
12 | wsHost: "localhost", // WS-RPC server listening interface (default: "localhost")
13 | wsPort: 8546 // WS-RPC server listening port (default: 8546)
14 | },
15 |
16 | // default environment, merges with the settings in default
17 | // assumed to be the intended environment by `embark run` and `embark blockchain`
18 | development: {
19 | networkType: "custom", // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId
20 | networkId: "1337", // Network id used when networkType is custom
21 | isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled
22 | datadir: ".embark/development/datadir", // Data directory for the databases and keystore
23 | mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed
24 | nodiscover: true, // Disables the peer discovery mechanism (manual peer addition)
25 | maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25)
26 | proxy: true, // Proxy is used to present meaningful information about transactions
27 | targetGasLimit: 8000000, // Target gas limit sets the artificial target gas floor for the blocks to mine
28 | simulatorMnemonic: "example exile argue silk regular smile grass bomb merge arm assist farm", // Mnemonic used by the simulator to generate a wallet
29 | simulatorBlocktime: 0, // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining.
30 | account: {
31 | // numAccounts: 3, // When specified, creates accounts for use in the dapp. This option only works in the development environment, and can be used as a quick start option that bypasses the need for MetaMask in development. These accounts are unlocked and funded with the below settings.
32 | // password: "config/development/password", // Password for the created accounts (as specified in the `numAccounts` setting). If `mineWhenNeeded` is enabled (and isDev is not), this password is used to create a development account controlled by the node.
33 | // balance: "5 ether" // Balance to be given to the created accounts (as specified in the `numAccounts` setting)
34 | }
35 | },
36 |
37 | // merges with the settings in default
38 | // used with "embark run privatenet" and/or "embark blockchain privatenet"
39 | privatenet: {
40 | networkType: "custom",
41 | networkId: "1337",
42 | isDev: false,
43 | datadir: ".embark/privatenet/datadir",
44 | // -- mineWhenNeeded --
45 | // This options is only valid when isDev is false.
46 | // Enabling this option uses our custom script to mine only when needed.
47 | // Embark creates a development account for you (using `geth account new`) and funds the account. This account can be used for
48 | // development (and even imported in to MetaMask). To enable correct usage, a password for this account must be specified
49 | // in the `account > password` setting below.
50 | // NOTE: once `mineWhenNeeded` is enabled, you must run an `embark reset` on your dApp before running
51 | // `embark blockchain` or `embark run` for the first time.
52 | mineWhenNeeded: true,
53 | // -- genesisBlock --
54 | // This option is only valid when mineWhenNeeded is true (which is only valid if isDev is false).
55 | // When enabled, geth uses POW to mine transactions as it would normally, instead of using POA as it does in --dev mode.
56 | // On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a
57 | // genesis block, which can be configured using the `genesisBlock` configuration option below.
58 | genesisBlock: "config/privatenet/genesis.json", // Genesis block to initiate on first creation of a development node
59 | nodiscover: true,
60 | maxpeers: 0,
61 | proxy: true,
62 | account: {
63 | // "address": "", // When specified, uses that address instead of the default one for the network
64 | password: "config/privatenet/password" // Password to unlock the account. If `mineWhenNeeded` is enabled (and isDev is not), this password is used to create a development account controlled by the node.
65 | },
66 | targetGasLimit: 8000000,
67 | simulatorMnemonic: "example exile argue silk regular smile grass bomb merge arm assist farm",
68 | simulatorBlocktime: 0
69 | },
70 |
71 | // merges with the settings in default
72 | // used with "embark run testnet" and/or "embark blockchain testnet"
73 | testnet: {
74 | networkType: "testnet",
75 | syncMode: "light",
76 | account: {
77 | password: "config/testnet/password"
78 | }
79 | },
80 |
81 | // merges with the settings in default
82 | // used with "embark run livenet" and/or "embark blockchain livenet"
83 | livenet: {
84 | networkType: "livenet",
85 | syncMode: "light",
86 | rpcCorsDomain: "http://localhost:8000",
87 | wsOrigins: "http://localhost:8000",
88 | account: {
89 | password: "config/livenet/password"
90 | }
91 | }
92 |
93 | // you can name an environment with specific settings and then specify with
94 | // "embark run custom_name" or "embark blockchain custom_name"
95 | //custom_name: {
96 | //}
97 | };
98 |
--------------------------------------------------------------------------------
/test-dapp/config/communication.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // default applies to all environments
3 | default: {
4 | enabled: true,
5 | provider: "whisper", // Communication provider. Currently, Embark only supports whisper
6 | available_providers: ["whisper"], // Array of available providers
7 | },
8 |
9 | // default environment, merges with the settings in default
10 | // assumed to be the intended environment by `embark run`
11 | development: {
12 | connection: {
13 | host: "localhost", // Host of the blockchain node
14 | port: 8546, // Port of the blockchain node
15 | type: "ws" // Type of connection (ws or rpc)
16 | }
17 | },
18 |
19 | // merges with the settings in default
20 | // used with "embark run privatenet"
21 | privatenet: {
22 | },
23 |
24 |
25 | // merges with the settings in default
26 | // used with "embark run testnet"
27 | testnet: {
28 | },
29 |
30 | // merges with the settings in default
31 | // used with "embark run livenet"
32 | livenet: {
33 | },
34 |
35 | // you can name an environment with specific settings and then specify with
36 | // "embark run custom_name"
37 | //custom_name: {
38 | //}
39 | };
40 |
41 |
--------------------------------------------------------------------------------
/test-dapp/config/contracts.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // default applies to all environments
3 |
4 | default: {
5 | // Blockchain node to deploy the contracts
6 | deployment: {
7 | host: "localhost", // Host of the blockchain node
8 | port: 8545, // Port of the blockchain node
9 | type: "rpc" // Type of connection (ws or rpc),
10 | // Accounts to use instead of the default account to populate your wallet
11 | /*,accounts: [
12 | {
13 | privateKey: "your_private_key",
14 | balance: "5 ether" // You can set the balance of the account in the dev environment
15 | // Balances are in Wei, but you can specify the unit with its name
16 | },
17 | {
18 | privateKeyFile: "path/to/file", // Either a keystore or a list of keys, separated by , or ;
19 | password: "passwordForTheKeystore" // Needed to decrypt the keystore file
20 | },
21 | {
22 | mnemonic: "12 word mnemonic",
23 | addressIndex: "0", // Optionnal. The index to start getting the address
24 | numAddresses: "1", // Optionnal. The number of addresses to get
25 | hdpath: "m/44'/60'/0'/0/" // Optionnal. HD derivation path
26 | }
27 | ]*/
28 | },
29 | // order of connections the dapp should connect to
30 | dappConnection: [
31 | "$WEB3", // uses pre existing web3 object if available (e.g in Mist)
32 | "ws://localhost:8546",
33 | "http://localhost:8545"
34 | ],
35 | gas: "auto",
36 | contracts: {
37 | "Identity": {"deploy": false},
38 | "ERC20Receiver": {"deploy": false},
39 | "TestToken": {"deploy": false},
40 | "SafeMath": {"deploy": false},
41 | "DelayedUpdatableInstance": {"deploy": false},
42 | "DelayedUpdatableInstanceStorage": {"deploy": false},
43 | "Factory": {"deploy": false},
44 | "Instance": {"deploy": false},
45 | "InstanceStorage": {"deploy": false},
46 | "MiniMeTokenFactory": {"args":[]},
47 | "MiniMeToken": {"deploy": false},
48 | "TestMiniMeToken": {"deploy": false},
49 | "UpdatedIdentityKernel": {"deploy": false},
50 | "UpdatableInstance": {"deploy": false},
51 | "Controlled": {"deploy": false},
52 | "Owned": {"deploy": false},
53 | "IdentityKernel": {"deploy": false},
54 | "STT": {
55 | "instanceOf": "TestMiniMeToken",
56 | "args":["$MiniMeTokenFactory", "0x0", "0x0", "Status Gas Relayer Test Token", 18, "STT", true],
57 | "gasLimit": 4000000
58 | },
59 | "SNTController": {
60 | "args": ["$accounts[0]", "$STT"],
61 |
62 | },
63 | "IdentityGasRelay": {
64 | "deploy": true,
65 | "args": [[], [], [], 1, 1, "0x0000000000000000000000000000000000000000"]
66 | },
67 | "IdentityFactory": {
68 | "args":[],
69 | "gasLimit": 5000000
70 | },
71 | "TestContract": {
72 | "args": ["$STT"]
73 | }
74 |
75 |
76 | },
77 | "afterDeploy": [
78 | "STT.methods.changeController(SNTController.options.address).send()",
79 | "SNTController.methods.enablePublicExecution(TestContract.options.address, true).send()"
80 | ]
81 |
82 | },
83 |
84 | // default environment, merges with the settings in default
85 | // assumed to be the intended environment by `embark run`
86 | development: {
87 | dappConnection: [
88 | "ws://localhost:8546",
89 | "http://localhost:8545",
90 | "$WEB3" // uses pre existing web3 object if available (e.g in Mist)
91 | ]
92 | },
93 |
94 | // merges with the settings in default
95 | // used with "embark run privatenet"
96 | privatenet: {
97 | },
98 |
99 | // merges with the settings in default
100 | // used with "embark run testnet"
101 | testnet: {
102 | deployment: {
103 | accounts: [
104 | {
105 | privateKey: '5AC0FF313884BC134DC5B8D92C40360BFB9FC16F8552B60587D27C942564201E',
106 | }
107 | ],
108 | host: "ropsten.infura.io/v3/e62b6ada19b042ee9c6d68746b965ccf",
109 | port: false,
110 | protocol: 'https', // <=== must be specified for infura, can also be http, or ws
111 | type: "rpc"
112 | },
113 | contracts: {
114 | // Reuse contracts on testnet
115 | "MiniMeTokenFactory": {
116 | "address": "0x6bfa86a71a7dbc68566d5c741f416e3009804279"
117 | },
118 | "STT": {
119 | "instanceOf": "TestMiniMeToken",
120 | "address": "0x139724523662E54447B70d043b711b2A00c5EF49"
121 | },
122 | "SNTController": {
123 | "address": "0x1f42B87b375b8ac6C77A8CAd8E78319c18695E75"
124 | },
125 | "IdentityGasRelay": {
126 | "deploy": false
127 | },
128 | "IdentityFactory": {
129 | "address": "0xCf3473C2A50F7A94D3D7Dcc2BeBbeE989dAA014E"
130 | },
131 | "TestContract": {
132 | "address": "0x19fbEE3C3eB0070Df1ab9ba4cB8ab24F0efEBdF8"
133 | }
134 |
135 | // Deploy new contracts to testnet
136 | /*
137 | "MiniMeTokenFactory": {
138 | "address": "0x6bfa86a71a7dbc68566d5c741f416e3009804279"
139 | },
140 | "STT": {
141 | "instanceOf": "TestMiniMeToken",
142 | "args":["$MiniMeTokenFactory", "0x0", "0x0", "Status Gas Relayer Test Token", 18, "SGasT", true],
143 | "gasLimit": 4000000
144 | },
145 | "SNTController": {
146 | "args": ["$accounts[0]", "$STT"],
147 | },
148 | "IdentityGasRelay": {
149 | "deploy": true,
150 | "args": [[], [], [], 1, 1, "0x0000000000000000000000000000000000000000"]
151 | },
152 | "IdentityFactory": {
153 | "args":[],
154 | "gasLimit": 5000000
155 | },
156 | "TestContract": {
157 | "args": ["$STT"]
158 | }*/
159 | },
160 | "afterDeploy": [
161 | "STT.methods.changeController(SNTController.options.address).send()",
162 | "SNTController.methods.enablePublicExecution(TestContract.options.address, true).send()"
163 | ]
164 | },
165 |
166 | // merges with the settings in default
167 | // used with "embark run livenet"
168 | livenet: {
169 | },
170 |
171 | // you can name an environment with specific settings and then specify with
172 | // "embark run custom_name" or "embark blockchain custom_name"
173 | //custom_name: {
174 | //}
175 | };
176 |
--------------------------------------------------------------------------------
/test-dapp/config/namesystem.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // default applies to all environments
3 | default: {
4 | enabled: false,
5 | available_providers: ["ens"],
6 | provider: "ens"
7 | },
8 |
9 | // default environment, merges with the settings in default
10 | // assumed to be the intended environment by `embark run`
11 | development: {
12 | register: {
13 | rootDomain: "embark.eth",
14 | subdomains: {
15 | 'status': '0x1a2f3b98e434c02363f3dac3174af93c1d690914'
16 | }
17 | }
18 | },
19 |
20 | // merges with the settings in default
21 | // used with "embark run privatenet"
22 | privatenet: {
23 | },
24 |
25 | // merges with the settings in default
26 | // used with "embark run testnet"
27 | testnet: {
28 | },
29 |
30 | // merges with the settings in default
31 | // used with "embark run livenet"
32 | livenet: {
33 | },
34 |
35 | // you can name an environment with specific settings and then specify with
36 | // "embark run custom_name" or "embark blockchain custom_name"
37 | //custom_name: {
38 | //}
39 | };
40 |
--------------------------------------------------------------------------------
/test-dapp/config/privatenet/genesis.json:
--------------------------------------------------------------------------------
1 | {
2 | "config": {
3 | "homesteadBlock": 0,
4 | "byzantiumBlock": 0,
5 | "daoForkSupport": true
6 | },
7 | "nonce": "0x0000000000000042",
8 | "difficulty": "0x0",
9 | "alloc": {
10 | "0x3333333333333333333333333333333333333333": {"balance": "15000000000000000000"}
11 | },
12 | "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
13 | "coinbase": "0x3333333333333333333333333333333333333333",
14 | "timestamp": "0x00",
15 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
16 | "extraData": "0x",
17 | "gasLimit": "0x7a1200"
18 | }
19 |
--------------------------------------------------------------------------------
/test-dapp/config/privatenet/password:
--------------------------------------------------------------------------------
1 | dev_password
2 |
--------------------------------------------------------------------------------
/test-dapp/config/storage.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // default applies to all environments
3 | default: {
4 | enabled: false,
5 | ipfs_bin: "ipfs",
6 | provider: "ipfs",
7 | available_providers: ["ipfs"],
8 | upload: {
9 | host: "localhost",
10 | port: 5001
11 | },
12 | dappConnection: [
13 | {
14 | provider: "ipfs",
15 | host: "localhost",
16 | port: 5001,
17 | getUrl: "http://localhost:8080/ipfs/"
18 | }
19 | ]
20 | // Configuration to start Swarm in the same terminal as `embark run`
21 | /*,account: {
22 | address: "YOUR_ACCOUNT_ADDRESS", // Address of account accessing Swarm
23 | password: "PATH/TO/PASSWORD/FILE" // File containing the password of the account
24 | },
25 | swarmPath: "PATH/TO/SWARM/EXECUTABLE" // Path to swarm executable (default: swarm)*/
26 | },
27 |
28 | // default environment, merges with the settings in default
29 | // assumed to be the intended environment by `embark run`
30 | development: {
31 | enabled: false,
32 | provider: "ipfs",
33 | upload: {
34 | host: "localhost",
35 | port: 5001,
36 | getUrl: "http://localhost:8080/ipfs/"
37 | }
38 | },
39 |
40 | // merges with the settings in default
41 | // used with "embark run privatenet"
42 | privatenet: {
43 | },
44 |
45 | // merges with the settings in default
46 | // used with "embark run testnet"
47 | testnet: {
48 | },
49 |
50 | // merges with the settings in default
51 | // used with "embark run livenet"
52 | livenet: {
53 | },
54 |
55 | // you can name an environment with specific settings and then specify with
56 | // "embark run custom_name"
57 | //custom_name: {
58 | //}
59 | };
60 |
--------------------------------------------------------------------------------
/test-dapp/config/testnet/password:
--------------------------------------------------------------------------------
1 | test_password
2 |
--------------------------------------------------------------------------------
/test-dapp/config/webserver.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | enabled: true,
3 | host: "localhost",
4 | openBrowser: true,
5 | port: 8000
6 | };
7 |
--------------------------------------------------------------------------------
/test-dapp/contracts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/status-im/snt-gas-relay/4d563f44c8bf0d53fe8165183ef705e6bfd55c8e/test-dapp/contracts/.gitkeep
--------------------------------------------------------------------------------
/test-dapp/contracts/common/Controlled.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | contract Controlled {
4 | /// @notice The address of the controller is the only address that can call
5 | /// a function with this modifier
6 | modifier onlyController {
7 | require(msg.sender == controller);
8 | _;
9 | }
10 |
11 | address public controller;
12 |
13 | constructor() internal {
14 | controller = msg.sender;
15 | }
16 |
17 | /// @notice Changes the controller of the contract
18 | /// @param _newController The new controller of the contract
19 | function changeController(address _newController) public onlyController {
20 | controller = _newController;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test-dapp/contracts/common/MessageSigned.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.21;
2 |
3 | /**
4 | * @notice Uses ethereum signed messages
5 | */
6 | contract MessageSigned {
7 |
8 | constructor() internal {
9 |
10 | }
11 |
12 | /**
13 | * @notice recovers address who signed the message
14 | * @param _signHash operation ethereum signed message hash
15 | * @param _messageSignature message `_signHash` signature
16 | */
17 | function recoverAddress(
18 | bytes32 _signHash,
19 | bytes _messageSignature
20 | )
21 | pure
22 | internal
23 | returns(address)
24 | {
25 | uint8 v;
26 | bytes32 r;
27 | bytes32 s;
28 | (v,r,s) = signatureSplit(_messageSignature);
29 | return ecrecover(
30 | _signHash,
31 | v,
32 | r,
33 | s
34 | );
35 | }
36 |
37 | /**
38 | * @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"`
39 | * @param _hash Sign to hash.
40 | * @return signHash Hash to be signed.
41 | */
42 | function getSignHash(
43 | bytes32 _hash
44 | )
45 | pure
46 | internal
47 | returns (bytes32 signHash)
48 | {
49 | signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash);
50 | }
51 |
52 | /**
53 | * @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`
54 | */
55 | function signatureSplit(bytes _signature)
56 | pure
57 | internal
58 | returns (uint8 v, bytes32 r, bytes32 s)
59 | {
60 | // The signature format is a compact form of:
61 | // {bytes32 r}{bytes32 s}{uint8 v}
62 | // Compact means, uint8 is not padded to 32 bytes.
63 | assembly {
64 | r := mload(add(_signature, 32))
65 | s := mload(add(_signature, 64))
66 | // Here we are loading the last 32 bytes, including 31 bytes
67 | // of 's'. There is no 'mload8' to do this.
68 | //
69 | // 'byte' is not working due to the Solidity parser, so lets
70 | // use the second best option, 'and'
71 | v := and(mload(add(_signature, 65)), 0xff)
72 | }
73 |
74 | require(v == 27 || v == 28);
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/common/Owned.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | /// @dev `Owned` is a base level contract that assigns an `owner` that can be
4 | /// later changed
5 | contract Owned {
6 |
7 | /// @dev `owner` is the only address that can call a function with this
8 | /// modifier
9 | modifier onlyOwner() {
10 | require(msg.sender == owner);
11 | _;
12 | }
13 |
14 | address public owner;
15 |
16 | /// @notice The Constructor assigns the message sender to be `owner`
17 | constructor() internal {
18 | owner = msg.sender;
19 | }
20 |
21 | address public newOwner;
22 |
23 | /// @notice `owner` can step down and assign some other address to this role
24 | /// @param _newOwner The address of the new owner. 0x0 can be used to create
25 | /// an unowned neutral vault, however that cannot be undone
26 | function changeOwner(address _newOwner) public onlyOwner {
27 | newOwner = _newOwner;
28 | }
29 |
30 |
31 | function acceptOwnership() public {
32 | if (msg.sender == newOwner) {
33 | owner = newOwner;
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/common/SafeMath.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | /**
4 | * Math operations with safety checks
5 | */
6 | library SafeMath {
7 | function mul(uint a, uint b) internal pure returns (uint) {
8 | uint c = a * b;
9 | assert(a == 0 || c / a == b);
10 | return c;
11 | }
12 |
13 | function div(uint a, uint b) internal pure returns (uint) {
14 | // assert(b > 0); // Solidity automatically throws when dividing by 0
15 | uint c = a / b;
16 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold
17 | return c;
18 | }
19 |
20 | function sub(uint a, uint b) internal pure returns (uint) {
21 | assert(b <= a);
22 | return a - b;
23 | }
24 |
25 | function add(uint a, uint b) internal pure returns (uint) {
26 | uint c = a + b;
27 | assert(c >= a);
28 | return c;
29 | }
30 |
31 | function max64(uint64 a, uint64 b) internal pure returns (uint64) {
32 | return a >= b ? a : b;
33 | }
34 |
35 | function min64(uint64 a, uint64 b) internal pure returns (uint64) {
36 | return a < b ? a : b;
37 | }
38 |
39 | function max256(uint256 a, uint256 b) internal pure returns (uint256) {
40 | return a >= b ? a : b;
41 | }
42 |
43 | function min256(uint256 a, uint256 b) internal pure returns (uint256) {
44 | return a < b ? a : b;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test-dapp/contracts/deploy/DelayedUpdatableInstance.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.21;
2 |
3 | import "./DelayedUpdatableInstanceStorage.sol";
4 | import "./DelegatedCall.sol";
5 |
6 | /**
7 | * @title DelayedUpdatableInstance
8 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
9 | * @dev Contract that can be updated by a call from itself.
10 | */
11 | contract DelayedUpdatableInstance is DelayedUpdatableInstanceStorage, DelegatedCall {
12 |
13 | event UpdateRequested(address newKernel, uint256 activation);
14 | event UpdateCancelled();
15 | event UpdateConfirmed(address oldKernel, address newKernel);
16 |
17 | constructor(address _kernel) public {
18 | kernel = _kernel;
19 | }
20 |
21 | /**
22 | * @dev delegatecall everything (but declared functions) to `_target()`
23 | * @notice Verify `kernel()` code to predict behavior
24 | */
25 | function ()
26 | external
27 | delegated
28 | {
29 | //all goes to kernel
30 | }
31 |
32 | function updateRequestUpdatableInstance(
33 | address _kernel
34 | )
35 | external
36 | {
37 | require(msg.sender == address(this));
38 | uint activation = block.timestamp + 30 days;
39 | update = Update(_kernel, activation);
40 | emit UpdateRequested(_kernel, activation);
41 | }
42 |
43 | function updateConfirmUpdatableInstance(
44 | address _kernel
45 | )
46 | external
47 | {
48 | require(msg.sender == address(this));
49 | Update memory pending = update;
50 | require(pending.kernel == _kernel);
51 | require(pending.activation < block.timestamp);
52 | kernel = pending.kernel;
53 | delete update;
54 | emit UpdateConfirmed(kernel, pending.kernel);
55 | }
56 |
57 | function updateCancelUpdatableInstance()
58 | external
59 | {
60 | require(msg.sender == address(this));
61 | delete update;
62 | }
63 |
64 | /**
65 | * @dev returns configured kernel
66 | * @return kernel address
67 | */
68 | function targetDelegatedCall()
69 | internal
70 | view
71 | returns(address)
72 | {
73 | return kernel;
74 | }
75 |
76 |
77 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/deploy/DelayedUpdatableInstanceStorage.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.17;
2 |
3 | /**
4 | * @title DelayedUpdatableInstanceStorage
5 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
6 | * @dev Defines kernel vars that Kernel contract share with DelayedUpdatableInstance.
7 | * Important to avoid overwriting wrong storage pointers is that
8 | * InstanceStorage should be always the first contract at heritance.
9 | */
10 | contract DelayedUpdatableInstanceStorage {
11 | // protected zone start (InstanceStorage vars)
12 | address public kernel;
13 | Update public update;
14 |
15 | struct Update {
16 | address kernel;
17 | uint256 activation;
18 | }
19 | // protected zone end
20 |
21 | constructor() internal { }
22 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/deploy/DelegatedCall.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 |
4 | /**
5 | * @title DelegatedCall
6 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
7 | * @dev Abstract contract that delegates calls by `delegated` modifier to result of `targetDelegatedCall()`
8 | * Important to avoid overwriting wrong storage pointers is that never define storage to this contract
9 | */
10 | contract DelegatedCall {
11 |
12 | constructor() internal {
13 |
14 | }
15 | /**
16 | * @dev delegates the call of this function
17 | */
18 | modifier delegated {
19 | //require successfull delegate call to remote `_target()`
20 | require(targetDelegatedCall().delegatecall(msg.data));
21 | assembly {
22 | let outSize := returndatasize
23 | let outDataPtr := mload(0x40) //load memory
24 | returndatacopy(outDataPtr, 0, outSize) //copy last return into pointer
25 | return(outDataPtr, outSize)
26 | }
27 | assert(false); //should never reach here
28 | _; //never will execute local logic
29 | }
30 |
31 | /**
32 | * @dev defines the address for delegation of calls
33 | */
34 | function targetDelegatedCall()
35 | internal
36 | view
37 | returns(address);
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/test-dapp/contracts/deploy/Factory.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | import "../common/Controlled.sol";
4 |
5 | contract Factory is Controlled {
6 |
7 | event NewKernel(address newKernel, bytes32 codeHash);
8 |
9 | struct Version {
10 | uint256 blockNumber;
11 | uint256 timestamp;
12 | address kernel;
13 | }
14 |
15 | mapping(bytes32 => uint256) public hashToVersion;
16 | mapping(address => uint256) public versionMap;
17 |
18 | Version[] public versionLog;
19 | uint256 public latestUpdate;
20 | address public latestKernel;
21 |
22 | constructor(address _kernel)
23 | public
24 | {
25 | _setKernel(_kernel);
26 | }
27 |
28 | function setKernel(address _kernel)
29 | external
30 | onlyController
31 | {
32 | _setKernel(_kernel);
33 | }
34 |
35 | function getVersion(uint256 index)
36 | public
37 | view
38 | returns(
39 | uint256 blockNumber,
40 | uint256 timestamp,
41 | address kernel
42 | )
43 | {
44 | return (
45 | versionLog[index].blockNumber,
46 | versionLog[index].timestamp,
47 | versionLog[index].kernel
48 | );
49 | }
50 |
51 | function isKernel(bytes32 _codeHash) public view returns (bool){
52 | return hashToVersion[_codeHash] > 0;
53 | }
54 |
55 | function isKernel(address _addr) public view returns (bool){
56 | return versionMap[_addr] > 0;
57 | }
58 |
59 | function getCodeHash(address _addr)
60 | public
61 | view
62 | returns (bytes32 codeHash)
63 | {
64 | bytes memory o_code;
65 | uint size;
66 | assembly {
67 | // retrieve the size of the code, this needs assembly
68 | size := extcodesize(_addr)
69 | }
70 | require (size > 0);
71 | assembly {
72 | // allocate output byte array - this could also be done without assembly
73 | // by using o_code = new bytes(size)
74 | o_code := mload(0x40)
75 | // new "memory end" including padding
76 | mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
77 | // store length in memory
78 | mstore(o_code, size)
79 | // actually retrieve the code, this needs assembly
80 | extcodecopy(_addr, add(o_code, 0x20), 0, size)
81 | }
82 | codeHash = keccak256(o_code);
83 | }
84 |
85 | function _setKernel(address _kernel)
86 | internal
87 | {
88 | require(_kernel != latestKernel);
89 | bytes32 _codeHash = getCodeHash(_kernel);
90 | versionMap[_kernel] = versionLog.length;
91 | versionLog.push(Version({blockNumber: block.number, timestamp: block.timestamp, kernel: _kernel}));
92 | latestUpdate = block.timestamp;
93 | latestKernel = _kernel;
94 | emit NewKernel(_kernel, _codeHash);
95 | }
96 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/deploy/Instance.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.21;
2 |
3 | import "./InstanceStorage.sol";
4 | import "./DelegatedCall.sol";
5 |
6 | /**
7 | * @title Instance
8 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
9 | * @dev Contract that forward everything through delegatecall to defined kernel
10 | */
11 | contract Instance is InstanceStorage, DelegatedCall {
12 |
13 | constructor(address _kernel) public {
14 | kernel = _kernel;
15 | }
16 |
17 | /**
18 | * @dev delegatecall everything (but declared functions) to `_target()`
19 | * @notice Verify `kernel()` code to predict behavior
20 | */
21 | function () external delegated {
22 | //all goes to kernel
23 | }
24 |
25 | /**
26 | * @dev returns kernel if kernel that is configured
27 | * @return kernel address
28 | */
29 | function targetDelegatedCall()
30 | internal
31 | view
32 | returns(address)
33 | {
34 | return kernel;
35 | }
36 |
37 |
38 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/deploy/InstanceStorage.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 |
4 | /**
5 | * @title InstanceStorage
6 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
7 | * @dev Defines kernel vars that Kernel contract share with Instance.
8 | * Important to avoid overwriting wrong storage pointers is that
9 | * InstanceStorage should be always the first contract at heritance.
10 | */
11 | contract InstanceStorage {
12 | // protected zone start (InstanceStorage vars)
13 | address public kernel;
14 | // protected zone end
15 | constructor() internal { }
16 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/deploy/UpdatableInstance.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | import "./Instance.sol";
4 |
5 |
6 | /**
7 | * @title UpdatableInstance
8 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
9 | * @dev Contract that can be updated by a call from itself.
10 | */
11 | contract UpdatableInstance is Instance {
12 |
13 | event InstanceUpdated(address oldKernel, address newKernel);
14 |
15 | constructor(address _kernel)
16 | Instance(_kernel)
17 | public
18 | {
19 |
20 | }
21 |
22 | function updateUpdatableInstance(address _kernel) external {
23 | require(msg.sender == address(this));
24 | emit InstanceUpdated(kernel, _kernel);
25 | kernel = _kernel;
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/identity/ERC725.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.21;
2 |
3 | contract ERC725 {
4 |
5 | uint256 constant MANAGEMENT_KEY = 1;
6 | uint256 constant ACTION_KEY = 2;
7 | uint256 constant CLAIM_SIGNER_KEY = 3;
8 | uint256 constant ENCRYPTION_KEY = 4;
9 |
10 | event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
11 | event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
12 | event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
13 | event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
14 | event ExecutionFailed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
15 | event Approved(uint256 indexed executionId, bool approved);
16 |
17 | struct Key {
18 | uint256[] purposes; //e.g., MANAGEMENT_KEY = 1, ACTION_KEY = 2, etc.
19 | uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc.
20 | bytes32 key;
21 | }
22 |
23 | function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId);
24 | function approve(uint256 _id, bool _approve) public returns (bool success);
25 | function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) public returns (bool success);
26 | function removeKey(bytes32 _key, uint256 _purpose) public returns (bool success);
27 | function getKey(bytes32 _key) public view returns(uint256[] purposes, uint256 keyType, bytes32 key);
28 | function getKeyPurpose(bytes32 _key) public view returns(uint256[] purpose);
29 | function getKeysByPurpose(uint256 _purpose) public view returns(bytes32[] keys);
30 | function keyHasPurpose(bytes32 _key, uint256 purpose) public view returns(bool exists);
31 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/identity/ERC735.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.21;
2 |
3 | contract ERC735 {
4 |
5 | event ClaimRequested(bytes32 indexed claimRequestId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
6 | event ClaimAdded(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
7 | event ClaimRemoved(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
8 | event ClaimChanged(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
9 |
10 | struct Claim {
11 | uint256 topic;
12 | uint256 scheme;
13 | address issuer; // msg.sender
14 | bytes signature; // this.address + topic + data
15 | bytes data;
16 | string uri;
17 | }
18 |
19 | function getClaim(bytes32 _claimId) public view returns(uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
20 | function getClaimIdsByTopic(uint256 _topic) public view returns(bytes32[] claimIds);
21 | function addClaim(uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) public returns (bytes32 claimRequestId);
22 | function removeClaim(bytes32 _claimId) public returns (bool success);
23 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/identity/IdentityFactory.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.24;
2 |
3 | import "../deploy/Factory.sol";
4 | import "../deploy/DelayedUpdatableInstance.sol";
5 | import "./IdentityKernel.sol";
6 |
7 |
8 | contract IdentityFactory is Factory {
9 |
10 | event IdentityCreated(address instance);
11 |
12 | constructor()
13 | public
14 | Factory(new IdentityKernel())
15 | {
16 | }
17 |
18 | function createIdentity()
19 | external
20 | returns (address)
21 | {
22 |
23 | bytes32[] memory initKeys = new bytes32[](2);
24 | uint256[] memory initPurposes = new uint256[](2);
25 | uint256[] memory initTypes = new uint256[](2);
26 | initKeys[0] = keccak256(abi.encodePacked(msg.sender));
27 | initKeys[1] = initKeys[0];
28 | initPurposes[0] = 1;
29 | initPurposes[1] = 2;
30 | initTypes[0] = 0;
31 | initTypes[1] = 0;
32 | return createIdentity(
33 | initKeys,
34 | initPurposes,
35 | initTypes,
36 | 1,
37 | 1,
38 | 0
39 | );
40 | }
41 |
42 | function createIdentity(
43 | bytes32[] _keys,
44 | uint256[] _purposes,
45 | uint256[] _types,
46 | uint256 _managerThreshold,
47 | uint256 _actorThreshold,
48 | address _recoveryContract
49 | )
50 | public
51 | returns (address)
52 | {
53 | IdentityKernel instance = IdentityKernel(new DelayedUpdatableInstance(address(latestKernel)));
54 |
55 | // Saving hash for version in case it does not exist
56 | bytes32 codeHash = getCodeHash(address(instance));
57 | if(hashToVersion[codeHash] == 0){
58 | hashToVersion[codeHash] = versionLog.length;
59 | }
60 |
61 | instance.initIdentity(_keys,_purposes,_types,_managerThreshold,_actorThreshold,_recoveryContract);
62 | emit IdentityCreated(address(instance));
63 | return instance;
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/test-dapp/contracts/identity/IdentityKernel.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.17;
2 |
3 | import "../deploy/DelayedUpdatableInstanceStorage.sol";
4 | import "./IdentityGasRelay.sol";
5 |
6 | contract IdentityKernel is DelayedUpdatableInstanceStorage, IdentityGasRelay {
7 |
8 | constructor()
9 | IdentityGasRelay(
10 | new bytes32[](0),
11 | new uint256[](0),
12 | new uint256[](0),
13 | 0,
14 | 0,
15 | 0
16 | )
17 | public
18 | {
19 |
20 | }
21 |
22 | function initIdentity(
23 | bytes32[] _keys,
24 | uint256[] _purposes,
25 | uint256[] _types,
26 | uint256 _managerThreshold,
27 | uint256 _actorThreshold,
28 | address _recoveryContract
29 | ) external {
30 | _constructIdentity(
31 | _keys,
32 | _purposes,
33 | _types,
34 | _managerThreshold,
35 | _actorThreshold,
36 | _recoveryContract);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test-dapp/contracts/status/SNTController.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.17;
2 |
3 | import "../token/TokenController.sol";
4 | import "../common/Owned.sol";
5 | import "../common/MessageSigned.sol";
6 | import "../token/ERC20Token.sol";
7 | import "../token/MiniMeToken.sol";
8 |
9 | /**
10 | * @title SNTController
11 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
12 | * @notice enables economic abstraction for SNT
13 | */
14 | contract SNTController is TokenController, Owned, MessageSigned {
15 |
16 |
17 | bytes4 public constant TRANSFER_PREFIX = bytes4(
18 | keccak256("transferSNT(address,uint256,uint256,uint256)")
19 | );
20 | bytes4 public constant EXECUTE_PREFIX = bytes4(
21 | keccak256("executeGasRelayed(address,bytes,uint256,uint256,uint256)")
22 | );
23 |
24 | MiniMeToken public snt;
25 | mapping (address => uint256) public signNonce;
26 | mapping (address => bool) public allowPublicExecution;
27 |
28 | event PublicExecutionEnabled(address indexed contractAddress, bool enabled);
29 | event GasRelayedExecution(address indexed msgSigner, bytes32 signedHash, bool executed);
30 | event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
31 | event ControllerChanged(address indexed _newController);
32 |
33 | /**
34 | * @notice Constructor
35 | * @param _owner Authority address
36 | * @param _snt SNT token
37 | */
38 | constructor(address _owner, address _snt) public {
39 | owner = _owner;
40 | snt = MiniMeToken(_snt);
41 | }
42 |
43 | /**
44 | * @notice allows externally owned address sign a message to transfer SNT and pay
45 | * @param _to address receving the tokens from message signer
46 | * @param _amount total being transfered
47 | * @param _nonce current signNonce of message signer
48 | * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
49 | * @param _signature concatenated rsv of message
50 | */
51 | function transferSNT(
52 | address _to,
53 | uint256 _amount,
54 | uint256 _nonce,
55 | uint256 _gasPrice,
56 | bytes _signature
57 | )
58 | external
59 | {
60 | uint256 startGas = gasleft();
61 | bytes32 msgSigned = getSignHash(
62 | getTransferSNTHash(
63 | _to,
64 | _amount,
65 | _nonce,
66 | _gasPrice
67 | )
68 | );
69 |
70 | address msgSigner = recoverAddress(msgSigned, _signature);
71 | require(signNonce[msgSigner] == _nonce, "Bad nonce");
72 | signNonce[msgSigner]++;
73 | if (snt.transferFrom(msgSigner, _to, _amount)) {
74 | require(
75 | snt.transferFrom(
76 | msgSigner,
77 | msg.sender,
78 | (21000 + startGas - gasleft()) * _gasPrice
79 | ),
80 | "Gas transfer fail"
81 | );
82 | }
83 | }
84 |
85 | /**
86 | * @notice allows externally owned address sign a message to offer SNT for a execution
87 | * @param _allowedContract address of a contracts in execution trust list;
88 | * @param _data msg.data to be sent to `_allowedContract`
89 | * @param _nonce current signNonce of message signer
90 | * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
91 | * @param _gasMinimal minimal amount of gas needed to complete the execution
92 | * @param _signature concatenated rsv of message
93 | */
94 | function executeGasRelayed(
95 | address _allowedContract,
96 | bytes _data,
97 | uint256 _nonce,
98 | uint256 _gasPrice,
99 | uint256 _gasMinimal,
100 | bytes _signature
101 | )
102 | external
103 | {
104 | uint256 startGas = gasleft();
105 | require(startGas >= _gasMinimal, "Bad gas left");
106 | require(allowPublicExecution[_allowedContract], "Unallowed call");
107 | bytes32 msgSigned = getSignHash(
108 | getExecuteGasRelayedHash(
109 | _allowedContract,
110 | _data,
111 | _nonce,
112 | _gasPrice,
113 | _gasMinimal
114 | )
115 | );
116 |
117 | address msgSigner = recoverAddress(msgSigned, _signature);
118 | require(signNonce[msgSigner] == _nonce, "Bad nonce");
119 | signNonce[msgSigner]++;
120 | bool success = _allowedContract.call(_data);
121 | emit GasRelayedExecution(msgSigner, msgSigned, success);
122 | require(
123 | snt.transferFrom(
124 | msgSigner,
125 | msg.sender,
126 | (21000 + startGas-gasleft()) * _gasPrice
127 | ),
128 | "Gas transfer fail"
129 | );
130 | }
131 |
132 | /**
133 | * @notice The owner of this contract can change the controller of the SNT token
134 | * Please, be sure that the owner is a trusted agent or 0x0 address.
135 | * @param _newController The address of the new controller
136 | */
137 | function changeController(address _newController) public onlyOwner {
138 | snt.changeController(_newController);
139 | emit ControllerChanged(_newController);
140 | }
141 |
142 | function enablePublicExecution(address _contract, bool _enable) public onlyOwner {
143 | allowPublicExecution[_contract] = _enable;
144 | emit PublicExecutionEnabled(_contract, _enable);
145 | }
146 |
147 | //////////
148 | // Safety Methods
149 | //////////
150 |
151 | /**
152 | * @notice This method can be used by the controller to extract mistakenly
153 | * sent tokens to this contract.
154 | * @param _token The address of the token contract that you want to recover
155 | * set to 0 in case you want to extract ether.
156 | */
157 | function claimTokens(address _token) public onlyOwner {
158 | if (snt.controller() == address(this)) {
159 | snt.claimTokens(_token);
160 | }
161 | if (_token == 0x0) {
162 | address(owner).transfer(address(this).balance);
163 | return;
164 | }
165 |
166 | ERC20Token token = ERC20Token(_token);
167 | uint256 balance = token.balanceOf(this);
168 | token.transfer(owner, balance);
169 | emit ClaimedTokens(_token, owner, balance);
170 | }
171 |
172 |
173 | //////////
174 | // MiniMe Controller Interface functions
175 | //////////
176 |
177 | // In between the offering and the network. Default settings for allowing token transfers.
178 | function proxyPayment(address) public payable returns (bool) {
179 | return false;
180 | }
181 |
182 | function onTransfer(address, address, uint256) public returns (bool) {
183 | return true;
184 | }
185 |
186 | function onApprove(address, address, uint256) public returns (bool) {
187 | return true;
188 | }
189 |
190 | /**
191 | * @notice get execution hash
192 | * @param _allowedContract address of a contracts in execution trust list;
193 | * @param _data msg.data to be sent to `_allowedContract`
194 | * @param _nonce current signNonce of message signer
195 | * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
196 | * @param _gasMinimal minimal amount of gas needed to complete the execution
197 | */
198 | function getExecuteGasRelayedHash(
199 | address _allowedContract,
200 | bytes _data,
201 | uint256 _nonce,
202 | uint256 _gasPrice,
203 | uint256 _gasMinimal
204 | )
205 | public
206 | view
207 | returns (bytes32 execHash)
208 | {
209 | execHash = keccak256(
210 | abi.encodePacked(
211 | address(this),
212 | EXECUTE_PREFIX,
213 | _allowedContract,
214 | keccak256(_data),
215 | _nonce,
216 | _gasPrice,
217 | _gasMinimal
218 | )
219 | );
220 | }
221 |
222 | /**
223 | * @notice get transfer hash
224 | * @param _to address receving the tokens from message signer
225 | * @param _amount total being transfered
226 | * @param _nonce current signNonce of message signer
227 | * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
228 | */
229 | function getTransferSNTHash(
230 | address _to,
231 | uint256 _amount,
232 | uint256 _nonce,
233 | uint256 _gasPrice
234 | )
235 | public
236 | view
237 | returns (bytes32 txHash)
238 | {
239 | txHash = keccak256(
240 | abi.encodePacked(
241 | address(this),
242 | TRANSFER_PREFIX,
243 | _to,
244 | _amount,
245 | _nonce,
246 | _gasPrice
247 | )
248 | );
249 | }
250 |
251 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/tests/TestContract.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 | import "../token/ERC20Token.sol";
3 |
4 | contract TestContract {
5 |
6 | event TestFunctionExecuted(uint val);
7 |
8 | uint public val = 0;
9 | ERC20Token public token;
10 |
11 | constructor(address _token) public {
12 | token = ERC20Token(_token);
13 | }
14 |
15 | function test() public {
16 | val++;
17 | emit TestFunctionExecuted(val);
18 | }
19 |
20 | function sentSTT(uint _amount) public {
21 | require(token.allowance(msg.sender, address(this)) >= _amount);
22 | require(token.transferFrom(msg.sender, address(this), _amount));
23 | }
24 |
25 | /*
26 | Helper function to be used in unit testing due to error in web3
27 | web3.utils.soliditySha3([1, 2, 3])
28 | Error: Autodetection of array types is not supported.
29 | at _processSoliditySha3Args (node_modules/web3-utils/src/soliditySha3.js:176:15)
30 | */
31 | function hash(
32 | address identity,
33 | bytes32 _revealedSecret,
34 | address _dest,
35 | bytes _data,
36 | bytes32 _newSecret,
37 | bytes32[] _newFriendsHashes)
38 | public
39 | pure
40 | returns(bytes32)
41 | {
42 | return keccak256(identity, _revealedSecret, _dest, _data, _newSecret, _newFriendsHashes);
43 |
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/tests/UpdatedIdentityKernel.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | import "../identity/IdentityKernel.sol";
4 |
5 |
6 | contract UpdatedIdentityKernel is IdentityKernel {
7 |
8 | event TestFunctionExecuted(uint256 minApprovalsByManagementKeys);
9 |
10 | function test() public {
11 | emit TestFunctionExecuted(purposeThreshold[MANAGEMENT_KEY]);
12 | }
13 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/token/ApproveAndCallFallBack.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | contract ApproveAndCallFallBack {
4 | function receiveApproval(address from, uint256 _amount, address _token, bytes _data) public;
5 | }
6 |
--------------------------------------------------------------------------------
/test-dapp/contracts/token/ERC20Receiver.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | import "./ERC20Token.sol";
4 |
5 | contract ERC20Receiver {
6 |
7 | event TokenDeposited(address indexed token, address indexed sender, uint256 amount);
8 | event TokenWithdrawn(address indexed token, address indexed sender, uint256 amount);
9 |
10 | mapping (address => mapping(address => uint256)) tokenBalances;
11 |
12 | constructor() public {
13 |
14 | }
15 |
16 | function depositToken(
17 | ERC20Token _token
18 | )
19 | external
20 | {
21 | _depositToken(
22 | msg.sender,
23 | _token,
24 | _token.allowance(
25 | msg.sender,
26 | address(this)
27 | )
28 | );
29 | }
30 |
31 | function withdrawToken(
32 | ERC20Token _token,
33 | uint256 _amount
34 | )
35 | external
36 | {
37 | _withdrawToken(msg.sender, _token, _amount);
38 | }
39 |
40 | function depositToken(
41 | ERC20Token _token,
42 | uint256 _amount
43 | )
44 | external
45 | {
46 | require(_token.allowance(msg.sender, address(this)) >= _amount);
47 | _depositToken(msg.sender, _token, _amount);
48 | }
49 |
50 | function tokenBalanceOf(
51 | ERC20Token _token,
52 | address _from
53 | )
54 | external
55 | view
56 | returns(uint256 fromTokenBalance)
57 | {
58 | return tokenBalances[address(_token)][_from];
59 | }
60 |
61 | function _depositToken(
62 | address _from,
63 | ERC20Token _token,
64 | uint256 _amount
65 | )
66 | private
67 | {
68 | require(_amount > 0);
69 | if (_token.transferFrom(_from, address(this), _amount)) {
70 | tokenBalances[address(_token)][_from] += _amount;
71 | emit TokenDeposited(address(_token), _from, _amount);
72 | }
73 | }
74 |
75 | function _withdrawToken(
76 | address _from,
77 | ERC20Token _token,
78 | uint256 _amount
79 | )
80 | private
81 | {
82 | require(_amount > 0);
83 | require(tokenBalances[address(_token)][_from] >= _amount);
84 | tokenBalances[address(_token)][_from] -= _amount;
85 | require(_token.transfer(_from, _amount));
86 | emit TokenWithdrawn(address(_token), _from, _amount);
87 | }
88 |
89 |
90 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/token/ERC20Token.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | // Abstract contract for the full ERC 20 Token standard
4 | // https://github.com/ethereum/EIPs/issues/20
5 |
6 | interface ERC20Token {
7 |
8 | /**
9 | * @notice send `_value` token to `_to` from `msg.sender`
10 | * @param _to The address of the recipient
11 | * @param _value The amount of token to be transferred
12 | * @return Whether the transfer was successful or not
13 | */
14 | function transfer(address _to, uint256 _value) external returns (bool success);
15 |
16 | /**
17 | * @notice `msg.sender` approves `_spender` to spend `_value` tokens
18 | * @param _spender The address of the account able to transfer the tokens
19 | * @param _value The amount of tokens to be approved for transfer
20 | * @return Whether the approval was successful or not
21 | */
22 | function approve(address _spender, uint256 _value) external returns (bool success);
23 |
24 | /**
25 | * @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
26 | * @param _from The address of the sender
27 | * @param _to The address of the recipient
28 | * @param _value The amount of token to be transferred
29 | * @return Whether the transfer was successful or not
30 | */
31 | function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
32 |
33 | /**
34 | * @param _owner The address from which the balance will be retrieved
35 | * @return The balance
36 | */
37 | function balanceOf(address _owner) external view returns (uint256 balance);
38 |
39 | /**
40 | * @param _owner The address of the account owning tokens
41 | * @param _spender The address of the account able to transfer the tokens
42 | * @return Amount of remaining tokens allowed to spent
43 | */
44 | function allowance(address _owner, address _spender) external view returns (uint256 remaining);
45 |
46 | /**
47 | * @notice return total supply of tokens
48 | */
49 | function totalSupply() external view returns (uint256 supply);
50 |
51 | event Transfer(address indexed _from, address indexed _to, uint256 _value);
52 | event Approval(address indexed _owner, address indexed _spender, uint256 _value);
53 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/token/MiniMeTokenFactory.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.11;
2 |
3 | import "./MiniMeToken.sol";
4 |
5 | ////////////////
6 | // MiniMeTokenFactory
7 | ////////////////
8 |
9 | /**
10 | * @dev This contract is used to generate clone contracts from a contract.
11 | * In solidity this is the way to create a contract from a contract of the
12 | * same class
13 | */
14 | contract MiniMeTokenFactory {
15 |
16 | /**
17 | * @notice Update the DApp by creating a new token with new functionalities
18 | * the msg.sender becomes the controller of this clone token
19 | * @param _parentToken Address of the token being cloned
20 | * @param _snapshotBlock Block of the parent token that will
21 | * determine the initial distribution of the clone token
22 | * @param _tokenName Name of the new token
23 | * @param _decimalUnits Number of decimals of the new token
24 | * @param _tokenSymbol Token Symbol for the new token
25 | * @param _transfersEnabled If true, tokens will be able to be transferred
26 | * @return The address of the new token contract
27 | */
28 | function createCloneToken(
29 | address _parentToken,
30 | uint _snapshotBlock,
31 | string _tokenName,
32 | uint8 _decimalUnits,
33 | string _tokenSymbol,
34 | bool _transfersEnabled
35 | ) public returns (MiniMeToken) {
36 | MiniMeToken newToken = new MiniMeToken(
37 | this,
38 | _parentToken,
39 | _snapshotBlock,
40 | _tokenName,
41 | _decimalUnits,
42 | _tokenSymbol,
43 | _transfersEnabled
44 | );
45 |
46 | newToken.changeController(msg.sender);
47 | return newToken;
48 | }
49 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/token/MiniMeTokenInterface.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.17;
2 |
3 | import "./ERC20Token.sol";
4 |
5 |
6 | contract MiniMeTokenInterface is ERC20Token {
7 |
8 | /**
9 | * @notice `msg.sender` approves `_spender` to send `_amount` tokens on
10 | * its behalf, and then a function is triggered in the contract that is
11 | * being approved, `_spender`. This allows users to use their tokens to
12 | * interact with contracts in one function call instead of two
13 | * @param _spender The address of the contract able to transfer the tokens
14 | * @param _amount The amount of tokens to be approved for transfer
15 | * @return True if the function call was successful
16 | */
17 | function approveAndCall(
18 | address _spender,
19 | uint256 _amount,
20 | bytes _extraData
21 | )
22 | public
23 | returns (bool success);
24 |
25 | /**
26 | * @notice Creates a new clone token with the initial distribution being
27 | * this token at `_snapshotBlock`
28 | * @param _cloneTokenName Name of the clone token
29 | * @param _cloneDecimalUnits Number of decimals of the smallest unit
30 | * @param _cloneTokenSymbol Symbol of the clone token
31 | * @param _snapshotBlock Block when the distribution of the parent token is
32 | * copied to set the initial distribution of the new clone token;
33 | * if the block is zero than the actual block, the current block is used
34 | * @param _transfersEnabled True if transfers are allowed in the clone
35 | * @return The address of the new MiniMeToken Contract
36 | */
37 | function createCloneToken(
38 | string _cloneTokenName,
39 | uint8 _cloneDecimalUnits,
40 | string _cloneTokenSymbol,
41 | uint _snapshotBlock,
42 | bool _transfersEnabled
43 | )
44 | public
45 | returns(address);
46 |
47 | /**
48 | * @notice Generates `_amount` tokens that are assigned to `_owner`
49 | * @param _owner The address that will be assigned the new tokens
50 | * @param _amount The quantity of tokens generated
51 | * @return True if the tokens are generated correctly
52 | */
53 | function generateTokens(
54 | address _owner,
55 | uint _amount
56 | )
57 | public
58 | returns (bool);
59 |
60 | /**
61 | * @notice Burns `_amount` tokens from `_owner`
62 | * @param _owner The address that will lose the tokens
63 | * @param _amount The quantity of tokens to burn
64 | * @return True if the tokens are burned correctly
65 | */
66 | function destroyTokens(
67 | address _owner,
68 | uint _amount
69 | )
70 | public
71 | returns (bool);
72 |
73 | /**
74 | * @notice Enables token holders to transfer their tokens freely if true
75 | * @param _transfersEnabled True if transfers are allowed in the clone
76 | */
77 | function enableTransfers(bool _transfersEnabled) public;
78 |
79 | /**
80 | * @notice This method can be used by the controller to extract mistakenly
81 | * sent tokens to this contract.
82 | * @param _token The address of the token contract that you want to recover
83 | * set to 0 in case you want to extract ether.
84 | */
85 | function claimTokens(address _token) public;
86 |
87 | /**
88 | * @dev Queries the balance of `_owner` at a specific `_blockNumber`
89 | * @param _owner The address from which the balance will be retrieved
90 | * @param _blockNumber The block number when the balance is queried
91 | * @return The balance at `_blockNumber`
92 | */
93 | function balanceOfAt(
94 | address _owner,
95 | uint _blockNumber
96 | )
97 | public
98 | constant
99 | returns (uint);
100 |
101 | /**
102 | * @notice Total amount of tokens at a specific `_blockNumber`.
103 | * @param _blockNumber The block number when the totalSupply is queried
104 | * @return The total amount of tokens at `_blockNumber`
105 | */
106 | function totalSupplyAt(uint _blockNumber) public view returns(uint);
107 |
108 | }
--------------------------------------------------------------------------------
/test-dapp/contracts/token/StandardToken.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | import "./ERC20Token.sol";
4 |
5 | contract StandardToken is ERC20Token {
6 |
7 | uint256 private supply;
8 | mapping (address => uint256) balances;
9 | mapping (address => mapping (address => uint256)) allowed;
10 |
11 | constructor() internal { }
12 |
13 | function transfer(
14 | address _to,
15 | uint256 _value
16 | )
17 | external
18 | returns (bool success)
19 | {
20 | return transfer(msg.sender, _to, _value);
21 | }
22 |
23 | function approve(address _spender, uint256 _value)
24 | external
25 | returns (bool success)
26 | {
27 | allowed[msg.sender][_spender] = _value;
28 | emit Approval(msg.sender, _spender, _value);
29 | return true;
30 | }
31 |
32 | function transferFrom(
33 | address _from,
34 | address _to,
35 | uint256 _value
36 | )
37 | external
38 | returns (bool success)
39 | {
40 | if (balances[_from] >= _value &&
41 | allowed[_from][msg.sender] >= _value &&
42 | _value > 0) {
43 | allowed[_from][msg.sender] -= _value;
44 | return transfer(_from, _to, _value);
45 | } else {
46 | return false;
47 | }
48 | }
49 |
50 | function allowance(address _owner, address _spender)
51 | external
52 | view
53 | returns (uint256 remaining)
54 | {
55 | return allowed[_owner][_spender];
56 | }
57 |
58 | function balanceOf(address _owner)
59 | external
60 | view
61 | returns (uint256 balance)
62 | {
63 | return balances[_owner];
64 | }
65 |
66 | function totalSupply()
67 | external
68 | view
69 | returns(uint256 currentTotalSupply)
70 | {
71 | return supply;
72 | }
73 |
74 | function mint(
75 | address _to,
76 | uint256 _amount
77 | )
78 | internal
79 | {
80 | balances[_to] += _amount;
81 | supply += _amount;
82 | emit Transfer(0x0, _to, _amount);
83 | }
84 |
85 | function transfer(
86 | address _from,
87 | address _to,
88 | uint256 _value
89 | )
90 | internal
91 | returns (bool success)
92 | {
93 | if (balances[_from] >= _value && _value > 0) {
94 | balances[_from] -= _value;
95 | balances[_to] += _value;
96 | emit Transfer(_from, _to, _value);
97 | return true;
98 | } else {
99 | return false;
100 | }
101 | }
102 |
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/test-dapp/contracts/token/TestToken.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.23;
2 |
3 | import "./StandardToken.sol";
4 |
5 | /**
6 | * @notice ERC20Token for test scripts, can be minted by anyone.
7 | */
8 | contract TestToken is StandardToken {
9 |
10 | constructor() public { }
11 |
12 | /**
13 | * @notice any caller can mint any `_amount`
14 | * @param _amount how much to be minted
15 | */
16 | function mint(uint256 _amount) public {
17 | mint(msg.sender, _amount);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test-dapp/contracts/token/TokenController.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.14;
2 | /**
3 | * @dev The token controller contract must implement these functions
4 | */
5 | contract TokenController {
6 | /**
7 | * @notice Called when `_owner` sends ether to the MiniMe Token contract
8 | * @param _owner The address that sent the ether to create tokens
9 | * @return True if the ether is accepted, false if it throws
10 | */
11 | function proxyPayment(address _owner) payable returns(bool);
12 |
13 | /**
14 | * @notice Notifies the controller about a token transfer allowing the
15 | * controller to react if desired
16 | * @param _from The origin of the transfer
17 | * @param _to The destination of the transfer
18 | * @param _amount The amount of the transfer
19 | * @return False if the controller does not authorize the transfer
20 | */
21 | function onTransfer(address _from, address _to, uint _amount) returns(bool);
22 |
23 | /**
24 | * @notice Notifies the controller about an approval allowing the
25 | * controller to react if desired
26 | * @param _owner The address that calls `approve()`
27 | * @param _spender The spender in the `approve()` call
28 | * @param _amount The amount in the `approve()` call
29 | * @return False if the controller does not authorize the approval
30 | */
31 | function onApprove(address _owner, address _spender, uint _amount)
32 | returns(bool);
33 | }
34 |
--------------------------------------------------------------------------------
/test-dapp/embark.json:
--------------------------------------------------------------------------------
1 | {
2 | "contracts": ["contracts/**"],
3 | "app": {
4 | "index.html": "app/index.html",
5 | "js/identity.js": ["app/identity.js"],
6 | "identity.html": "app/identity.html",
7 | "js/sntcontroller.js": ["app/sntcontroller.js"],
8 | "sntcontroller.html": "app/sntcontroller.html",
9 | "css/dapp.css": ["app/css/**"],
10 | "images/": ["app/images/**"]
11 | },
12 | "buildDir": "dist/",
13 | "config": "config/",
14 | "versions": {
15 | "web3": "1.0.0-beta",
16 | "solc": "0.4.25",
17 | "ipfs-api": "17.2.4"
18 | },
19 | "plugins": {
20 | },
21 | "options": {
22 | "solc": {
23 | "optimize": true,
24 | "optimize-runs": 200
25 | }
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/test-dapp/launch-geth-testnet.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | geth --testnet --syncmode=light --port=30303 --ws --wsport=8546 --wsaddr=localhost --wsorigins=http://localhost:8000,embark,gas-relayer --maxpeers=25 --shh --shh.pow=0.002 --wsapi=eth,web3,net,shh
3 |
4 |
--------------------------------------------------------------------------------
/test-dapp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "snt-gas-relay",
3 | "version": "0.0.1",
4 | "description": "",
5 | "scripts": {
6 | "test": "embark test"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/status-im/contracts.git"
11 | },
12 | "author": "Status Research & Development GMBH",
13 | "license": "ISC",
14 | "bugs": {
15 | "url": "https://github.com/status-im/contracts/issues"
16 | },
17 | "homepage": "https://github.com/status-im/contracts#readme",
18 | "devDependencies": {
19 | "@babel/plugin-proposal-class-properties": "^7.0.0",
20 | "@babel/plugin-proposal-decorators": "^7.0.0",
21 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
22 | "@babel/plugin-proposal-function-sent": "^7.0.0",
23 | "@babel/plugin-proposal-json-strings": "^7.0.0",
24 | "@babel/plugin-proposal-numeric-separator": "^7.0.0",
25 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
26 | "@babel/plugin-proposal-throw-expressions": "^7.0.0",
27 | "@babel/plugin-syntax-dynamic-import": "^7.0.0",
28 | "@babel/plugin-syntax-import-meta": "^7.0.0"
29 | },
30 | "dependencies": {
31 | "@material-ui/core": "^3.0.0",
32 | "@material-ui/icons": "^3.0.0",
33 | "@material-ui/lab": "^1.0.0-alpha.12",
34 | "react": "^16.4.2",
35 | "react-blockies": "^1.3.0",
36 | "react-bootstrap": "^0.32.1",
37 | "react-dom": "^16.4.2",
38 | "web3": "^1.0.0-beta.35"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test-dapp/setup_dev_env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | STT=`cat chains.json | awk /STT/,/}/ | grep -Pzo "0x[0-9A-Za-z]+"`
3 | IdentityFactory=`cat chains.json | awk /IdentityFactory/,/}/ | grep -Pzo "0x[0-9A-Za-z]+"`
4 | SNTController=`cat chains.json | awk /SNTController/,/}/ | grep -Pzo "0x[0-9A-Za-z]+"`
5 |
6 | git checkout ../gas-relayer/config/config.js
7 | sed -i 's/%STTAddress%/'"$STT"'/g' ../gas-relayer/config/config.js
8 | sed -i 's/%IdentityFactoryAddress%/'"$IdentityFactory"'/g' ../gas-relayer/config/config.js
9 | sed -i 's/%SNTController%/'"$SNTController"'/g' ../gas-relayer/config/config.js
--------------------------------------------------------------------------------
/test-dapp/test/contract_spec.js:
--------------------------------------------------------------------------------
1 | // /*global contract, config, it, assert*/
2 | /*
3 | const SimpleStorage = require('Embark/contracts/SimpleStorage');
4 |
5 | let accounts;
6 |
7 | // For documentation please see https://embark.status.im/docs/contracts_testing.html
8 | config({
9 | //deployment: {
10 | // accounts: [
11 | // // you can configure custom accounts with a custom balance
12 | // // see https://embark.status.im/docs/contracts_testing.html#Configuring-accounts
13 | // ]
14 | //},
15 | contracts: {
16 | "SimpleStorage": {
17 | args: [100]
18 | }
19 | }
20 | }, (_err, web3_accounts) => {
21 | accounts = web3_accounts
22 | });
23 |
24 | contract("SimpleStorage", function () {
25 | this.timeout(0);
26 |
27 | it("should set constructor value", async function () {
28 | let result = await SimpleStorage.methods.storedData().call();
29 | assert.strictEqual(parseInt(result, 10), 100);
30 | });
31 |
32 | it("set storage value", async function () {
33 | await SimpleStorage.methods.set(150).send();
34 | let result = await SimpleStorage.methods.get().call();
35 | assert.strictEqual(parseInt(result, 10), 150);
36 | });
37 |
38 | it("should have account with balance", async function() {
39 | let balance = await web3.eth.getBalance(accounts[0]);
40 | assert.ok(parseInt(balance, 10) > 0);
41 | });
42 | }
43 | */
44 |
--------------------------------------------------------------------------------
/test-dapp/test/erc20token.js:
--------------------------------------------------------------------------------
1 | describe("ERC20Token", async function() {
2 | this.timeout(0);
3 | var ERC20Token;
4 | var accountsArr;
5 | before(function(done) {
6 | this.timeout(0);
7 | var contractsConfig = {
8 | "TestToken": { },
9 | "ERC20Receiver": { }
10 | };
11 | EmbarkSpec.deployAll(contractsConfig, async function(accounts) {
12 | ERC20Token = TestToken;
13 | accountsArr = accounts;
14 | for(i=0;i {
25 | accounts = _accounts;
26 | done();
27 | });
28 | });
29 |
30 |
31 | it("Creates a new identity", async () => {
32 | let tx = await IdentityFactory.methods.createIdentity().send({from: accounts[0]});
33 |
34 | const logEntry = tx.events.IdentityCreated;
35 |
36 | assert(logEntry !== undefined, "IdentityCreated was not triggered");
37 |
38 | let identity = new web3.eth.Contract(identityJson.abi, logEntry.returnValues.instance, {from: accounts[0]});
39 |
40 | assert.equal(
41 | await identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])).call(),
42 | idUtils.purposes.MANAGEMENT,
43 | identity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY")
44 | });
45 |
46 |
47 | it("Registers a updated identity contract", async() => {
48 | const infoHash = "0xbbbb";
49 | let receipt = await IdentityFactory.methods.setKernel(UpdatedIdentityKernel.address, infoHash).send({from: accounts[0]});
50 |
51 | const newKernel = TestUtils.eventValues(receipt, "NewKernel");
52 | assert(newKernel.infohash, infoHash, "Infohash is not correct");
53 | });
54 |
55 |
56 | it("Creates a new identity using latest version", async() => {
57 | let tx = await IdentityFactory.methods.createIdentity().send({from: accounts[0]});
58 |
59 | assert.notEqual(tx.events.IdentityCreated, undefined, "IdentityCreated wasn't triggered");
60 |
61 | const contractAddress = tx.events.IdentityCreated.returnValues.instance;
62 |
63 |
64 | let updatedIdentity = new web3.eth.Contract(updatedIdentityKernelJson.abi, contractAddress, {from: accounts[0]});
65 |
66 | tx = await updatedIdentity.methods.test().send({from: accounts[0]});
67 | assert.notEqual(tx.events.TestFunctionExecuted, undefined, "TestFunctionExecuted wasn't triggered");
68 |
69 | // Test if it still executes identity functions as expected
70 | let baseIdentity = new web3.eth.Contract(identityJson.abi, contractAddress, {from: accounts[0]});
71 |
72 | assert.equal(
73 | await baseIdentity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])).call(),
74 | 1,
75 | baseIdentity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY")
76 | });
77 |
78 |
79 | it("Updates an identity to the latest version", async() => {
80 | let tx1 = await Identity.methods.execute(
81 | Identity.address,
82 | 0,
83 | idUtils.encode.updateRequestUpdatableInstance(UpdatedIdentityKernel.address))
84 | .send({from: accounts[0]});
85 |
86 | assert.notEqual(tx1.events.Executed, undefined, "Executed wasn't triggered");
87 |
88 | // Updating EVM timestamp to test delay
89 | const plus31days = 60 * 60 * 24 * 31;
90 |
91 | /*
92 | // @rramos - The following code is supposed to increase by 31 days the evm date,
93 | // and mine one block. It is commented because it seems to not be working on web3 1.0.
94 | // Also, sendAsync is supposed to be named send in this version, yet it shows an error
95 | // that it does not support synchronous executions. (?)
96 | // TODO: figure it out!
97 |
98 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [plus31days], id: 0}, function(){console.log(1);});
99 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 0}, function(){console.log(2);})
100 |
101 |
102 |
103 | // Confirm update
104 | let tx2 = await Identity.methods.execute(
105 | Identity.address,
106 | 0,
107 | idUtils.encode.updateConfirmUpdatableInstance(UpdatedIdentityKernel.address))
108 | .send({from: accounts[0]});
109 |
110 | assert.notEqual(tx2.events.Executed, undefined, "Executed wasn't triggered");
111 |
112 |
113 | let updatedIdentity1 = new web3.eth.Contract(updatedIdentityKernelJson.abi, Identity.address, {from: accounts[0]});
114 |
115 | // Calling
116 | let tx3 = await updatedIdentity1.methods.test().send({from: accounts[0]});
117 | assert.notEqual(tx3.events.TestFunctionExecuted, undefined, "TestFunctionExecuted wasn't triggered");
118 | assert.equal(
119 | tx3.events.TestFunctionExecuted.returnValues.minApprovalsByManagementKeys.toString(10),
120 | 1,
121 | Identity.address + " wasn't updated to last version");
122 |
123 | */
124 | })
125 |
126 |
127 | });
128 |
129 |
--------------------------------------------------------------------------------
/test-dapp/test/identityExtended.js:
--------------------------------------------------------------------------------
1 | /*
2 | COMMENTED TEMPORARLY WHILE PROJECT IS MIGRATED TO EMBARK - @rramos
3 |
4 | const TestUtils = require("../utils/testUtils.js")
5 | var ethUtils = require('ethereumjs-util')
6 |
7 | const Identity = artifacts.require("./identity/Identity.sol");
8 |
9 | contract('Identity - Extended Functionality', function(accounts) {
10 |
11 | let identity;
12 |
13 | beforeEach(async () => {
14 | identity = await Identity.new({from: accounts[0]});
15 | })
16 |
17 |
18 | describe("Identity()", () => {
19 |
20 | let privateKey = new Buffer('61bffea9215f65164ad18b45aff1436c0c165d0d5dd2087ef61b4232ba6d2c1a', 'hex')
21 | let publicKey = ethUtils.privateToPublic(privateKey);
22 | let pkSha = web3.sha3(publicKey.toString('hex'), {encoding: 'hex'});
23 |
24 | it("Add ECDSA Management Key", async () => {
25 |
26 | await identity.addKey(pkSha, 2, 1, {from: accounts[0]})
27 |
28 | await identity.addPublicKey(pkSha, '0x' + publicKey.toString('hex'), {from: accounts[0]});
29 |
30 | assert.equal(
31 | await identity.getPublicKey(pkSha, {from: accounts[0]}),
32 | '0x' + publicKey.toString('hex'),
33 | identity.address+".getPublicKey("+pkSha+") is not correct");
34 |
35 | });
36 |
37 |
38 | it("Test Execution", async () => {
39 |
40 | let to = accounts[1];
41 | let value = 100;
42 | let data = '';
43 |
44 | let message = ethUtils.toBuffer("SignedMessage");
45 | let msgHash = ethUtils.hashPersonalMessage(message);
46 | let sig = ethUtils.ecsign(msgHash, privateKey);
47 |
48 | let r = '0x' + sig.r.toString('hex');
49 | let s = '0x' + sig.s.toString('hex');
50 | let v = sig.v;
51 |
52 |
53 | await identity.addKey(pkSha, 2, 1, {from: accounts[0]})
54 |
55 | await identity.addPublicKey(pkSha, '0x' + publicKey.toString('hex'), {from: accounts[0]});
56 |
57 | let tx = await identity.executeECDSA(to, value, data, pkSha, '0x' + msgHash.toString('hex'), v, r, s, {from: accounts[0]});
58 |
59 | // TODO Assert ExecutionRequested Event
60 | console.log(tx)
61 |
62 | });
63 | });
64 |
65 |
66 |
67 | });
68 | */
--------------------------------------------------------------------------------
/test-dapp/test/identityfactory.js:
--------------------------------------------------------------------------------
1 | const IdentityFactory = require('Embark/contracts/IdentityFactory');
2 | const IdentityGasRelay = require('Embark/contracts/IdentityGasRelay');
3 |
4 |
5 | var contractsConfig = {
6 | "IdentityFactory": {
7 |
8 | }
9 | };
10 |
11 | config({ contracts: contractsConfig });
12 |
13 | contract('IdentityFactory', function () {
14 |
15 | describe('createIdentity()', function () {
16 | it('should create an IdentityGasRelay', async function () {
17 | let result = await IdentityFactory.createIdentity();
18 | createdIdentity = new web3.eth.Contract(
19 | IdentityGasRelay.options.jsonInterface,
20 | result.events.IdentityCreated.returnValues.instance
21 | );
22 |
23 | assert(await createdIdentity.methods.nonce().call(), 0);
24 | });
25 |
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test-dapp/test/testtoken.js:
--------------------------------------------------------------------------------
1 | describe("TestToken", async function() {
2 | this.timeout(0);
3 | var accountsArr;
4 |
5 | before(function(done) {
6 | this.timeout(0);
7 | var contractsConfig = {
8 | "TestToken": {
9 | }
10 | };
11 | EmbarkSpec.deployAll(contractsConfig, async function(accounts) {
12 | accountsArr = accounts
13 | for(i=0;i {
19 | input = input.replace(/^0x/i,'');
20 | const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + input;
21 | return "0x" + stringed.substring(stringed.length - 64, stringed.length);
22 | }
23 |
24 | const _addKey = function(key, purpose, type){
25 | if(!/^(0x)?[0-9a-f]{0,64}$/i.test(key))
26 | throw new Error('Key "'+ key +'" is not a valid hex string');
27 |
28 | if (Object.values(_purposes).indexOf(purpose) == -1)
29 | throw new Error('Purpose "'+ purpose +'" is not a valid purpose');
30 |
31 | if (Object.values(_types).indexOf(type) == -1)
32 | throw new Error('Type "'+ type +'" is not a valid type');
33 |
34 | return web3EthAbi.encodeFunctionCall({
35 | name: 'addKey',
36 | type: 'function',
37 | inputs: [{
38 | type: 'bytes32',
39 | name: '_key'
40 | },{
41 | type: 'uint256',
42 | name: '_purpose'
43 | },{
44 | type: 'uint256',
45 | name: '_type'
46 | }]
47 | }, [hexToBytes32(key), purpose, type]);
48 | }
49 |
50 | const _removeKey = function(key, purpose){
51 | if(!/^(0x)?[0-9a-f]{0,64}$/i.test(key))
52 | throw new Error('Key "'+ key +'" is not a valid hex string');
53 |
54 | if (Object.values(_purposes).indexOf(purpose) == -1)
55 | throw new Error('Purpose "'+ purpose +'" is not a valid purpose');
56 |
57 | return web3EthAbi.encodeFunctionCall({
58 | name: 'removeKey',
59 | type: 'function',
60 | inputs: [{
61 | type: 'bytes32',
62 | name: '_key'
63 | },{
64 | type: 'uint256',
65 | name: '_purpose'
66 | }]
67 | }, [hexToBytes32(key), purpose]);
68 | }
69 |
70 |
71 | const _setMinimumApprovalsByKeyType = function(type, minimumApprovals) {
72 |
73 | if (Object.values(_types).indexOf(type) == -1)
74 | throw new Error('Type "'+ type +'" is not a valid type');
75 |
76 | // TODO valdate minimumApprovals
77 |
78 | return web3EthAbi.encodeFunctionCall({
79 | name: 'setMinimumApprovalsByKeyType',
80 | type: 'function',
81 | inputs: [{
82 | type: 'uint256',
83 | name: '_type'
84 | },{
85 | type: 'uint8',
86 | name: '_minimumApprovals'
87 | }]
88 | }, arguments);
89 | }
90 |
91 |
92 | const _setupRecovery = function(address){
93 | if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
94 | throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
95 |
96 | return web3EthAbi.encodeFunctionCall({
97 | name: 'setupRecovery',
98 | type: 'function',
99 | inputs: [{
100 | type: 'address',
101 | name: '_recoveryContract'
102 | }]
103 | }, [address]);
104 | }
105 |
106 | const _managerReset = function(address){
107 | if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
108 | throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
109 |
110 | return web3EthAbi.encodeFunctionCall({
111 | name: 'managerReset',
112 | type: 'function',
113 | inputs: [{
114 | type: 'address',
115 | name: '_newKey'
116 | }]
117 | }, [address]);
118 | }
119 |
120 | const _updateUpdatableInstance = function(address){
121 | if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
122 | throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
123 |
124 | return web3EthAbi.encodeFunctionCall({
125 | name: 'updateUpdatableInstance',
126 | type: 'function',
127 | inputs: [{
128 | type: 'address',
129 | name: '_kernel'
130 | }]
131 | }, [address]);
132 | }
133 |
134 | const _updateRequestUpdatableInstance = function(address){
135 | if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
136 | throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
137 |
138 | return web3EthAbi.encodeFunctionCall({
139 | name: 'updateRequestUpdatableInstance',
140 | type: 'function',
141 | inputs: [{
142 | type: 'address',
143 | name: '_kernel'
144 | }]
145 | }, [address]);
146 | }
147 |
148 | const _updateConfirmUpdatableInstance = function(address){
149 | if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
150 | throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
151 |
152 | return web3EthAbi.encodeFunctionCall({
153 | name: 'updateConfirmUpdatableInstance',
154 | type: 'function',
155 | inputs: [{
156 | type: 'address',
157 | name: '_kernel'
158 | }]
159 | }, [address]);
160 | }
161 |
162 | const _updateCancelUpdatableInstance = function(address){
163 | if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
164 | throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
165 |
166 | return web3EthAbi.encodeFunctionCall({
167 | name: 'updateCancelUpdatableInstance',
168 | type: 'function',
169 | inputs: []
170 | }, []);
171 | }
172 |
173 |
174 |
175 |
176 | module.exports = {
177 | types: _types,
178 | purposes: _purposes,
179 | encode: {
180 | addKey: _addKey,
181 | removeKey: _removeKey,
182 | setMinimumApprovalsByKeyType: _setMinimumApprovalsByKeyType,
183 | setupRecovery: _setupRecovery,
184 | managerReset: _managerReset,
185 | updateUpdatableInstance: _updateUpdatableInstance,
186 | updateRequestUpdatableInstance: _updateRequestUpdatableInstance,
187 | updateConfirmUpdatableInstance: _updateConfirmUpdatableInstance,
188 | updateCancelUpdatableInstance: _updateCancelUpdatableInstance
189 | }
190 | }
--------------------------------------------------------------------------------
/test-dapp/utils/testUtils.js:
--------------------------------------------------------------------------------
1 |
2 | // This has been tested with the real Ethereum network and Testrpc.
3 | // Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
4 | exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
5 | return new Promise((resolve, reject) => {
6 | try {
7 | resolve(contractMethodCall())
8 | } catch (error) {
9 | reject(error)
10 | }
11 | })
12 | .then(tx => {
13 | assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed")
14 | })
15 | .catch(error => {
16 | if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) {
17 | // Checks if the error is from TestRpc. If it is then ignore it.
18 | // Otherwise relay/throw the error produced by the above assertion.
19 | // Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
20 | throw error
21 | }
22 | })
23 | }
24 |
25 | exports.listenForEvent = event => new Promise((resolve, reject) => {
26 | event({}, (error, response) => {
27 | if (!error) {
28 | resolve(response.args)
29 | } else {
30 | reject(error)
31 | }
32 | event.stopWatching()
33 | })
34 | });
35 |
36 | exports.eventValues = (receipt, eventName) => {
37 | if(receipt.events[eventName])
38 | return receipt.events[eventName].returnValues;
39 | }
40 |
41 | exports.addressToBytes32 = (address) => {
42 | const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2);
43 | return "0x" + stringed.substring(stringed.length - 64, stringed.length);
44 | }
45 |
46 |
47 | // OpenZeppelin's expectThrow helper -
48 | // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
49 | exports.expectThrow = async promise => {
50 | try {
51 | await promise;
52 | } catch (error) {
53 | // TODO: Check jump destination to destinguish between a throw
54 | // and an actual invalid jump.
55 | const invalidOpcode = error.message.search('invalid opcode') >= 0;
56 | // TODO: When we contract A calls contract B, and B throws, instead
57 | // of an 'invalid jump', we get an 'out of gas' error. How do
58 | // we distinguish this from an actual out of gas event? (The
59 | // testrpc log actually show an 'invalid jump' event.)
60 | const outOfGas = error.message.search('out of gas') >= 0;
61 | const revert = error.message.search('revert') >= 0;
62 | assert(
63 | invalidOpcode || outOfGas || revert,
64 | 'Expected throw, got \'' + error + '\' instead',
65 | );
66 | return;
67 | }
68 | assert.fail('Expected throw not received');
69 | };
70 |
71 |
72 |
73 | exports.assertJump = (error) => {
74 | assert(error.message.search('revert') > -1, 'Revert should happen');
75 | }
76 |
77 |
78 | var callbackToResolve = function (resolve, reject) {
79 | return function (error, value) {
80 | if (error) {
81 | reject(error);
82 | } else {
83 | resolve(value);
84 | }
85 | };
86 | };
87 |
88 | exports.promisify = (func) =>
89 | (...args) => {
90 | return new Promise((resolve, reject) => {
91 | const callback = (err, data) => err ? reject(err) : resolve(data);
92 | func.apply(this, [...args, callback]);
93 | });
94 | }
95 |
96 |
97 | // This has been tested with the real Ethereum network and Testrpc.
98 | // Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
99 | exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
100 | return new Promise((resolve, reject) => {
101 | try {
102 | resolve(contractMethodCall())
103 | } catch (error) {
104 | reject(error)
105 | }
106 | })
107 | .then(tx => {
108 | assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed")
109 | })
110 | .catch(error => {
111 | if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) {
112 | // Checks if the error is from TestRpc. If it is then ignore it.
113 | // Otherwise relay/throw the error produced by the above assertion.
114 | // Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
115 | throw error
116 | }
117 | })
118 | }
119 |
120 | exports.listenForEvent = event => new Promise((resolve, reject) => {
121 | event({}, (error, response) => {
122 | if (!error) {
123 | resolve(response.args)
124 | } else {
125 | reject(error)
126 | }
127 | event.stopWatching()
128 | })
129 | });
130 |
131 | exports.eventValues = (receipt, eventName) => {
132 | if(receipt.events[eventName])
133 | return receipt.events[eventName].returnValues;
134 | }
135 |
136 | exports.addressToBytes32 = (address) => {
137 | const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2);
138 | return "0x" + stringed.substring(stringed.length - 64, stringed.length);
139 | }
140 |
141 |
142 | // OpenZeppelin's expectThrow helper -
143 | // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
144 | exports.expectThrow = async promise => {
145 | try {
146 | await promise;
147 | } catch (error) {
148 | // TODO: Check jump destination to destinguish between a throw
149 | // and an actual invalid jump.
150 | const invalidOpcode = error.message.search('invalid opcode') >= 0;
151 | // TODO: When we contract A calls contract B, and B throws, instead
152 | // of an 'invalid jump', we get an 'out of gas' error. How do
153 | // we distinguish this from an actual out of gas event? (The
154 | // testrpc log actually show an 'invalid jump' event.)
155 | const outOfGas = error.message.search('out of gas') >= 0;
156 | const revert = error.message.search('revert') >= 0;
157 | assert(
158 | invalidOpcode || outOfGas || revert,
159 | 'Expected throw, got \'' + error + '\' instead',
160 | );
161 | return;
162 | }
163 | assert.fail('Expected throw not received');
164 | };
165 |
166 | exports.assertJump = (error) => {
167 | assert(error.message.search('revert') > -1, 'Revert should happen');
168 | }
169 |
170 | var callbackToResolve = function (resolve, reject) {
171 | return function (error, value) {
172 | if (error) {
173 | reject(error);
174 | } else {
175 | resolve(value);
176 | }
177 | };
178 | };
179 |
180 | exports.promisify = (func) =>
181 | (...args) => {
182 | return new Promise((resolve, reject) => {
183 | const callback = (err, data) => err ? reject(err) : resolve(data);
184 | func.apply(this, [...args, callback]);
185 | });
186 | }
187 |
188 | exports.zeroAddress = '0x0000000000000000000000000000000000000000';
189 | exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
190 | exports.ensureException = function(error) {
191 | assert(isException(error), error.toString());
192 | };
193 |
194 | function isException(error) {
195 | let strError = error.toString();
196 | return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
197 | }
198 |
--------------------------------------------------------------------------------