Merge pull request #32 from gnosis/feature/WA-238-withdrawn-blockchain-requirements

WA-238 Blockchain requirements
This commit is contained in:
Adolfo Panizo 2018-06-04 14:41:59 +02:00 committed by GitHub
commit 46a1737605
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 580 additions and 96 deletions

View File

@ -5,15 +5,16 @@ os:
- linux
before_script:
- yarn global add surge
- export NODE_ENV=testing
after_success:
- yarn build-storybook
- yarn build
- |
if [ ${TRAVIS_BRANCH} = "master" ]; then
export NODE_ENV=production;
else
export NODE_ENV=development;
fi
- yarn build-storybook
- yarn build
- cd build_webpack/ && cp index.html 200.html && cd ..
- chmod ugo+x ./config/deploy/deploy.sh
- ./config/deploy/deploy.sh

View File

@ -9,7 +9,7 @@
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "run-with-testrpc -l 40000000 'node scripts/test.js --env=jsdom'",
"test-local": "node scripts/test.js --env=jsdom",
"test-local": "NODE_ENV=test && node scripts/test.js --env=jsdom",
"precommit": "./precommit.sh",
"flow": "flow",
"storybook": "start-storybook -p 6006",
@ -50,6 +50,7 @@
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
"babel-plugin-transform-es3-property-literals": "^6.22.0",
"bignumber.js": "^7.2.1",
"classnames": "^2.2.5",
"css-loader": "^0.28.10",
"detect-port": "^1.2.2",

View File

@ -1262,10 +1262,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0x251be274c3a4580fc02223ee33edfa9a2dc386e5",
"transactionHash": "0x7e0d20efecc34a31abf9e22cedf9e709811549a444d77d4889739930ad68ffb3"
"address": "0x18449f1e7a7397fcc0efb8021e55766f5874d6bb",
"transactionHash": "0x288775644c087eed5e41b96fecebdb23be4e6d40bef5b6fb9a2876c2a3145157"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0x4541a795062934411e9aa7debc9cd288d1ca3c3d",
"transactionHash": "0x1de7d532cd599c2eeb5bd5eed12038de0f6556b5d22a0a4f4f8cc15a68ef552c"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0x5fd674bc2873513f8e5a19d69637d0211e476380",
"transactionHash": "0x288775644c087eed5e41b96fecebdb23be4e6d40bef5b6fb9a2876c2a3145157"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.822Z"
"updatedAt": "2018-06-04T10:56:37.138Z"
}

View File

@ -6685,10 +6685,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0x904a781310cc19b414bafa5499628cd10173cbad",
"transactionHash": "0x2ec83a0174a651dec98e783aa4e23d0b97e8487f788329f82789da4d5067c614"
"address": "0x7318910138c97bec533269843cac7cc32e30d627",
"transactionHash": "0xf501438a4ec967e2928d922e4af568a2a5365002f8b3f9e32117bbacfaa49331"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0xb3a1d7348482ad2556b7cc56d84e8650e9288cd2",
"transactionHash": "0xd6ea4f9e92e49e53ee69faafc7c7ab1c54d811fd1552351f8ec5cdc6f1795843"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0x3bdceb07fddd50d259a059ca9a75ecda561d4afc",
"transactionHash": "0xf501438a4ec967e2928d922e4af568a2a5365002f8b3f9e32117bbacfaa49331"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.821Z"
"updatedAt": "2018-06-04T10:56:37.129Z"
}

View File

@ -9850,10 +9850,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0x5aba3ebfac41cb9be55a7ab67b506c960982e427",
"transactionHash": "0x370c8b71ac770f90d44d7bc1f447930a01806b3d8d7b218b21e4020865c45321"
"address": "0x177c7d89d62a0d8bdab4a0a8a5e7225370fd1486",
"transactionHash": "0x67117c1452ee2f4b904621b6f30790ff998d1f1a72f11c6b71ef47e3dd254724"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0x4dbf7fe3ee192b4ea00005c0a7219e4ca19971d6",
"transactionHash": "0xbbf11bb9369aeb027629ec2279e2be554fee6c522da76160a6b535d25d966b1e"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0x8c55b458a53e8c6e9efa7f54e7be9ca76b43dd9b",
"transactionHash": "0x67117c1452ee2f4b904621b6f30790ff998d1f1a72f11c6b71ef47e3dd254724"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.830Z"
"updatedAt": "2018-06-04T10:56:37.124Z"
}

