-
yarn add @gnosis.pm/safe-contracts
-
Optional for testing
yarn add eth-lightwallet
Add Imports.sol
contract to build Safe sources:
pragma solidity >=0.5.0 <0.6.0;
import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
Add Safe imports to module contract:
// Required for triggering execution
import "@gnosis.pm/safe-contracts/contracts/common/Enum.sol";
import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
contract ExampleModule {
function tokenTransfer(GnosisSafe safe, address token, address to, uint amount) public {
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount);
require(safe.execTransactionFromModule(token, 0, data, Enum.Operation.Call), "Could not execute token transfer");
}
}
Setup test artifacts
const GnosisSafe = artifacts.require("./GnosisSafe.sol")
const ProxyFactory = artifacts.require("./ProxyFactory.sol");
const ExampleModule = artifacts.require("./ExampleModule.sol")
Optional: Setup light wallet for signing requests to the Safe (if using off-chain signatures)
contract('ExampleModule', function(accounts) {
let lw
beforeEach(async function() {
// Create lightwallet
lw = await utils.createLightwallet()
// More setup code
})
// More test code
}
Create master copy and proxy factory
contract('ExampleModule', function(accounts) {
const ADDRESS_0 = "0x0000000000000000000000000000000000000000"
let gnosisSafeMasterCopy
let proxyFactory
beforeEach(async function() {
// Create lightwallet
lw = await utils.createLightwallet()
// Create Master Copy
gnosisSafeMasterCopy = await GnosisSafe.new()
// create Proxy factory
proxyFactory = await ProxyFactory.new()
})
// More test code
}
Optional: Add helper method to execute Safe transction (if using off-chain signatures)
const utils = require('@gnosis.pm/safe-contracts/test/utils/general')
let execTransaction = async function(safe, to, value, data, operation, message) {
let nonce = await safe.nonce()
let transactionHash = await safe.getTransactionHash(to, value, data, operation, 0, 0, 0, ADDRESS_0, ADDRESS_0, nonce)
let sigs = utils.signTransaction(lw, [lw.accounts[0], lw.accounts[1]], transactionHash)
utils.logGasUsage(
'execTransaction ' + message,
await safe.execTransaction(to, value, data, operation, 0, 0, 0, ADDRESS_0, ADDRESS_0, sigs)
)
}
Create test with Safe and module
contract('ExampleModule', function(accounts) {
const CALL = 0
it('should execute token transfer', async () => {
const setupData = await gnosisSafeMasterCopy.setup([lw.accounts[0], lw.accounts[1]], 2, ADDRESS_0, "0x", 0, 0, 0, 0)
// Find event in tx and create contract instance
const safe = utils.getParamFromTxEvent(
await proxyFactory.createProxy(gnosisSafeMasterCopy.address, setupData),
'ProxyCreation', 'proxy', proxyFactory.address, GnosisSafe, 'create Gnosis Safe'
)
// Setup module
const testModule = await ExampleModule.new()
const enableModuleData = await safe.enableModule(testModule.address).encodeABI()
await execTransaction(safe.address, 0, enableModuleData, CALL, "enable module")
const modules = await safe.getModules()
assert.equal(1, modules.length)
assert.equal(testModule.address, modules[0])
// Setup test token
const token = await TestToken.new({from: accounts[0]})
await token.transfer(gnosisSafe.address, 1000, {from: accounts[0]})
assert.equal(1000, await token.balanceOf(gnosisSafe.address))
assert.equal(0, await token.balanceOf(accounts[1]))
// Execute token transfer via module
await testModule.tokenTransfer(
safe.address, token.address, accounts[1], 600
)
assert.equal(400, await token.balanceOf(gnosisSafe.address))
assert.equal(600, await token.balanceOf(accounts[1]))
}
}
- requires
yarn add @gnosis.pm/util-contracts
pragma solidity >=0.4.21 <0.6.0;
import "@gnosis.pm/util-contracts/contracts/GnosisStandardToken.sol";
contract TestToken is GnosisStandardToken{
constructor() public {
balances[msg.sender] = 10000000;
}
}
It is currently not possible to directly use the Safe contracts with the latest compiler version. In this case it is required to define interfaces for the Safe contracts and use a mock Safe for the tests. Also some of the provided utility methods might not work as expected.
An example can be found at https://gist.github.com/rmeissner/208e35db13d0ebf53b6e35838f1c8122
The example code is based on https://github.com/rmeissner/transfer-limit
I want to enable a module from a smart contract by calling
setUp
The
setUp
function looks like it's designed to allow this, because it contains asetupModules(address to, bytes memory data)
functionI assume
to
is the address of the proxy. But I'm not sure what to pass intodata
. I tried passing abi encodedenableModule(address)
and the address, but it's not workingany clue how to do this?