diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2f3d4b0b0..2387e4a15 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Hardhat Test +name: Monorepo Test on: [push, pull_request] env: INFURA_KEY: ${{ secrets.INFURA_KEY }} diff --git a/.github/workflows/test_V1_2_0.yml b/.github/workflows/test_V1_2_0.yml new file mode 100644 index 000000000..8005161d2 --- /dev/null +++ b/.github/workflows/test_V1_2_0.yml @@ -0,0 +1,21 @@ +name: Safe Core SDK Test (Safe v1.2.0) +on: [push, pull_request] +env: + INFURA_KEY: ${{ secrets.INFURA_KEY }} +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: | + yarn install + yarn build + cd packages/safe-core-sdk + yarn test:hardhat:ethers:v1.2.0 diff --git a/.github/workflows/test_V1_3_0.yml b/.github/workflows/test_V1_3_0.yml new file mode 100644 index 000000000..9a2056b79 --- /dev/null +++ b/.github/workflows/test_V1_3_0.yml @@ -0,0 +1,21 @@ +name: Safe Core SDK Test (Safe v1.3.0) +on: [push, pull_request] +env: + INFURA_KEY: ${{ secrets.INFURA_KEY }} +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: | + yarn install + yarn build + cd packages/safe-core-sdk + yarn test:hardhat:ethers:v1.3.0 diff --git a/packages/safe-core-sdk/README.md b/packages/safe-core-sdk/README.md index 8c04ddae5..4b82bc7d3 100644 --- a/packages/safe-core-sdk/README.md +++ b/packages/safe-core-sdk/README.md @@ -68,13 +68,36 @@ const safeFactory = await SafeFactory.create({ ethAdapter }) const owners = ['0x
', '0x
', '0x
'] const threshold = 3 -const safeAccountConfig: SafeAccountConfig = { owners, threshold } +const safeAccountConfig: SafeAccountConfig = { + owners, + threshold, + // ... +} const safeSdk: Safe = await safeFactory.deploySafe(safeAccountConfig) ``` The method `deploySafe` executes a transaction from `owner1` account, deploys a new Safe and returns an instance of the Safe Core SDK connected to the new Safe. +The `SafeFactory` will deploy the last version of the Safe contracts available by default (currently `v1.3.0`). To deploy an older version of the Safe contracts instantiate the `SafeFactory` adding the property `safeVersion` with the desired version number. + +```js +const safeFactoryV1_1_1 = await SafeFactory.create({ ethAdapter, safeVersion: '1.1.1' }) +``` + +The property `contractNetworks` can also be used to provide the Safe contract addresses in case the SDK is used in a network where the Safe contracts are not deployed. + +```js +const contractNetworks: ContractNetworksConfig = { + [chainId]: { + multiSendAddress: '0x', + safeMasterCopyAddress: '0x', + safeProxyFactoryAddress: '0x' + } +} +const safeFactory = await SafeFactory.create({ ethAdapter, contractNetworks }) +``` + Call the method `getAddress`, for example, to check the address of the newly deployed Safe. ```js @@ -153,7 +176,9 @@ The property `contractNetworks` can be added to provide the Safe contract addres ```js const contractNetworks: ContractNetworksConfig = { [chainId]: { - multiSendAddress: '0x' + multiSendAddress: '0x', + safeMasterCopyAddress: '0x', + safeProxyFactoryAddress: '0x' } } const safeSdk = await Safe.create({ ethAdapter, safeAddress, contractNetworks }) @@ -172,7 +197,9 @@ The property `contractNetworks` can be added to provide the Safe contract addres ```js const contractNetworks: ContractNetworksConfig = { [chainId]: { - multiSendAddress: '0x' + multiSendAddress: '0x', + safeMasterCopyAddress: '0x', + safeProxyFactoryAddress: '0x' } } const safeSdk = await Safe.create({ ethAdapter, safeAddress, contractNetworks }) diff --git a/packages/safe-core-sdk/contracts/Deps.sol b/packages/safe-core-sdk/contracts/Deps.sol deleted file mode 100644 index 29f702a84..000000000 --- a/packages/safe-core-sdk/contracts/Deps.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity >=0.5.0 <0.7.0; - -import { GnosisSafeProxyFactory } from "@gnosis.pm/safe-contracts/contracts/proxies/GnosisSafeProxyFactory.sol"; -import { GnosisSafe } from "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol"; -import { MultiSend } from "@gnosis.pm/safe-contracts/contracts/libraries/MultiSend.sol"; -import { DailyLimitModule } from "@gnosis.pm/safe-contracts/contracts/modules/DailyLimitModule.sol"; -import { SocialRecoveryModule } from "@gnosis.pm/safe-contracts/contracts/modules/SocialRecoveryModule.sol"; -import { ERC20Mintable } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; diff --git a/packages/safe-core-sdk/contracts/Deps_V1_2_0.sol b/packages/safe-core-sdk/contracts/Deps_V1_2_0.sol new file mode 100644 index 000000000..a365e9702 --- /dev/null +++ b/packages/safe-core-sdk/contracts/Deps_V1_2_0.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0 <0.9.0; + +import { GnosisSafeProxyFactory } from "@gnosis.pm/safe-contracts-v1.2.0/contracts/proxies/GnosisSafeProxyFactory.sol"; +import { GnosisSafe } from "@gnosis.pm/safe-contracts-v1.2.0/contracts/GnosisSafe.sol"; +import { MultiSend } from "@gnosis.pm/safe-contracts-v1.2.0/contracts/libraries/MultiSend.sol"; +import { DailyLimitModule } from "@gnosis.pm/safe-contracts-v1.2.0/contracts/modules/DailyLimitModule.sol"; +import { SocialRecoveryModule } from "@gnosis.pm/safe-contracts-v1.2.0/contracts/modules/SocialRecoveryModule.sol"; +import { ERC20Mintable } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; + +contract ProxyFactory_SV1_2_0 is GnosisSafeProxyFactory {} +contract GnosisSafe_SV1_2_0 is GnosisSafe {} +contract MultiSend_SV1_2_0 is MultiSend {} diff --git a/packages/safe-core-sdk/contracts/Deps_V1_3_0.sol b/packages/safe-core-sdk/contracts/Deps_V1_3_0.sol new file mode 100644 index 000000000..122460b1e --- /dev/null +++ b/packages/safe-core-sdk/contracts/Deps_V1_3_0.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.7.0 <0.9.0; + +import { GnosisSafeProxyFactory } from "@gnosis.pm/safe-contracts-v1.3.0/contracts/proxies/GnosisSafeProxyFactory.sol"; +import { GnosisSafe } from "@gnosis.pm/safe-contracts-v1.3.0/contracts/GnosisSafe.sol"; +import { MultiSend } from "@gnosis.pm/safe-contracts-v1.3.0/contracts/libraries/MultiSend.sol"; + +contract ProxyFactory_SV1_3_0 is GnosisSafeProxyFactory {} +contract GnosisSafe_SV1_3_0 is GnosisSafe {} +contract MultiSend_SV1_3_0 is MultiSend {} diff --git a/packages/safe-core-sdk/hardhat.config.ts b/packages/safe-core-sdk/hardhat.config.ts index b721b0b31..f3071bcb2 100644 --- a/packages/safe-core-sdk/hardhat.config.ts +++ b/packages/safe-core-sdk/hardhat.config.ts @@ -37,7 +37,9 @@ const config: HardhatUserConfig = { defaultNetwork: "hardhat", solidity: { compilers: [ - { version: '0.5.17' } + { version: '0.5.17' }, + { version: '0.5.3' }, + { version: '0.8.0' }, ] }, paths: { diff --git a/packages/safe-core-sdk/hardhat/deploy/deploy-contracts.ts b/packages/safe-core-sdk/hardhat/deploy/deploy-contracts.ts index f016a79f4..8916a56d7 100644 --- a/packages/safe-core-sdk/hardhat/deploy/deploy-contracts.ts +++ b/packages/safe-core-sdk/hardhat/deploy/deploy-contracts.ts @@ -1,26 +1,52 @@ import { DeployFunction } from 'hardhat-deploy/types' import { HardhatRuntimeEnvironment } from 'hardhat/types' -const deploy: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { +type SafeVersion = '1.3.0' | '1.2.0' | '1.1.1' + +export const safeVersionDeployed = process.env.SAFE_VERSION as SafeVersion + +const gnosisSafeContracts = { + '1.3.0': { name: 'GnosisSafe_SV1_3_0' }, + '1.2.0': { name: 'GnosisSafe_SV1_2_0' }, + '1.1.1': { name: 'GnosisSafe_SV1_1_1' } +} + +const proxyFactoryContracts = { + '1.3.0': { name: 'ProxyFactory_SV1_3_0' }, + '1.2.0': { name: 'ProxyFactory_SV1_2_0' }, + '1.1.1': { name: 'ProxyFactory_SV1_1_1' } +} + +const multiSendContracts = { + '1.3.0': { name: 'MultiSend_SV1_3_0' }, + '1.2.0': { name: 'MultiSend_SV1_2_0' }, + '1.1.1': { name: 'MultiSend_SV1_1_1' } +} + +export const gnosisSafeDeployed = gnosisSafeContracts[safeVersionDeployed] +export const proxyFactoryDeployed = proxyFactoryContracts[safeVersionDeployed] +export const multiSendDeployed = multiSendContracts[safeVersionDeployed] + +const deploy: DeployFunction = async (hre: HardhatRuntimeEnvironment): Promise => { const { deployments, getNamedAccounts } = hre const { deployer } = await getNamedAccounts() const { deploy } = deployments - await deploy('GnosisSafe', { + await deploy(gnosisSafeDeployed.name, { from: deployer, args: [], log: true, deterministicDeployment: true }) - await deploy('GnosisSafeProxyFactory', { + await deploy(proxyFactoryDeployed.name, { from: deployer, args: [], log: true, deterministicDeployment: true }) - await deploy('MultiSend', { + await deploy(multiSendDeployed.name, { from: deployer, args: [], log: true, diff --git a/packages/safe-core-sdk/package.json b/packages/safe-core-sdk/package.json index 817e09050..a567d2761 100644 --- a/packages/safe-core-sdk/package.json +++ b/packages/safe-core-sdk/package.json @@ -11,16 +11,19 @@ "SDK" ], "scripts": { - "typechain": "node scripts/generateTypechainFiles.ts", + "typechain": "ts-node scripts/generateTypechainFiles.ts", "unbuild": "rimraf dist artifacts cache .nyc_output typechain", "build": "hardhat compile && yarn typechain && tsc", - "test:ganache:web3": "export TEST_NETWORK=ganache && export ETH_LIB=web3 && hardhat --network localhost deploy && nyc hardhat --network localhost test", - "test:ganache:ethers": "export TEST_NETWORK=ganache && export ETH_LIB=ethers && hardhat --network localhost deploy && nyc hardhat --network localhost test", - "test:hardhat:web3": "export TEST_NETWORK=hardhat && export ETH_LIB=web3 && hardhat deploy && nyc hardhat test", - "test:hardhat:ethers": "export TEST_NETWORK=hardhat && export ETH_LIB=ethers && hardhat deploy && nyc hardhat test", - "test": "yarn test:hardhat:web3 && yarn test:hardhat:ethers", + "test:ganache:web3:v1.2.0": "export TEST_NETWORK=ganache && export ETH_LIB=web3 && export SAFE_VERSION=1.2.0 && hardhat --network localhost deploy && nyc hardhat --network localhost test", + "test:ganache:web3:v1.3.0": "export TEST_NETWORK=ganache && export ETH_LIB=web3 && export SAFE_VERSION=1.3.0 && hardhat --network localhost deploy && nyc hardhat --network localhost test", + "test:ganache:ethers:v1.2.0": "export TEST_NETWORK=ganache && export ETH_LIB=ethers && export SAFE_VERSION=1.2.0 && hardhat --network localhost deploy && nyc hardhat --network localhost test", + "test:ganache:ethers:v1.3.0": "export TEST_NETWORK=ganache && export ETH_LIB=ethers && export SAFE_VERSION=1.3.0 && hardhat --network localhost deploy && nyc hardhat --network localhost test", + "test:hardhat:web3:v1.2.0": "export TEST_NETWORK=hardhat && export ETH_LIB=web3 && export SAFE_VERSION=1.2.0 && hardhat deploy && nyc hardhat test", + "test:hardhat:web3:v1.3.0": "export TEST_NETWORK=hardhat && export ETH_LIB=web3 && export SAFE_VERSION=1.3.0 && hardhat deploy && nyc hardhat test", + "test:hardhat:ethers:v1.2.0": "export TEST_NETWORK=hardhat && export ETH_LIB=ethers && export SAFE_VERSION=1.2.0 && hardhat deploy && nyc hardhat test", + "test:hardhat:ethers:v1.3.0": "export TEST_NETWORK=hardhat && export ETH_LIB=ethers && export SAFE_VERSION=1.3.0 && hardhat deploy && nyc hardhat test", "coverage": "nyc report --reporter=text-lcov | coveralls", - "format": "prettier --write \"{src,tests,hardhat}/**/*.ts\"", + "format": "prettier --write \"{src,tests,hardhat,scripts}/**/*.ts\"", "lint": "tslint -p tsconfig.json" }, "repository": { @@ -38,7 +41,8 @@ ], "homepage": "https://github.com/gnosis/safe-core-sdk#readme", "devDependencies": { - "@gnosis.pm/safe-contracts": "1.2.0", + "@gnosis.pm/safe-contracts-v1.2.0": "npm:@gnosis.pm/safe-contracts@1.2.0", + "@gnosis.pm/safe-contracts-v1.3.0": "npm:@gnosis.pm/safe-contracts@1.3.0", "@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-web3": "^2.0.0", @@ -87,6 +91,7 @@ }, "dependencies": { "@gnosis.pm/safe-core-sdk-types": "^0.1.1", + "@gnosis.pm/safe-deployments": "^1.4.0", "ethereumjs-util": "^7.0.10" } } diff --git a/packages/safe-core-sdk/scripts/generateTypechainFiles.ts b/packages/safe-core-sdk/scripts/generateTypechainFiles.ts index e083ef31e..ac35bcb67 100644 --- a/packages/safe-core-sdk/scripts/generateTypechainFiles.ts +++ b/packages/safe-core-sdk/scripts/generateTypechainFiles.ts @@ -1,66 +1,67 @@ -const { execSync } = require('child_process') -const { readdir, mkdirSync, existsSync } = require('fs') -const path = require('path') +import { execSync } from 'child_process' +import { existsSync, mkdirSync, readdir } from 'fs' +import path from 'path' // Directories where the Typechain files will be generated const outDirSrc = 'typechain/src/' const typeChainDirectorySrcPath = path.join(__dirname, `../${outDirSrc}`) -const outDirTests = 'typechain/tests/' -const typeChainDirectoryTestsPath = path.join(__dirname, `../${outDirTests}`) - const outDirBuild = 'dist/typechain/src/' const typeChainDirectoryBuildPath = path.join(__dirname, `../${outDirBuild}`) +const outDirTests = 'typechain/tests/' + // Contract list for which the Typechain files will be generated // Will be included in dist/ folder -const safeContractsPath = '../../node_modules/@gnosis.pm/safe-contracts/build/contracts' -const openZeppelinContractsPath = '../../node_modules/openzeppelin-solidity/build/contracts' -const safeContracts = [ - `${safeContractsPath}/GnosisSafe.json`, - `${safeContractsPath}/GnosisSafeProxyFactory.json`, - `${safeContractsPath}/MultiSend.json`, +const safeContractsPath = '../../node_modules/@gnosis.pm/safe-deployments/dist/assets' + +const safeContracts_V1_3_0 = [ + `${safeContractsPath}/v1.3.0/gnosis_safe.json`, + `${safeContractsPath}/v1.3.0/proxy_factory.json`, + `${safeContractsPath}/v1.3.0/multi_send.json`, +].join(' ') +const safeContracts_V1_2_0 = [ + `${safeContractsPath}/v1.2.0/gnosis_safe.json`, ].join(' ') +const safeContracts_V1_1_1 = [ + `${safeContractsPath}/v1.1.1/gnosis_safe.json`, + `${safeContractsPath}/v1.1.1/proxy_factory.json`, + `${safeContractsPath}/v1.1.1/multi_send.json`, +].join(' ') + // Won't be included in dist/ folder +const safeContractsTestPath = '../../node_modules/@gnosis.pm/safe-contracts-v1.2.0/build/contracts' +const openZeppelinContractsPath = '../../node_modules/openzeppelin-solidity/build/contracts' const testContracts = [ - `${safeContractsPath}/DailyLimitModule.json`, - `${safeContractsPath}/SocialRecoveryModule.json`, + `${safeContractsTestPath}/DailyLimitModule.json`, + `${safeContractsTestPath}/SocialRecoveryModule.json`, `${openZeppelinContractsPath}/ERC20Mintable.json` ].join(' ') // Remove existing Typechain files -execSync(`rimraf ${outDirSrc} ${outDirTests}`, (error) => { - if (error) { - console.log(error.message) - return - } -}) +execSync(`rimraf ${outDirSrc} ${outDirTests}`) // Generate Typechain files -function generateTypechainFiles(typechainVersion, outDir, contractList) { - execSync(`typechain --target ${typechainVersion} --out-dir ${outDir}${typechainVersion} ${contractList}`, (error) => { - if (error) { - console.log(error.message) - } - }) +function generateTypechainFiles(typechainVersion: string, outDir: string, contractList: string): void { + execSync(`typechain --target ${typechainVersion} --out-dir ${outDir} ${contractList}`) console.log(`Generated typechain ${typechainVersion} at ${outDir}`) } // Copy Typechain files with the right extension (.d.ts -> .ts) allows them to be included in the build folder -function moveTypechainFiles(typechainVersion, inDir, outDir) { - readdir(`${inDir}${typechainVersion}`, (error, files) => { +function moveTypechainFiles(inDir: string, outDir: string): void { + readdir(`${inDir}`, (error, files) => { if (error) { console.log(error) } - if (!existsSync(`${outDir}${typechainVersion}`)) { - mkdirSync(`${outDir}${typechainVersion}`, { recursive: true }) + if (!existsSync(`${outDir}`)) { + mkdirSync(`${outDir}`, { recursive: true }) } files.forEach(file => { const pattern = /.d.ts/ if (!file.match(pattern)) { return } - execSync(`cp ${inDir}${typechainVersion}/${file} ${outDir}${typechainVersion}/${file}`) + execSync(`cp ${inDir}/${file} ${outDir}/${file}`) }) }) } @@ -69,12 +70,20 @@ const web3V1 = 'web3-v1' const ethersV5 = 'ethers-v5' // Src: Web3 V1 types -generateTypechainFiles(web3V1, outDirSrc, safeContracts) -moveTypechainFiles(web3V1, typeChainDirectorySrcPath, typeChainDirectoryBuildPath) +generateTypechainFiles(web3V1, `${outDirSrc}${web3V1}/v1.3.0`, safeContracts_V1_3_0) +generateTypechainFiles(web3V1, `${outDirSrc}${web3V1}/v1.2.0`, safeContracts_V1_2_0) +generateTypechainFiles(web3V1, `${outDirSrc}${web3V1}/v1.1.1`, safeContracts_V1_1_1) +moveTypechainFiles(`${typeChainDirectorySrcPath}${web3V1}/v1.3.0`, `${typeChainDirectoryBuildPath}${web3V1}/v1.3.0`) +moveTypechainFiles(`${typeChainDirectorySrcPath}${web3V1}/v1.2.0`, `${typeChainDirectoryBuildPath}${web3V1}/v1.2.0`) +moveTypechainFiles(`${typeChainDirectorySrcPath}${web3V1}/v1.1.1`, `${typeChainDirectoryBuildPath}${web3V1}/v1.1.1`) // Src: Ethers V5 types -generateTypechainFiles(ethersV5, outDirSrc, safeContracts) -moveTypechainFiles(ethersV5, typeChainDirectorySrcPath, typeChainDirectoryBuildPath) +generateTypechainFiles(ethersV5, `${outDirSrc}${ethersV5}/v1.3.0`, safeContracts_V1_3_0) +generateTypechainFiles(ethersV5, `${outDirSrc}${ethersV5}/v1.2.0`, safeContracts_V1_2_0) +generateTypechainFiles(ethersV5, `${outDirSrc}${ethersV5}/v1.1.1`, safeContracts_V1_1_1) +moveTypechainFiles(`${typeChainDirectorySrcPath}${ethersV5}/v1.3.0`, `${typeChainDirectoryBuildPath}${ethersV5}/v1.3.0`) +moveTypechainFiles(`${typeChainDirectorySrcPath}${ethersV5}/v1.2.0`, `${typeChainDirectoryBuildPath}${ethersV5}/v1.2.0`) +moveTypechainFiles(`${typeChainDirectorySrcPath}${ethersV5}/v1.1.1`, `${typeChainDirectoryBuildPath}${ethersV5}/v1.1.1`) // Tests: Ethers V5 types -generateTypechainFiles(ethersV5, outDirTests, testContracts) +generateTypechainFiles(ethersV5, `${outDirTests}${ethersV5}`, testContracts) diff --git a/packages/safe-core-sdk/src/Safe.ts b/packages/safe-core-sdk/src/Safe.ts index 99276beae..cf927f7c7 100644 --- a/packages/safe-core-sdk/src/Safe.ts +++ b/packages/safe-core-sdk/src/Safe.ts @@ -6,11 +6,11 @@ import { SafeTransaction, SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' -import { ContractNetworksConfig } from './configuration/contracts' import EthAdapter from './ethereumLibs/EthAdapter' import ContractManager from './managers/contractManager' import ModuleManager from './managers/moduleManager' import OwnerManager from './managers/ownerManager' +import { ContractNetworksConfig } from './types' import { sameString } from './utils' import { generatePreValidatedSignature, generateSignature } from './utils/signatures' import { estimateGasForTransactionExecution } from './utils/transactions/gas' @@ -31,6 +31,8 @@ export interface SafeConfig { ethAdapter: EthAdapter /** safeAddress - The address of the Safe account to use */ safeAddress: string + /** isL1SafeMasterCopy - Forces to use the Gnosis Safe L1 version of the contract instead of the L2 version */ + isL1SafeMasterCopy?: boolean /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig } @@ -40,6 +42,8 @@ export interface ConnectSafeConfig { ethAdapter?: EthAdapter /** safeAddress - The address of the Safe account to use */ safeAddress?: string + /** isL1SafeMasterCopy - Forces to use the Gnosis Safe L1 version of the contract instead of the L2 version */ + isL1SafeMasterCopy?: boolean /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig } @@ -75,13 +79,17 @@ class Safe { * Creates an instance of the Safe Core SDK. * @param config - Ethers Safe configuration * @returns The Safe Core SDK instance - * @throws "Safe contracts not found in the current network" * @throws "Safe Proxy contract is not deployed in the current network" * @throws "MultiSend contract is not deployed in the current network" */ - static async create({ ethAdapter, safeAddress, contractNetworks }: SafeConfig): Promise { + static async create({ + ethAdapter, + safeAddress, + isL1SafeMasterCopy, + contractNetworks + }: SafeConfig): Promise { const safeSdk = new Safe() - await safeSdk.init({ ethAdapter, safeAddress, contractNetworks }) + await safeSdk.init({ ethAdapter, safeAddress, isL1SafeMasterCopy, contractNetworks }) return safeSdk } @@ -89,17 +97,22 @@ class Safe { * Initializes the Safe Core SDK instance. * @param config - Safe configuration * @throws "Signer must be connected to a provider" - * @throws "Safe contracts not found in the current network" * @throws "Safe Proxy contract is not deployed in the current network" * @throws "MultiSend contract is not deployed in the current network" */ - private async init({ ethAdapter, safeAddress, contractNetworks }: SafeConfig): Promise { + private async init({ + ethAdapter, + safeAddress, + isL1SafeMasterCopy, + contractNetworks + }: SafeConfig): Promise { this.#ethAdapter = ethAdapter - this.#contractManager = await ContractManager.create( - this.#ethAdapter, + this.#contractManager = await ContractManager.create({ + ethAdapter: this.#ethAdapter, safeAddress, + isL1SafeMasterCopy, contractNetworks - ) + }) this.#ownerManager = new OwnerManager(this.#ethAdapter, this.#contractManager.safeContract) this.#moduleManager = new ModuleManager(this.#ethAdapter, this.#contractManager.safeContract) } @@ -107,14 +120,19 @@ class Safe { /** * Returns a new instance of the Safe Core SDK. * @param config - Connect Safe configuration - * @throws "Safe contracts not found in the current network" * @throws "Safe Proxy contract is not deployed in the current network" * @throws "MultiSend contract is not deployed in the current network" */ - async connect({ ethAdapter, safeAddress, contractNetworks }: ConnectSafeConfig): Promise { + async connect({ + ethAdapter, + safeAddress, + isL1SafeMasterCopy, + contractNetworks + }: ConnectSafeConfig): Promise { return await Safe.create({ ethAdapter: ethAdapter || this.#ethAdapter, safeAddress: safeAddress || this.getAddress(), + isL1SafeMasterCopy: isL1SafeMasterCopy || this.#contractManager.isL1SafeMasterCopy, contractNetworks: contractNetworks || this.#contractManager.contractNetworks }) } diff --git a/packages/safe-core-sdk/src/configuration/contracts.ts b/packages/safe-core-sdk/src/configuration/contracts.ts deleted file mode 100644 index d5e3fbc29..000000000 --- a/packages/safe-core-sdk/src/configuration/contracts.ts +++ /dev/null @@ -1,58 +0,0 @@ -export interface ContractNetworkConfig { - /** multiSendAddress - Address of the MultiSend contract deployed on a specific network */ - multiSendAddress: string - /** safeMasterCopyAddress - Address of the Gnosis Safe Master Copy contract deployed on a specific network */ - safeMasterCopyAddress: string - /** safeProxyFactoryAddress - Address of the Gnosis Safe Proxy Factory contract deployed on a specific network */ - safeProxyFactoryAddress: string -} - -export interface ContractNetworksConfig { - /** id - Network id */ - [id: string]: ContractNetworkConfig -} - -export const defaultContractNetworks: ContractNetworksConfig = { - // mainnet - 1: { - multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD', - safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185', - safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B' - }, - // rinkeby - 4: { - multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD', - safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185', - safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B' - }, - // goerli - 5: { - multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD', - safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185', - safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B' - }, - // kovan - 42: { - multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD', - safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185', - safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B' - }, - // xdai - 100: { - multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD', - safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185', - safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B' - }, - // energy web chain - 246: { - multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD', - safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185', - safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B' - }, - // energy web volta - 73799: { - multiSendAddress: '0x8D29bE29923b68abfDD21e541b9374737B49cdAD', - safeMasterCopyAddress: '0x6851D6fDFAfD08c0295C392436245E5bc78B0185', - safeProxyFactoryAddress: '0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B' - } -} diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeEthersV5Contract.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeContractEthers.ts similarity index 82% rename from packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeEthersV5Contract.ts rename to packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeContractEthers.ts index a1420f13c..b19cc629a 100644 --- a/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeEthersV5Contract.ts +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeContractEthers.ts @@ -1,7 +1,12 @@ import { BigNumber } from '@ethersproject/bignumber' import { ContractTransaction } from '@ethersproject/contracts' import { SafeTransaction, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' -import { GnosisSafe, GnosisSafeInterface } from '../../../typechain/src/ethers-v5/GnosisSafe' +import { GnosisSafe as GnosisSafe_V1_1_1 } from '../../../typechain/src/ethers-v5/v1.1.1/GnosisSafe' +import { GnosisSafe as GnosisSafe_V1_2_0 } from '../../../typechain/src/ethers-v5/v1.2.0/GnosisSafe' +import { + GnosisSafe as GnosisSafe_V1_3_0, + GnosisSafeInterface +} from '../../../typechain/src/ethers-v5/v1.3.0/GnosisSafe' import { EthersTransactionResult, TransactionOptions } from '../../utils/transactions/types' import GnosisSafeContract from './GnosisSafeContract' @@ -16,8 +21,8 @@ function toTxResult( } } -class GnosisSafeEthersV5Contract implements GnosisSafeContract { - constructor(public contract: GnosisSafe) {} +abstract class GnosisSafeContractEthers implements GnosisSafeContract { + constructor(public contract: GnosisSafe_V1_1_1 | GnosisSafe_V1_2_0 | GnosisSafe_V1_3_0) {} async getVersion(): Promise { return this.contract.VERSION() @@ -67,13 +72,9 @@ class GnosisSafeEthersV5Contract implements GnosisSafeContract { return toTxResult(txResponse, options) } - async getModules(): Promise { - return this.contract.getModules() - } + abstract getModules(): Promise - async isModuleEnabled(moduleAddress: string): Promise { - return this.contract.isModuleEnabled(moduleAddress) - } + abstract isModuleEnabled(moduleAddress: string): Promise async execTransaction( safeTransaction: SafeTransaction, @@ -108,4 +109,4 @@ class GnosisSafeEthersV5Contract implements GnosisSafeContract { } } -export default GnosisSafeEthersV5Contract +export default GnosisSafeContractEthers diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeWeb3Contract.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeContractWeb3.ts similarity index 84% rename from packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeWeb3Contract.ts rename to packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeContractWeb3.ts index 508f56bcb..b08e1a104 100644 --- a/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeWeb3Contract.ts +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/GnosisSafeContractWeb3.ts @@ -1,7 +1,9 @@ import { BigNumber } from '@ethersproject/bignumber' import { SafeTransaction, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import { PromiEvent, TransactionReceipt } from 'web3-core/types' -import { GnosisSafe } from '../../../typechain/src/web3-v1/GnosisSafe' +import { GnosisSafe as GnosisSafe_V1_1_1 } from '../../../typechain/src/web3-v1/v1.1.1/gnosis_safe' +import { GnosisSafe as GnosisSafe_V1_2_0 } from '../../../typechain/src/web3-v1/v1.2.0/gnosis_safe' +import { GnosisSafe as GnosisSafe_V1_3_0 } from '../../../typechain/src/web3-v1/v1.3.0/gnosis_safe' import { TransactionOptions, Web3TransactionResult } from '../../utils/transactions/types' import GnosisSafeContract from './GnosisSafeContract' @@ -16,8 +18,8 @@ function toTxResult( ) } -class GnosisSafeWeb3Contract implements GnosisSafeContract { - constructor(public contract: GnosisSafe) {} +abstract class GnosisSafeContractWeb3 implements GnosisSafeContract { + constructor(public contract: GnosisSafe_V1_1_1 | GnosisSafe_V1_2_0 | GnosisSafe_V1_3_0) {} async getVersion(): Promise { return this.contract.methods.VERSION().call() @@ -69,13 +71,9 @@ class GnosisSafeWeb3Contract implements GnosisSafeContract { return toTxResult(txResponse, options) } - async getModules(): Promise { - return this.contract.methods.getModules().call() - } + abstract getModules(): Promise - async isModuleEnabled(moduleAddress: string): Promise { - return this.contract.methods.isModuleEnabled(moduleAddress).call() - } + abstract isModuleEnabled(moduleAddress: string): Promise async execTransaction( safeTransaction: SafeTransaction, @@ -111,4 +109,4 @@ class GnosisSafeWeb3Contract implements GnosisSafeContract { } } -export default GnosisSafeWeb3Contract +export default GnosisSafeContractWeb3 diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/SafeAbiV1-2-0.json b/packages/safe-core-sdk/src/contracts/GnosisSafe/SafeAbiV1-2-0.json deleted file mode 100644 index 245c5fd3d..000000000 --- a/packages/safe-core-sdk/src/contracts/GnosisSafe/SafeAbiV1-2-0.json +++ /dev/null @@ -1,474 +0,0 @@ -[ - { - "constant": true, - "inputs": [], - "name": "nonce", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "VERSION", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getOwners", - "outputs": [ - { - "internalType": "address[]", - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getThreshold", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getModules", - "outputs": [ - { - "internalType": "address[]", - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "contract Module", - "name": "module", - "type": "address" - } - ], - "name": "isModuleEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_owners", - "type": "address[]" - }, - { - "name": "_threshold", - "type": "uint256" - }, - { - "name": "to", - "type": "address" - }, - { - "name": "data", - "type": "bytes" - }, - { - "name": "fallbackHandler", - "type": "address" - }, - { - "name": "paymentToken", - "type": "address" - }, - { - "name": "payment", - "type": "uint256" - }, - { - "name": "paymentReceiver", - "type": "address" - } - ], - "name": "setup", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "to", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - }, - { - "name": "data", - "type": "bytes" - }, - { - "name": "operation", - "type": "uint8" - }, - { - "name": "safeTxGas", - "type": "uint256" - }, - { - "name": "baseGas", - "type": "uint256" - }, - { - "name": "gasPrice", - "type": "uint256" - }, - { - "name": "gasToken", - "type": "address" - }, - { - "name": "refundReceiver", - "type": "address" - }, - { - "name": "signatures", - "type": "bytes" - } - ], - "name": "execTransaction", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "enum Enum.Operation", - "name": "operation", - "type": "uint8" - }, - { - "internalType": "uint256", - "name": "safeTxGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "baseGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasPrice", - "type": "uint256" - }, - { - "internalType": "address", - "name": "gasToken", - "type": "address" - }, - { - "internalType": "address", - "name": "refundReceiver", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - } - ], - "name": "getTransactionHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "bytes32", - "name": "hashToApprove", - "type": "bytes32" - } - ], - "name": "approveHash", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "approvedHashes", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "to", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - }, - { - "name": "data", - "type": "bytes" - }, - { - "name": "operation", - "type": "uint8" - } - ], - "name": "requiredTxGas", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "contract Module", - "name": "module", - "type": "address" - } - ], - "name": "enableModule", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "contract Module", - "name": "prevModule", - "type": "address" - }, - { - "internalType": "contract Module", - "name": "module", - "type": "address" - } - ], - "name": "disableModule", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "isOwner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_threshold", - "type": "uint256" - } - ], - "name": "addOwnerWithThreshold", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "prevOwner", - "type": "address" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_threshold", - "type": "uint256" - } - ], - "name": "removeOwner", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "prevOwner", - "type": "address" - }, - { - "internalType": "address", - "name": "oldOwner", - "type": "address" - }, - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "swapOwner", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "_threshold", - "type": "uint256" - } - ], - "name": "changeThreshold", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Ethers.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Ethers.ts new file mode 100644 index 000000000..213cf18f4 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Ethers.ts @@ -0,0 +1,23 @@ +import { GnosisSafe } from '../../../../typechain/src/ethers-v5/v1.1.1/GnosisSafe' +import { sameString } from '../../../utils' +import GnosisSafeContractEthers from '../GnosisSafeContractEthers' + +class GnosisSafeContract_V1_1_1_Ethers extends GnosisSafeContractEthers { + constructor(public contract: GnosisSafe) { + super(contract) + } + + async getModules(): Promise { + return (super.contract as GnosisSafe).getModules() + } + + async isModuleEnabled(moduleAddress: string): Promise { + const modules = await this.getModules() + const isModuleEnabled = modules.some((enabledModuleAddress: string) => + sameString(enabledModuleAddress, moduleAddress) + ) + return isModuleEnabled + } +} + +export default GnosisSafeContract_V1_1_1_Ethers diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Web3.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Web3.ts new file mode 100644 index 000000000..34191adaf --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Web3.ts @@ -0,0 +1,23 @@ +import { GnosisSafe } from '../../../../typechain/src/web3-v1/v1.1.1/gnosis_safe' +import { sameString } from '../../../utils' +import GnosisSafeContractWeb3 from '../GnosisSafeContractWeb3' + +class GnosisSafeContract_V1_1_1_Web3 extends GnosisSafeContractWeb3 { + constructor(public contract: GnosisSafe) { + super(contract) + } + + async getModules(): Promise { + return (super.contract as GnosisSafe).methods.getModules().call() + } + + async isModuleEnabled(moduleAddress: string): Promise { + const modules = await this.getModules() + const isModuleEnabled = modules.some((enabledModuleAddress: string) => + sameString(enabledModuleAddress, moduleAddress) + ) + return isModuleEnabled + } +} + +export default GnosisSafeContract_V1_1_1_Web3 diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Ethers.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Ethers.ts new file mode 100644 index 000000000..10131c451 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Ethers.ts @@ -0,0 +1,18 @@ +import { GnosisSafe } from '../../../../typechain/src/ethers-v5/v1.2.0/GnosisSafe' +import GnosisSafeContractEthers from '../GnosisSafeContractEthers' + +class GnosisSafeContract_V1_2_0_Ethers extends GnosisSafeContractEthers { + constructor(public contract: GnosisSafe) { + super(contract) + } + + async getModules(): Promise { + return this.contract.getModules() + } + + async isModuleEnabled(moduleAddress: string): Promise { + return this.contract.isModuleEnabled(moduleAddress) + } +} + +export default GnosisSafeContract_V1_2_0_Ethers diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Web3.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Web3.ts new file mode 100644 index 000000000..075ff93fd --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Web3.ts @@ -0,0 +1,18 @@ +import { GnosisSafe } from '../../../../typechain/src/web3-v1/v1.2.0/gnosis_safe' +import GnosisSafeContractWeb3 from '../GnosisSafeContractWeb3' + +class GnosisSafeContract_V1_2_0_Web3 extends GnosisSafeContractWeb3 { + constructor(public contract: GnosisSafe) { + super(contract) + } + + async getModules(): Promise { + return this.contract.methods.getModules().call() + } + + async isModuleEnabled(moduleAddress: string): Promise { + return this.contract.methods.isModuleEnabled(moduleAddress).call() + } +} + +export default GnosisSafeContract_V1_2_0_Web3 diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Ethers.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Ethers.ts new file mode 100644 index 000000000..56db6ccfe --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Ethers.ts @@ -0,0 +1,20 @@ +import { GnosisSafe } from '../../../../typechain/src/ethers-v5/v1.3.0/GnosisSafe' +import { SENTINEL_ADDRESS } from '../../../utils/constants' +import GnosisSafeContractEthers from '../GnosisSafeContractEthers' + +class GnosisSafeContract_V1_3_0_Ethers extends GnosisSafeContractEthers { + constructor(public contract: GnosisSafe) { + super(contract) + } + + async getModules(): Promise { + const { array } = await this.contract.getModulesPaginated(SENTINEL_ADDRESS, 10) + return array + } + + async isModuleEnabled(moduleAddress: string): Promise { + return this.contract.isModuleEnabled(moduleAddress) + } +} + +export default GnosisSafeContract_V1_3_0_Ethers diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Web3.ts b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Web3.ts new file mode 100644 index 000000000..5ecd81220 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Web3.ts @@ -0,0 +1,20 @@ +import { GnosisSafe } from '../../../../typechain/src/web3-v1/v1.3.0/gnosis_safe' +import { SENTINEL_ADDRESS } from '../../../utils/constants' +import GnosisSafeContractWeb3 from '../GnosisSafeContractWeb3' + +class GnosisSafeContract_V1_3_0_Web3 extends GnosisSafeContractWeb3 { + constructor(public contract: GnosisSafe) { + super(contract) + } + + async getModules(): Promise { + const { array } = await this.contract.methods.getModulesPaginated(SENTINEL_ADDRESS, 10).call() + return array + } + + async isModuleEnabled(moduleAddress: string): Promise { + return this.contract.methods.isModuleEnabled(moduleAddress).call() + } +} + +export default GnosisSafeContract_V1_3_0_Web3 diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryAbiV1-2-0.json b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryAbiV1-2-0.json deleted file mode 100644 index 17201aafd..000000000 --- a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryAbiV1-2-0.json +++ /dev/null @@ -1,156 +0,0 @@ -[ - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "masterCopy", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "createProxy", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "proxyRuntimeCode", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "proxyCreationCode", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_mastercopy", - "type": "address" - }, - { - "internalType": "bytes", - "name": "initializer", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "saltNonce", - "type": "uint256" - } - ], - "name": "createProxyWithNonce", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_mastercopy", - "type": "address" - }, - { - "internalType": "bytes", - "name": "initializer", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "saltNonce", - "type": "uint256" - }, - { - "internalType": "contract IProxyCreationCallback", - "name": "callback", - "type": "address" - } - ], - "name": "createProxyWithCallback", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_mastercopy", - "type": "address" - }, - { - "internalType": "bytes", - "name": "initializer", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "saltNonce", - "type": "uint256" - } - ], - "name": "calculateCreateProxyWithNonceAddress", - "outputs": [ - { - "internalType": "contract GnosisSafeProxy", - "name": "proxy", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersV5Contract.ts b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts similarity index 74% rename from packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersV5Contract.ts rename to packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts index 1d059640f..0f14e16ed 100644 --- a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersV5Contract.ts +++ b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract.ts @@ -1,9 +1,10 @@ import { ContractTransaction, Event } from '@ethersproject/contracts' -import { GnosisSafeProxyFactory } from '../../../typechain/src/ethers-v5/GnosisSafeProxyFactory' +import { ProxyFactory as ProxyFactory_V1_1_1 } from '../../../typechain/src/ethers-v5/v1.1.1/ProxyFactory' +import { ProxyFactory as ProxyFactory_V1_3_0 } from '../../../typechain/src/ethers-v5/v1.3.0/ProxyFactory' import GnosisSafeProxyFactoryContract, { CreateProxyProps } from './GnosisSafeProxyFactoryContract' -class GnosisSafeProxyFactoryEthersV5Contract implements GnosisSafeProxyFactoryContract { - constructor(public contract: GnosisSafeProxyFactory) {} +class GnosisSafeProxyFactoryEthersContract implements GnosisSafeProxyFactoryContract { + constructor(public contract: ProxyFactory_V1_3_0 | ProxyFactory_V1_1_1) {} getAddress(): string { return this.contract.address @@ -42,4 +43,4 @@ class GnosisSafeProxyFactoryEthersV5Contract implements GnosisSafeProxyFactoryCo } } -export default GnosisSafeProxyFactoryEthersV5Contract +export default GnosisSafeProxyFactoryEthersContract diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts index 0cc0a2584..2de5b629e 100644 --- a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts +++ b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract.ts @@ -1,9 +1,10 @@ import { PromiEvent, TransactionReceipt } from 'web3-core/types' -import { GnosisSafeProxyFactory } from '../../../typechain/src/web3-v1/GnosisSafeProxyFactory' +import { ProxyFactory as ProxyFactory_V1_1_1 } from '../../../typechain/src/web3-v1/v1.1.1/proxy_factory' +import { ProxyFactory as ProxyFactory_V1_3_0 } from '../../../typechain/src/web3-v1/v1.3.0/proxy_factory' import GnosisSafeProxyFactoryContract, { CreateProxyProps } from './GnosisSafeProxyFactoryContract' class GnosisSafeProxyFactoryWeb3Contract implements GnosisSafeProxyFactoryContract { - constructor(public contract: GnosisSafeProxyFactory) {} + constructor(public contract: ProxyFactory_V1_3_0 | ProxyFactory_V1_1_1) {} getAddress(): string { return this.contract.options.address diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Ethers.ts b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Ethers.ts new file mode 100644 index 000000000..6a822ce09 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Ethers.ts @@ -0,0 +1,10 @@ +import { ProxyFactory } from '../../../../typechain/src/ethers-v5/v1.1.1/ProxyFactory' +import GnosisSafeProxyFactoryEthersContract from '../GnosisSafeProxyFactoryEthersContract' + +class GnosisSafeProxyFactoryContract_V1_1_1_Ethers extends GnosisSafeProxyFactoryEthersContract { + constructor(public contract: ProxyFactory) { + super(contract) + } +} + +export default GnosisSafeProxyFactoryContract_V1_1_1_Ethers diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Web3.ts b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Web3.ts new file mode 100644 index 000000000..0bbcf5b03 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Web3.ts @@ -0,0 +1,10 @@ +import { ProxyFactory } from '../../../../typechain/src/web3-v1/v1.1.1/proxy_factory' +import GnosisSafeProxyFactoryWeb3Contract from '../GnosisSafeProxyFactoryWeb3Contract' + +class GnosisSafeProxyFactoryContract_V1_1_1_Web3 extends GnosisSafeProxyFactoryWeb3Contract { + constructor(public contract: ProxyFactory) { + super(contract) + } +} + +export default GnosisSafeProxyFactoryContract_V1_1_1_Web3 diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Ethers.ts b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Ethers.ts new file mode 100644 index 000000000..d66447ad3 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Ethers.ts @@ -0,0 +1,10 @@ +import { ProxyFactory } from '../../../../typechain/src/ethers-v5/v1.3.0/ProxyFactory' +import GnosisSafeProxyFactoryEthersContract from '../GnosisSafeProxyFactoryEthersContract' + +class GnosisSafeProxyFactoryContract_V1_3_0_Ethers extends GnosisSafeProxyFactoryEthersContract { + constructor(public contract: ProxyFactory) { + super(contract) + } +} + +export default GnosisSafeProxyFactoryContract_V1_3_0_Ethers diff --git a/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Web3.ts b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Web3.ts new file mode 100644 index 000000000..31c559cce --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Web3.ts @@ -0,0 +1,10 @@ +import { ProxyFactory } from '../../../../typechain/src/web3-v1/v1.3.0/proxy_factory' +import GnosisSafeProxyFactoryWeb3Contract from '../GnosisSafeProxyFactoryWeb3Contract' + +class GnosisSafeProxyFactoryContract_V1_3_0_Web3 extends GnosisSafeProxyFactoryWeb3Contract { + constructor(public contract: ProxyFactory) { + super(contract) + } +} + +export default GnosisSafeProxyFactoryContract_V1_3_0_Web3 diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendAbi.json b/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendAbi.json deleted file mode 100644 index d47e79d3f..000000000 --- a/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendAbi.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "constant": false, - "inputs": [ - { - "internalType": "bytes", - "name": "transactions", - "type": "bytes" - } - ], - "name": "multiSend", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendEthersContract.ts b/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendEthersContract.ts new file mode 100644 index 000000000..ac5542919 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendEthersContract.ts @@ -0,0 +1,20 @@ +import { MultiSend as MultiSend_V1_1_1 } from '../../../typechain/src/ethers-v5/v1.1.1/MultiSend' +import { + MultiSend as MultiSend_V1_3_0, + MultiSendInterface +} from '../../../typechain/src/ethers-v5/v1.3.0/MultiSend' +import MultiSendContract from './MultiSendContract' + +abstract class MultiSendEthersContract implements MultiSendContract { + constructor(public contract: MultiSend_V1_1_1 | MultiSend_V1_3_0) {} + + getAddress(): string { + return this.contract.address + } + + encode: MultiSendInterface['encodeFunctionData'] = (methodName: any, params: any): string => { + return this.contract.interface.encodeFunctionData(methodName, params) + } +} + +export default MultiSendEthersContract diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendEthersV5Contract.ts b/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendEthersV5Contract.ts deleted file mode 100644 index 8ccf3aec9..000000000 --- a/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendEthersV5Contract.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { MultiSend, MultiSendInterface } from '../../../typechain/src/ethers-v5/MultiSend' -import MultiSendContract from './MultiSendContract' - -class MultiSendEthersV5Contract implements MultiSendContract { - constructor(public contract: MultiSend) {} - - getAddress(): string { - return this.contract.address - } - - encode: MultiSendInterface['encodeFunctionData'] = (methodName: any, params: any): string => { - return this.contract.interface.encodeFunctionData(methodName, params) - } -} - -export default MultiSendEthersV5Contract diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendWeb3Contract.ts b/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendWeb3Contract.ts index 0f3a10410..db1f5201b 100644 --- a/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendWeb3Contract.ts +++ b/packages/safe-core-sdk/src/contracts/MultiSend/MultiSendWeb3Contract.ts @@ -1,8 +1,9 @@ -import { MultiSend } from '../../../typechain/src/web3-v1/MultiSend' +import { MultiSend as MultiSend_V1_1_1 } from '../../../typechain/src/web3-v1/v1.1.1/multi_send' +import { MultiSend as MultiSend_V1_3_0 } from '../../../typechain/src/web3-v1/v1.3.0/multi_send' import MultiSendContract from './MultiSendContract' -class MultiSendWeb3Contract implements MultiSendContract { - constructor(public contract: MultiSend) {} +abstract class MultiSendWeb3Contract implements MultiSendContract { + constructor(public contract: MultiSend_V1_3_0 | MultiSend_V1_1_1) {} getAddress(): string { return this.contract.options.address diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/v1.1.1/MultiSendContract_V1_1_1_Ethers.ts b/packages/safe-core-sdk/src/contracts/MultiSend/v1.1.1/MultiSendContract_V1_1_1_Ethers.ts new file mode 100644 index 000000000..c280443ef --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/MultiSend/v1.1.1/MultiSendContract_V1_1_1_Ethers.ts @@ -0,0 +1,10 @@ +import { MultiSend } from '../../../../typechain/src/ethers-v5/v1.1.1/MultiSend' +import MultiSendEthersContract from '../MultiSendEthersContract' + +class MultiSendContract_V1_1_1_Ethers extends MultiSendEthersContract { + constructor(public contract: MultiSend) { + super(contract) + } +} + +export default MultiSendContract_V1_1_1_Ethers diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/v1.1.1/MultiSendContract_V1_1_1_Web3.ts b/packages/safe-core-sdk/src/contracts/MultiSend/v1.1.1/MultiSendContract_V1_1_1_Web3.ts new file mode 100644 index 000000000..6bfb026d6 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/MultiSend/v1.1.1/MultiSendContract_V1_1_1_Web3.ts @@ -0,0 +1,10 @@ +import { MultiSend } from '../../../../typechain/src/web3-v1/v1.1.1/multi_send' +import MultiSendWeb3Contract from '../MultiSendWeb3Contract' + +class MultiSendContract_V1_1_1_Web3 extends MultiSendWeb3Contract { + constructor(public contract: MultiSend) { + super(contract) + } +} + +export default MultiSendContract_V1_1_1_Web3 diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/v1.3.0/MultiSendContract_V1_3_0_Ethers.ts b/packages/safe-core-sdk/src/contracts/MultiSend/v1.3.0/MultiSendContract_V1_3_0_Ethers.ts new file mode 100644 index 000000000..685542fbe --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/MultiSend/v1.3.0/MultiSendContract_V1_3_0_Ethers.ts @@ -0,0 +1,10 @@ +import { MultiSend } from '../../../../typechain/src/ethers-v5/v1.3.0/MultiSend' +import MultiSendEthersContract from '../MultiSendEthersContract' + +class MultiSendContract_V1_3_0_Ethers extends MultiSendEthersContract { + constructor(public contract: MultiSend) { + super(contract) + } +} + +export default MultiSendContract_V1_3_0_Ethers diff --git a/packages/safe-core-sdk/src/contracts/MultiSend/v1.3.0/MultiSendContract_V1_3_0_Web3.ts b/packages/safe-core-sdk/src/contracts/MultiSend/v1.3.0/MultiSendContract_V1_3_0_Web3.ts new file mode 100644 index 000000000..abdcd4a70 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/MultiSend/v1.3.0/MultiSendContract_V1_3_0_Web3.ts @@ -0,0 +1,10 @@ +import { MultiSend } from '../../../../typechain/src/web3-v1/v1.3.0/multi_send' +import MultiSendWeb3Contract from '../MultiSendWeb3Contract' + +class MultiSendContract_V1_3_0_Web3 extends MultiSendWeb3Contract { + constructor(public contract: MultiSend) { + super(contract) + } +} + +export default MultiSendContract_V1_3_0_Web3 diff --git a/packages/safe-core-sdk/src/contracts/config.ts b/packages/safe-core-sdk/src/contracts/config.ts new file mode 100644 index 000000000..6470cbbfd --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/config.ts @@ -0,0 +1,38 @@ +export type SafeVersion = '1.3.0' | '1.2.0' | '1.1.1' + +export const SAFE_LAST_VERSION: SafeVersion = '1.3.0' +export const SAFE_BASE_VERSION: SafeVersion = '1.1.1' + +type SafeDeploymentsVersions = { + [version: string]: { + safeMasterCopyVersion: string + safeMasterCopyL2Version: string | undefined + safeProxyFactoryVersion: string + multiSendVersion: string + } +} + +export const safeDeploymentsVersions: SafeDeploymentsVersions = { + '1.3.0': { + safeMasterCopyVersion: '1.3.0', + safeMasterCopyL2Version: '1.3.0', + safeProxyFactoryVersion: '1.3.0', + multiSendVersion: '1.3.0' + }, + '1.2.0': { + safeMasterCopyVersion: '1.2.0', + safeMasterCopyL2Version: undefined, + safeProxyFactoryVersion: '1.1.1', + multiSendVersion: '1.1.1' + }, + '1.1.1': { + safeMasterCopyVersion: '1.1.1', + safeMasterCopyL2Version: undefined, + safeProxyFactoryVersion: '1.1.1', + multiSendVersion: '1.1.1' + } +} + +export const safeDeploymentsL1ChainIds = [ + 1 // Ethereum Mainnet +] diff --git a/packages/safe-core-sdk/src/contracts/contractInstancesEthers.ts b/packages/safe-core-sdk/src/contracts/contractInstancesEthers.ts new file mode 100644 index 000000000..305616468 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/contractInstancesEthers.ts @@ -0,0 +1,78 @@ +import { Signer } from '@ethersproject/abstract-signer' +import { GnosisSafe__factory as SafeMasterCopy_V1_1_1 } from '../../typechain/src/ethers-v5/v1.1.1/factories/GnosisSafe__factory' +import { MultiSend__factory as MultiSend_V1_1_1 } from '../../typechain/src/ethers-v5/v1.1.1/factories/MultiSend__factory' +import { ProxyFactory__factory as SafeProxyFactory_V1_1_1 } from '../../typechain/src/ethers-v5/v1.1.1/factories/ProxyFactory__factory' +import { GnosisSafe__factory as SafeMasterCopy_V1_2_0 } from '../../typechain/src/ethers-v5/v1.2.0/factories/GnosisSafe__factory' +import { GnosisSafe__factory as SafeMasterCopy_V1_3_0 } from '../../typechain/src/ethers-v5/v1.3.0/factories/GnosisSafe__factory' +import { MultiSend__factory as MultiSend_V1_3_0 } from '../../typechain/src/ethers-v5/v1.3.0/factories/MultiSend__factory' +import { ProxyFactory__factory as SafeProxyFactory_V1_3_0 } from '../../typechain/src/ethers-v5/v1.3.0/factories/ProxyFactory__factory' +import { SafeVersion } from './config' +import GnosisSafeContract_V1_1_1_Ethers from './GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Ethers' +import GnosisSafeContract_V1_2_0_Ethers from './GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Ethers' +import GnosisSafeContract_V1_3_0_Ethers from './GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Ethers' +import GnosisSafeProxyFactoryContract_V1_1_1_Ethers from './GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Ethers' +import GnosisSafeProxyFactoryContract_V1_3_0_Ethers from './GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Ethers' +import MultiSendContract_V1_1_1_Ethers from './MultiSend/v1.1.1/MultiSendContract_V1_1_1_Ethers' +import MultiSendContract_V1_3_0_Ethers from './MultiSend/v1.3.0/MultiSendContract_V1_3_0_Ethers' + +export function getSafeContractInstance( + safeVersion: SafeVersion, + contractAddress: string, + signer: Signer +): + | GnosisSafeContract_V1_3_0_Ethers + | GnosisSafeContract_V1_2_0_Ethers + | GnosisSafeContract_V1_1_1_Ethers { + let safeContract + switch (safeVersion) { + case '1.3.0': + safeContract = SafeMasterCopy_V1_3_0.connect(contractAddress, signer) + return new GnosisSafeContract_V1_3_0_Ethers(safeContract) + case '1.2.0': + safeContract = SafeMasterCopy_V1_2_0.connect(contractAddress, signer) + return new GnosisSafeContract_V1_2_0_Ethers(safeContract) + case '1.1.1': + safeContract = SafeMasterCopy_V1_1_1.connect(contractAddress, signer) + return new GnosisSafeContract_V1_1_1_Ethers(safeContract) + default: + throw new Error('Invalid Safe version') + } +} + +export function getMultiSendContractInstance( + safeVersion: SafeVersion, + contractAddress: string, + signer: Signer +): MultiSendContract_V1_3_0_Ethers | MultiSendContract_V1_1_1_Ethers { + let multiSendContract + switch (safeVersion) { + case '1.3.0': + multiSendContract = MultiSend_V1_3_0.connect(contractAddress, signer) + return new MultiSendContract_V1_3_0_Ethers(multiSendContract) + case '1.2.0': + case '1.1.1': + multiSendContract = MultiSend_V1_1_1.connect(contractAddress, signer) + return new MultiSendContract_V1_1_1_Ethers(multiSendContract) + default: + throw new Error('Invalid Safe version') + } +} + +export function getSafeProxyFactoryContractInstance( + safeVersion: SafeVersion, + contractAddress: string, + signer: Signer +): GnosisSafeProxyFactoryContract_V1_3_0_Ethers | GnosisSafeProxyFactoryContract_V1_1_1_Ethers { + let gnosisSafeProxyFactoryContract + switch (safeVersion) { + case '1.3.0': + gnosisSafeProxyFactoryContract = SafeProxyFactory_V1_3_0.connect(contractAddress, signer) + return new GnosisSafeProxyFactoryContract_V1_3_0_Ethers(gnosisSafeProxyFactoryContract) + case '1.2.0': + case '1.1.1': + gnosisSafeProxyFactoryContract = SafeProxyFactory_V1_1_1.connect(contractAddress, signer) + return new GnosisSafeProxyFactoryContract_V1_1_1_Ethers(gnosisSafeProxyFactoryContract) + default: + throw new Error('Invalid Safe version') + } +} diff --git a/packages/safe-core-sdk/src/contracts/contractInstancesWeb3.ts b/packages/safe-core-sdk/src/contracts/contractInstancesWeb3.ts new file mode 100644 index 000000000..a5fd4009d --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/contractInstancesWeb3.ts @@ -0,0 +1,68 @@ +import { GnosisSafe as SafeMasterCopy_V1_1_1 } from '../../typechain/src/web3-v1/v1.1.1/gnosis_safe' +import { MultiSend as MultiSend_V1_1_1 } from '../../typechain/src/web3-v1/v1.1.1/multi_send' +import { ProxyFactory as GnosisSafeProxyFactory_V1_1_1 } from '../../typechain/src/web3-v1/v1.1.1/proxy_factory' +import { GnosisSafe as SafeMasterCopy_V1_2_0 } from '../../typechain/src/web3-v1/v1.2.0/gnosis_safe' +import { GnosisSafe as SafeMasterCopy_V1_3_0 } from '../../typechain/src/web3-v1/v1.3.0/gnosis_safe' +import { MultiSend as MultiSend_V1_3_0 } from '../../typechain/src/web3-v1/v1.3.0/multi_send' +import { ProxyFactory as GnosisSafeProxyFactory_V1_3_0 } from '../../typechain/src/web3-v1/v1.3.0/proxy_factory' +import { SafeVersion } from './config' +import GnosisSafeContract_V1_1_1_Web3 from './GnosisSafe/v1.1.1/GnosisSafeContract_V1_1_1_Web3' +import GnosisSafeContract_V1_2_0_Web3 from './GnosisSafe/v1.2.0/GnosisSafeContract_V1_2_0_Web3' +import GnosisSafeContract_V1_3_0_Web3 from './GnosisSafe/v1.3.0/GnosisSafeContract_V1_3_0_Web3' +import GnosisSafeProxyFactoryContract_V1_1_1_Web3 from './GnosisSafeProxyFactory/v1.1.1/GnosisSafeProxyFactoryContract_V1_1_1_Web3' +import GnosisSafeProxyFactoryContract_V1_3_0_Web3 from './GnosisSafeProxyFactory/v1.3.0/GnosisSafeProxyFactoryContract_V1_3_0_Web3' +import MultiSendContract_V1_1_1_Web3 from './MultiSend/v1.1.1/MultiSendContract_V1_1_1_Web3' +import MultiSendContract_V1_3_0_Web3 from './MultiSend/v1.3.0/MultiSendContract_V1_3_0_Web3' + +export function getSafeContractInstance( + safeVersion: SafeVersion, + safeContract: SafeMasterCopy_V1_3_0 | SafeMasterCopy_V1_2_0 | SafeMasterCopy_V1_1_1 +): + | GnosisSafeContract_V1_3_0_Web3 + | GnosisSafeContract_V1_2_0_Web3 + | GnosisSafeContract_V1_1_1_Web3 { + switch (safeVersion) { + case '1.3.0': + return new GnosisSafeContract_V1_3_0_Web3(safeContract as SafeMasterCopy_V1_3_0) + case '1.2.0': + return new GnosisSafeContract_V1_2_0_Web3(safeContract as SafeMasterCopy_V1_2_0) + case '1.1.1': + return new GnosisSafeContract_V1_1_1_Web3(safeContract as SafeMasterCopy_V1_1_1) + default: + throw new Error('Invalid Safe version') + } +} + +export function getMultiSendContractInstance( + safeVersion: SafeVersion, + multiSendContract: MultiSend_V1_3_0 | MultiSend_V1_1_1 +): MultiSendContract_V1_3_0_Web3 | MultiSendContract_V1_1_1_Web3 { + switch (safeVersion) { + case '1.3.0': + return new MultiSendContract_V1_3_0_Web3(multiSendContract as MultiSend_V1_3_0) + case '1.2.0': + case '1.1.1': + return new MultiSendContract_V1_1_1_Web3(multiSendContract as MultiSend_V1_1_1) + default: + throw new Error('Invalid Safe version') + } +} + +export function getGnosisSafeProxyFactoryContractInstance( + safeVersion: SafeVersion, + gnosisSafeProxyFactoryContract: GnosisSafeProxyFactory_V1_3_0 | GnosisSafeProxyFactory_V1_1_1 +): GnosisSafeProxyFactoryContract_V1_3_0_Web3 | GnosisSafeProxyFactoryContract_V1_1_1_Web3 { + switch (safeVersion) { + case '1.3.0': + return new GnosisSafeProxyFactoryContract_V1_3_0_Web3( + gnosisSafeProxyFactoryContract as GnosisSafeProxyFactory_V1_3_0 + ) + case '1.2.0': + case '1.1.1': + return new GnosisSafeProxyFactoryContract_V1_1_1_Web3( + gnosisSafeProxyFactoryContract as GnosisSafeProxyFactory_V1_1_1 + ) + default: + throw new Error('Invalid Safe version') + } +} diff --git a/packages/safe-core-sdk/src/contracts/safeDeploymentContracts.ts b/packages/safe-core-sdk/src/contracts/safeDeploymentContracts.ts new file mode 100644 index 000000000..3d93f7417 --- /dev/null +++ b/packages/safe-core-sdk/src/contracts/safeDeploymentContracts.ts @@ -0,0 +1,38 @@ +import { + DeploymentFilter, + getMultiSendDeployment, + getProxyFactoryDeployment, + getSafeL2SingletonDeployment, + getSafeSingletonDeployment, + SingletonDeployment +} from '@gnosis.pm/safe-deployments' +import { safeDeploymentsL1ChainIds, safeDeploymentsVersions, SafeVersion } from './config' + +export function getSafeContractDeployment( + safeVersion: SafeVersion, + chainId: number, + isL1SafeMasterCopy: boolean = false +): SingletonDeployment | undefined { + const version = safeDeploymentsVersions[safeVersion].safeMasterCopyVersion + const filters: DeploymentFilter = { version, network: chainId.toString(), released: true } + if (safeDeploymentsL1ChainIds.includes(chainId) || isL1SafeMasterCopy) { + return getSafeSingletonDeployment(filters) + } + return getSafeL2SingletonDeployment(filters) +} + +export function getMultiSendContractDeployment( + safeVersion: SafeVersion, + chainId: number +): SingletonDeployment | undefined { + const version = safeDeploymentsVersions[safeVersion].multiSendVersion + return getMultiSendDeployment({ version, network: chainId.toString(), released: true }) +} + +export function getSafeProxyFactoryContractDeployment( + safeVersion: SafeVersion, + chainId: number +): SingletonDeployment | undefined { + const version = safeDeploymentsVersions[safeVersion].safeProxyFactoryVersion + return getProxyFactoryDeployment({ version, network: chainId.toString(), released: true }) +} diff --git a/packages/safe-core-sdk/src/ethereumLibs/EthAdapter.ts b/packages/safe-core-sdk/src/ethereumLibs/EthAdapter.ts index b671e9a9f..4aaba31cb 100644 --- a/packages/safe-core-sdk/src/ethereumLibs/EthAdapter.ts +++ b/packages/safe-core-sdk/src/ethereumLibs/EthAdapter.ts @@ -1,4 +1,5 @@ import { BigNumber } from '@ethersproject/bignumber' +import { SafeVersion } from '../contracts/config' import GnosisSafeContract from '../contracts/GnosisSafe/GnosisSafeContract' import GnosisSafeProxyFactoryContract from '../contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryContract' import MultiSendContract from '../contracts/MultiSend/MultiSendContract' @@ -13,9 +14,11 @@ export interface EthAdapterTransaction { gasLimit?: number } -export interface GnosisSafeContracts { - gnosisSafeContract: GnosisSafeContract - multiSendContract: MultiSendContract +export interface GetSafeContractProps { + safeVersion: SafeVersion + chainId: number + isL1SafeMasterCopy?: boolean + customContractAddress?: string } interface EthAdapter { @@ -23,11 +26,22 @@ interface EthAdapter { getBalance(address: string): Promise getChainId(): Promise getContract(address: string, abi: AbiItem[]): any - getSafeContract(safeAddress: string): Promise - getMultiSendContract(multiSendAddress: string): Promise - getGnosisSafeProxyFactoryContract( - proxyFactoryAddress: string - ): Promise + getSafeContract({ + safeVersion, + chainId, + isL1SafeMasterCopy, + customContractAddress + }: GetSafeContractProps): GnosisSafeContract + getMultiSendContract( + safeVersion: SafeVersion, + chainId: number, + customContractAddress?: string + ): MultiSendContract + getSafeProxyFactoryContract( + safeVersion: SafeVersion, + chainId: number, + customContractAddress?: string + ): GnosisSafeProxyFactoryContract getContractCode(address: string): Promise getTransaction(transactionHash: string): Promise getSignerAddress(): Promise diff --git a/packages/safe-core-sdk/src/ethereumLibs/EthersAdapter.ts b/packages/safe-core-sdk/src/ethereumLibs/EthersAdapter.ts index 71a4345e9..615cb55ba 100644 --- a/packages/safe-core-sdk/src/ethereumLibs/EthersAdapter.ts +++ b/packages/safe-core-sdk/src/ethereumLibs/EthersAdapter.ts @@ -3,14 +3,22 @@ import { Signer } from '@ethersproject/abstract-signer' import { BigNumber } from '@ethersproject/bignumber' import { Contract } from '@ethersproject/contracts' import { Provider } from '@ethersproject/providers' -import { GnosisSafeProxyFactory__factory } from '../../typechain/src/ethers-v5/factories/GnosisSafeProxyFactory__factory' -import { GnosisSafe__factory } from '../../typechain/src/ethers-v5/factories/GnosisSafe__factory' -import { MultiSend__factory } from '../../typechain/src/ethers-v5/factories/MultiSend__factory' -import GnosisSafeEthersV5Contract from '../contracts/GnosisSafe/GnosisSafeEthersV5Contract' -import GnosisSafeProxyFactoryEthersV5Contract from '../contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersV5Contract' -import MultiSendEthersV5Contract from '../contracts/MultiSend/MultiSendEthersV5Contract' +import { SafeVersion } from '../contracts/config' +import { + getMultiSendContractInstance, + getSafeContractInstance, + getSafeProxyFactoryContractInstance +} from '../contracts/contractInstancesEthers' +import GnosisSafeContractEthers from '../contracts/GnosisSafe/GnosisSafeContractEthers' +import GnosisSafeProxyFactoryEthersContract from '../contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryEthersContract' +import MultiSendEthersContract from '../contracts/MultiSend/MultiSendEthersContract' +import { + getMultiSendContractDeployment, + getSafeContractDeployment, + getSafeProxyFactoryContractDeployment +} from '../contracts/safeDeploymentContracts' import { AbiItem } from '../types' -import EthAdapter, { EthAdapterTransaction } from './EthAdapter' +import EthAdapter, { EthAdapterTransaction, GetSafeContractProps } from './EthAdapter' export interface EthersAdapterConfig { /** ethers - Ethers v5 library */ @@ -56,41 +64,63 @@ class EthersAdapter implements EthAdapter { return (await this.#provider.getNetwork()).chainId } - async getSafeContract(safeAddress: string): Promise { - const safeContractCode = await this.getContractCode(safeAddress) - if (safeContractCode === '0x') { - throw new Error('Safe Proxy contract is not deployed in the current network') + getSafeContract({ + safeVersion, + chainId, + isL1SafeMasterCopy, + customContractAddress + }: GetSafeContractProps): GnosisSafeContractEthers { + let contractAddress: string | undefined + if (customContractAddress) { + contractAddress = customContractAddress + } else { + const safeSingletonDeployment = getSafeContractDeployment( + safeVersion, + chainId, + isL1SafeMasterCopy + ) + contractAddress = safeSingletonDeployment?.networkAddresses[chainId] } - const safeContract = GnosisSafe__factory.connect(safeAddress, this.#signer) - const wrapperSafeContract = new GnosisSafeEthersV5Contract(safeContract) - return wrapperSafeContract - } - - async getMultiSendContract(multiSendAddress: string): Promise { - const multiSendContractCode = await this.getContractCode(multiSendAddress) - if (multiSendContractCode === '0x') { - throw new Error('MultiSend contract is not deployed in the current network') + if (!contractAddress) { + throw new Error('Invalid Safe Proxy contract address') } - const multiSendContract = MultiSend__factory.connect(multiSendAddress, this.#signer) - const wrappedMultiSendContract = new MultiSendEthersV5Contract(multiSendContract) - return wrappedMultiSendContract - } - - async getGnosisSafeProxyFactoryContract( - proxyFactoryAddress: string - ): Promise { - const proxyFactoryContractCode = await this.getContractCode(proxyFactoryAddress) - if (proxyFactoryContractCode === '0x') { - throw new Error('Safe Proxy Factory contract is not deployed in the current network') + return getSafeContractInstance(safeVersion, contractAddress, this.#signer) + } + + getMultiSendContract( + safeVersion: SafeVersion, + chainId: number, + customContractAddress?: string + ): MultiSendEthersContract { + let contractAddress: string | undefined + if (customContractAddress) { + contractAddress = customContractAddress + } else { + const multiSendDeployment = getMultiSendContractDeployment(safeVersion, chainId) + contractAddress = multiSendDeployment?.networkAddresses[chainId] + } + if (!contractAddress) { + throw new Error('Invalid Multi Send contract address') + } + return getMultiSendContractInstance(safeVersion, contractAddress, this.#signer) + } + + getSafeProxyFactoryContract( + safeVersion: SafeVersion, + chainId: number, + customContractAddress?: string + ): GnosisSafeProxyFactoryEthersContract { + let contractAddress: string | undefined + if (customContractAddress) { + contractAddress = customContractAddress + } else { + const proxyFactoryDeployment = getSafeProxyFactoryContractDeployment(safeVersion, chainId) + contractAddress = proxyFactoryDeployment?.networkAddresses[chainId] + } + if (!contractAddress) { + throw new Error('Invalid Safe Proxy Factory contract address') } - const proxyFactoryContract = GnosisSafeProxyFactory__factory.connect( - proxyFactoryAddress, - this.#signer - ) - const wrappedProxyFactoryContract = new GnosisSafeProxyFactoryEthersV5Contract( - proxyFactoryContract - ) - return wrappedProxyFactoryContract + return getSafeProxyFactoryContractInstance(safeVersion, contractAddress, this.#signer) } getContract(address: string, abi: AbiItem[]): Contract { diff --git a/packages/safe-core-sdk/src/ethereumLibs/Web3Adapter.ts b/packages/safe-core-sdk/src/ethereumLibs/Web3Adapter.ts index 58f6e09f6..8d78986af 100644 --- a/packages/safe-core-sdk/src/ethereumLibs/Web3Adapter.ts +++ b/packages/safe-core-sdk/src/ethereumLibs/Web3Adapter.ts @@ -1,15 +1,20 @@ import { BigNumber } from '@ethersproject/bignumber' -import { GnosisSafe } from '../../typechain/src/web3-v1/GnosisSafe' -import { GnosisSafeProxyFactory } from '../../typechain/src/web3-v1/GnosisSafeProxyFactory' -import { MultiSend } from '../../typechain/src/web3-v1/MultiSend' -import GnosisSafeWeb3Contract from '../contracts/GnosisSafe/GnosisSafeWeb3Contract' -import SafeAbiV120 from '../contracts/GnosisSafe/SafeAbiV1-2-0.json' -import GnosisSafeProxyFactoryAbiV120 from '../contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryAbiV1-2-0.json' +import { SafeVersion } from '../contracts/config' +import { + getGnosisSafeProxyFactoryContractInstance, + getMultiSendContractInstance, + getSafeContractInstance +} from '../contracts/contractInstancesWeb3' +import GnosisSafeContractWeb3 from '../contracts/GnosisSafe/GnosisSafeContractWeb3' import GnosisSafeProxyFactoryWeb3Contract from '../contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryWeb3Contract' -import MultiSendAbi from '../contracts/MultiSend/MultiSendAbi.json' import MultiSendWeb3Contract from '../contracts/MultiSend/MultiSendWeb3Contract' +import { + getMultiSendContractDeployment, + getSafeContractDeployment, + getSafeProxyFactoryContractDeployment +} from '../contracts/safeDeploymentContracts' import { AbiItem } from '../types' -import EthAdapter, { EthAdapterTransaction } from './EthAdapter' +import EthAdapter, { EthAdapterTransaction, GetSafeContractProps } from './EthAdapter' export interface Web3AdapterConfig { /** web3 - Web3 library */ @@ -42,43 +47,66 @@ class Web3Adapter implements EthAdapter { return this.#web3.eth.getChainId() } - async getContract(address: string, abi: AbiItem[]): Promise { - return new this.#web3.eth.Contract(abi, address) + getSafeContract({ + safeVersion, + chainId, + isL1SafeMasterCopy, + customContractAddress + }: GetSafeContractProps): GnosisSafeContractWeb3 { + const safeSingletonDeployment = getSafeContractDeployment( + safeVersion, + chainId, + isL1SafeMasterCopy + ) + const contractAddress = + customContractAddress ?? safeSingletonDeployment?.networkAddresses[chainId] + if (!contractAddress) { + throw new Error('Invalid Safe Proxy contract address') + } + const safeContract = this.getContract( + contractAddress, + safeSingletonDeployment?.abi as AbiItem[] + ) + return getSafeContractInstance(safeVersion, safeContract) } - async getSafeContract(safeAddress: string): Promise { - const safeContractCode = await this.getContractCode(safeAddress) - if (safeContractCode === '0x') { - throw new Error('Safe Proxy contract is not deployed in the current network') + getMultiSendContract( + safeVersion: SafeVersion, + chainId: number, + customContractAddress?: string + ): MultiSendWeb3Contract { + const multiSendDeployment = getMultiSendContractDeployment(safeVersion, chainId) + const contractAddress = customContractAddress ?? multiSendDeployment?.networkAddresses[chainId] + if (!contractAddress) { + throw new Error('Invalid Multi Send contract addresss') } - const safeContract = (await this.getContract(safeAddress, SafeAbiV120)) as GnosisSafe - const wrapperSafeContract = new GnosisSafeWeb3Contract(safeContract) - return wrapperSafeContract + const multiSendContract = this.getContract( + contractAddress, + multiSendDeployment?.abi as AbiItem[] + ) + return getMultiSendContractInstance(safeVersion, multiSendContract) } - async getMultiSendContract(multiSendAddress: string): Promise { - const multiSendContractCode = await this.getContractCode(multiSendAddress) - if (multiSendContractCode === '0x') { - throw new Error('MultiSend contract is not deployed in the current network') + getSafeProxyFactoryContract( + safeVersion: SafeVersion, + chainId: number, + customContractAddress?: string + ): GnosisSafeProxyFactoryWeb3Contract { + const proxyFactoryDeployment = getSafeProxyFactoryContractDeployment(safeVersion, chainId) + const contractAddress = + customContractAddress ?? proxyFactoryDeployment?.networkAddresses[chainId] + if (!contractAddress) { + throw new Error('Invalid Safe Proxy Factory contract address') } - const multiSendContract = (await this.getContract(multiSendAddress, MultiSendAbi)) as MultiSend - const wrappedMultiSendContract = new MultiSendWeb3Contract(multiSendContract) - return wrappedMultiSendContract + const proxyFactoryContract = this.getContract( + contractAddress, + proxyFactoryDeployment?.abi as AbiItem[] + ) + return getGnosisSafeProxyFactoryContractInstance(safeVersion, proxyFactoryContract) } - async getGnosisSafeProxyFactoryContract( - proxyFactoryAddress: string - ): Promise { - const proxyFactoryContractCode = await this.getContractCode(proxyFactoryAddress) - if (proxyFactoryContractCode === '0x') { - throw new Error('Safe Proxy Factory contract is not deployed in the current network') - } - const proxyFactoryContract = (await this.getContract( - proxyFactoryAddress, - GnosisSafeProxyFactoryAbiV120 - )) as GnosisSafeProxyFactory - const wrappedProxyFactoryContract = new GnosisSafeProxyFactoryWeb3Contract(proxyFactoryContract) - return wrappedProxyFactoryContract + getContract(address: string, abi: AbiItem[]): any { + return new this.#web3.eth.Contract(abi, address) } async getContractCode(address: string): Promise { diff --git a/packages/safe-core-sdk/src/index.ts b/packages/safe-core-sdk/src/index.ts index 4552edb1c..ad1aad542 100644 --- a/packages/safe-core-sdk/src/index.ts +++ b/packages/safe-core-sdk/src/index.ts @@ -1,4 +1,4 @@ -import { ContractNetworksConfig } from './configuration/contracts' +import { SafeVersion } from './contracts/config' import EthAdapter, { EthAdapterTransaction } from './ethereumLibs/EthAdapter' import EthersAdapter, { EthersAdapterConfig } from './ethereumLibs/EthersAdapter' import Web3Adapter, { Web3AdapterConfig } from './ethereumLibs/Web3Adapter' @@ -14,6 +14,7 @@ import SafeFactory, { SafeDeploymentConfig, SafeFactoryConfig } from './safeFactory' +import { ContractNetworksConfig } from './types' import EthSignSignature from './utils/signatures/SafeSignature' import { SafeTransactionOptionalProps, @@ -23,6 +24,7 @@ import { export default Safe export { + SafeVersion, SafeFactory, SafeFactoryConfig, SafeAccountConfig, diff --git a/packages/safe-core-sdk/src/managers/contractManager.ts b/packages/safe-core-sdk/src/managers/contractManager.ts index 20bf4e2e8..e53d2613e 100644 --- a/packages/safe-core-sdk/src/managers/contractManager.ts +++ b/packages/safe-core-sdk/src/managers/contractManager.ts @@ -1,43 +1,77 @@ -import { ContractNetworksConfig, defaultContractNetworks } from '../configuration/contracts' +import { SafeVersion, SAFE_BASE_VERSION } from '../contracts/config' import GnosisSafeContract from '../contracts/GnosisSafe/GnosisSafeContract' import MultiSendContract from '../contracts/MultiSend/MultiSendContract' -import EthAdapter from '../ethereumLibs/EthAdapter' +import { SafeConfig } from '../Safe' +import { ContractNetworksConfig } from '../types' class ContractManager { - #contractNetworks!: ContractNetworksConfig + #contractNetworks?: ContractNetworksConfig + #isL1SafeMasterCopy?: boolean #safeContract!: GnosisSafeContract #multiSendContract!: MultiSendContract - static async create( - ethAdapter: EthAdapter, - safeAddress: string, - contractNetworks?: ContractNetworksConfig - ): Promise { + static async create({ + ethAdapter, + safeAddress, + isL1SafeMasterCopy, + contractNetworks + }: SafeConfig): Promise { const contractManager = new ContractManager() - await contractManager.init(ethAdapter, safeAddress, contractNetworks) + await contractManager.init({ ethAdapter, safeAddress, isL1SafeMasterCopy, contractNetworks }) return contractManager } - async init( - ethAdapter: EthAdapter, - safeAddress: string, - contractNetworks?: ContractNetworksConfig - ): Promise { + async init({ + ethAdapter, + safeAddress, + isL1SafeMasterCopy, + contractNetworks + }: SafeConfig): Promise { const chainId = await ethAdapter.getChainId() - const contractNetworksConfig = { ...defaultContractNetworks, ...contractNetworks } - const contracts = contractNetworksConfig[chainId] - if (!contracts) { - throw new Error('Safe contracts not found in the current network') + const temporarySafeContract = ethAdapter.getSafeContract({ + safeVersion: SAFE_BASE_VERSION, + chainId, + isL1SafeMasterCopy, + customContractAddress: safeAddress + }) + if ((await ethAdapter.getContractCode(temporarySafeContract.getAddress())) === '0x') { + throw new Error('Safe Proxy contract is not deployed in the current network') } - this.#contractNetworks = contractNetworksConfig - this.#safeContract = await ethAdapter.getSafeContract(safeAddress) - this.#multiSendContract = await ethAdapter.getMultiSendContract(contracts.multiSendAddress) + const safeVersion = (await temporarySafeContract.getVersion()) as SafeVersion + + const customContracts = contractNetworks?.[chainId] + this.#contractNetworks = contractNetworks + this.#isL1SafeMasterCopy = isL1SafeMasterCopy + const safeContract = ethAdapter.getSafeContract({ + safeVersion, + chainId, + isL1SafeMasterCopy, + customContractAddress: safeAddress + }) + if ((await ethAdapter.getContractCode(safeContract.getAddress())) === '0x') { + throw new Error('Safe Proxy contract is not deployed in the current network') + } + this.#safeContract = safeContract + + const multiSendContract = await ethAdapter.getMultiSendContract( + safeVersion, + chainId, + customContracts?.multiSendAddress + ) + if ((await ethAdapter.getContractCode(multiSendContract.getAddress())) === '0x') { + throw new Error('Multi Send contract is not deployed in the current network') + } + this.#multiSendContract = multiSendContract } - get contractNetworks(): ContractNetworksConfig { + get contractNetworks(): ContractNetworksConfig | undefined { return this.#contractNetworks } + get isL1SafeMasterCopy(): boolean | undefined { + return this.#isL1SafeMasterCopy + } + get safeContract(): GnosisSafeContract { return this.#safeContract } diff --git a/packages/safe-core-sdk/src/safeFactory/index.ts b/packages/safe-core-sdk/src/safeFactory/index.ts index f713528e8..390420e59 100644 --- a/packages/safe-core-sdk/src/safeFactory/index.ts +++ b/packages/safe-core-sdk/src/safeFactory/index.ts @@ -1,8 +1,9 @@ -import { ContractNetworksConfig, defaultContractNetworks } from '../configuration/contracts' +import { SafeVersion, SAFE_LAST_VERSION } from '../contracts/config' import GnosisSafeContract from '../contracts/GnosisSafe/GnosisSafeContract' import GnosisSafeProxyFactoryContract from '../contracts/GnosisSafeProxyFactory/GnosisSafeProxyFactoryContract' import EthAdapter from '../ethereumLibs/EthAdapter' import Safe from '../Safe' +import { ContractNetworksConfig } from '../types' import { EMPTY_DATA, ZERO_ADDRESS } from '../utils/constants' import { validateSafeAccountConfig } from './utils' @@ -24,42 +25,90 @@ export interface SafeDeploymentConfig { export interface SafeFactoryConfig { /** ethAdapter - Ethereum adapter */ ethAdapter: EthAdapter + /** safeVersion - Versions of the Safe deployed by this Factory contract */ + safeVersion?: SafeVersion + /** isL1SafeMasterCopy - Forces to use the Gnosis Safe L1 version of the contract instead of the L2 version */ + isL1SafeMasterCopy?: boolean + /** contractNetworks - Contract network configuration */ + contractNetworks?: ContractNetworksConfig +} + +interface SafeFactoryInitConfig { + /** ethAdapter - Ethereum adapter */ + ethAdapter: EthAdapter + /** safeVersion - Versions of the Safe deployed by this Factory contract */ + safeVersion: SafeVersion + /** isL1SafeMasterCopy - Forces to use the Gnosis Safe L1 version of the contract instead of the L2 version */ + isL1SafeMasterCopy?: boolean /** contractNetworks - Contract network configuration */ contractNetworks?: ContractNetworksConfig } class SafeFactory { - #contractNetworks!: ContractNetworksConfig + #contractNetworks?: ContractNetworksConfig + #isL1SafeMasterCopy?: boolean + #safeVersion!: SafeVersion #ethAdapter!: EthAdapter #safeProxyFactoryContract!: GnosisSafeProxyFactoryContract #gnosisSafeContract!: GnosisSafeContract - static async create({ ethAdapter, contractNetworks }: SafeFactoryConfig): Promise { + static async create({ + ethAdapter, + safeVersion = SAFE_LAST_VERSION, + isL1SafeMasterCopy = false, + contractNetworks + }: SafeFactoryConfig): Promise { const safeFactorySdk = new SafeFactory() - await safeFactorySdk.init({ ethAdapter, contractNetworks }) + await safeFactorySdk.init({ ethAdapter, safeVersion, isL1SafeMasterCopy, contractNetworks }) return safeFactorySdk } - private async init({ ethAdapter, contractNetworks }: SafeFactoryConfig): Promise { + private async init({ + ethAdapter, + safeVersion, + isL1SafeMasterCopy, + contractNetworks + }: SafeFactoryInitConfig): Promise { this.#ethAdapter = ethAdapter - this.#contractNetworks = { ...defaultContractNetworks, ...contractNetworks } + this.#safeVersion = safeVersion + this.#isL1SafeMasterCopy = isL1SafeMasterCopy + this.#contractNetworks = contractNetworks const chainId = await this.#ethAdapter.getChainId() - const contractNetworksConfig = this.#contractNetworks[chainId] - if (!contractNetworksConfig) { - throw new Error('Safe contracts not found in the current network') - } - this.#safeProxyFactoryContract = await ethAdapter.getGnosisSafeProxyFactoryContract( - this.#contractNetworks[chainId].safeProxyFactoryAddress - ) - this.#gnosisSafeContract = await ethAdapter.getSafeContract( - contractNetworksConfig.safeMasterCopyAddress + const customContracts = contractNetworks?.[chainId] + const safeProxyFactoryContract = await ethAdapter.getSafeProxyFactoryContract( + this.#safeVersion, + chainId, + customContracts?.safeProxyFactoryAddress ) + if ((await this.#ethAdapter.getContractCode(safeProxyFactoryContract.getAddress())) === '0x') { + throw new Error('Safe Proxy Factory contract is not deployed in the current network') + } + this.#safeProxyFactoryContract = safeProxyFactoryContract + + const gnosisSafeContract = ethAdapter.getSafeContract({ + safeVersion: this.#safeVersion, + chainId, + isL1SafeMasterCopy, + customContractAddress: customContracts?.safeMasterCopyAddress + }) + if ((await this.#ethAdapter.getContractCode(gnosisSafeContract.getAddress())) === '0x') { + throw new Error('Safe Proxy contract is not deployed in the current network') + } + this.#gnosisSafeContract = gnosisSafeContract } getEthAdapter(): EthAdapter { return this.#ethAdapter } + getSafeVersion(): SafeVersion { + return this.#safeVersion + } + + getAddress(): string { + return this.#safeProxyFactoryContract.getAddress() + } + async getChainId(): Promise { return this.#ethAdapter.getChainId() } @@ -86,15 +135,12 @@ class SafeFactory { ]) } - getAddress() { - return this.#safeProxyFactoryContract.getAddress() - } - async deploySafe( safeAccountConfig: SafeAccountConfig, safeDeploymentConfig?: SafeDeploymentConfig ): Promise { validateSafeAccountConfig(safeAccountConfig) + const chainId = await this.#ethAdapter.getChainId() const signerAddress = await this.#ethAdapter.getSignerAddress() const initializer = await this.encodeSetupCallData(safeAccountConfig) const safeAddress = await this.#safeProxyFactoryContract.createProxy({ @@ -103,10 +149,19 @@ class SafeFactory { saltNonce: safeDeploymentConfig?.saltNonce, options: { from: signerAddress } }) - const safeContract = await this.#ethAdapter.getSafeContract(safeAddress) - const safe = Safe.create({ + const safeContract = await this.#ethAdapter.getSafeContract({ + safeVersion: this.#safeVersion, + chainId, + isL1SafeMasterCopy: this.#isL1SafeMasterCopy, + customContractAddress: safeAddress + }) + if ((await this.#ethAdapter.getContractCode(safeContract.getAddress())) === '0x') { + throw new Error('Safe Proxy contract is not deployed in the current network') + } + const safe = await Safe.create({ ethAdapter: this.#ethAdapter, safeAddress: safeContract.getAddress(), + isL1SafeMasterCopy: this.#isL1SafeMasterCopy, contractNetworks: this.#contractNetworks }) return safe diff --git a/packages/safe-core-sdk/src/types/index.ts b/packages/safe-core-sdk/src/types/index.ts index b2e45b434..d75107122 100644 --- a/packages/safe-core-sdk/src/types/index.ts +++ b/packages/safe-core-sdk/src/types/index.ts @@ -24,3 +24,17 @@ export interface AbiItem { type: string gas?: number } + +export interface ContractNetworkConfig { + /** multiSendAddress - Address of the MultiSend contract deployed on a specific network */ + multiSendAddress: string + /** safeMasterCopyAddress - Address of the Gnosis Safe Master Copy contract deployed on a specific network */ + safeMasterCopyAddress: string + /** safeProxyFactoryAddress - Address of the Gnosis Safe Proxy Factory contract deployed on a specific network */ + safeProxyFactoryAddress: string +} + +export interface ContractNetworksConfig { + /** id - Network id */ + [id: string]: ContractNetworkConfig +} diff --git a/packages/safe-core-sdk/tests/contractManager.test.ts b/packages/safe-core-sdk/tests/contractManager.test.ts index cf1a7fb02..f46339afb 100644 --- a/packages/safe-core-sdk/tests/contractManager.test.ts +++ b/packages/safe-core-sdk/tests/contractManager.test.ts @@ -46,7 +46,7 @@ describe('Safe contracts manager', () => { safeAddress: safe.address }) ) - .to.be.rejectedWith('Safe contracts not found in the current network') + .to.be.rejectedWith('Invalid Multi Send contract') }) it('should fail if Safe Proxy contract is not deployed in the current network', async () => { @@ -65,7 +65,7 @@ describe('Safe contracts manager', () => { }) it('should fail if MultiSend contract is specified in contractNetworks but not deployed', async () => { - const { safe, accounts, chainId, contractNetworks } = await setupTests() + const { safe, accounts, chainId } = await setupTests() const customContractNetworks: ContractNetworksConfig = { [chainId]: { multiSendAddress: ZERO_ADDRESS, @@ -83,7 +83,7 @@ describe('Safe contracts manager', () => { contractNetworks: customContractNetworks }) ) - .to.be.rejectedWith('MultiSend contract is not deployed in the current network') + .to.be.rejectedWith('Multi Send contract is not deployed in the current network') }) it('should set the MultiSend contract available in the current network', async () => { diff --git a/packages/safe-core-sdk/tests/core.test.ts b/packages/safe-core-sdk/tests/core.test.ts index 880329b5e..264ffa63d 100644 --- a/packages/safe-core-sdk/tests/core.test.ts +++ b/packages/safe-core-sdk/tests/core.test.ts @@ -3,6 +3,7 @@ import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' +import { safeVersionDeployed } from '../hardhat/deploy/deploy-contracts' import Safe, { ContractNetworksConfig } from '../src' import { getFactory, @@ -16,7 +17,7 @@ import { waitSafeTxReceipt } from './utils/transactions' chai.use(chaiAsPromised) -describe('Safe Core SDK', () => { +describe('Safe Info', () => { const setupTests = deployments.createFixture(async ({ deployments }) => { await deployments.fixture() const accounts = await getAccounts() @@ -78,7 +79,7 @@ describe('Safe Core SDK', () => { contractNetworks }) const contractVersion = await safeSdk.getContractVersion() - chai.expect(contractVersion).to.be.eq('1.2.0') + chai.expect(contractVersion).to.be.eq(safeVersionDeployed) }) }) diff --git a/packages/safe-core-sdk/tests/ethAdapters.test.ts b/packages/safe-core-sdk/tests/ethAdapters.test.ts new file mode 100644 index 000000000..ed2406bd7 --- /dev/null +++ b/packages/safe-core-sdk/tests/ethAdapters.test.ts @@ -0,0 +1,142 @@ +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import { deployments, waffle } from 'hardhat' +import { ContractNetworksConfig, SafeVersion } from '../src' +import { getFactory, getMultiSend, getSafeSingleton } from './utils/setupContracts' +import { getEthAdapter } from './utils/setupEthAdapter' +import { getAccounts } from './utils/setupTestNetwork' + +chai.use(chaiAsPromised) + +describe('Safe contracts', () => { + const setupTests = deployments.createFixture(async ({ deployments }) => { + await deployments.fixture() + const accounts = await getAccounts() + const chainId: number = (await waffle.provider.getNetwork()).chainId + const contractNetworks: ContractNetworksConfig = { + [chainId]: { + multiSendAddress: (await getMultiSend()).address, + safeMasterCopyAddress: (await getSafeSingleton()).address, + safeProxyFactoryAddress: (await getFactory()).address + } + } + return { + accounts, + contractNetworks, + chainId + } + }) + + describe('getSafeContract', async () => { + it('should return an L1 Safe contract from safe-deployments', async () => { + const { accounts } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const chainId = 1 + const safeContract = await ethAdapter.getSafeContract({ safeVersion, chainId }) + chai + .expect(await safeContract.getAddress()) + .to.be.eq('0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552') + }) + + it('should return an L2 Safe contract from safe-deployments', async () => { + const { accounts } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const chainId = 100 + const safeContract = await ethAdapter.getSafeContract({ safeVersion, chainId }) + chai + .expect(await safeContract.getAddress()) + .to.be.eq('0x3E5c63644E683549055b9Be8653de26E0B4CD36E') + }) + + it('should return an L1 Safe contract from safe-deployments using the L1 flag', async () => { + const { accounts } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const chainId = 100 + const isL1SafeMasterCopy = true + const safeContract = await ethAdapter.getSafeContract({ + safeVersion, + chainId, + isL1SafeMasterCopy + }) + chai + .expect(await safeContract.getAddress()) + .to.be.eq('0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552') + }) + + it('should return a Safe contract from the custom addresses', async () => { + const { accounts, contractNetworks, chainId } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const customContractAddress = contractNetworks[chainId].safeMasterCopyAddress + const safeContract = await ethAdapter.getSafeContract({ + safeVersion, + chainId, + customContractAddress + }) + chai.expect(await safeContract.getAddress()).to.be.eq((await getSafeSingleton()).address) + }) + }) + + describe('getMultiSendContract', async () => { + it('should return a Multi Send contract from safe-deployments', async () => { + const { accounts } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const chainId = 1 + const multiSendContract = await ethAdapter.getMultiSendContract(safeVersion, chainId) + chai + .expect(await multiSendContract.getAddress()) + .to.be.eq('0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761') + }) + + it('should return a Multi Send contract from the custom addresses', async () => { + const { accounts, contractNetworks, chainId } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const customContractAddress = contractNetworks[chainId].multiSendAddress + const multiSendContract = await ethAdapter.getMultiSendContract( + safeVersion, + chainId, + customContractAddress + ) + chai.expect(await multiSendContract.getAddress()).to.be.eq((await getMultiSend()).address) + }) + }) + + describe('getSafeProxyFactoryContract', async () => { + it('should return a Safe Proxy Factory contract from safe-deployments', async () => { + const { accounts } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const chainId = 1 + const factoryContract = await ethAdapter.getSafeProxyFactoryContract(safeVersion, chainId) + chai + .expect(await factoryContract.getAddress()) + .to.be.eq('0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2') + }) + + it('should return a Safe Proxy Factory contract from the custom addresses', async () => { + const { accounts, contractNetworks, chainId } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const safeVersion: SafeVersion = '1.3.0' + const customContractAddress = contractNetworks[chainId].safeProxyFactoryAddress + const factoryContract = await ethAdapter.getSafeProxyFactoryContract( + safeVersion, + chainId, + customContractAddress + ) + chai.expect(await factoryContract.getAddress()).to.be.eq((await getFactory()).address) + }) + }) +}) diff --git a/packages/safe-core-sdk/tests/execution.test.ts b/packages/safe-core-sdk/tests/execution.test.ts index dc6067980..a48396119 100644 --- a/packages/safe-core-sdk/tests/execution.test.ts +++ b/packages/safe-core-sdk/tests/execution.test.ts @@ -3,6 +3,7 @@ import { MetaTransactionData, SafeTransactionDataPartial } from '@gnosis.pm/safe import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' +import { safeVersionDeployed } from '../hardhat/deploy/deploy-contracts' import Safe, { ContractNetworksConfig, TransactionOptions } from '../src' import { getERC20Mintable, @@ -141,7 +142,7 @@ describe('Transactions execution', () => { await safeSdk1.signTransaction(tx) await chai .expect(safeSdk2.executeTransaction(tx)) - .to.be.rejectedWith('Invalid owner provided') + .to.be.rejectedWith(safeVersionDeployed === '1.3.0' ? 'GS026' : 'Invalid owner provided') }) it('should execute a transaction with threshold 1', async () => { diff --git a/packages/safe-core-sdk/tests/safeFactory.test.ts b/packages/safe-core-sdk/tests/safeFactory.test.ts index 091f9b9f6..f7c692c99 100644 --- a/packages/safe-core-sdk/tests/safeFactory.test.ts +++ b/packages/safe-core-sdk/tests/safeFactory.test.ts @@ -1,12 +1,16 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' +import { safeVersionDeployed } from '../hardhat/deploy/deploy-contracts' import { ContractNetworksConfig, SafeAccountConfig, SafeDeploymentConfig, SafeFactory } from '../src' +import { SAFE_LAST_VERSION } from '../src/contracts/config' +import { ZERO_ADDRESS } from '../src/utils/constants' +import { itif } from './utils/helpers' import { getFactory, getMultiSend, getSafeSingleton } from './utils/setupContracts' import { getEthAdapter } from './utils/setupEthAdapter' import { getAccounts } from './utils/setupTestNetwork' @@ -39,7 +43,23 @@ describe('Safe Proxy Factory', () => { const ethAdapter = await getEthAdapter(account1.signer) chai .expect(SafeFactory.create({ ethAdapter })) - .rejectedWith('Safe contracts not found in the current network') + .rejectedWith('Invalid Safe Proxy Factory contract') + }) + + it('should fail if the contractNetworks provided are not deployed', async () => { + const { accounts, chainId } = await setupTests() + const [account1] = accounts + const ethAdapter = await getEthAdapter(account1.signer) + const contractNetworks: ContractNetworksConfig = { + [chainId]: { + multiSendAddress: ZERO_ADDRESS, + safeMasterCopyAddress: ZERO_ADDRESS, + safeProxyFactoryAddress: ZERO_ADDRESS + } + } + chai + .expect(SafeFactory.create({ ethAdapter, contractNetworks })) + .rejectedWith('Safe Proxy Factory contract is not deployed in the current network') }) it('should instantiate the Safe Proxy Factory', async () => { @@ -120,13 +140,15 @@ describe('Safe Proxy Factory', () => { const { accounts, contractNetworks } = await setupTests() const [account1, account2] = accounts const ethAdapter = await getEthAdapter(account1.signer) - const safeFactory = await SafeFactory.create({ ethAdapter, contractNetworks }) + const safeFactory = await SafeFactory.create({ + ethAdapter, + safeVersion: safeVersionDeployed, + contractNetworks + }) const owners = [account1.address, account2.address] const threshold = 2 const safeAccountConfig: SafeAccountConfig = { owners, threshold } const safe = await safeFactory.deploySafe(safeAccountConfig) - const contractCodeFinal = ethAdapter.getContractCode(safe.getAddress()) - chai.expect(contractCodeFinal).to.not.be.eq('0x') const deployedSafeOwners = await safe.getOwners() chai.expect(deployedSafeOwners.toString()).to.be.eq(owners.toString()) const deployedSafeThreshold = await safe.getThreshold() @@ -137,18 +159,53 @@ describe('Safe Proxy Factory', () => { const { accounts, contractNetworks } = await setupTests() const [account1, account2] = accounts const ethAdapter = await getEthAdapter(account1.signer) - const safeFactory = await SafeFactory.create({ ethAdapter, contractNetworks }) + 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 safe = await safeFactory.deploySafe(safeAccountConfig, safeDeploymentConfig) - const contractCodeFinal = ethAdapter.getContractCode(safe.getAddress()) - chai.expect(contractCodeFinal).to.not.be.eq('0x') const deployedSafeOwners = await safe.getOwners() chai.expect(deployedSafeOwners.toString()).to.be.eq(owners.toString()) const deployedSafeThreshold = await safe.getThreshold() chai.expect(deployedSafeThreshold).to.be.eq(threshold) }) + + itif(safeVersionDeployed === SAFE_LAST_VERSION)( + 'should deploy last Safe version by default', + 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 = 2 + const safeAccountConfig: SafeAccountConfig = { owners, threshold } + const safe = await safeFactory.deploySafe(safeAccountConfig) + const safeInstanceVersion = await safe.getContractVersion() + chai.expect(safeInstanceVersion).to.be.eq(safeVersionDeployed) + } + ) + + it('should deploy a specific Safe version', 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 safe = await safeFactory.deploySafe(safeAccountConfig) + const safeInstanceVersion = await safe.getContractVersion() + chai.expect(safeInstanceVersion).to.be.eq(safeVersionDeployed) + }) }) }) diff --git a/packages/safe-core-sdk/tests/utils/helpers.ts b/packages/safe-core-sdk/tests/utils/helpers.ts new file mode 100644 index 000000000..c3e7a1264 --- /dev/null +++ b/packages/safe-core-sdk/tests/utils/helpers.ts @@ -0,0 +1 @@ +export const itif = (condition: boolean) => (condition ? it : it.skip) diff --git a/packages/safe-core-sdk/tests/utils/setupContracts.ts b/packages/safe-core-sdk/tests/utils/setupContracts.ts index 460a7253e..f2d5f3a4e 100644 --- a/packages/safe-core-sdk/tests/utils/setupContracts.ts +++ b/packages/safe-core-sdk/tests/utils/setupContracts.ts @@ -1,37 +1,59 @@ import { AddressZero } from '@ethersproject/constants' import { deployments, ethers } from 'hardhat' -import { GnosisSafe, MultiSend } from '../../typechain/src/ethers-v5' +import { + gnosisSafeDeployed, + multiSendDeployed, + proxyFactoryDeployed +} from '../../hardhat/deploy/deploy-contracts' +import { + GnosisSafe as GnosisSafe_V1_1_1, + MultiSend as MultiSend_V1_1_1, + ProxyFactory as ProxyFactory_V1_1_1 +} from '../../typechain/src/ethers-v5/v1.1.1' +import { GnosisSafe as GnosisSafe_V1_2_0 } from '../../typechain/src/ethers-v5/v1.2.0/' +import { + GnosisSafe as GnosisSafe_V1_3_0, + MultiSend as MultiSend_V1_3_0, + ProxyFactory as ProxyFactory_V1_3_0 +} from '../../typechain/src/ethers-v5/v1.3.0/' import { DailyLimitModule, ERC20Mintable, SocialRecoveryModule } from '../../typechain/tests/ethers-v5' -export const getSafeSingleton = async () => { - const SafeDeployment = await deployments.get('GnosisSafe') - const Safe = await ethers.getContractFactory('GnosisSafe') - return Safe.attach(SafeDeployment.address) +export const getSafeSingleton = async (): Promise< + GnosisSafe_V1_3_0 | GnosisSafe_V1_2_0 | GnosisSafe_V1_1_1 +> => { + const SafeDeployment = await deployments.get(gnosisSafeDeployed.name) + const Safe = await ethers.getContractFactory(gnosisSafeDeployed.name) + return Safe.attach(SafeDeployment.address) as + | GnosisSafe_V1_3_0 + | GnosisSafe_V1_2_0 + | GnosisSafe_V1_1_1 } -export const getFactory = async () => { - const FactoryDeployment = await deployments.get('GnosisSafeProxyFactory') - const Factory = await ethers.getContractFactory('GnosisSafeProxyFactory') - return Factory.attach(FactoryDeployment.address) +export const getFactory = async (): Promise => { + const FactoryDeployment = await deployments.get(proxyFactoryDeployed.name) + const Factory = await ethers.getContractFactory(proxyFactoryDeployed.name) + return Factory.attach(FactoryDeployment.address) as ProxyFactory_V1_3_0 | ProxyFactory_V1_1_1 } -export const getSafeTemplate = async (): Promise => { +export const getSafeTemplate = async (): Promise< + GnosisSafe_V1_3_0 | GnosisSafe_V1_2_0 | GnosisSafe_V1_1_1 +> => { const singleton = await getSafeSingleton() const factory = await getFactory() const template = await factory.callStatic.createProxy(singleton.address, '0x') await factory.createProxy(singleton.address, '0x').then((tx: any) => tx.wait()) - const Safe = await ethers.getContractFactory('GnosisSafe') - return Safe.attach(template) as GnosisSafe + const Safe = await ethers.getContractFactory(gnosisSafeDeployed.name) + return Safe.attach(template) as GnosisSafe_V1_3_0 | GnosisSafe_V1_2_0 | GnosisSafe_V1_1_1 } export const getSafeWithOwners = async ( owners: string[], threshold?: number -): Promise => { +): Promise => { const template = await getSafeTemplate() await template.setup( owners, @@ -43,13 +65,13 @@ export const getSafeWithOwners = async ( 0, AddressZero ) - return template + return template as GnosisSafe_V1_3_0 | GnosisSafe_V1_2_0 | GnosisSafe_V1_1_1 } -export const getMultiSend = async (): Promise => { - const MultiSendDeployment = await deployments.get('MultiSend') - const MultiSend = await ethers.getContractFactory('MultiSend') - return MultiSend.attach(MultiSendDeployment.address) as MultiSend +export const getMultiSend = async (): Promise => { + const MultiSendDeployment = await deployments.get(multiSendDeployed.name) + const MultiSend = await ethers.getContractFactory(multiSendDeployed.name) + return MultiSend.attach(MultiSendDeployment.address) as MultiSend_V1_3_0 | MultiSend_V1_1_1 } export const getDailyLimitModule = async (): Promise => { diff --git a/yarn.lock b/yarn.lock index e227bd102..d34ca8d56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1126,7 +1126,7 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/strings" "^5.4.0" -"@gnosis.pm/safe-contracts@1.2.0": +"@gnosis.pm/safe-contracts-v1.2.0@npm:@gnosis.pm/safe-contracts@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.2.0.tgz#33e8332e09c19e8822fccb06c7d3eff806d091f6" integrity sha512-lcpZodqztDgMICB0kAc8aJdQePwCaLJnbeNjgVTfLAo3fBckVRV06VHXg5IsZ26qLA4JfZ690Cb7TsDVE9ZF3w== @@ -1138,6 +1138,11 @@ solc "0.5.17" truffle "^5.1.21" +"@gnosis.pm/safe-contracts-v1.3.0@npm:@gnosis.pm/safe-contracts@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc" + integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw== + "@gnosis.pm/safe-core-sdk@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-core-sdk/-/safe-core-sdk-0.3.1.tgz#15171657490a90ac3773b6965c2af4b873af136f" @@ -1151,6 +1156,11 @@ resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-deployments/-/safe-deployments-1.1.0.tgz#63133f4576c5d5f2779f61d322e1e449fb701b1c" integrity sha512-eIH4gvBQLEksQXFN2vOG+JZpdSCR+q6uoZD7n8Jx/PF2nIaWVYV1CWo6kbWDwfwzgAQkDddM+8WBjnc84fbB+Q== +"@gnosis.pm/safe-deployments@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-deployments/-/safe-deployments-1.4.0.tgz#d49d3d36cc014ef62306d09c0f4761895a17d7a6" + integrity sha512-q4salJNQ/Gx0DnZJytAFO/U4OwGI6xTGtTJSOZK+C9Fh2NW8sep+YfSunHQvCLcu4b7WgWEBhxnCV6rpyveLHg== + "@graphql-tools/batch-delegate@^6.2.4", "@graphql-tools/batch-delegate@^6.2.6": version "6.2.6" resolved "https://registry.yarnpkg.com/@graphql-tools/batch-delegate/-/batch-delegate-6.2.6.tgz#fbea98dc825f87ef29ea5f3f371912c2a2aa2f2c"