View File

@ -6862,10 +6862,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0xb25140d7a0383c103745ce78d394f13ba4cf05ce",
"transactionHash": "0x6443dda4014bb6968582b264cb829cade880976d4c08e3f3c038cbc7d180f619"
"address": "0xb8d09a68423900f75635ae045562a8338060c9aa",
"transactionHash": "0xa71d3b0b3752acc18733fa881f70c256d63562f28ccca9af910fad3beee9181a"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0x8bec74b923b0d2ac349b920ab7566f9c2de06ed1",
"transactionHash": "0x846890db3c85ef38bf8ee86c789f20fbdf1c6339e44f897e15e80ba166b50de3"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0xd4edae2f2d5718d1798deb48c062b939d6e9d4f4",
"transactionHash": "0xa71d3b0b3752acc18733fa881f70c256d63562f28ccca9af910fad3beee9181a"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.817Z"
"updatedAt": "2018-06-04T10:56:37.114Z"
}

View File

@ -1384,10 +1384,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0x3058a32c81f9e74ace6fa808b1906af0252c7638",
"transactionHash": "0x025b6461e9bfe4546ffada7cbf308b8eb4c12287d93fd6079297fb9c847de4df"
"address": "0x954fe52de6d6d18bef1da4972b7bd26dfae4b90c",
"transactionHash": "0xb6a19a7a679a1474c09c651e4151421f210afa3f47effed019d4c0206144ee5f"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0xad40476dc43dbb9546709731e1112be5817db3d2",
"transactionHash": "0xb329d36a169f7dae30b168e57ae72496354fde3b36d9c285a92da9ff012a6399"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0x2ebea54cbbd4f5491deba7a37605f8f0be3e3c9b",
"transactionHash": "0xb6a19a7a679a1474c09c651e4151421f210afa3f47effed019d4c0206144ee5f"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.833Z"
"updatedAt": "2018-06-04T10:56:37.139Z"
}

View File

@ -346,10 +346,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0xe423b2291101fb206218ce4eeb03150e94446086",
"transactionHash": "0xa78e4749df5af5dff57145c85a9c9ca882d3b61e6125da675388fdda421622a2"
"address": "0x8e9d29708810d650d0b43058b41687ea02c0baee",
"transactionHash": "0xd044f1662e339061a8cabf2b06ac94a9f86fcccf3f5d80ebd1bea2a7542d4021"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0x6655c7792ebda0b1dbea3607591995ea391c8a9e",
"transactionHash": "0x70aa0d9af1bc4978d61d3f0c67523717246c4a588a4c1808d45609e0adb04a05"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0x3946fcaaa0ba21aaffc5e06a3cc45debc9e07f7f",
"transactionHash": "0xd044f1662e339061a8cabf2b06ac94a9f86fcccf3f5d80ebd1bea2a7542d4021"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.823Z"
"updatedAt": "2018-06-04T10:56:37.139Z"
}

View File

@ -997,10 +997,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0x1f7aea763a0714a857aa537a630aa9c1489a9e8d",
"transactionHash": "0xe6856fb57fd2c5da970b640fe9a5f8ee38444b5dbc381bb11bef2bb9cb80d648"
"address": "0xa16e2ea106b09506111fcd01eb82c9864afa4315",
"transactionHash": "0x75ad1066b44cd801ac66a316dbe4c09e72636d72b70fd62eb647295a0fc5e285"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0xf2bf237dc75c3a466b457a5eff498061dac4c341",
"transactionHash": "0x0a1bf7e8030e9d4d24dd7002f35ece3848830cd266ab089664fd1d44ea95d506"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0xbefd9f4a40b1bec8ec730969a3508d1739fb2742",
"transactionHash": "0x75ad1066b44cd801ac66a316dbe4c09e72636d72b70fd62eb647295a0fc5e285"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.811Z"
"updatedAt": "2018-06-04T10:56:37.112Z"
}

