* Adds update safe modal * Implements update safe transaction * Moves the safeMasterCopyAddress and defaultFallbackHandlerAddress to a constant * Adds mainnet network Refactor logic * Removes the update button to non-owners * Updates safe-contracts package Removes hardcoded addresses * Refactor upgradeSafeToLastVersion Refactor getSafeVersion Refactor getEncodedMultiSendCallData Removes unused constants Adds tests for getEncodedMultiSendCallData and checkIfSafeNeedUpdate * Adds retro-compatibility with v1.0.0 * Improves test description * Fixs comments * Fix typo Removes spread * Exports proxy code to historicProxyCode.js * Updates yarn.lock
This commit is contained in:
parent
d51303f348
commit
2698dcb6e8
|
@ -33,8 +33,8 @@
|
|||
"precommit"
|
||||
],
|
||||
"dependencies": {
|
||||
"@gnosis.pm/safe-contracts": "1.0.0",
|
||||
"@gnosis.pm/util-contracts": "2.0.4",
|
||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.1",
|
||||
"@gnosis.pm/util-contracts": "2.0.6",
|
||||
"@material-ui/core": "4.8.0",
|
||||
"@material-ui/icons": "4.5.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.39",
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// @flow
|
||||
|
||||
// Code of the safe v1.0.0
|
||||
const proxyCodeV10 =
|
||||
'0x608060405273ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415603d573d6000fd5b3d6000f3fe'
|
||||
// Old PayingProxyCode
|
||||
const oldProxyCode =
|
||||
'0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
|
||||
|
||||
export const isProxyCode = codeToCheck =>
|
||||
codeToCheck === oldProxyCode || codeToCheck === proxyCodeV10
|
|
@ -1,15 +1,21 @@
|
|||
// @flow
|
||||
import contract from 'truffle-contract'
|
||||
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFactory.json'
|
||||
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxyFactory.json'
|
||||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
|
||||
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxy.json'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { simpleMemoize } from '~/components/forms/validator'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||
import { isProxyCode } from '~/logic/contracts/historicProxyCode'
|
||||
|
||||
export const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001'
|
||||
export const MULTI_SEND_ADDRESS = '0xB522a9f781924eD250A11C54105E51840B138AdD'
|
||||
export const SAFE_MASTER_COPY_ADDRESS = '0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F'
|
||||
export const DEFAULT_FALLBACK_HANDLER_ADDRESS = '0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44'
|
||||
export const SAFE_MASTER_COPY_ADDRESS_V10 = '0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A'
|
||||
|
||||
|
||||
let proxyFactoryMaster
|
||||
let safeMaster
|
||||
|
@ -66,7 +72,7 @@ export const getSafeMasterContract = async () => {
|
|||
|
||||
export const deploySafeContract = async (safeAccounts: string[], numConfirmations: number, userAccount: string) => {
|
||||
const gnosisSafeData = await safeMaster.contract.methods
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.encodeABI()
|
||||
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
||||
.createProxy(safeMaster.address, gnosisSafeData)
|
||||
|
@ -88,7 +94,7 @@ export const estimateGasForDeployingSafe = async (
|
|||
userAccount: string,
|
||||
) => {
|
||||
const gnosisSafeData = await safeMaster.contract.methods
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.encodeABI()
|
||||
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
||||
.createProxy(safeMaster.address, gnosisSafeData)
|
||||
|
@ -126,7 +132,49 @@ export const validateProxy = async (safeAddress: string): Promise<boolean> => {
|
|||
return true
|
||||
}
|
||||
}
|
||||
// Old PayingProxyCode
|
||||
const oldProxyCode = '0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
|
||||
return codeWithoutMetadata === oldProxyCode
|
||||
|
||||
|
||||
return isProxyCode(codeWithoutMetadata)
|
||||
}
|
||||
|
||||
export type MultiSendTransactionInstanceType = {
|
||||
operation: number,
|
||||
to: string,
|
||||
value: number,
|
||||
data: string,
|
||||
}
|
||||
|
||||
export const getEncodedMultiSendCallData = (txs: Array<MultiSendTransactionInstanceType>, web3: Object) => {
|
||||
const multiSendAbi = [
|
||||
{
|
||||
type: 'function',
|
||||
name: 'multiSend',
|
||||
constant: false,
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [{ type: 'bytes', name: 'transactions' }],
|
||||
outputs: [],
|
||||
},
|
||||
]
|
||||
const multiSend = new web3.eth.Contract(multiSendAbi, MULTI_SEND_ADDRESS)
|
||||
const encodeMultiSendCallData = multiSend.methods
|
||||
.multiSend(
|
||||
`0x${txs
|
||||
.map((tx) => [
|
||||
web3.eth.abi.encodeParameter('uint8', 0).slice(-2),
|
||||
web3.eth.abi.encodeParameter('address', tx.to).slice(-40),
|
||||
web3.eth.abi.encodeParameter('uint256', tx.value).slice(-64),
|
||||
web3.eth.abi
|
||||
.encodeParameter(
|
||||
'uint256',
|
||||
web3.utils.hexToBytes(tx.data).length,
|
||||
)
|
||||
.slice(-64),
|
||||
tx.data.replace(/^0x/, ''),
|
||||
].join(''))
|
||||
.join('')}`,
|
||||
)
|
||||
.encodeABI()
|
||||
|
||||
return encodeMultiSendCallData
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { type Operation } from '~/logic/safe/transactions'
|
|||
|
||||
export const CALL = 0
|
||||
export const DELEGATE_CALL = 1
|
||||
|
||||
export const TX_TYPE_EXECUTION = 'execution'
|
||||
export const TX_TYPE_CONFIRMATION = 'confirmation'
|
||||
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
// @flow
|
||||
import semverValid from 'semver/functions/valid'
|
||||
import semverLessThan from 'semver/functions/lt'
|
||||
import { getSafeMasterContract } from '~/logic/contracts/safeContracts'
|
||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||
import { getSafeLastVersion } from '~/config'
|
||||
|
||||
export const getSafeVersion = async () => {
|
||||
let current
|
||||
let latest
|
||||
try {
|
||||
const safeMaster = await getSafeMasterContract()
|
||||
const safeMasterVersion = await safeMaster.VERSION()
|
||||
current = semverValid(safeMasterVersion)
|
||||
latest = semverValid(getSafeLastVersion())
|
||||
const needUpdate = latest ? semverLessThan(current, latest) : false
|
||||
export const checkIfSafeNeedsUpdate = async (gnosisSafeInstance, lastSafeVersion) => {
|
||||
if (!gnosisSafeInstance || !lastSafeVersion) {
|
||||
return null
|
||||
}
|
||||
const safeMasterVersion = await gnosisSafeInstance.VERSION()
|
||||
const current = semverValid(safeMasterVersion)
|
||||
const latest = semverValid(lastSafeVersion)
|
||||
const needUpdate = latest ? semverLessThan(current, latest) : false
|
||||
return { current, latest, needUpdate }
|
||||
}
|
||||
|
||||
return { current, latest, needUpdate }
|
||||
export const getSafeVersion = async (safeAddress: string) => {
|
||||
try {
|
||||
const safeMaster = await getGnosisSafeInstanceAt(safeAddress)
|
||||
return checkIfSafeNeedsUpdate(safeMaster, getSafeLastVersion())
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
throw err
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// @flow
|
||||
import {
|
||||
DEFAULT_FALLBACK_HANDLER_ADDRESS,
|
||||
getEncodedMultiSendCallData,
|
||||
getGnosisSafeInstanceAt,
|
||||
MULTI_SEND_ADDRESS, SAFE_MASTER_COPY_ADDRESS,
|
||||
} from '~/logic/contracts/safeContracts'
|
||||
import type { MultiSendTransactionInstanceType } from '~/logic/contracts/safeContracts'
|
||||
import { DELEGATE_CALL } from '~/logic/safe/transactions'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
|
||||
export const upgradeSafeToLastVersion = async (safeAddress: string, createTransaction: Function) => {
|
||||
const sendTransactions = async (txs: Array<MultiSendTransactionInstanceType>) => {
|
||||
const web3 = getWeb3()
|
||||
const encodeMultiSendCallData = getEncodedMultiSendCallData(txs, web3)
|
||||
createTransaction({
|
||||
safeAddress,
|
||||
to: MULTI_SEND_ADDRESS,
|
||||
valueInWei: 0,
|
||||
txData: encodeMultiSendCallData,
|
||||
notifiedTransaction: 'STANDARD_TX',
|
||||
enqueueSnackbar: () => {},
|
||||
closeSnackbar: () => {},
|
||||
operation: DELEGATE_CALL,
|
||||
})
|
||||
}
|
||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
||||
const fallbackHandlerTxData = safeInstance.contract.methods.setFallbackHandler(DEFAULT_FALLBACK_HANDLER_ADDRESS).encodeABI()
|
||||
const updateSafeTxData = safeInstance.contract.methods.changeMasterCopy(SAFE_MASTER_COPY_ADDRESS).encodeABI()
|
||||
const txs = [
|
||||
{
|
||||
operation: 0,
|
||||
to: safeAddress,
|
||||
value: 0,
|
||||
data: fallbackHandlerTxData,
|
||||
},
|
||||
{
|
||||
operation: 0,
|
||||
to: safeAddress,
|
||||
value: 0,
|
||||
data: updateSafeTxData,
|
||||
},
|
||||
]
|
||||
return sendTransactions(txs)
|
||||
}
|
|
@ -14,7 +14,7 @@ import Paragraph from '~/components/layout/Paragraph'
|
|||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
|
||||
import { getSafeMasterContract, SAFE_MASTER_COPY_ADDRESS_V10, validateProxy } from '~/logic/contracts/safeContracts'
|
||||
import { secondary } from '~/theme/variables'
|
||||
|
||||
type Props = {
|
||||
|
@ -72,8 +72,7 @@ export const safeFieldsValidation = async (values: Object) => {
|
|||
)
|
||||
const safeMaster = await getSafeMasterContract()
|
||||
const masterCopy = safeMaster.address
|
||||
|
||||
const sameMasterCopy = checksummedProxyAddress === masterCopy
|
||||
const sameMasterCopy = checksummedProxyAddress === masterCopy || checksummedProxyAddress === SAFE_MASTER_COPY_ADDRESS_V10
|
||||
if (!sameMasterCopy) {
|
||||
errors[FIELD_LOAD_ADDRESS] = SAFE_MASTERCOPY_ERROR
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ export const calculateValuesAfterRemoving = (
|
|||
const initialValues = { ...values }
|
||||
|
||||
const numOwnersAfterRemoving = notRemovedOwners - 1
|
||||
// muevo indices
|
||||
|
||||
for (let i = index; i < numOwnersAfterRemoving; i += 1) {
|
||||
initialValues[getOwnerNameBy(i)] = values[getOwnerNameBy(i + 1)]
|
||||
initialValues[getOwnerAddressBy(i)] = values[getOwnerAddressBy(i + 1)]
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// @flow
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { withSnackbar } from 'notistack'
|
||||
import { useSelector } from 'react-redux'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Col from '~/components/layout/Col'
|
||||
import Field from '~/components/forms/Field'
|
||||
|
@ -16,15 +17,20 @@ import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
|
|||
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||
import { styles } from './style'
|
||||
import { getSafeVersion } from '~/logic/safe/utils/safeVersion'
|
||||
import UpdateSafeModal from '~/routes/safe/components/Settings/UpdateSafeModal'
|
||||
import Modal from '~/components/Modal'
|
||||
import { grantedSelector } from '~/routes/safe/container/selector'
|
||||
|
||||
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
|
||||
export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn'
|
||||
export const SAFE_NAME_UPDATE_SAFE_BTN_TEST_ID = 'update-safe-name-btn'
|
||||
|
||||
type Props = {
|
||||
safeAddress: string,
|
||||
safeName: string,
|
||||
updateSafe: Function,
|
||||
enqueueSnackbar: Function,
|
||||
createTransaction: Function,
|
||||
closeSnackbar: Function,
|
||||
}
|
||||
|
||||
|
@ -33,10 +39,17 @@ const useStyles = makeStyles(styles)
|
|||
const SafeDetails = (props: Props) => {
|
||||
const classes = useStyles()
|
||||
const [safeVersions, setSafeVersions] = React.useState({ current: null, latest: null, needUpdate: false })
|
||||
const isUserOwner = useSelector(grantedSelector)
|
||||
const {
|
||||
safeAddress, safeName, updateSafe, enqueueSnackbar, closeSnackbar,
|
||||
safeAddress, safeName, updateSafe, enqueueSnackbar, closeSnackbar, createTransaction,
|
||||
} = props
|
||||
|
||||
const [isModalOpen, setModalOpen] = useState(false)
|
||||
|
||||
const toggleModal = () => {
|
||||
setModalOpen((prevOpen) => !prevOpen)
|
||||
}
|
||||
|
||||
const handleSubmit = (values) => {
|
||||
updateSafe({ address: safeAddress, name: values.safeName })
|
||||
|
||||
|
@ -44,10 +57,14 @@ const SafeDetails = (props: Props) => {
|
|||
showSnackbar(notification.afterExecution.noMoreConfirmationsNeeded, enqueueSnackbar, closeSnackbar)
|
||||
}
|
||||
|
||||
const handleUpdateSafe = () => {
|
||||
setModalOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const getVersion = async () => {
|
||||
try {
|
||||
const { current, latest, needUpdate } = await getSafeVersion()
|
||||
const { current, latest, needUpdate } = await getSafeVersion(safeAddress)
|
||||
setSafeVersions({ current, latest, needUpdate })
|
||||
} catch (err) {
|
||||
setSafeVersions({ current: 'Version not defined' })
|
||||
|
@ -70,6 +87,22 @@ const SafeDetails = (props: Props) => {
|
|||
{safeVersions.needUpdate && ` (there's a newer version: ${safeVersions.latest})`}
|
||||
</Paragraph>
|
||||
</Row>
|
||||
{safeVersions.needUpdate && isUserOwner ? (
|
||||
<Row align="end" grow>
|
||||
<Paragraph>
|
||||
<Button
|
||||
onClick={handleUpdateSafe}
|
||||
className={classes.saveBtn}
|
||||
size="small"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
testId={SAFE_NAME_UPDATE_SAFE_BTN_TEST_ID}
|
||||
>
|
||||
Update Safe
|
||||
</Button>
|
||||
</Paragraph>
|
||||
</Row>
|
||||
) : null}
|
||||
</Block>
|
||||
<Block className={classes.formContainer}>
|
||||
<Heading tag="h2">Modify Safe name</Heading>
|
||||
|
@ -104,6 +137,14 @@ const SafeDetails = (props: Props) => {
|
|||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Modal
|
||||
title="Update Safe"
|
||||
description="Update Safe"
|
||||
handleClose={toggleModal}
|
||||
open={isModalOpen}
|
||||
>
|
||||
<UpdateSafeModal onClose={toggleModal} safeAddress={safeAddress} createTransaction={createTransaction} />
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
</GnoForm>
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import { withStyles } from '@material-ui/styles'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Hairline from '~/components/layout/Hairline'
|
||||
import GnoForm from '~/components/forms/GnoForm'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Button from '~/components/layout/Button'
|
||||
import { styles } from './style'
|
||||
import { upgradeSafeToLastVersion } from '~/logic/safe/utils/updateSafe'
|
||||
|
||||
|
||||
type Props = {
|
||||
onClose: Function,
|
||||
createTransaction: Function,
|
||||
classes: Object,
|
||||
safeAddress: string,
|
||||
}
|
||||
|
||||
|
||||
const UpdateSafeModal = ({
|
||||
onClose, classes, safeAddress, createTransaction,
|
||||
}: Props) => {
|
||||
const handleSubmit = async () => {
|
||||
// Call the update safe method
|
||||
await upgradeSafeToLastVersion(safeAddress, createTransaction)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row align="center" grow className={classes.heading}>
|
||||
<Paragraph className={classes.headingText} weight="bolder" noMargin>
|
||||
Change required confirmations
|
||||
</Paragraph>
|
||||
<IconButton onClick={onClose} disableRipple>
|
||||
<Close className={classes.close} />
|
||||
</IconButton>
|
||||
</Row>
|
||||
<Hairline />
|
||||
<GnoForm onSubmit={handleSubmit}>
|
||||
{() => (
|
||||
<>
|
||||
<Block className={classes.modalContent}>
|
||||
<Row>
|
||||
<Paragraph>
|
||||
Update now to take advantage of new features and the highest security standards available.
|
||||
</Paragraph>
|
||||
<Block>
|
||||
This update includes:
|
||||
<ul>
|
||||
<li>Compatibility with new asset types (ERC-721 / ERC-1155)</li>
|
||||
<li>Improved interoperability with modules</li>
|
||||
<li>Minor security improvements</li>
|
||||
</ul>
|
||||
</Block>
|
||||
<Paragraph>
|
||||
You will need to confirm this update just like any other transaction. This means other owners will have to confirm the update in case more than one confirmation is required for this Safe.
|
||||
</Paragraph>
|
||||
</Row>
|
||||
</Block>
|
||||
<Hairline style={{ position: 'absolute', bottom: 85 }} />
|
||||
<Row align="center" className={classes.buttonRow}>
|
||||
<Button minWidth={140} onClick={onClose}>
|
||||
Back
|
||||
</Button>
|
||||
<Button type="submit" color="primary" minWidth={140} variant="contained">
|
||||
Update Safe
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</GnoForm>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles)(UpdateSafeModal)
|
|
@ -0,0 +1,46 @@
|
|||
// @flow
|
||||
import {
|
||||
lg, md, sm, secondaryText,
|
||||
} from '~/theme/variables'
|
||||
|
||||
export const styles = () => ({
|
||||
heading: {
|
||||
padding: `${sm} ${lg}`,
|
||||
justifyContent: 'space-between',
|
||||
boxSizing: 'border-box',
|
||||
maxHeight: '75px',
|
||||
},
|
||||
annotation: {
|
||||
letterSpacing: '-1px',
|
||||
color: secondaryText,
|
||||
marginRight: 'auto',
|
||||
marginLeft: '20px',
|
||||
},
|
||||
headingText: {
|
||||
fontSize: '20px',
|
||||
},
|
||||
close: {
|
||||
height: '35px',
|
||||
width: '35px',
|
||||
},
|
||||
modalContent: {
|
||||
padding: `${md} ${lg}`,
|
||||
},
|
||||
ownersText: {
|
||||
marginLeft: sm,
|
||||
},
|
||||
buttonRow: {
|
||||
height: '84px',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
},
|
||||
inputRow: {
|
||||
position: 'relative',
|
||||
},
|
||||
errorText: {
|
||||
position: 'absolute',
|
||||
bottom: '-25px',
|
||||
},
|
||||
})
|
|
@ -170,11 +170,7 @@ class Settings extends React.Component<Props, State> {
|
|||
<Col className={classes.contents} layout="column">
|
||||
<Block className={classes.container}>
|
||||
{menuOptionIndex === 1 && (
|
||||
<SafeDetails
|
||||
safeAddress={safeAddress}
|
||||
safeName={safeName}
|
||||
updateSafe={updateSafe}
|
||||
/>
|
||||
<SafeDetails safeAddress={safeAddress} safeName={safeName} updateSafe={updateSafe} createTransaction={createTransaction} />
|
||||
)}
|
||||
{menuOptionIndex === 2 && (
|
||||
<ManageOwners
|
||||
|
|
|
@ -116,35 +116,13 @@ const createTransaction = ({
|
|||
try {
|
||||
if (isExecution) {
|
||||
tx = await getExecutionTransaction(
|
||||
safeInstance,
|
||||
to,
|
||||
valueInWei,
|
||||
txData,
|
||||
operation,
|
||||
nonce,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ZERO_ADDRESS,
|
||||
ZERO_ADDRESS,
|
||||
from,
|
||||
sigs
|
||||
safeInstance, to, valueInWei, txData, operation, nonce,
|
||||
0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, from, sigs,
|
||||
)
|
||||
} else {
|
||||
tx = await getApprovalTransaction(
|
||||
safeInstance,
|
||||
to,
|
||||
valueInWei,
|
||||
txData,
|
||||
operation,
|
||||
nonce,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ZERO_ADDRESS,
|
||||
ZERO_ADDRESS,
|
||||
from,
|
||||
sigs
|
||||
safeInstance, to, valueInWei, txData, operation, nonce,
|
||||
0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, from, sigs,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -118,8 +118,9 @@ const notificationsMiddleware = (store: Store<GlobalState>) => (
|
|||
break
|
||||
}
|
||||
case ADD_SAFE: {
|
||||
const { needUpdate } = await getSafeVersion()
|
||||
const { safe } = action.payload
|
||||
const { needUpdate } = await getSafeVersion(safe.address)
|
||||
|
||||
const notificationKey = `${safe.address}`
|
||||
if (needUpdate) {
|
||||
dispatch(
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import Web3 from 'web3'
|
||||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||
import {
|
||||
DEFAULT_FALLBACK_HANDLER_ADDRESS,
|
||||
getEncodedMultiSendCallData,
|
||||
SAFE_MASTER_COPY_ADDRESS,
|
||||
} from '~/logic/contracts/safeContracts'
|
||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||
|
||||
|
||||
describe('Upgrade a Safe', () => {
|
||||
it('Calls getEncodedMultiSendCallData with a list of MultiSendTransactionInstanceType and returns the multiSend data encoded', async () => {
|
||||
const safeAddress = ZERO_ADDRESS
|
||||
const web3 = new Web3(new Web3.providers.HttpProvider(''))
|
||||
const safeInstance = new web3.eth.Contract(GnosisSafeSol.abi)
|
||||
const fallbackHandlerTxData = safeInstance.methods.setFallbackHandler(DEFAULT_FALLBACK_HANDLER_ADDRESS).encodeABI()
|
||||
const updateSafeTxData = safeInstance.methods.changeMasterCopy(SAFE_MASTER_COPY_ADDRESS).encodeABI()
|
||||
const txs = [
|
||||
{
|
||||
operation: 0,
|
||||
to: safeAddress,
|
||||
value: 0,
|
||||
data: fallbackHandlerTxData,
|
||||
},
|
||||
{
|
||||
operation: 0,
|
||||
to: safeAddress,
|
||||
value: 0,
|
||||
data: updateSafeTxData,
|
||||
},
|
||||
]
|
||||
const expectedEncodedData = '0x8d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f08a0323000000000000000000000000d5d82b6addc9027b22dca772aa68d5d74cdbdf44000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247de7edef00000000000000000000000034cfac646f301356faa8b21e94227e3583fe3f5f0000000000000000000000000000'
|
||||
const multiSendTxData = getEncodedMultiSendCallData(txs, web3)
|
||||
expect(multiSendTxData).toEqual(expectedEncodedData)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,32 @@
|
|||
// @flow
|
||||
import Web3 from 'web3'
|
||||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||
import { checkIfSafeNeedsUpdate } from '~/logic/safe/utils/safeVersion'
|
||||
|
||||
|
||||
describe('Check safe version', () => {
|
||||
it('Calls checkIfSafeNeedUpdate, should return true if the safe version is bellow the target one', async () => {
|
||||
const web3 = new Web3(new Web3.providers.HttpProvider(''))
|
||||
const safeInstance = new web3.eth.Contract(GnosisSafeSol.abi)
|
||||
safeInstance.VERSION = () => '1.0.0'
|
||||
const targetVersion = '1.1.1'
|
||||
const { needUpdate } = await checkIfSafeNeedsUpdate(safeInstance, targetVersion)
|
||||
expect(needUpdate).toEqual(true)
|
||||
})
|
||||
it('Calls checkIfSafeNeedUpdate, should return false if the safe version is over the target one', async () => {
|
||||
const web3 = new Web3(new Web3.providers.HttpProvider(''))
|
||||
const safeInstance = new web3.eth.Contract(GnosisSafeSol.abi)
|
||||
safeInstance.VERSION = () => '2.0.0'
|
||||
const targetVersion = '1.1.1'
|
||||
const { needUpdate } = await checkIfSafeNeedsUpdate(safeInstance, targetVersion)
|
||||
expect(needUpdate).toEqual(false)
|
||||
})
|
||||
it('Calls checkIfSafeNeedUpdate, should return false if the safe version is equal the target one', async () => {
|
||||
const web3 = new Web3(new Web3.providers.HttpProvider(''))
|
||||
const safeInstance = new web3.eth.Contract(GnosisSafeSol.abi)
|
||||
safeInstance.VERSION = () => '1.1.1'
|
||||
const targetVersion = '1.1.1'
|
||||
const { needUpdate } = await checkIfSafeNeedsUpdate(safeInstance, targetVersion)
|
||||
expect(needUpdate).toEqual(false)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue