From 1e42b3d67e9843937cdd99db31b72d5e1828b3ae Mon Sep 17 00:00:00 2001 From: apanizo Date: Fri, 11 May 2018 15:25:16 +0200 Subject: [PATCH 01/20] WA-238 Adapting dailyLimit in safes to store the amount spent --- src/routes/safe/component/Safe/DailyLimit.jsx | 38 +++++++++++-------- src/routes/safe/component/Safe/index.jsx | 2 +- src/routes/safe/store/actions/addSafe.js | 8 +++- src/routes/safe/store/model/safe.js | 14 ++++++- .../safe/store/test/builder/safe.builder.js | 5 ++- 5 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/routes/safe/component/Safe/DailyLimit.jsx b/src/routes/safe/component/Safe/DailyLimit.jsx index a19bb462..23b909f1 100644 --- a/src/routes/safe/component/Safe/DailyLimit.jsx +++ b/src/routes/safe/component/Safe/DailyLimit.jsx @@ -7,26 +7,32 @@ import Button from '~/components/layout/Button' import ListItemText from '~/components/List/ListItemText' type Props = { - limit: number, + dailyLimit: number, onWithdrawn: () => void, } export const WITHDRAWN_BUTTON_TEXT = 'Withdrawn' -const DailyLimit = ({ limit, onWithdrawn }: Props) => ( - - - - - - - -) +const DailyLimit = ({ dailyLimit, onWithdrawn }: Props) => { + const limit = dailyLimit.get('value') + const disabled = dailyLimit.get('todaySpent') > limit + + return ( + + + + + + + + ) +} export default DailyLimit diff --git a/src/routes/safe/component/Safe/index.jsx b/src/routes/safe/component/Safe/index.jsx index 563f069c..752a9907 100644 --- a/src/routes/safe/component/Safe/index.jsx +++ b/src/routes/safe/component/Safe/index.jsx @@ -55,7 +55,7 @@ class GnoSafe extends React.PureComponent {
- + diff --git a/src/routes/safe/store/actions/addSafe.js b/src/routes/safe/store/actions/addSafe.js index e4a1801e..d5a3836c 100644 --- a/src/routes/safe/store/actions/addSafe.js +++ b/src/routes/safe/store/actions/addSafe.js @@ -1,7 +1,7 @@ // @flow import { List } from 'immutable' import { createAction } from 'redux-actions' -import { type SafeProps } from '~/routes/safe/store/model/safe' +import { makeDailyLimit, type DailyLimit, type SafeProps } from '~/routes/safe/store/model/safe' import { makeOwner, type Owner } from '~/routes/safe/store/model/owner' export const ADD_SAFE = 'ADD_SAFE' @@ -12,14 +12,18 @@ export const buildOwnersFrom = (names: string[], addresses: string[]) => { return List(owners) } +export const buildDailyLimitFrom = (dailyLimit: number): DailyLimit => + makeDailyLimit({ value: dailyLimit, spentToday: 0 }) + const addSafe = createAction( ADD_SAFE, ( name: string, address: string, - confirmations: number, dailyLimit: number, + confirmations: number, limit: number, ownersName: string[], ownersAddress: string[], ): SafeProps => { const owners: List = buildOwnersFrom(ownersName, ownersAddress) + const dailyLimit: DailyLimit = buildDailyLimitFrom(limit) return ({ address, name, confirmations, owners, dailyLimit, diff --git a/src/routes/safe/store/model/safe.js b/src/routes/safe/store/model/safe.js index 28e25209..cc9149d8 100644 --- a/src/routes/safe/store/model/safe.js +++ b/src/routes/safe/store/model/safe.js @@ -3,6 +3,18 @@ import { List, Record } from 'immutable' import type { RecordFactory, RecordOf } from 'immutable' import type { Owner } from '~/routes/safe/store/model/owner' +export type DailyLimitProps = { + value: number, + spentToday: number, +} + +export const makeDailyLimit: RecordFactory = Record({ + value: 0, + spentToday: 0, +}) + +export type DailyLimit = RecordOf + export type SafeProps = { name: string, address: string, @@ -16,7 +28,7 @@ export const makeSafe: RecordFactory = Record({ address: '', confirmations: 0, owners: List([]), - dailyLimit: 0, + dailyLimit: makeDailyLimit(), }) export type Safe = RecordOf diff --git a/src/routes/safe/store/test/builder/safe.builder.js b/src/routes/safe/store/test/builder/safe.builder.js index 0bde9e05..5edc8e25 100644 --- a/src/routes/safe/store/test/builder/safe.builder.js +++ b/src/routes/safe/store/test/builder/safe.builder.js @@ -1,6 +1,6 @@ // @flow import { makeSafe, type Safe } from '~/routes/safe/store/model/safe' -import { buildOwnersFrom } from '~/routes/safe/store/actions' +import { buildOwnersFrom, buildDailyLimitFrom } from '~/routes/safe/store/actions' class SafeBuilder { safe: Safe @@ -25,7 +25,8 @@ class SafeBuilder { } withDailyLimit(limit: number) { - this.safe = this.safe.set('dailyLimit', limit) + const dailyLimit = buildDailyLimitFrom(limit) + this.safe = this.safe.set('dailyLimit', dailyLimit) return this } From 86fad267ca33f92eb6b5d8d7f635b736fe24029c Mon Sep 17 00:00:00 2001 From: apanizo Date: Fri, 11 May 2018 18:07:02 +0200 Subject: [PATCH 02/20] WA-238 get Daily Limit by token from the safe smart contract (useful for spentToday) --- src/routes/safe/component/Safe.test.js | 21 +++++++++++++++- .../safe/component/Withdrawn/withdrawn.js | 24 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/routes/safe/component/Safe.test.js b/src/routes/safe/component/Safe.test.js index 50049ea0..653bf16e 100644 --- a/src/routes/safe/component/Safe.test.js +++ b/src/routes/safe/component/Safe.test.js @@ -12,8 +12,9 @@ import SafeView from '~/routes/safe/component/Safe' import AppRoutes from '~/routes' import { WITHDRAWN_BUTTON_TEXT } from '~/routes/safe/component/Safe/DailyLimit' import WithdrawnComponent, { SEE_TXS_BUTTON_TEXT } from '~/routes/safe/component/Withdrawn' -import { getBalanceInEtherOf } from '~/wallets/getWeb3' +import { getBalanceInEtherOf, getProviderInfo } from '~/wallets/getWeb3' import { sleep } from '~/utils/timer' +import withdrawn, { DESTINATION_PARAM, VALUE_PARAM, getDailyLimitFrom } from '~/routes/safe/component/Withdrawn/withdrawn' describe('React DOM TESTS > Withdrawn funds from safe', () => { let SafeDom @@ -70,4 +71,22 @@ describe('React DOM TESTS > Withdrawn funds from safe', () => { const visitTxsButton = withdrawnButtons[0] expect(visitTxsButton.props.children).toEqual(SEE_TXS_BUTTON_TEXT) }) + + it('spentToday dailyLimitModule property is updated correctly', async () => { + const providerInfo = await getProviderInfo() + const userAddress = providerInfo.account + + const values = { + [DESTINATION_PARAM]: userAddress, + [VALUE_PARAM]: '0.01', + } + await withdrawn(values, address, userAddress) + await withdrawn(values, address, userAddress) + + const ethAddress = 0 + const dailyLimit: DailyLimitProps = await getDailyLimitFrom(address, ethAddress) + + expect(dailyLimit.value).toBe(0.5) + expect(dailyLimit.spentToday).toBe(0.02) + }) }) diff --git a/src/routes/safe/component/Withdrawn/withdrawn.js b/src/routes/safe/component/Withdrawn/withdrawn.js index e2001e3b..95c72ca4 100644 --- a/src/routes/safe/component/Withdrawn/withdrawn.js +++ b/src/routes/safe/component/Withdrawn/withdrawn.js @@ -1,11 +1,14 @@ // @flow import { getWeb3 } from '~/wallets/getWeb3' import { getGnosisSafeContract, getCreateDailyLimitExtensionContract } from '~/wallets/safeContracts' +import { type DailyLimitProps } from '~/routes/safe/store/model/safe' +export const LIMIT_POSITION = 0 +export const SPENT_TODAY_POS = 1 export const DESTINATION_PARAM = 'destination' export const VALUE_PARAM = 'ether' -const withdrawn = async (values: Object, safeAddress: string, userAccount: string): Promise => { +const getDailyLimitModuleFrom = async (safeAddress) => { const web3 = getWeb3() const gnosisSafe = getGnosisSafeContract(web3).at(safeAddress) @@ -17,6 +20,25 @@ const withdrawn = async (values: Object, safeAddress: string, userAccount: strin throw new Error('Using an extension of different safe') } + return dailyLimitModule +} + +export const getDailyLimitFrom = async (safeAddress, tokenAddress): DailyLimitProps => { + const web3 = getWeb3() + const dailyLimitModule = await getDailyLimitModuleFrom(safeAddress) + + const dailyLimitEth = await dailyLimitModule.dailyLimits(tokenAddress) + + const limit = web3.fromWei(dailyLimitEth[LIMIT_POSITION].valueOf(), 'ether').toString() + const spentToday = web3.fromWei(dailyLimitEth[SPENT_TODAY_POS].valueOf(), 'ether').toString() + + return { value: Number(limit), spentToday: Number(spentToday) } +} + +const withdrawn = async (values: Object, safeAddress: string, userAccount: string): Promise => { + const web3 = getWeb3() + const dailyLimitModule = await getDailyLimitModuleFrom(safeAddress) + const destination = values[DESTINATION_PARAM] const value = web3.toWei(values[VALUE_PARAM], 'ether') From 79ae47570f33efa426ee0694cbce6bdb952e0cc2 Mon Sep 17 00:00:00 2001 From: apanizo Date: Sun, 13 May 2018 13:25:02 +0200 Subject: [PATCH 03/20] WA-238 creation of updateDailyLimit action --- .../safe/store/actions/updateDailyLimit.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/routes/safe/store/actions/updateDailyLimit.js diff --git a/src/routes/safe/store/actions/updateDailyLimit.js b/src/routes/safe/store/actions/updateDailyLimit.js new file mode 100644 index 00000000..876f10e0 --- /dev/null +++ b/src/routes/safe/store/actions/updateDailyLimit.js @@ -0,0 +1,19 @@ +// @flow +import { createAction } from 'redux-actions' + +export const UPDATE_DAILY_LIMIT = 'UPDATE_DAILY_LIMIT' + +type SpentTodayProps = { + safeAddress: string, + dailyLimit: DailyLimitProps, +} + +const updateDailyLimit = createAction( + UPDATE_DAILY_LIMIT, + (safeAddress: string, dailyLimit: DailyLimitProps): SpentTodayProps => ({ + safeAddress, + dailyLimit, + }), +) + +export default updateDailyLimit From aa93a8301bb9c3a6b0778256306b758d7d82ba1d Mon Sep 17 00:00:00 2001 From: apanizo Date: Sun, 13 May 2018 13:30:20 +0200 Subject: [PATCH 04/20] WA-238 refactor update deployedSafe builder including execture withdrawn helper --- src/routes/safe/component/Safe.test.js | 20 +++++++----------- .../test/builder/deployedSafe.builder.js | 21 +++++++++++++++---- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/routes/safe/component/Safe.test.js b/src/routes/safe/component/Safe.test.js index 653bf16e..719b8db8 100644 --- a/src/routes/safe/component/Safe.test.js +++ b/src/routes/safe/component/Safe.test.js @@ -6,15 +6,15 @@ import { ConnectedRouter } from 'react-router-redux' import Button from '~/components/layout/Button' import { aNewStore, history } from '~/store' import { addEtherTo } from '~/test/addEtherTo' -import { aDeployedSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder' +import { aDeployedSafe, executeWithdrawnOn } from '~/routes/safe/store/test/builder/deployedSafe.builder' import { SAFELIST_ADDRESS } from '~/routes/routes' import SafeView from '~/routes/safe/component/Safe' import AppRoutes from '~/routes' import { WITHDRAWN_BUTTON_TEXT } from '~/routes/safe/component/Safe/DailyLimit' import WithdrawnComponent, { SEE_TXS_BUTTON_TEXT } from '~/routes/safe/component/Withdrawn' -import { getBalanceInEtherOf, getProviderInfo } from '~/wallets/getWeb3' +import { getBalanceInEtherOf } from '~/wallets/getWeb3' import { sleep } from '~/utils/timer' -import withdrawn, { DESTINATION_PARAM, VALUE_PARAM, getDailyLimitFrom } from '~/routes/safe/component/Withdrawn/withdrawn' +import { getDailyLimitFrom } from '~/routes/safe/component/Withdrawn/withdrawn' describe('React DOM TESTS > Withdrawn funds from safe', () => { let SafeDom @@ -73,19 +73,15 @@ describe('React DOM TESTS > Withdrawn funds from safe', () => { }) it('spentToday dailyLimitModule property is updated correctly', async () => { - const providerInfo = await getProviderInfo() - const userAddress = providerInfo.account - - const values = { - [DESTINATION_PARAM]: userAddress, - [VALUE_PARAM]: '0.01', - } - await withdrawn(values, address, userAddress) - await withdrawn(values, address, userAddress) + // GIVEN in beforeEach + // WHEN + await executeWithdrawnOn(address, 0.01) + await executeWithdrawnOn(address, 0.01) const ethAddress = 0 const dailyLimit: DailyLimitProps = await getDailyLimitFrom(address, ethAddress) + // THEN expect(dailyLimit.value).toBe(0.5) expect(dailyLimit.spentToday).toBe(0.02) }) diff --git a/src/routes/safe/store/test/builder/deployedSafe.builder.js b/src/routes/safe/store/test/builder/deployedSafe.builder.js index ee976ca0..c4f6ad73 100644 --- a/src/routes/safe/store/test/builder/deployedSafe.builder.js +++ b/src/routes/safe/store/test/builder/deployedSafe.builder.js @@ -11,6 +11,7 @@ import { sleep } from '~/utils/timer' import { getProviderInfo } 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' export const renderSafe = async (localStore: Store) => { const provider = await getProviderInfo() @@ -28,7 +29,7 @@ export const renderSafe = async (localStore: Store) => { ) } -const deploySafe = async (safe: React$Component<{}>) => { +const deploySafe = async (safe: React$Component<{}>, dailyLimit: string) => { const inputs = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') const fieldName = inputs[0] const fieldOwners = inputs[1] @@ -42,7 +43,7 @@ const deploySafe = async (safe: React$Component<{}>) => { 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(fieldDailyLimit, { target: { value: '0.5' } }) + TestUtils.Simulate.change(fieldDailyLimit, { target: { value: dailyLimit } }) const form = TestUtils.findRenderedDOMComponentWithTag(safe, 'form') @@ -66,9 +67,21 @@ const deploySafe = async (safe: React$Component<{}>) => { return transactionHash } -export const aDeployedSafe = async (specificStore: Store) => { +export const aDeployedSafe = async (specificStore: Store, dailyLimit?: number = 0.5) => { const safe: React$Component<{}> = await renderSafe(specificStore) - const deployedSafe = await deploySafe(safe) + const deployedSafe = await deploySafe(safe, `${dailyLimit}`) return deployedSafe.logs[1].args.proxy } + +export const executeWithdrawnOn = async (safeAddress: string, value: number) => { + const providerInfo = await getProviderInfo() + const userAddress = providerInfo.account + + const values = { + [DESTINATION_PARAM]: userAddress, + [VALUE_PARAM]: `${value}`, + } + + await withdrawn(values, safeAddress, userAddress) +} From 3cba259fc1d48e7dbca591e9a6eea0a9a1073bf1 Mon Sep 17 00:00:00 2001 From: apanizo Date: Sun, 13 May 2018 13:31:33 +0200 Subject: [PATCH 05/20] WA-238 DailyLimit Record types --- src/routes/safe/store/actions/addSafe.js | 3 ++- src/routes/safe/store/model/dailyLimit.js | 15 +++++++++++++++ src/routes/safe/store/model/safe.js | 15 ++------------- 3 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 src/routes/safe/store/model/dailyLimit.js diff --git a/src/routes/safe/store/actions/addSafe.js b/src/routes/safe/store/actions/addSafe.js index d5a3836c..ab7cf916 100644 --- a/src/routes/safe/store/actions/addSafe.js +++ b/src/routes/safe/store/actions/addSafe.js @@ -1,7 +1,8 @@ // @flow import { List } from 'immutable' import { createAction } from 'redux-actions' -import { makeDailyLimit, type DailyLimit, type SafeProps } from '~/routes/safe/store/model/safe' +import { makeDailyLimit, type DailyLimit } from '~/routes/safe/store/model/dailyLimit' +import { type SafeProps } from '~/routes/safe/store/model/safe' import { makeOwner, type Owner } from '~/routes/safe/store/model/owner' export const ADD_SAFE = 'ADD_SAFE' diff --git a/src/routes/safe/store/model/dailyLimit.js b/src/routes/safe/store/model/dailyLimit.js new file mode 100644 index 00000000..2345c243 --- /dev/null +++ b/src/routes/safe/store/model/dailyLimit.js @@ -0,0 +1,15 @@ +// @flow +import { Record } from 'immutable' +import type { RecordFactory, RecordOf } from 'immutable' + +export type DailyLimitProps = { + value: number, + spentToday: number, +} + +export const makeDailyLimit: RecordFactory = Record({ + value: 0, + spentToday: 0, +}) + +export type DailyLimit = RecordOf diff --git a/src/routes/safe/store/model/safe.js b/src/routes/safe/store/model/safe.js index cc9149d8..22e10581 100644 --- a/src/routes/safe/store/model/safe.js +++ b/src/routes/safe/store/model/safe.js @@ -1,26 +1,15 @@ // @flow import { List, Record } from 'immutable' import type { RecordFactory, RecordOf } from 'immutable' +import { type DailyLimit, makeDailyLimit } from '~/routes/safe/store/model/dailyLimit' import type { Owner } from '~/routes/safe/store/model/owner' -export type DailyLimitProps = { - value: number, - spentToday: number, -} - -export const makeDailyLimit: RecordFactory = Record({ - value: 0, - spentToday: 0, -}) - -export type DailyLimit = RecordOf - export type SafeProps = { name: string, address: string, confirmations: number, owners: List, - dailyLimit: number, + dailyLimit: DailyLimit, } export const makeSafe: RecordFactory = Record({ From 4a30051858a5c278ada97a6cadfc0c8cb5fabd13 Mon Sep 17 00:00:00 2001 From: apanizo Date: Sun, 13 May 2018 13:33:40 +0200 Subject: [PATCH 06/20] WA-238 fetchDailyLimit action definition and its reducer --- src/routes/safe/store/actions/fetchDailyLimit.js | 12 ++++++++++++ src/routes/safe/store/reducer/safe.js | 7 +++++++ 2 files changed, 19 insertions(+) create mode 100644 src/routes/safe/store/actions/fetchDailyLimit.js diff --git a/src/routes/safe/store/actions/fetchDailyLimit.js b/src/routes/safe/store/actions/fetchDailyLimit.js new file mode 100644 index 00000000..5e3c8936 --- /dev/null +++ b/src/routes/safe/store/actions/fetchDailyLimit.js @@ -0,0 +1,12 @@ +// @flow +import type { Dispatch as ReduxDispatch } from 'redux' +import { type GlobalState } from '~/store/index' +import { getDailyLimitFrom } from '~/routes/safe/component/Withdrawn/withdrawn' +import updateDailyLimit from './updateDailyLimit' + +export default (safeAddress: string) => async (dispatch: ReduxDispatch) => { + const ethAddress = 0 + const dailyLimit: DailyLimitProps = await getDailyLimitFrom(safeAddress, ethAddress) + + return dispatch(updateDailyLimit(safeAddress, dailyLimit)) +} diff --git a/src/routes/safe/store/reducer/safe.js b/src/routes/safe/store/reducer/safe.js index a6a4bd31..f35c2ff7 100644 --- a/src/routes/safe/store/reducer/safe.js +++ b/src/routes/safe/store/reducer/safe.js @@ -2,9 +2,11 @@ import { Map, List } from 'immutable' import { handleActions, type ActionType } from 'redux-actions' import addSafe, { ADD_SAFE } from '~/routes/safe/store/actions/addSafe' +import updateDailyLimit, { UPDATE_DAILY_LIMIT } from '~/routes/safe/store/actions/updateDailyLimit' import { makeOwner } from '~/routes/safe/store/model/owner' import { type Safe, makeSafe } from '~/routes/safe/store/model/safe' import { loadSafes, saveSafes } from '~/utils/localStorage' +import { makeDailyLimit } from '~/routes/safe/store/model/dailyLimit' export const SAFE_REDUCER_ID = 'safes' @@ -45,4 +47,9 @@ export default handleActions({ saveSafes(safes.toJSON()) return safes }, + [UPDATE_DAILY_LIMIT]: (state: State, action: ActionType): State => { + const safes = state.updateIn([action.safeAddress, 'dailyLimit'], () => makeDailyLimit(action.dailyLimit)) + saveSafes(safes.toJSON()) + return safes + }, }, Map()) From cefa6d5567dd6e984264012b68d27c53443053fb Mon Sep 17 00:00:00 2001 From: apanizo Date: Mon, 14 May 2018 09:10:28 +0200 Subject: [PATCH 07/20] WA-238 Do not update safes in localStorage when updating DailyLimit --- src/routes/safe/store/reducer/safe.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/routes/safe/store/reducer/safe.js b/src/routes/safe/store/reducer/safe.js index f35c2ff7..f3cd7df0 100644 --- a/src/routes/safe/store/reducer/safe.js +++ b/src/routes/safe/store/reducer/safe.js @@ -47,9 +47,6 @@ export default handleActions({ saveSafes(safes.toJSON()) return safes }, - [UPDATE_DAILY_LIMIT]: (state: State, action: ActionType): State => { - const safes = state.updateIn([action.safeAddress, 'dailyLimit'], () => makeDailyLimit(action.dailyLimit)) - saveSafes(safes.toJSON()) - return safes - }, + [UPDATE_DAILY_LIMIT]: (state: State, action: ActionType): State => + state.updateIn([action.safeAddress, 'dailyLimit'], () => makeDailyLimit(action.dailyLimit)), }, Map()) From 8b5aad2bd547d154578075ffd3778cd202b1758b Mon Sep 17 00:00:00 2001 From: apanizo Date: Mon, 14 May 2018 09:48:41 +0200 Subject: [PATCH 08/20] WA-238 Updating deployment of sc --- safe-contracts/build/contracts/CreateAndAddModule.json | 8 +++++++- safe-contracts/build/contracts/DailyLimitModule.json | 8 +++++++- .../build/contracts/DailyLimitModuleWithSignature.json | 8 +++++++- .../build/contracts/GnosisSafePersonalEdition.json | 8 +++++++- .../build/contracts/GnosisSafeStateChannelEdition.json | 8 +++++++- safe-contracts/build/contracts/GnosisSafeTeamEdition.json | 8 +++++++- safe-contracts/build/contracts/Migrations.json | 8 +++++++- safe-contracts/build/contracts/MultiSend.json | 8 +++++++- safe-contracts/build/contracts/ProxyFactory.json | 8 +++++++- safe-contracts/build/contracts/SocialRecoveryModule.json | 8 +++++++- safe-contracts/build/contracts/WhitelistModule.json | 8 +++++++- 11 files changed, 77 insertions(+), 11 deletions(-) diff --git a/safe-contracts/build/contracts/CreateAndAddModule.json b/safe-contracts/build/contracts/CreateAndAddModule.json index a8d26405..f6c838d6 100644 --- a/safe-contracts/build/contracts/CreateAndAddModule.json +++ b/safe-contracts/build/contracts/CreateAndAddModule.json @@ -1208,8 +1208,14 @@ "links": {}, "address": "0x544c20ddcab0459a99c93823d0c02d50f75ced36", "transactionHash": "0x0e6adf453722b13530f4f8c8f947f6e5105156aa99a10b588447ed57e27d7b85" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0xdeabe313841db5cddcc1b5f01c6497ece16c2347", + "transactionHash": "0x0e6adf453722b13530f4f8c8f947f6e5105156aa99a10b588447ed57e27d7b85" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.706Z" + "updatedAt": "2018-05-14T07:39:37.967Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/DailyLimitModule.json b/safe-contracts/build/contracts/DailyLimitModule.json index 4e514417..99c698db 100644 --- a/safe-contracts/build/contracts/DailyLimitModule.json +++ b/safe-contracts/build/contracts/DailyLimitModule.json @@ -7909,8 +7909,14 @@ "links": {}, "address": "0xe52c225329d3fb9f6933bd52e7067a24d20f7983", "transactionHash": "0xaffd9cdbf1bd14f5f349af2782a1b4dbebd9ac97abedbcfb9aee5fb1707afe96" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0x452dd8d6f81786c3ad3ec3cbcf024687659c682a", + "transactionHash": "0xaffd9cdbf1bd14f5f349af2782a1b4dbebd9ac97abedbcfb9aee5fb1707afe96" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.692Z" + "updatedAt": "2018-05-14T07:39:37.963Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/DailyLimitModuleWithSignature.json b/safe-contracts/build/contracts/DailyLimitModuleWithSignature.json index 0c844f47..4ce1e783 100644 --- a/safe-contracts/build/contracts/DailyLimitModuleWithSignature.json +++ b/safe-contracts/build/contracts/DailyLimitModuleWithSignature.json @@ -2542,8 +2542,14 @@ "links": {}, "address": "0x788256524db64c2b23ff2e417a833927550a2d65", "transactionHash": "0x13942c7ebe4c7c49493ac8d9d8ee3c329a0be8b7a78717117e0c5d43cbf8632c" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0x3e2ade0d97956160691a96fb2adf83844155708d", + "transactionHash": "0x13942c7ebe4c7c49493ac8d9d8ee3c329a0be8b7a78717117e0c5d43cbf8632c" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.705Z" + "updatedAt": "2018-05-14T07:39:37.966Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/GnosisSafePersonalEdition.json b/safe-contracts/build/contracts/GnosisSafePersonalEdition.json index 96168a1b..c404e0ca 100644 --- a/safe-contracts/build/contracts/GnosisSafePersonalEdition.json +++ b/safe-contracts/build/contracts/GnosisSafePersonalEdition.json @@ -8185,8 +8185,14 @@ "links": {}, "address": "0x1f8829f66b8ac7a6893109dd298007f5dd3bcadf", "transactionHash": "0x9a582bc25c7705ede926f13bef0ba8fa76176d0ec80dc0871e1b28d87382f545" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0xad5d0371132b0959508b44c6221e6bc4de8df987", + "transactionHash": "0x9a582bc25c7705ede926f13bef0ba8fa76176d0ec80dc0871e1b28d87382f545" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.685Z" + "updatedAt": "2018-05-14T07:39:37.952Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/GnosisSafeStateChannelEdition.json b/safe-contracts/build/contracts/GnosisSafeStateChannelEdition.json index c15e4a87..52a0b462 100644 --- a/safe-contracts/build/contracts/GnosisSafeStateChannelEdition.json +++ b/safe-contracts/build/contracts/GnosisSafeStateChannelEdition.json @@ -5456,8 +5456,14 @@ "links": {}, "address": "0x9327970e8e29e8dba16b28acb177906a92447a0c", "transactionHash": "0x8b91e38b0bbafe43309aca9832bcd234b82d7ac9f81abe94891cd406a735549c" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0xf2eb76d41d54cc58f9a8e86e2b42d41ccd22a8fa", + "transactionHash": "0x8b91e38b0bbafe43309aca9832bcd234b82d7ac9f81abe94891cd406a735549c" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.688Z" + "updatedAt": "2018-05-14T07:39:37.959Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/GnosisSafeTeamEdition.json b/safe-contracts/build/contracts/GnosisSafeTeamEdition.json index 9266fee5..30774201 100644 --- a/safe-contracts/build/contracts/GnosisSafeTeamEdition.json +++ b/safe-contracts/build/contracts/GnosisSafeTeamEdition.json @@ -6495,8 +6495,14 @@ "links": {}, "address": "0xd16506c40cb044bf78552d9bea796c9d98fa0a45", "transactionHash": "0x782ef1b28e30afdcb711e7119c7bc794bbd7f42356ea5a68072b8f59400b05e3" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0xaadc387a6c96744064754aa1f2391cbe1bb55970", + "transactionHash": "0x782ef1b28e30afdcb711e7119c7bc794bbd7f42356ea5a68072b8f59400b05e3" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.681Z" + "updatedAt": "2018-05-14T07:39:37.956Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/Migrations.json b/safe-contracts/build/contracts/Migrations.json index 499d53d4..a8c86fc0 100644 --- a/safe-contracts/build/contracts/Migrations.json +++ b/safe-contracts/build/contracts/Migrations.json @@ -1392,8 +1392,14 @@ "links": {}, "address": "0xb97a46b50b9e38d540e9701d3d4afe71a65c09df", "transactionHash": "0x137111f15934455430bea53bd8a6721561285af6a431f174f090257877635ab6" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0x3b293b12ee278dba3d4350cd5b4434c228bad69b", + "transactionHash": "0x137111f15934455430bea53bd8a6721561285af6a431f174f090257877635ab6" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.707Z" + "updatedAt": "2018-05-14T07:39:37.978Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/MultiSend.json b/safe-contracts/build/contracts/MultiSend.json index 73a181b0..47cffc8a 100644 --- a/safe-contracts/build/contracts/MultiSend.json +++ b/safe-contracts/build/contracts/MultiSend.json @@ -348,8 +348,14 @@ "links": {}, "address": "0xa4604b882b2c10ce381c4e61ad9ac72ab32f350f", "transactionHash": "0xf4586ae05ae02801de1759128e43658bb0439e622a5ba84ad6bb4b652d641f4f" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0xf5cfa4069271285402ba2585c521c6c627810963", + "transactionHash": "0xf4586ae05ae02801de1759128e43658bb0439e622a5ba84ad6bb4b652d641f4f" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.706Z" + "updatedAt": "2018-05-14T07:39:37.970Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/ProxyFactory.json b/safe-contracts/build/contracts/ProxyFactory.json index 2145226d..8283a2fd 100644 --- a/safe-contracts/build/contracts/ProxyFactory.json +++ b/safe-contracts/build/contracts/ProxyFactory.json @@ -999,8 +999,14 @@ "links": {}, "address": "0x7686eac12d94e3c0bdfe0a00ec759f89fbd115f7", "transactionHash": "0x5b47c779cfd719a97f218a56d99b64b2c5b382549f3375822d5afed010cdb9c5" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0x321151783f8dfb4699370d1bd5cee4e82bc3b52a", + "transactionHash": "0x5b47c779cfd719a97f218a56d99b64b2c5b382549f3375822d5afed010cdb9c5" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.678Z" + "updatedAt": "2018-05-14T07:39:37.950Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/SocialRecoveryModule.json b/safe-contracts/build/contracts/SocialRecoveryModule.json index 84be143d..01e86479 100644 --- a/safe-contracts/build/contracts/SocialRecoveryModule.json +++ b/safe-contracts/build/contracts/SocialRecoveryModule.json @@ -6958,8 +6958,14 @@ "links": {}, "address": "0xfe2114e016fa8d92959754f25d4f63f155ad1a6a", "transactionHash": "0xa514f0c5c6fcab99a16bba503b6ed893935cedfafe2e5c8c825dfc117e1e266d" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0x12fb2fe4f6d4c08b14c694e163b9bee65697e708", + "transactionHash": "0xa514f0c5c6fcab99a16bba503b6ed893935cedfafe2e5c8c825dfc117e1e266d" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.698Z" + "updatedAt": "2018-05-14T07:39:37.975Z" } \ No newline at end of file diff --git a/safe-contracts/build/contracts/WhitelistModule.json b/safe-contracts/build/contracts/WhitelistModule.json index ca9c77e3..749a1ca1 100644 --- a/safe-contracts/build/contracts/WhitelistModule.json +++ b/safe-contracts/build/contracts/WhitelistModule.json @@ -3956,8 +3956,14 @@ "links": {}, "address": "0x15fd83fcf27f1726e692389be1b6e03fe7d56bb6", "transactionHash": "0xc7f84311daf6a72740fe5822cd6007cec3ce1ff6aeaf454559f3e5f36c81cfd8" + }, + "1526283540628": { + "events": {}, + "links": {}, + "address": "0x536f677993e3eada3e17f2f42888ee777441fc3e", + "transactionHash": "0xc7f84311daf6a72740fe5822cd6007cec3ce1ff6aeaf454559f3e5f36c81cfd8" } }, "schemaVersion": "2.0.0", - "updatedAt": "2018-05-10T11:07:04.695Z" + "updatedAt": "2018-05-14T07:39:37.968Z" } \ No newline at end of file From 725a9f6b139efff86eb45a245ac43e2694378725 Mon Sep 17 00:00:00 2001 From: apanizo Date: Mon, 14 May 2018 09:50:11 +0200 Subject: [PATCH 09/20] WA-238 Fix error on reducer not using action's payload property --- src/routes/safe/store/reducer/safe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/safe/store/reducer/safe.js b/src/routes/safe/store/reducer/safe.js index f3cd7df0..1b858460 100644 --- a/src/routes/safe/store/reducer/safe.js +++ b/src/routes/safe/store/reducer/safe.js @@ -48,5 +48,5 @@ export default handleActions({ return safes }, [UPDATE_DAILY_LIMIT]: (state: State, action: ActionType): State => - state.updateIn([action.safeAddress, 'dailyLimit'], () => makeDailyLimit(action.dailyLimit)), + state.updateIn([action.payload.safeAddress, 'dailyLimit'], () => makeDailyLimit(action.payload.dailyLimit)), }, Map()) From 4051852edaad0b49a8d0a4ee1d05e805cee7a4ea Mon Sep 17 00:00:00 2001 From: apanizo Date: Mon, 14 May 2018 10:42:08 +0200 Subject: [PATCH 10/20] WA-238 Returning promise when executing dailyLimit tx --- src/routes/safe/component/Withdrawn/withdrawn.js | 2 +- src/routes/safe/store/test/builder/deployedSafe.builder.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/safe/component/Withdrawn/withdrawn.js b/src/routes/safe/component/Withdrawn/withdrawn.js index 95c72ca4..1c06658e 100644 --- a/src/routes/safe/component/Withdrawn/withdrawn.js +++ b/src/routes/safe/component/Withdrawn/withdrawn.js @@ -42,7 +42,7 @@ const withdrawn = async (values: Object, safeAddress: string, userAccount: strin const destination = values[DESTINATION_PARAM] const value = web3.toWei(values[VALUE_PARAM], 'ether') - await dailyLimitModule.executeDailyLimit( + return dailyLimitModule.executeDailyLimit( destination, value, 0, diff --git a/src/routes/safe/store/test/builder/deployedSafe.builder.js b/src/routes/safe/store/test/builder/deployedSafe.builder.js index c4f6ad73..bd810818 100644 --- a/src/routes/safe/store/test/builder/deployedSafe.builder.js +++ b/src/routes/safe/store/test/builder/deployedSafe.builder.js @@ -83,5 +83,5 @@ export const executeWithdrawnOn = async (safeAddress: string, value: number) => [VALUE_PARAM]: `${value}`, } - await withdrawn(values, safeAddress, userAddress) + return withdrawn(values, safeAddress, userAddress) } From 26815af91cb209b9e93883c575d3f8e6ee57f350 Mon Sep 17 00:00:00 2001 From: apanizo Date: Mon, 14 May 2018 10:42:42 +0200 Subject: [PATCH 11/20] WA-238 Adding test handling exception when exceeding dailyLimit --- .../component/Withdrawn/withdrawn.test.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/routes/safe/component/Withdrawn/withdrawn.test.js diff --git a/src/routes/safe/component/Withdrawn/withdrawn.test.js b/src/routes/safe/component/Withdrawn/withdrawn.test.js new file mode 100644 index 00000000..b5fafd1a --- /dev/null +++ b/src/routes/safe/component/Withdrawn/withdrawn.test.js @@ -0,0 +1,29 @@ +// @flow +import { aNewStore } from '~/store' +import { addEtherTo } from '~/test/addEtherTo' +import { aDeployedSafe, executeWithdrawnOn } from '~/routes/safe/store/test/builder/deployedSafe.builder' + +describe('Safe Blockchain Test', () => { + let store + beforeEach(async () => { + store = aNewStore() + }) + + it('wihdrawn should return revert error if exceeded dailyLimit', async () => { + // GIVEN + const dailyLimitValue = 0.30 + const safeAddress = await aDeployedSafe(store, dailyLimitValue) + await addEtherTo(safeAddress, '0.7') + const value = 0.15 + + // WHEN + await executeWithdrawnOn(safeAddress, value) + await executeWithdrawnOn(safeAddress, value) + + // THEN + expect(executeWithdrawnOn(safeAddress, value)).rejects.toThrow('VM Exception while processing transaction: revert') + }) +}) +// } + +// export default updateDailyLimitReducerTests From 706bdba69b930c61c5e13041dfb93e20895ef159 Mon Sep 17 00:00:00 2001 From: apanizo Date: Mon, 14 May 2018 10:43:23 +0200 Subject: [PATCH 12/20] WA-238 Adding test updating safe's daily limit redux record after executing withdrawn --- .../safe/store/test/dailyLimit.reducer.js | 51 +++++++++++++++++++ src/routes/safe/store/test/safe.spec.js | 2 + 2 files changed, 53 insertions(+) create mode 100644 src/routes/safe/store/test/dailyLimit.reducer.js diff --git a/src/routes/safe/store/test/dailyLimit.reducer.js b/src/routes/safe/store/test/dailyLimit.reducer.js new file mode 100644 index 00000000..9ee1eb5a --- /dev/null +++ b/src/routes/safe/store/test/dailyLimit.reducer.js @@ -0,0 +1,51 @@ +// @flow +import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe' +import fetchDailyLimit from '~/routes/safe/store/actions/fetchDailyLimit' +import { aNewStore } from '~/store' +import { addEtherTo } from '~/test/addEtherTo' +import { aDeployedSafe, executeWithdrawnOn } from './builder/deployedSafe.builder' + +const updateDailyLimitReducerTests = () => { + describe('Safe Actions[updateDailyLimit]', () => { + let store + beforeEach(async () => { + store = aNewStore() + }) + + it('reducer should return 0 as spentToday value from just deployed safe', async () => { + // GIVEN + const dailyLimitValue = 0.5 + const safeAddress = await aDeployedSafe(store, 0.5) + // WHEN + await store.dispatch(fetchDailyLimit(safeAddress)) + + // THEN + const safes = store.getState()[SAFE_REDUCER_ID] + const dailyLimit = safes.get(safeAddress).get('dailyLimit') + expect(dailyLimit).not.toBe(undefined) + expect(dailyLimit.value).toBe(dailyLimitValue) + expect(dailyLimit.spentToday).toBe(0) + }) + + it('reducer should return 0.1456 ETH as spentToday if the user has withdrawn 0.1456 from MAX of 0.3 ETH', async () => { + // GIVEN + const dailyLimitValue = 0.3 + const safeAddress = await aDeployedSafe(store, dailyLimitValue) + await addEtherTo(safeAddress, '0.5') + const value = 0.1456 + + // WHEN + await executeWithdrawnOn(safeAddress, value) + await store.dispatch(fetchDailyLimit(safeAddress)) + + // THEN + const safes = store.getState()[SAFE_REDUCER_ID] + const dailyLimit = safes.get(safeAddress).get('dailyLimit') + expect(dailyLimit).not.toBe(undefined) + expect(dailyLimit.value).toBe(dailyLimitValue) + expect(dailyLimit.spentToday).toBe(value) + }) + }) +} + +export default updateDailyLimitReducerTests diff --git a/src/routes/safe/store/test/safe.spec.js b/src/routes/safe/store/test/safe.spec.js index ea07c3ee..57521e6f 100644 --- a/src/routes/safe/store/test/safe.spec.js +++ b/src/routes/safe/store/test/safe.spec.js @@ -1,6 +1,7 @@ // @flow import balanceReducerTests from './balance.reducer' import safeReducerTests from './safe.reducer' +import dailyLimitReducerTests from './dailyLimit.reducer' import balanceSelectorTests from './balance.selector' import safeSelectorTests from './safe.selector' @@ -8,6 +9,7 @@ describe('Safe Test suite', () => { // ACTIONS AND REDUCERS safeReducerTests() balanceReducerTests() + dailyLimitReducerTests() // SAFE SELECTOR safeSelectorTests() From f04eec5f779ad7bea77a9fad46437bf34f515c3d Mon Sep 17 00:00:00 2001 From: apanizo Date: Mon, 14 May 2018 11:14:09 +0200 Subject: [PATCH 13/20] WA-238 Storybook showing spentToday and disabling withdrawn buttons is limit has been exceeded --- src/routes/safe/component/Layout.stories.js | 16 ++++++++++++++-- src/routes/safe/component/Safe/DailyLimit.jsx | 12 +++++++----- src/routes/safe/store/actions/addSafe.js | 4 ++-- .../safe/store/test/builder/safe.builder.js | 16 ++++++++++++++-- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/routes/safe/component/Layout.stories.js b/src/routes/safe/component/Layout.stories.js index 83cfecea..30ecd637 100644 --- a/src/routes/safe/component/Layout.stories.js +++ b/src/routes/safe/component/Layout.stories.js @@ -30,8 +30,20 @@ storiesOf('Routes /safe:address', module) fetchBalance={() => {}} /> )) - .add('Safe with 2 owners', () => { - const safe = SafeFactory.twoOwnersSafe + .add('Safe with 2 owners and 10ETH as dailyLimit', () => { + const safe = SafeFactory.dailyLimitSafe(10, 1.345) + + return ( + {}} + /> + ) + }) + .add('Safe with dailyLimit reached', () => { + const safe = SafeFactory.dailyLimitSafe(10, 10) return ( void, } export const WITHDRAWN_BUTTON_TEXT = 'Withdrawn' -const DailyLimit = ({ dailyLimit, onWithdrawn }: Props) => { +const DailyLimitComponent = ({ dailyLimit, onWithdrawn }: Props) => { const limit = dailyLimit.get('value') - const disabled = dailyLimit.get('todaySpent') > limit + const spentToday = dailyLimit.get('spentToday') + const disabled = spentToday >= limit + const text = `${limit} ETH (spent today: ${spentToday} ETH)` return ( - +