View File

@ -7296,10 +7296,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0xaba91d2c011e7cc4e8a7b89c7731fc355f5733f4",
"transactionHash": "0x274449b108fc18df74c77e6dcd670fa9c486c1dd6ad7aece13cbedea6a77f75d"
"address": "0x72c57fd020ba5c1e42d7e9a7757dd40d5febc594",
"transactionHash": "0xf0cd95843453bdac02ad8018ef507479ea62989e56d69ad0ac1aad9d3a8515d2"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0xeda62e0821a48445ae6f6f80106e216c6a7f01ef",
"transactionHash": "0x36c8dd7c6308c6c439014348fa486a003b509f6740a3fbb01b00d57958c4eca7"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0xfb1771240bb7edf209c70bd520a5d5424d23b084",
"transactionHash": "0xf0cd95843453bdac02ad8018ef507479ea62989e56d69ad0ac1aad9d3a8515d2"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.837Z"
"updatedAt": "2018-06-04T10:56:37.135Z"
}

View File

@ -5867,10 +5867,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0x54385cc32f3e1cf1167a00d1031713005b58810d",
"transactionHash": "0x5f41f6fd32694378659292b40446fc88ab574830bb99b43380355fd9b2454ca4"
"address": "0xa3944632cf9b50c805788da58443efdf323e69bd",
"transactionHash": "0x0396e1c9da4fa7bd313286e6033446dbb6e491f267956f8cf13202ce534fd0e6"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0x5b0e6b22a1d89e20256ec666ad2acab10d506258",
"transactionHash": "0x6d60cded3abc0298631da9e4402c536e9c10604d213f4fb215f83795722ccda9"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0x589fd9eea7cca488a80e17a5105befff9616f11d",
"transactionHash": "0x0396e1c9da4fa7bd313286e6033446dbb6e491f267956f8cf13202ce534fd0e6"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.813Z"
"updatedAt": "2018-06-04T10:56:37.118Z"
}

View File

@ -4340,10 +4340,22 @@
"1527420696956": {
"events": {},
"links": {},
"address": "0x68493ac3f7b340768f44d3924cbf6f303a104107",
"transactionHash": "0x45669293d5726d8b49af10473522b1c3d6232f5cbded6b626e24d366214501c8"
"address": "0x0f2908be3c687fe9fecdfcd3f87cd7464c229723",
"transactionHash": "0x463374c2fbc7eaff5b87e65c6a8fdc1177ef82c66084df6e7b88b506f99b193c"
},
"1527678155804": {
"events": {},
"links": {},
"address": "0xe6986286e71b3cf130edfecefd140c7b0ef7bdc0",
"transactionHash": "0xf135f6e2d525ab0b8889f2860a2ff124ca2c3b8660bf5352cc05206a71be9a9a"
},
"1528109761438": {
"events": {},
"links": {},
"address": "0xca574a31a4cf1eeabeecaffc555bdc7f91c5caf9",
"transactionHash": "0x463374c2fbc7eaff5b87e65c6a8fdc1177ef82c66084df6e7b88b506f99b193c"
}
},
"schemaVersion": "2.0.0",
"updatedAt": "2018-05-28T13:58:28.834Z"
"updatedAt": "2018-06-04T10:56:37.132Z"
}

View File

@ -65,10 +65,10 @@ describe('React DOM TESTS > Create Safe form', () => {
const deployed = TestUtils.findRenderedDOMComponentWithClass(open, DEPLOYED_COMPONENT_ID)
if (deployed) {
const transactionHash = JSON.parse(deployed.getElementsByTagName('pre')[0].innerHTML)
delete transactionHash.logsBloom
const transaction = JSON.parse(deployed.getElementsByTagName('pre')[0].innerHTML)
delete transaction.receipt.logsBloom
// eslint-disable-next-line
console.log(transactionHash)
// console.log(transaction)
}
})
})

View File

