1

I want to build a smart contract called Portal.sol that has a deployeNewSafe() function that does the following:

  • deploys a new safe (from the contract)
  • sets Portal.sol as the only owner of the new safe contract
  • sets Portal.sol as a module of the new safe contract
  • sets the allowances module as a module of the new safe contract

I've looked in the safe global docs and I can't seem to find any guide on how to deploy safes from a smart contract.

Could you kindly point me to any materials that might help me build this, including example implementations if there are any?

Thank you!

2 Answers 2

3

a simple example can be found in the Safe Factories repository. The more tricky part will be to encode the setup data to set the correct modules. The easiest is to encode 2 enableModule transactions using MultiSend.

The encoding should be something along the lines of the following code (not tested so might require some adjustments:

    function buildEnabledModule(address safe, address module) internal returns (bytes memory data) {
        return abi.encodePacked(
            uint8(0), // Call Operation -> 0
            address(safe), // To -> Safe where the module will be enabled
            uint256(0), // Value -> 0
            abi.encodeWithSignature("enableModule(address module)", module)
        );
    }

    function buildMultiSendEnable(address safe, address module1, address module2) internal returns (bytes memory data) {
        return abi.encodePacked(
            buildEnabledModule(safe, module1),
            buildEnabledModule(safe, module2)
        );
    }

This should be used as the data field in the setup method and the to would be an instance of MultiSend/MultiSendCallOnly.

1
  • awesome thanks Richard I missed this answer I'll check it out. I got it to work by encoding a signature with v=1 – which bypasses most of the signature checks if caller is owner (in this case caller was the module I built) – and then calling execTransaction on the Safe with enableModule. I'll post the full answer soon. Thanks!
    – Fast Ackl
    Nov 23, 2023 at 23:56
0

Here's how I got it to work:

  1. Deploy a module, let's call it SafeGuard.sol
  2. In that module, define an initializeProxySafe() function
  3. initializeProxySafe() function calls createProxyWithNonce on the Gnosis Safe Proxy Factory contract (e.g. https://etherscan.io/address/0x76e2cfc1f5fa8f6a5b3fc4c8f4788f0116861f9b)
  4. Then initializeProxySafe() calls setup() on the newly deployed Gnosis safe proxy instance. When you call setup(), make sure you make SafeGuard.sol (or whatever your module is) one of the owners of the Safe
  5. abi encode enableModule(address) like
bytes memory data = abi.encodeWithSignature("enableModule(address)", address(this));
  1. Then initializeProxySafe() creates a signature - this is the tricky part. execTransaction() takes signature as a param, which is an r,s,v signature. The Safe proxy will do a bunch of checks on the signature which I had to trace through.

When you need to set the v value to 1, the Safe waives through calls to execTransaction() as long as an owner of the Safe is calling execTransaction().

Here's how I implemented the createContractSignature() function:

function createContractSignature()
      internal view
      returns (bytes memory)
  {

      // Safe signature checker waves through transactions with correct signatures sent by owners of the Safe
      // `r` is the address of a current owner of the Safe
      address addr = address(this);
      bytes32 r = bytes32(uint256(uint160(addr)));
      // `s` can be anything
      bytes32 s = bytes32(uint256(0x4100));
      // `v` set to 1 to indicate owner signature
      bytes1 v = bytes1(uint8(1));      

      // Manually concatenate the bytes together
      bytes memory signature = new bytes(65);
      for (uint i = 0; i < 32; i++) {
          signature[i] = r[i];
          signature[i+32] = s[i];
      }
      signature[64] = v;
      return signature;
  }
  1. Now we SafeGuard calls execTransaction() on the Safe proxy passing data created in step 5 above (which enables the module) as well as the contractSignature and you're apples
    try gnosisSafeProxyInstance.execTransaction(
      address(gnosisSafeProxy), //to
      0, //value
      data, //data (`enableModule` signature encoded)
      Enum.Operation.Call, //operation
      0, //safeTxGas
      0, //baseGas
      0, //gasPrice
      address(0), //gasToken
      payable(address(0)), //refundReceiver
      contractSignature //contractSignature
    ) {
      //do something if needed
    } catch (bytes memory /*lowLevelData*/) {
      revert("Enable module call failed");
    }

And that's it! SafeGuard is now enabled as a module of the newly deployed proxy

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.