Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Safe modules configuration #112

Merged
merged 15 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contracts/Deps.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import { ProxyFactory } from "@gnosis.pm/safe-contracts/contracts/proxies/ProxyF
import { MultiSend } from "@gnosis.pm/safe-contracts/contracts/libraries/MultiSend.sol";
import { GnosisSafe } from "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
import { DefaultCallbackHandler } from "@gnosis.pm/safe-contracts/contracts/handler/DefaultCallbackHandler.sol";
import { DailyLimitModule } from "@gnosis.pm/safe-contracts/contracts/modules/DailyLimitModule.sol";
import { ConditionalTokens } from "@gnosis.pm/conditional-tokens-contracts/contracts/ConditionalTokens.sol";
import { ERC20Mintable } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol";
1 change: 1 addition & 0 deletions migrations-ts/1-deploy-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = function(deployer: Truffle.Deployer, network: string) {
'MultiSend',
'DefaultCallbackHandler',
'Multistep',
'DailyLimitModule',
'ERC20Mintable',
'ConditionalTokens'
].forEach(deploy);
Expand Down
1 change: 1 addition & 0 deletions migrations/1-deploy-contracts.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 83 additions & 2 deletions src/CPK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import cpkFactoryAbi from './abis/CpkFactoryAbi.json'
import safeAbi from './abis/SafeAbi.json'
import multiSendAbi from './abis/MultiSendAbi.json'
import { Address } from './utils/basicTypes'
import { predeterminedSaltNonce } from './utils/constants'
import { predeterminedSaltNonce, sentinelModules } from './utils/constants'
import { joinHexData, getHexDataLength } from './utils/hexData'
import { OperationType, standardizeSafeAppsTransaction } from './utils/transactions'
import { OperationType, SendOptions, standardizeSafeAppsTransaction } from './utils/transactions'
import {
Transaction,
TransactionResult,
Expand Down Expand Up @@ -126,6 +126,20 @@ class CPK {
}
}

async isProxyDeployed(): Promise<boolean> {
if (!this.address) {
throw new Error('CPK address uninitialized')
}
if (!this.ethLibAdapter) {
throw new Error('CPK ethLibAdapter uninitialized')
}

const codeAtAddress = await this.ethLibAdapter.getCode(this.address)
const isDeployed = codeAtAddress !== '0x'

return isDeployed
}

isSafeApp(): boolean {
return this.#safeAppsSdkConnector.isSafeApp()
}
Expand Down Expand Up @@ -287,6 +301,73 @@ class CPK {
})
}

async getModules(): Promise<Address[]> {
const isProxyDeployed = await this.isProxyDeployed()
if (!isProxyDeployed) {
throw new Error('CPK Proxy contract is not deployed')
}
if (!this.#contract) {
throw new Error('CPK contract uninitialized')
}
return await this.#contract.call('getModules', [])
}

async isModuleEnabled(moduleAddress: Address): Promise<boolean> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.2.0 has a contract method for this ;)

const isProxyDeployed = await this.isProxyDeployed()
if (!isProxyDeployed) {
throw new Error('CPK Proxy contract is not deployed')
}
if (!this.#contract) {
throw new Error('CPK contract uninitialized')
}
const modules = await this.#contract.call('getModules', [])
const selectedModules = modules.filter(
(module: Address) => module.toLowerCase() === moduleAddress.toLowerCase()
)
return selectedModules.length > 0
}

async enableModule(moduleAddress: Address): Promise<TransactionResult> {
if (!this.#contract) {
throw new Error('CPK contract uninitialized')
}
if (!this.address) {
throw new Error('CPK address uninitialized')
}
return await this.execTransactions([
{
to: this.address,
data: await this.#contract.encode('enableModule', [moduleAddress]),
operation: CPK.Call
}
])
}