@ -8,6 +8,7 @@ import { getGnosisSafeContract } from '~/wallets/safeContracts'
import { getWeb3 } from '~/wallets/getWeb3'
import { type Safe } from '~/routes/safe/store/model/safe'
import { sameAddress } from '~/wallets/ethAddresses'
import executeTransaction from '~/wallets/ethTransactions'
export const TX_NAME_PARAM = 'txName'
export const TX_DESTINATION_PARAM = 'txDestination'
@ -96,13 +97,15 @@ export const createTransaction = async (
const thresholdIsOne = safe.get('confirmations') === 1
if (hasOneOwner(safe) || thresholdIsOne) {
const txReceipt = await gnosisSafe.execTransactionIfApproved(txDestination, valueInWei, '0x', CALL, nonce, { from: user, gas: '5000000' })
const txConfirmationData = gnosisSafe.contract.execTransactionIfApproved.getData(txDestination, valueInWei, '0x', CALL, nonce)
const txReceipt = await executeTransaction(txConfirmationData, user, safeAddress)
const executedConfirmations: List<Confirmation> = buildExecutedConfirmationFrom(safe.get('owners'), user)
return storeTransaction(txName, nonce, txDestination, txValue, user, executedConfirmations, txReceipt.tx, safeAddress, safe.get('confirmations'))
return storeTransaction(txName, nonce, txDestination, txValue, user, executedConfirmations, txReceipt, safeAddress, safe.get('confirmations'))
}
const txConfirmationHash = await gnosisSafe.approveTransactionWithParameters(txDestination, valueInWei, '0x', CALL, nonce, { from: user, gas: '5000000' })
const confirmations: List<Confirmation> = buildConfirmationsFrom(safe.get('owners'), user, txConfirmationHash.tx)
const txConfirmationData = gnosisSafe.contract.approveTransactionWithParameters.getData(txDestination, valueInWei, '0x', CALL, nonce)
const txConfirmationReceipt = await executeTransaction(txConfirmationData, user, safeAddress)
const confirmations: List<Confirmation> = buildConfirmationsFrom(safe.get('owners'), user, txConfirmationReceipt)
return storeTransaction(txName, nonce, txDestination, txValue, user, confirmations, '', safeAddress, safe.get('confirmations'))
}

View File

