#474 Feature: upgrade contract version from interface (#544)

* 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:
Agustin Pane 2020-02-17 09:56:23 -03:00 committed by GitHub
parent d51303f348
commit 2698dcb6e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2155 additions and 2069 deletions

View File

@ -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",

View File

@ -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

View File

@ -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
}

View File

@ -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'

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)]

View File

@ -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>

View File

@ -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)

View File

@ -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',
},
})

View File

@ -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

View File

@ -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,
)
}

View File

@ -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(

View File

@ -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)
})
})

View File

@ -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)
})
})

3786
yarn.lock

File diff suppressed because it is too large Load Diff