async disableModule(moduleAddress: Address): Promise<TransactionResult> {
const isProxyDeployed = await this.isProxyDeployed()
if (!isProxyDeployed) {
throw new Error('CPK Proxy contract is not deployed')
}
if (!this.#contract) {
throw new Error('CPK contract uninitialized')
}
if (!this.address) {
throw new Error('CPK address uninitialized')
}
const modules = await this.#contract.call('getModules', [])
const index = modules.findIndex(
(module: Address) => module.toLowerCase() === moduleAddress.toLowerCase()
)
const prevModuleAddress = index === 0 ? sentinelModules : modules[index - 1]
return await this.execTransactions([
{
to: this.address,
data: await this.#contract.encode('disableModule', [prevModuleAddress, moduleAddress]),
operation: CPK.Call
}
])
}

private getSafeExecTxParams(transactions: Transaction[]): StandardTransaction {
if (transactions.length === 1) {
return standardizeTransaction(transactions[0])
Expand Down
50 changes: 50 additions & 0 deletions src/abis/SafeAbi.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,5 +229,55 @@
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getModules",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"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"
}
]
7 changes: 0 additions & 7 deletions src/ethLibAdapters/EthersAdapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,6 @@ class EthersAdapter extends EthLibAdapter {
})
)
}

getSendOptions(ownerAccount: Address, options?: CallOptions): SendOptions {
return {
from: ownerAccount,
...options
}
}
}

export default EthersAdapter
2 changes: 2 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { OperationType } from './transactions'

export const zeroAddress = `0x${'0'.repeat(40)}`

export const sentinelModules = '0x0000000000000000000000000000000000000001'

export const defaultTxOperation = OperationType.Call
export const defaultTxValue = 0
export const defaultTxData = '0x'
Expand Down
12 changes: 6 additions & 6 deletions test/ethers/shouldWorkWithEthers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import should from 'should'
import Web3Maj1Min3 from 'web3-1-3'
import CPK, { NetworksConfig, EthersAdapter, TransactionManager, Transaction } from '../../src'
import CPK, { NetworksConfig, EthersAdapter, TransactionManager, Transaction, TransactionResult } from '../../src'
import { Address } from '../../src/utils/basicTypes'
import { testSafeTransactions } from '../transactions/testSafeTransactions'
import { testConnectedSafeTransactionsWithRelay } from '../transactions/testConnectedSafeTransactionsWithRelay'
Expand Down Expand Up @@ -88,7 +88,7 @@ export function shouldWorkWithEthers({
should.exist(transactionResponse)
should.exist(hash)
},
waitTxReceipt: ({ hash }: { hash: string }): any => signer.provider.waitForTransaction(hash)
waitTxReceipt: (txResult: TransactionResult): any => signer.provider.waitForTransaction(txResult.hash)
})

