From 7c1da37a69cce883cae7c975ae8023cac7431806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 27 Jul 2022 12:03:05 +0200 Subject: [PATCH 1/8] Add proxyCreationCode to proxy factory contract --- .../src/contracts/GnosisSafeProxyFactoryContract.ts | 3 ++- .../GnosisSafeProxyFactoryEthersContract.ts | 4 ++++ .../GnosisSafeProxyFactoryWeb3Contract.ts | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts b/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts index 4514ba9de..9bd94f555 100644 --- a/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts +++ b/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts @@ -1,4 +1,4 @@ -import { TransactionOptions } from '../types' +import { TransactionOptions } from '../types'; export interface CreateProxyProps { safeMasterCopyAddress: string @@ -10,6 +10,7 @@ export interface CreateProxyProps { export interface GnosisSafeProxyFactoryContract { getAddress(): string + proxyCreationCode(): Promise createProxy(options: CreateProxyProps): Promise encode(methodName: string, params: any[]): string estimateGas(methodName: string, params: any[], options: TransactionOptions): Promise diff --git a/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts b/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts index 4db19c4e2..198363d36 100644 --- a/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts +++ b/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts @@ -19,6 +19,10 @@ class GnosisSafeProxyFactoryEthersContract implements GnosisSafeProxyFactoryCont return this.contract.address } + async proxyCreationCode(): Promise { + return this.contract.proxyCreationCode() + } + async createProxy({ safeMasterCopyAddress, initializer, diff --git a/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts b/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts index c2d492a5f..8b58bc6e9 100644 --- a/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts +++ b/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts @@ -20,6 +20,10 @@ class GnosisSafeProxyFactoryWeb3Contract implements GnosisSafeProxyFactoryContra return this.contract.options.address } + async proxyCreationCode(): Promise { + return this.contract.methods.proxyCreationCode().call() + } + async createProxy({ safeMasterCopyAddress, initializer, From 10a4dd768fbc0723ffc531ee74facbc48c9dae5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 27 Jul 2022 12:12:28 +0200 Subject: [PATCH 2/8] Add getChecksummedAddress in ethAdapter --- packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts | 1 + packages/safe-ethers-lib/src/EthersAdapter.ts | 4 ++++ packages/safe-web3-lib/src/Web3Adapter.ts | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts index 099da7497..243ec13a1 100644 --- a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts +++ b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts @@ -28,6 +28,7 @@ export interface EthAdapter { getEip3770Address(fullAddress: string): Promise getBalance(address: string): Promise getChainId(): Promise + getChecksummedAddress(address: string): string getSafeContract({ safeVersion, chainId, diff --git a/packages/safe-ethers-lib/src/EthersAdapter.ts b/packages/safe-ethers-lib/src/EthersAdapter.ts index c86d0160b..e36fec6f1 100644 --- a/packages/safe-ethers-lib/src/EthersAdapter.ts +++ b/packages/safe-ethers-lib/src/EthersAdapter.ts @@ -72,6 +72,10 @@ class EthersAdapter implements EthAdapter { return (await this.#provider.getNetwork()).chainId } + getChecksummedAddress(address: string): string { + return this.#ethers.utils.getAddress(address) + } + getSafeContract({ safeVersion, chainId, diff --git a/packages/safe-web3-lib/src/Web3Adapter.ts b/packages/safe-web3-lib/src/Web3Adapter.ts index cadae2285..7b826a180 100644 --- a/packages/safe-web3-lib/src/Web3Adapter.ts +++ b/packages/safe-web3-lib/src/Web3Adapter.ts @@ -57,6 +57,10 @@ class Web3Adapter implements EthAdapter { return this.#web3.eth.getChainId() } + getChecksummedAddress(address: string): string { + return this.#web3.utils.toChecksumAddress(address) + } + getSafeContract({ safeVersion, chainId, From 56095ac286fd0afd2967cf1862ff99eb08a01a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 27 Jul 2022 12:19:58 +0200 Subject: [PATCH 3/8] Validate SafeDeploymentConfig --- packages/safe-core-sdk/src/safeFactory/utils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/safe-core-sdk/src/safeFactory/utils.ts b/packages/safe-core-sdk/src/safeFactory/utils.ts index 3d4fdda17..2ec47b48e 100644 --- a/packages/safe-core-sdk/src/safeFactory/utils.ts +++ b/packages/safe-core-sdk/src/safeFactory/utils.ts @@ -1,4 +1,4 @@ -import { SafeAccountConfig } from './' +import { SafeAccountConfig, SafeDeploymentConfig } from './' export const validateSafeAccountConfig = ({ owners, threshold }: SafeAccountConfig): void => { if (owners.length <= 0) throw new Error('Owner list must have at least one owner') @@ -6,3 +6,7 @@ export const validateSafeAccountConfig = ({ owners, threshold }: SafeAccountConf if (threshold > owners.length) throw new Error('Threshold must be lower than or equal to owners length') } + +export const validateSafeDeploymentConfig = ({ saltNonce }: SafeDeploymentConfig): void => { + if (saltNonce < 0) throw new Error('saltNonce must be greater than 0') +} From 0daabd102abe8ab9febec9afd2026ae08a269b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 27 Jul 2022 12:52:11 +0200 Subject: [PATCH 4/8] Predict counterfactual Safe address --- packages/safe-core-sdk/package.json | 2 + packages/safe-core-sdk/src/index.ts | 2 + .../safe-core-sdk/src/safeFactory/index.ts | 41 ++++++++++++++++++- yarn.lock | 7 ++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/safe-core-sdk/package.json b/packages/safe-core-sdk/package.json index bce3ba9c4..2a56ce64e 100644 --- a/packages/safe-core-sdk/package.json +++ b/packages/safe-core-sdk/package.json @@ -48,6 +48,7 @@ "@nomiclabs/hardhat-web3": "^2.0.0", "@types/chai": "^4.3.0", "@types/chai-as-promised": "^7.1.5", + "@types/ethereumjs-abi": "^0.6.3", "@types/mocha": "^9.1.0", "@types/node": "^17.0.23", "@types/semver": "^7.3.9", @@ -91,6 +92,7 @@ "@ethersproject/solidity": "^5.6.0", "@gnosis.pm/safe-core-sdk-types": "^1.2.1", "@gnosis.pm/safe-deployments": "1.15.0", + "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^7.1.4", "semver": "^7.3.5", "web3-utils": "^1.7.1" diff --git a/packages/safe-core-sdk/src/index.ts b/packages/safe-core-sdk/src/index.ts index 6cee1ea11..3643c4630 100644 --- a/packages/safe-core-sdk/src/index.ts +++ b/packages/safe-core-sdk/src/index.ts @@ -8,6 +8,7 @@ import Safe, { } from './Safe' import SafeFactory, { DeploySafeProps, + PredictSafeProps, SafeAccountConfig, SafeDeploymentConfig, SafeFactoryConfig @@ -24,6 +25,7 @@ export { SafeFactoryConfig, SafeAccountConfig, SafeDeploymentConfig, + PredictSafeProps, DeploySafeProps, SafeConfig, ConnectSafeConfig, diff --git a/packages/safe-core-sdk/src/safeFactory/index.ts b/packages/safe-core-sdk/src/safeFactory/index.ts index 9b154f2ae..af453b420 100644 --- a/packages/safe-core-sdk/src/safeFactory/index.ts +++ b/packages/safe-core-sdk/src/safeFactory/index.ts @@ -5,12 +5,14 @@ import { SafeVersion, TransactionOptions } from '@gnosis.pm/safe-core-sdk-types' +import abi from 'ethereumjs-abi' +import { generateAddress2, keccak256, toBuffer } from 'ethereumjs-util' import { SAFE_LAST_VERSION } from '../contracts/config' import { getProxyFactoryContract, getSafeContract } from '../contracts/safeDeploymentContracts' import Safe from '../Safe' import { ContractNetworksConfig } from '../types' import { EMPTY_DATA, ZERO_ADDRESS } from '../utils/constants' -import { validateSafeAccountConfig } from './utils' +import { validateSafeAccountConfig, validateSafeDeploymentConfig } from './utils' export interface SafeAccountConfig { owners: string[] @@ -27,6 +29,11 @@ export interface SafeDeploymentConfig { saltNonce: number } +export interface PredictSafeProps { + safeAccountConfig: SafeAccountConfig + safeDeploymentConfig: SafeDeploymentConfig +} + export interface DeploySafeProps { safeAccountConfig: SafeAccountConfig safeDeploymentConfig?: SafeDeploymentConfig @@ -140,6 +147,35 @@ class SafeFactory { ]) } + async predictSafeAddress({ + safeAccountConfig, + safeDeploymentConfig + }: PredictSafeProps): Promise { + validateSafeAccountConfig(safeAccountConfig) + if (safeDeploymentConfig) { + validateSafeDeploymentConfig(safeDeploymentConfig) + } + + const from = this.#safeProxyFactoryContract.getAddress() + + const initializer = await this.encodeSetupCallData(safeAccountConfig) + const saltNonce = safeDeploymentConfig.saltNonce.toString() + const encodedNonce = abi.rawEncode(['uint256'], [saltNonce]).toString('hex') + const salt = keccak256( + toBuffer('0x' + keccak256(toBuffer(initializer)).toString('hex') + encodedNonce) + ) + + const proxyCreationCode = await this.#safeProxyFactoryContract.proxyCreationCode() + const constructorData = abi + .rawEncode(['address'], [this.#gnosisSafeContract.getAddress()]) + .toString('hex') + const initCode = proxyCreationCode + constructorData + + const proxyAddress = + '0x' + generateAddress2(toBuffer(from), toBuffer(salt), toBuffer(initCode)).toString('hex') + return this.#ethAdapter.getChecksummedAddress(proxyAddress) + } + async deploySafe({ safeAccountConfig, safeDeploymentConfig, @@ -147,6 +183,9 @@ class SafeFactory { callback }: DeploySafeProps): Promise { validateSafeAccountConfig(safeAccountConfig) + if (safeDeploymentConfig) { + validateSafeDeploymentConfig(safeDeploymentConfig) + } const signerAddress = await this.#ethAdapter.getSignerAddress() const initializer = await this.encodeSetupCallData(safeAccountConfig) const saltNonce = diff --git a/yarn.lock b/yarn.lock index 2142c2643..fa573b298 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2538,6 +2538,13 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@types/ethereumjs-abi@^0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@types/ethereumjs-abi/-/ethereumjs-abi-0.6.3.tgz#eb5ed09fd86b9e2b1c0eb75d1e9bc29c50715c86" + integrity sha512-DnHvqPkrJS5w4yZexTa5bdPNb8IyKPYciou0+zZCIg5fpzvGtyptTvshy0uZKzti2/k/markwjlxWRBWt7Mjuw== + dependencies: + "@types/node" "*" + "@types/express-serve-static-core@4.17.29", "@types/express-serve-static-core@^4.17.18": version "4.17.29" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz#2a1795ea8e9e9c91b4a4bbe475034b20c1ec711c" From da87e8c4eb4cc600f561143ceeb6c56120dc2abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 27 Jul 2022 13:10:32 +0200 Subject: [PATCH 5/8] Test conterfactual Safe address --- .../safe-core-sdk/tests/safeFactory.test.ts | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/packages/safe-core-sdk/tests/safeFactory.test.ts b/packages/safe-core-sdk/tests/safeFactory.test.ts index 7b8189301..31d152283 100644 --- a/packages/safe-core-sdk/tests/safeFactory.test.ts +++ b/packages/safe-core-sdk/tests/safeFactory.test.ts @@ -5,6 +5,7 @@ import { safeVersionDeployed } from '../hardhat/deploy/deploy-contracts' import { ContractNetworksConfig, DeploySafeProps, + PredictSafeProps, SafeAccountConfig, SafeDeploymentConfig, SafeFactory @@ -95,6 +96,93 @@ describe('Safe Proxy Factory', () => { }) }) + describe('predictSafeAddress', async () => { + it('should fail if there are no owners', async () => { + const { accounts, contractNetworks } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeFactory = await SafeFactory.create({ ethAdapter, contractNetworks }) + const owners: string[] = [] + const threshold = 2 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 1 } + const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } + chai + .expect(safeFactory.predictSafeAddress(predictSafeProps)) + .rejectedWith('Owner list must have at least one owner') + }) + + it('should fail if the threshold is lower than 0', async () => { + const { accounts, contractNetworks } = await setupTests() + const [account1, account2] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeFactory = await SafeFactory.create({ ethAdapter, contractNetworks }) + const owners = [account1.address, account2.address] + const threshold = 0 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 1 } + const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } + chai + .expect(safeFactory.predictSafeAddress(predictSafeProps)) + .rejectedWith('Threshold must be greater than or equal to 1') + }) + + it('should fail if the threshold is higher than the threshold', async () => { + const { accounts, contractNetworks } = await setupTests() + const [account1, account2] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeFactory = await SafeFactory.create({ ethAdapter, contractNetworks }) + const owners = [account1.address, account2.address] + const threshold = 3 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 1 } + const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } + chai + .expect(safeFactory.predictSafeAddress(predictSafeProps)) + .rejectedWith('Threshold must be lower than or equal to owners length') + }) + + it('should fail if the saltNonce is lower than 0', async () => { + const { accounts, contractNetworks } = await setupTests() + const [account1, account2] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeFactory = await SafeFactory.create({ + ethAdapter, + safeVersion: safeVersionDeployed, + contractNetworks + }) + const owners = [account1.address, account2.address] + const threshold = 2 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: -1 } + const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } + chai + .expect(safeFactory.predictSafeAddress(predictSafeProps)) + .rejectedWith('saltNonce must be greater than 0') + }) + + it('should predict a new Safe with saltNonce', async () => { + const { accounts, contractNetworks } = await setupTests() + const [account1, account2] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeFactory = await SafeFactory.create({ + ethAdapter, + safeVersion: safeVersionDeployed, + contractNetworks + }) + const owners = [account1.address, account2.address] + const threshold = 2 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 12345 } + const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } + const counterfactualSafeAddress = await safeFactory.predictSafeAddress(predictSafeProps) + const deploySafeProps: DeploySafeProps = { safeAccountConfig, safeDeploymentConfig } + const safe = await safeFactory.deploySafe(deploySafeProps) + const safeAddress = await safe.getAddress() + chai.expect(counterfactualSafeAddress).to.be.eq(safeAddress) + }) + }) + describe('deploySafe', async () => { it('should fail if there are no owners', async () => { const { accounts, contractNetworks } = await setupTests() From f7f52305b26e02c73da04a31b388f5172c70e333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 27 Jul 2022 22:37:21 +0200 Subject: [PATCH 6/8] Type saltNonce as string --- .../src/contracts/GnosisSafeProxyFactoryContract.ts | 4 ++-- packages/safe-core-sdk/src/safeFactory/utils.ts | 4 +++- .../GnosisSafeProxyFactoryEthersContract.ts | 8 ++++---- .../GnosisSafeProxyFactoryWeb3Contract.ts | 8 ++++---- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts b/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts index 9bd94f555..973124e64 100644 --- a/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts +++ b/packages/safe-core-sdk-types/src/contracts/GnosisSafeProxyFactoryContract.ts @@ -1,9 +1,9 @@ -import { TransactionOptions } from '../types'; +import { TransactionOptions } from '../types' export interface CreateProxyProps { safeMasterCopyAddress: string initializer: string - saltNonce: number + saltNonce: string options?: TransactionOptions callback?: (txHash: string) => void } diff --git a/packages/safe-core-sdk/src/safeFactory/utils.ts b/packages/safe-core-sdk/src/safeFactory/utils.ts index 2ec47b48e..f0d612f23 100644 --- a/packages/safe-core-sdk/src/safeFactory/utils.ts +++ b/packages/safe-core-sdk/src/safeFactory/utils.ts @@ -1,3 +1,4 @@ +import { BigNumber } from '@ethersproject/bignumber' import { SafeAccountConfig, SafeDeploymentConfig } from './' export const validateSafeAccountConfig = ({ owners, threshold }: SafeAccountConfig): void => { @@ -8,5 +9,6 @@ export const validateSafeAccountConfig = ({ owners, threshold }: SafeAccountConf } export const validateSafeDeploymentConfig = ({ saltNonce }: SafeDeploymentConfig): void => { - if (saltNonce < 0) throw new Error('saltNonce must be greater than 0') + if (BigNumber.from(saltNonce).lt(0)) + throw new Error('saltNonce must be greater than or equal to 0') } diff --git a/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts b/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts index 198363d36..7c515ab4f 100644 --- a/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts +++ b/packages/safe-ethers-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts @@ -1,3 +1,4 @@ +import { BigNumber } from '@ethersproject/bignumber' import { Event } from '@ethersproject/contracts' import { GnosisSafeProxyFactoryContract } from '@gnosis.pm/safe-core-sdk-types' import { ProxyFactory as ProxyFactory_V1_1_1 } from '../../../typechain/src/ethers-v5/v1.1.1/ProxyFactory' @@ -7,7 +8,7 @@ import { EthersTransactionOptions } from '../../types' export interface CreateProxyProps { safeMasterCopyAddress: string initializer: string - saltNonce: number + saltNonce: string options?: EthersTransactionOptions callback?: (txHash: string) => void } @@ -30,9 +31,8 @@ class GnosisSafeProxyFactoryEthersContract implements GnosisSafeProxyFactoryCont options, callback }: CreateProxyProps): Promise { - if (saltNonce < 0) { - throw new Error('saltNonce must be greater than 0') - } + if (BigNumber.from(saltNonce).lt(0)) + throw new Error('saltNonce must be greater than or equal to 0') if (options && !options.gasLimit) { options.gasLimit = await this.estimateGas( 'createProxyWithNonce', diff --git a/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts b/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts index 8b58bc6e9..0a624f735 100644 --- a/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts +++ b/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts @@ -8,7 +8,7 @@ import { toTxResult } from '../../utils' export interface CreateProxyProps { safeMasterCopyAddress: string initializer: string - saltNonce: number + saltNonce: string options?: Web3TransactionOptions callback?: (txHash: string) => void } @@ -31,9 +31,9 @@ class GnosisSafeProxyFactoryWeb3Contract implements GnosisSafeProxyFactoryContra options, callback }: CreateProxyProps): Promise { - if (saltNonce < 0) { - throw new Error('saltNonce must be greater than 0') - } + //if (saltNonce < 0) { + // throw new Error('saltNonce must be greater than or equal to 0') + //} if (options && !options.gas) { options.gas = await this.estimateGas( 'createProxyWithNonce', From ddd88c7e30d11256ce3b12ff3d5244d318895467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Fri, 29 Jul 2022 11:50:40 +0200 Subject: [PATCH 7/8] Remove ethereumjs-abi dependency --- .../src/ethereumLibs/EthAdapter.ts | 1 + packages/safe-core-sdk/package.json | 2 -- .../safe-core-sdk/src/safeFactory/index.ts | 23 ++++++++++--------- packages/safe-ethers-lib/src/EthersAdapter.ts | 4 ++++ packages/safe-web3-lib/src/Web3Adapter.ts | 4 ++++ 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts index 243ec13a1..e623867fc 100644 --- a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts +++ b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts @@ -64,4 +64,5 @@ export interface EthAdapter { callback?: (error: Error, gas: number) => void ): Promise call(transaction: EthAdapterTransaction): Promise + encodeParameter(type: string, value: any): string } diff --git a/packages/safe-core-sdk/package.json b/packages/safe-core-sdk/package.json index 2a56ce64e..bce3ba9c4 100644 --- a/packages/safe-core-sdk/package.json +++ b/packages/safe-core-sdk/package.json @@ -48,7 +48,6 @@ "@nomiclabs/hardhat-web3": "^2.0.0", "@types/chai": "^4.3.0", "@types/chai-as-promised": "^7.1.5", - "@types/ethereumjs-abi": "^0.6.3", "@types/mocha": "^9.1.0", "@types/node": "^17.0.23", "@types/semver": "^7.3.9", @@ -92,7 +91,6 @@ "@ethersproject/solidity": "^5.6.0", "@gnosis.pm/safe-core-sdk-types": "^1.2.1", "@gnosis.pm/safe-deployments": "1.15.0", - "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^7.1.4", "semver": "^7.3.5", "web3-utils": "^1.7.1" diff --git a/packages/safe-core-sdk/src/safeFactory/index.ts b/packages/safe-core-sdk/src/safeFactory/index.ts index af453b420..2af7eeefe 100644 --- a/packages/safe-core-sdk/src/safeFactory/index.ts +++ b/packages/safe-core-sdk/src/safeFactory/index.ts @@ -5,7 +5,6 @@ import { SafeVersion, TransactionOptions } from '@gnosis.pm/safe-core-sdk-types' -import abi from 'ethereumjs-abi' import { generateAddress2, keccak256, toBuffer } from 'ethereumjs-util' import { SAFE_LAST_VERSION } from '../contracts/config' import { getProxyFactoryContract, getSafeContract } from '../contracts/safeDeploymentContracts' @@ -26,7 +25,7 @@ export interface SafeAccountConfig { } export interface SafeDeploymentConfig { - saltNonce: number + saltNonce: string } export interface PredictSafeProps { @@ -152,23 +151,24 @@ class SafeFactory { safeDeploymentConfig }: PredictSafeProps): Promise { validateSafeAccountConfig(safeAccountConfig) - if (safeDeploymentConfig) { - validateSafeDeploymentConfig(safeDeploymentConfig) - } + validateSafeDeploymentConfig(safeDeploymentConfig) const from = this.#safeProxyFactoryContract.getAddress() const initializer = await this.encodeSetupCallData(safeAccountConfig) - const saltNonce = safeDeploymentConfig.saltNonce.toString() - const encodedNonce = abi.rawEncode(['uint256'], [saltNonce]).toString('hex') + const saltNonce = safeDeploymentConfig.saltNonce + const encodedNonce = toBuffer(this.#ethAdapter.encodeParameter('uint256', saltNonce)).toString( + 'hex' + ) + const salt = keccak256( toBuffer('0x' + keccak256(toBuffer(initializer)).toString('hex') + encodedNonce) ) const proxyCreationCode = await this.#safeProxyFactoryContract.proxyCreationCode() - const constructorData = abi - .rawEncode(['address'], [this.#gnosisSafeContract.getAddress()]) - .toString('hex') + const constructorData = toBuffer( + this.#ethAdapter.encodeParameter('address', this.#gnosisSafeContract.getAddress()) + ).toString('hex') const initCode = proxyCreationCode + constructorData const proxyAddress = @@ -189,7 +189,8 @@ class SafeFactory { const signerAddress = await this.#ethAdapter.getSignerAddress() const initializer = await this.encodeSetupCallData(safeAccountConfig) const saltNonce = - safeDeploymentConfig?.saltNonce ?? Date.now() * 1000 + Math.floor(Math.random() * 1000) + safeDeploymentConfig?.saltNonce ?? + (Date.now() * 1000 + Math.floor(Math.random() * 1000)).toString() if (options?.gas && options?.gasLimit) { throw new Error('Cannot specify gas and gasLimit together in transaction options') diff --git a/packages/safe-ethers-lib/src/EthersAdapter.ts b/packages/safe-ethers-lib/src/EthersAdapter.ts index e36fec6f1..a2945dddc 100644 --- a/packages/safe-ethers-lib/src/EthersAdapter.ts +++ b/packages/safe-ethers-lib/src/EthersAdapter.ts @@ -163,6 +163,10 @@ class EthersAdapter implements EthAdapter { call(transaction: EthAdapterTransaction): Promise { return this.#provider.call(transaction) } + + encodeParameter(type: string, value: any) { + return new this.#ethers.utils.AbiCoder().encode([type], [value]) + } } export default EthersAdapter diff --git a/packages/safe-web3-lib/src/Web3Adapter.ts b/packages/safe-web3-lib/src/Web3Adapter.ts index 7b826a180..82b9dea2c 100644 --- a/packages/safe-web3-lib/src/Web3Adapter.ts +++ b/packages/safe-web3-lib/src/Web3Adapter.ts @@ -191,6 +191,10 @@ class Web3Adapter implements EthAdapter { call(transaction: EthAdapterTransaction): Promise { return this.#web3.eth.call(transaction) } + + encodeParameter(type: string, value: any): string { + return this.#web3.eth.abi.encodeParameter(type, value) + } } export default Web3Adapter From 6f1b2e587e138f734e60ee3360a5c275678a1144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Fri, 29 Jul 2022 12:00:41 +0200 Subject: [PATCH 8/8] Replace encodeParameter with encodeParameters --- .../src/ethereumLibs/EthAdapter.ts | 2 +- .../safe-core-sdk/src/safeFactory/index.ts | 4 +-- .../safe-core-sdk/tests/safeFactory.test.ts | 34 +++++++++---------- packages/safe-ethers-lib/src/EthersAdapter.ts | 4 +-- packages/safe-web3-lib/src/Web3Adapter.ts | 4 +-- .../GnosisSafeProxyFactoryWeb3Contract.ts | 6 ++-- yarn.lock | 7 ---- 7 files changed, 27 insertions(+), 34 deletions(-) diff --git a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts index e623867fc..b275c0e95 100644 --- a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts +++ b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts @@ -64,5 +64,5 @@ export interface EthAdapter { callback?: (error: Error, gas: number) => void ): Promise call(transaction: EthAdapterTransaction): Promise - encodeParameter(type: string, value: any): string + encodeParameters(types: string[], values: any[]): string } diff --git a/packages/safe-core-sdk/src/safeFactory/index.ts b/packages/safe-core-sdk/src/safeFactory/index.ts index 2af7eeefe..7a2e1e3c8 100644 --- a/packages/safe-core-sdk/src/safeFactory/index.ts +++ b/packages/safe-core-sdk/src/safeFactory/index.ts @@ -157,7 +157,7 @@ class SafeFactory { const initializer = await this.encodeSetupCallData(safeAccountConfig) const saltNonce = safeDeploymentConfig.saltNonce - const encodedNonce = toBuffer(this.#ethAdapter.encodeParameter('uint256', saltNonce)).toString( + const encodedNonce = toBuffer(this.#ethAdapter.encodeParameters(['uint256'], [saltNonce])).toString( 'hex' ) @@ -167,7 +167,7 @@ class SafeFactory { const proxyCreationCode = await this.#safeProxyFactoryContract.proxyCreationCode() const constructorData = toBuffer( - this.#ethAdapter.encodeParameter('address', this.#gnosisSafeContract.getAddress()) + this.#ethAdapter.encodeParameters(['address'], [this.#gnosisSafeContract.getAddress()]) ).toString('hex') const initCode = proxyCreationCode + constructorData diff --git a/packages/safe-core-sdk/tests/safeFactory.test.ts b/packages/safe-core-sdk/tests/safeFactory.test.ts index 31d152283..cc14300b2 100644 --- a/packages/safe-core-sdk/tests/safeFactory.test.ts +++ b/packages/safe-core-sdk/tests/safeFactory.test.ts @@ -105,9 +105,9 @@ describe('Safe Proxy Factory', () => { const owners: string[] = [] const threshold = 2 const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 1 } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: '1' } const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } - chai + await chai .expect(safeFactory.predictSafeAddress(predictSafeProps)) .rejectedWith('Owner list must have at least one owner') }) @@ -120,9 +120,9 @@ describe('Safe Proxy Factory', () => { const owners = [account1.address, account2.address] const threshold = 0 const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 1 } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: '1' } const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } - chai + await chai .expect(safeFactory.predictSafeAddress(predictSafeProps)) .rejectedWith('Threshold must be greater than or equal to 1') }) @@ -135,9 +135,9 @@ describe('Safe Proxy Factory', () => { const owners = [account1.address, account2.address] const threshold = 3 const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 1 } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: '1' } const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } - chai + await chai .expect(safeFactory.predictSafeAddress(predictSafeProps)) .rejectedWith('Threshold must be lower than or equal to owners length') }) @@ -154,11 +154,11 @@ describe('Safe Proxy Factory', () => { const owners = [account1.address, account2.address] const threshold = 2 const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: -1 } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: '-1' } const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } - chai + await chai .expect(safeFactory.predictSafeAddress(predictSafeProps)) - .rejectedWith('saltNonce must be greater than 0') + .rejectedWith('saltNonce must be greater than or equal to 0') }) it('should predict a new Safe with saltNonce', async () => { @@ -173,7 +173,7 @@ describe('Safe Proxy Factory', () => { const owners = [account1.address, account2.address] const threshold = 2 const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 12345 } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: '12345' } const predictSafeProps: PredictSafeProps = { safeAccountConfig, safeDeploymentConfig } const counterfactualSafeAddress = await safeFactory.predictSafeAddress(predictSafeProps) const deploySafeProps: DeploySafeProps = { safeAccountConfig, safeDeploymentConfig } @@ -193,7 +193,7 @@ describe('Safe Proxy Factory', () => { const threshold = 2 const safeAccountConfig: SafeAccountConfig = { owners, threshold } const safeDeployProps: DeploySafeProps = { safeAccountConfig } - chai + await chai .expect(safeFactory.deploySafe(safeDeployProps)) .rejectedWith('Owner list must have at least one owner') }) @@ -207,7 +207,7 @@ describe('Safe Proxy Factory', () => { const threshold = 0 const safeAccountConfig: SafeAccountConfig = { owners, threshold } const safeDeployProps: DeploySafeProps = { safeAccountConfig } - chai + await chai .expect(safeFactory.deploySafe(safeDeployProps)) .rejectedWith('Threshold must be greater than or equal to 1') }) @@ -221,7 +221,7 @@ describe('Safe Proxy Factory', () => { const threshold = 3 const safeAccountConfig: SafeAccountConfig = { owners, threshold } const deploySafeProps: DeploySafeProps = { safeAccountConfig } - chai + await chai .expect(safeFactory.deploySafe(deploySafeProps)) .rejectedWith('Threshold must be lower than or equal to owners length') }) @@ -238,11 +238,11 @@ describe('Safe Proxy Factory', () => { const owners = [account1.address, account2.address] const threshold = 2 const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: -1 } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: '-1' } const safeDeployProps: DeploySafeProps = { safeAccountConfig, safeDeploymentConfig } - chai + await chai .expect(safeFactory.deploySafe(safeDeployProps)) - .rejectedWith('saltNonce must be greater than 0') + .rejectedWith('saltNonce must be greater than or equal to 0') }) it('should deploy a new Safe without saltNonce', async () => { @@ -277,7 +277,7 @@ describe('Safe Proxy Factory', () => { const owners = [account1.address, account2.address] const threshold = 2 const safeAccountConfig: SafeAccountConfig = { owners, threshold } - const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: 1 } + const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce: '1' } const deploySafeProps: DeploySafeProps = { safeAccountConfig, safeDeploymentConfig } const safe = await safeFactory.deploySafe(deploySafeProps) const deployedSafeOwners = await safe.getOwners() diff --git a/packages/safe-ethers-lib/src/EthersAdapter.ts b/packages/safe-ethers-lib/src/EthersAdapter.ts index a2945dddc..707e8a179 100644 --- a/packages/safe-ethers-lib/src/EthersAdapter.ts +++ b/packages/safe-ethers-lib/src/EthersAdapter.ts @@ -164,8 +164,8 @@ class EthersAdapter implements EthAdapter { return this.#provider.call(transaction) } - encodeParameter(type: string, value: any) { - return new this.#ethers.utils.AbiCoder().encode([type], [value]) + encodeParameters(types: string[], values: any[]) { + return new this.#ethers.utils.AbiCoder().encode(types, values) } } diff --git a/packages/safe-web3-lib/src/Web3Adapter.ts b/packages/safe-web3-lib/src/Web3Adapter.ts index 82b9dea2c..dd35c887c 100644 --- a/packages/safe-web3-lib/src/Web3Adapter.ts +++ b/packages/safe-web3-lib/src/Web3Adapter.ts @@ -192,8 +192,8 @@ class Web3Adapter implements EthAdapter { return this.#web3.eth.call(transaction) } - encodeParameter(type: string, value: any): string { - return this.#web3.eth.abi.encodeParameter(type, value) + encodeParameters(types: string[], values: any[]): string { + return this.#web3.eth.abi.encodeParameters(types, values) } } diff --git a/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts b/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts index 0a624f735..2d7491424 100644 --- a/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts +++ b/packages/safe-web3-lib/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts @@ -1,3 +1,4 @@ +import { BigNumber } from '@ethersproject/bignumber' import { GnosisSafeProxyFactoryContract } from '@gnosis.pm/safe-core-sdk-types' import { TransactionReceipt } from 'web3-core/types' import { ProxyFactory as ProxyFactory_V1_1_1 } from '../../../typechain/src/web3-v1/v1.1.1/proxy_factory' @@ -31,9 +32,8 @@ class GnosisSafeProxyFactoryWeb3Contract implements GnosisSafeProxyFactoryContra options, callback }: CreateProxyProps): Promise { - //if (saltNonce < 0) { - // throw new Error('saltNonce must be greater than or equal to 0') - //} + if (BigNumber.from(saltNonce).lt(0)) + throw new Error('saltNonce must be greater than or equal to 0') if (options && !options.gas) { options.gas = await this.estimateGas( 'createProxyWithNonce', diff --git a/yarn.lock b/yarn.lock index fa573b298..2142c2643 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2538,13 +2538,6 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/ethereumjs-abi@^0.6.3": - version "0.6.3" - resolved "https://registry.yarnpkg.com/@types/ethereumjs-abi/-/ethereumjs-abi-0.6.3.tgz#eb5ed09fd86b9e2b1c0eb75d1e9bc29c50715c86" - integrity sha512-DnHvqPkrJS5w4yZexTa5bdPNb8IyKPYciou0+zZCIg5fpzvGtyptTvshy0uZKzti2/k/markwjlxWRBWt7Mjuw== - dependencies: - "@types/node" "*" - "@types/express-serve-static-core@4.17.29", "@types/express-serve-static-core@^4.17.18": version "4.17.29" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz#2a1795ea8e9e9c91b4a4bbe475034b20c1ec711c"