@ -10,14 +10,16 @@ import { type DailyLimit } from '~/routes/safe/store/model/dailyLimit'
type Props = {
dailyLimit: DailyLimit,
onWithdrawn: () => void,
balance: string,
}
export const WITHDRAWN_BUTTON_TEXT = 'Withdrawn'
const DailyLimitComponent = ({ dailyLimit, onWithdrawn }: Props) => {
const DailyLimitComponent = ({ dailyLimit, balance, onWithdrawn }: Props) => {
const limit = dailyLimit.get('value')
const spentToday = dailyLimit.get('spentToday')
const disabled = spentToday >= limit
const disabled = spentToday >= limit || Number(balance) === 0
const text = `${limit} ETH (spent today: ${spentToday} ETH)`
return (

View File

@ -71,7 +71,7 @@ class GnoSafe extends React.PureComponent<SafeProps, State> {
<Owners owners={safe.owners} />
<Confirmations confirmations={safe.get('confirmations')} />
<Address address={safe.get('address')} />
<DailyLimit dailyLimit={safe.get('dailyLimit')} onWithdrawn={this.onWithdrawn} />
<DailyLimit balance={balance} dailyLimit={safe.get('dailyLimit')} onWithdrawn={this.onWithdrawn} />
<MultisigTx balance={balance} onAddTx={this.onAddTx} onSeeTxs={this.onListTransactions} />
</List>
</Col>

View File

@ -8,6 +8,7 @@ import { getGnosisSafeContract } from '~/wallets/safeContracts'
import { getWeb3 } from '~/wallets/getWeb3'
import { sameAddress } from '~/wallets/ethAddresses'
import { EXECUTED_CONFIRMATION_HASH } from '~/routes/safe/component/AddTransaction/createTransactions'
import executeTransaction from '~/wallets/ethTransactions'
export const updateTransaction = (
name: string,
@ -49,9 +50,9 @@ const execTransaction = async (
const CALL = getOperation()
const web3 = getWeb3()
const valueInWei = web3.toWei(txValue, 'ether')
const txReceipt = await gnosisSafe.execTransactionIfApproved(destination, valueInWei, data, CALL, nonce, { from: executor, gas: '5000000' })
const txData = await gnosisSafe.contract.execTransactionIfApproved.getData(destination, valueInWei, data, CALL, nonce)
return txReceipt
return executeTransaction(txData, executor, gnosisSafe.address)
}
const execConfirmation = async (
@ -65,9 +66,10 @@ const execConfirmation = async (
const CALL = getOperation()
const web3 = getWeb3()
const valueInWei = web3.toWei(txValue, 'ether')
const txConfirmationReceipt = await gnosisSafe.approveTransactionWithParameters(txDestination, valueInWei, data, CALL, nonce, { from: executor, gas: '5000000' })
const txConfirmationData =
await gnosisSafe.contract.approveTransactionWithParameters.getData(txDestination, valueInWei, data, CALL, nonce)
return txConfirmationReceipt
return executeTransaction(txConfirmationData, executor, gnosisSafe.address)
}
const updateConfirmations = (confirmations: List<Confirmation>, userAddress: string, txHash: string) =>
@ -103,7 +105,7 @@ export const processTransaction = async (
}
const threshold = tx.get('threshold')
const thresholdReached = threshold >= alreadyConfirmed + 1
const thresholdReached = threshold === alreadyConfirmed + 1
const nonce = tx.get('nonce')
const txName = tx.get('name')
const txValue = tx.get('value')
@ -113,7 +115,7 @@ export const processTransaction = async (
? await execTransaction(gnosisSafe, txDestination, txValue, nonce, userAddress)
: await execConfirmation(gnosisSafe, txDestination, txValue, nonce, userAddress)
const confirmationHash = thresholdReached ? EXECUTED_CONFIRMATION_HASH : txReceipt.tx
const confirmationHash = thresholdReached ? EXECUTED_CONFIRMATION_HASH : txReceipt
const executedConfirmations: List<Confirmation> = updateConfirmations(tx.get('confirmations'), userAddress, confirmationHash)
return updateTransaction(
@ -123,7 +125,7 @@ export const processTransaction = async (
txValue,
userAddress,
executedConfirmations,
txReceipt.tx,
thresholdReached ? txReceipt : '',
safeAddress,
threshold,
)

View File

@ -2,6 +2,7 @@
import { getWeb3 } from '~/wallets/getWeb3'
import { getGnosisSafeContract, getCreateDailyLimitExtensionContract } from '~/wallets/safeContracts'
import { type DailyLimitProps } from '~/routes/safe/store/model/dailyLimit'
import executeTransaction from '~/wallets/ethTransactions'
export const LIMIT_POSITION = 0
export const SPENT_TODAY_POS = 1
@ -42,12 +43,9 @@ const withdrawn = async (values: Object, safeAddress: string, userAccount: strin
const destination = values[DESTINATION_PARAM]
const value = web3.toWei(values[VALUE_PARAM], 'ether')
return dailyLimitModule.executeDailyLimit(
0,
destination,
value,
{ from: userAccount, gas: '5000000' },
)
const dailyLimitData = dailyLimitModule.contract.executeDailyLimit.getData(0, destination, value)
return executeTransaction(dailyLimitData, userAccount, dailyLimitModule.address)
}
export default withdrawn

View File

@ -8,10 +8,11 @@ import { DEPLOYED_COMPONENT_ID } from '~/routes/open/components/FormConfirmation
import Open from '~/routes/open/container/Open'
import { history, type GlobalState } from '~/store'
import { sleep } from '~/utils/timer'
import { getProviderInfo } from '~/wallets/getWeb3'
import { getProviderInfo, getWeb3 } from '~/wallets/getWeb3'
import addProvider from '~/wallets/store/actions/addProvider'
import { makeProvider } from '~/wallets/store/model/provider'
import withdrawn, { DESTINATION_PARAM, VALUE_PARAM } from '~/routes/safe/component/Withdrawn/withdrawn'
import { promisify } from '~/utils/promisify'
export const renderSafe = async (localStore: Store<GlobalState>) => {
const provider = await getProviderInfo()
@ -29,20 +30,34 @@ export const renderSafe = async (localStore: Store<GlobalState>) => {
)
}
const deploySafe = async (safe: React$Component<{}>, dailyLimit: string) => {
const deploySafe = async (safe: React$Component<{}>, dailyLimit: string, threshold: number, numOwners: number) => {
expect(threshold).toBeLessThanOrEqual(numOwners)
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input')
const fieldName = inputs[0]
const fieldOwners = inputs[1]
const fieldConfirmations = inputs[2]
const fieldDailyLimit = inputs[3]
TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } })
const web3 = getWeb3()
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
TestUtils.Simulate.change(fieldOwners, { target: { value: `${numOwners}` } })
await sleep(800)
const inputsExpanded = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input')
const ownerName = inputsExpanded[2]
expect(inputsExpanded.length).toBe((numOwners * 2) + 4) // 2 per owner + name, dailyLimit, confirmations, numOwners
for (let i = 0; i < numOwners; i += 1) {
const nameIndex = (i * 2) + 2
const addressIndex = (i * 2) + 3
const ownerName = inputsExpanded[nameIndex]
const account = inputsExpanded[addressIndex]
TestUtils.Simulate.change(ownerName, { target: { value: `Adolfo ${i + 1} Eth Account` } })
TestUtils.Simulate.change(account, { target: { value: accounts[i] } })
}
TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } })
TestUtils.Simulate.change(fieldConfirmations, { target: { value: '1' } })
TestUtils.Simulate.change(ownerName, { target: { value: 'Adolfo Eth Account' } })
TestUtils.Simulate.change(fieldConfirmations, { target: { value: `${threshold}` } })
TestUtils.Simulate.change(fieldDailyLimit, { target: { value: dailyLimit } })
const form = TestUtils.findRenderedDOMComponentWithTag(safe, 'form')
@ -53,7 +68,7 @@ const deploySafe = async (safe: React$Component<{}>, dailyLimit: string) => {
// giving some time to the component for updating its state with safe
// before destroying its context
await sleep(3500)
await sleep(9000)
// THEN
const deployed = TestUtils.findRenderedDOMComponentWithClass(safe, DEPLOYED_COMPONENT_ID)
@ -67,9 +82,14 @@ const deploySafe = async (safe: React$Component<{}>, dailyLimit: string) => {
return transactionHash
}
export const aDeployedSafe = async (specificStore: Store<GlobalState>, dailyLimit?: number = 0.5) => {
export const aDeployedSafe = async (
specificStore: Store<GlobalState>,
dailyLimit?: number = 0.5,
threshold?: number = 1,
numOwners?: number = 1,
) => {
const safe: React$Component<{}> = await renderSafe(specificStore)
const deployedSafe = await deploySafe(safe, `${dailyLimit}`)
const deployedSafe = await deploySafe(safe, `${dailyLimit}`, threshold, numOwners)
return deployedSafe.logs[1].args.proxy
}

View File

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

View File

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

View File

@ -41,6 +41,7 @@ describe('React DOM TESTS > Withdrawn funds from safe', () => {
it('should withdrawn funds under dailyLimit without needing confirmations', async () => {
// add funds to safe
await addEtherTo(address, '0.1')
await sleep(3000)
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
@ -48,7 +49,7 @@ describe('React DOM TESTS > Withdrawn funds from safe', () => {
const withdrawnButton = buttons[0]
expect(withdrawnButton.props.children).toEqual(WITHDRAWN_BUTTON_TEXT)
TestUtils.Simulate.click(TestUtils.scryRenderedDOMComponentsWithTag(withdrawnButton, 'button')[0])
await sleep(4000)
const Withdrawn = TestUtils.findRenderedComponentWithType(SafeDom, WithdrawnComponent)
// $FlowFixMe
@ -63,7 +64,7 @@ describe('React DOM TESTS > Withdrawn funds from safe', () => {
TestUtils.Simulate.submit(form) // fill the form
TestUtils.Simulate.submit(form) // confirming data
await sleep(4000)
await sleep(6000)
const safeBalance = await getBalanceInEtherOf(address)
expect(safeBalance).toBe('0.09')
@ -104,4 +105,18 @@ describe('React DOM TESTS > Withdrawn funds from safe', () => {
expect(addTxButton.props.disabled).toBe(false)
})
it('Withdrawn button disabled when balance is 0', async () => {
const Safe = TestUtils.findRenderedComponentWithType(SafeDom, SafeView)
// $FlowFixMe
const buttons = TestUtils.scryRenderedComponentsWithType(Safe, Button)
const addTxButton = buttons[0]
expect(addTxButton.props.children).toEqual(WITHDRAWN_BUTTON_TEXT)
expect(addTxButton.props.disabled).toBe(true)
await addEtherTo(address, '0.1')
await sleep(1800)
expect(addTxButton.props.disabled).toBe(false)
})
})

View File

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

View File

@ -0,0 +1,53 @@
// @flow
import { BigNumber } from 'bignumber.js'
import { getWeb3 } from '~/wallets/getWeb3'
import { promisify } from '~/utils/promisify'
// const MAINNET_NETWORK = 1
export const calculateGasPrice = async () => {
/*
const web3 = getWeb3()
const { network } = web3.version
const isMainnet = MAINNET_NETWORK === network
const url = isMainnet
? 'https://safe-relay.staging.gnosisdev.com/api/v1/gas-station/'
: 'https://safe-relay.dev.gnosisdev.com/'
*/
const response = await fetch('https://ethgasstation.info/json/ethgasAPI.json', { mode: 'cors' })
if (!response.ok) {
throw new Error('Error querying gast station')
}
const json = await response.json()
return new BigNumber(json.average).multipliedBy(1e8).toString()
}
export const calculateGasOf = async (data: Object, from: string, to: string) => {
const web3 = getWeb3()
const gas = await promisify(cb => web3.eth.estimateGas({ data, from, to }, cb))
return gas * 2
}
const executeTransaction = async (data: Object, from: string, to: string) => {
const web3 = getWeb3()
const gas = await calculateGasOf(data, from, to)
let gasPrice
try {
gasPrice = await calculateGasPrice()
} catch (err) {
gasPrice = await promisify(cb => web3.eth.getGasPrice(cb))
}
return promisify(cb => web3.eth.sendTransaction({
from, to, data, gas, gasPrice,
}, cb))
}
export default executeTransaction

View File

@ -28,8 +28,11 @@ export const getProviderInfo: Function = async (): Promise<ProviderProps> => {
// Use MetaMask's provider.
web3 = new Web3(window.web3.currentProvider)
// eslint-disable-next-line
console.log('Injected web3 detected.')
if (process.env.NODE_ENV !== 'test') {
// eslint-disable-next-line
console.log('Injected web3 detected.')
}
const name = isMetamask(web3) ? 'METAMASK' : 'UNKNOWN'
const account = await getAccountFrom(web3)

View File

@ -1,12 +1,13 @@
// @flow
import contract from 'truffle-contract'
import { promisify } from '~/utils/promisify'
import { ensureOnce } from '~/utils/singleton'
import { getWeb3 } from '~/wallets/getWeb3'
import { promisify } from '~/utils/promisify'
import GnosisSafeSol from '#/GnosisSafeTeamEdition.json'
import ProxyFactorySol from '#/ProxyFactory.json'
import CreateAndAddModules from '#/CreateAndAddModules.json'
import DailyLimitModule from '#/DailyLimitModule.json'
import { calculateGasOf, calculateGasPrice } from '~/wallets/ethTransactions'
let proxyFactoryMaster
let createAndAddModuleMaster
@ -63,45 +64,46 @@ const getCreateProxyFactoryContract = ensureOnce(createProxyFactoryContract)
const getCreateAddExtensionContract = ensureOnce(createAddExtensionContract)
export const getCreateDailyLimitExtensionContract = ensureOnce(createDailyLimitExtensionContract)
const instanciateMasterCopies = async () => {
const web3 = getWeb3()
// Create ProxyFactory Master Copy
const ProxyFactory = getCreateProxyFactoryContract(web3)
proxyFactoryMaster = await ProxyFactory.deployed()
// Create AddExtension Master Copy
const CreateAndAddExtension = getCreateAddExtensionContract(web3)
createAndAddModuleMaster = await CreateAndAddExtension.deployed()
// Initialize safe master copy
const GnosisSafe = getGnosisSafeContract(web3)
safeMaster = await GnosisSafe.deployed()
// Initialize extension master copy
const DailyLimitExtension = getCreateDailyLimitExtensionContract(web3)
dailyLimitMaster = await DailyLimitExtension.deployed()
}
// ONLY USED IN TEST ENVIRONMENT
const createMasterCopies = async () => {
const web3 = getWeb3()
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
const userAccount = accounts[0]
// Create ProxyFactory Master Copy
const ProxyFactory = getCreateProxyFactoryContract(web3)
try {
proxyFactoryMaster = await ProxyFactory.deployed()
} catch (err) {
proxyFactoryMaster = await ProxyFactory.new({ from: userAccount, gas: '5000000' })
}
proxyFactoryMaster = await ProxyFactory.new({ from: userAccount, gas: '5000000' })
// Create AddExtension Master Copy
const CreateAndAddExtension = getCreateAddExtensionContract(web3)
try {
createAndAddModuleMaster = await CreateAndAddExtension.deployed()
} catch (err) {
createAndAddModuleMaster = await CreateAndAddExtension.new({ from: userAccount, gas: '5000000' })
}
createAndAddModuleMaster = await CreateAndAddExtension.new({ from: userAccount, gas: '5000000' })
// Initialize safe master copy
const GnosisSafe = getGnosisSafeContract(web3)
try {
safeMaster = await GnosisSafe.deployed()
} catch (err) {
safeMaster = await GnosisSafe.new([userAccount], 1, 0, 0, { from: userAccount, gas: '5000000' })
}
safeMaster = await GnosisSafe.new([userAccount], 1, 0, 0, { from: userAccount, gas: '5000000' })
// Initialize extension master copy
const DailyLimitExtension = getCreateDailyLimitExtensionContract(web3)
try {
dailyLimitMaster = await DailyLimitExtension.deployed()
} catch (err) {
dailyLimitMaster = await DailyLimitExtension.new([], [], { from: userAccount, gas: '5000000' })
}
dailyLimitMaster = await DailyLimitExtension.new([], [], { from: userAccount, gas: '5000000' })
}
export const initContracts = ensureOnce(createMasterCopies)
export const initContracts = ensureOnce(process.env.NODE_ENV === 'test' ? createMasterCopies : instanciateMasterCopies)
const getSafeDataBasedOn = async (accounts, numConfirmations, dailyLimitInEth) => {
const web3 = getWeb3()
@ -128,5 +130,9 @@ export const deploySafeContract = async (
userAccount: string,
) => {
const gnosisSafeData = await getSafeDataBasedOn(safeAccounts, numConfirmations, dailyLimit)
return proxyFactoryMaster.createProxy(safeMaster.address, gnosisSafeData, { from: userAccount, gas: '5000000' })
const proxyFactoryData = proxyFactoryMaster.contract.createProxy.getData(safeMaster.address, gnosisSafeData)
const gas = await calculateGasOf(proxyFactoryData, userAccount, proxyFactoryMaster.address)
const gasPrice = await calculateGasPrice()
return proxyFactoryMaster.createProxy(safeMaster.address, gnosisSafeData, { from: userAccount, gas, gasPrice })
}

View File

@ -2474,6 +2474,10 @@ bignumber.js@^2.1.4:
version "2.4.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8"
bignumber.js@^7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f"
"bignumber.js@git+https://github.com/debris/bignumber.js#master":
version "2.0.7"
resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9"