before('setup contracts', async () => {
Expand Down Expand Up @@ -166,7 +166,7 @@ export function shouldWorkWithEthers({
web3.eth.sendTransaction({
from: defaultAccountBox[0],
to: signer.address,
value: `${2e18}`,
value: `${3e18}`,
gas: '0x5b8d80'
})
)
Expand All @@ -186,7 +186,7 @@ export function shouldWorkWithEthers({
web3.eth.sendTransaction({
from: defaultAccountBox[0],
to: cpk.address,
value: `${2e18}`
value: `${3e18}`
})
)
}
Expand Down Expand Up @@ -226,7 +226,7 @@ export function shouldWorkWithEthers({
web3.eth.sendTransaction({
from: defaultAccountBox[0],
to: freshSignerBox[0].address,
value: `${2e18}`,
value: `${3e18}`,
gas: '0x5b8d80'
})
)
Expand Down Expand Up @@ -258,7 +258,7 @@ export function shouldWorkWithEthers({
web3.eth.sendTransaction({
from: defaultAccountBox[0],
to: cpk.address,
value: `${2e18}`
value: `${3e18}`
})
)
}
Expand Down
40 changes: 39 additions & 1 deletion test/transactions/testSafeTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function testSafeTransactions({
let conditionalTokens: any
let multiStep: any
let erc20: any
let dailyLimitModule: any

beforeEach('rebind symbols', async () => {
cpk = await getCPK()
Expand All @@ -68,8 +69,9 @@ export function testSafeTransactions({
proxyOwner = pOwner
})

before('deploy conditional tokens', async () => {
before('deploy conditional tokens and daily limit module', async () => {
conditionalTokens = await getContracts().ConditionalTokens.new()
dailyLimitModule = await getContracts().DailyLimitModule.new()
})

beforeEach('deploy mock contracts', async () => {
Expand Down Expand Up @@ -332,5 +334,41 @@ export function testSafeTransactions({
gasCosts.should.be.equal(gasPrice * gasUsed)
}
)

it('can enable modules', async () => {
let moduleList: Address[]

if (accountType !== AccountType.Fresh) {
moduleList = await cpk.getModules()
moduleList.length.should.equal(0)
;(await cpk.isModuleEnabled(dailyLimitModule.address)).should.equal(false)
} else {
await sendTransaction({
from: await cpk.getOwnerAccount(),
to: cpk.address,
value: '0xde0b6b3a7640000',
gas: '0x5b8d80'
})
}

await waitTxReceipt(await cpk.enableModule(dailyLimitModule.address))

moduleList = await cpk.getModules()
moduleList.length.should.equal(1)
;(await cpk.isModuleEnabled(dailyLimitModule.address)).should.equal(true)
})
;(accountType === AccountType.Fresh ? it.skip : it)('can disable modules', async () => {
let moduleList: Address[]

moduleList = await cpk.getModules()
moduleList.length.should.equal(1)
;(await cpk.isModuleEnabled(dailyLimitModule.address)).should.equal(true)

await waitTxReceipt(await cpk.disableModule(dailyLimitModule.address))

moduleList = await cpk.getModules()
moduleList.length.should.equal(0)
;(await cpk.isModuleEnabled(dailyLimitModule.address)).should.equal(false)
})
})
}
16 changes: 14 additions & 2 deletions test/utils/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ProxyFactoryJson from '../../build/contracts/ProxyFactory.json'
import MultistepJson from '../../build/contracts/Multistep.json'
import ERC20MintableJson from '../../build/contracts/ERC20Mintable.json'
import ConditionalTokensJson from '../../build/contracts/ConditionalTokens.json'
import DailyLimitModuleJson from '../../build/contracts/DailyLimitModule.json'
import { Address } from '../../src/utils/basicTypes'

let CPKFactory: any
Expand All @@ -18,6 +19,7 @@ let DefaultCallbackHandler: any
let MultiStep: any
let ERC20Mintable: any
let ConditionalTokens: any
let DailyLimitModule: any

let cpkFactory: any
let gnosisSafe: any
Expand All @@ -27,6 +29,7 @@ let defaultCallbackHandler: any
let multiStep: any
let erc20: any
let conditionalTokens: any
let dailyLimitModule: any

export interface TestContractInstances {
cpkFactory: any
Expand All @@ -37,6 +40,7 @@ export interface TestContractInstances {
multiStep: any
erc20: any
conditionalTokens: any
dailyLimitModule: any
}

export interface TestContracts {
Expand All @@ -48,6 +52,7 @@ export interface TestContracts {
MultiStep: any
ERC20Mintable: any
ConditionalTokens: any
DailyLimitModule: any
}

export const initializeContracts = async (safeOwner: Address): Promise<void> => {
Expand Down Expand Up @@ -92,6 +97,11 @@ export const initializeContracts = async (safeOwner: Address): Promise<void> =>
ConditionalTokens.setProvider(provider)
ConditionalTokens.defaults({ from: safeOwner })
conditionalTokens = await ConditionalTokens.deployed()

DailyLimitModule = TruffleContract(DailyLimitModuleJson)
DailyLimitModule.setProvider(provider)
DailyLimitModule.defaults({ from: safeOwner })
dailyLimitModule = await DailyLimitModule.deployed()
}

export const getContracts = (): TestContracts => ({
Expand All @@ -102,7 +112,8 @@ export const getContracts = (): TestContracts => ({
DefaultCallbackHandler,
MultiStep,
ERC20Mintable,
ConditionalTokens
ConditionalTokens,
DailyLimitModule,
})

export const getContractInstances = (): TestContractInstances => ({
Expand All @@ -113,5 +124,6 @@ export const getContractInstances = (): TestContractInstances => ({
defaultCallbackHandler,
multiStep,
erc20,
conditionalTokens
conditionalTokens,
dailyLimitModule,
})
Loading