Skip to content

Latest commit

 

History

History
108 lines (76 loc) · 9.71 KB

sphinx-module-proxy-factory.md

File metadata and controls

108 lines (76 loc) · 9.71 KB

SphinxModuleProxyFactory Contract Specification

The SphinxModuleProxyFactory deploys minimal, non-upgradeable EIP-1167 proxy contracts at deterministic addresses, which delegate all calls to a single SphinxModule implementation contract. The SphinxModuleProxyFactory can also enable SphinxModule proxies within Gnosis Safe contracts.

Vocabulary notes:

  • A SphinxModuleProxy is an EIP-1167 proxy that delegates calls to a SphinxModule implementation contract. There is no source file for the SphinxModuleProxy because we use OpenZeppelin's Clones for deploying EIP-1167 proxies and calculating their addresses.
  • A SphinxModule is the SphinxModule implementation contract.
  • A Gnosis Safe is a Gnosis Safe proxy contract that delegates calls to a Gnosis Safe implementation.

Table of Contents

Relevant Files

Use Cases

There are two use cases for the SphinxModuleProxyFactory:

  1. Deploy a SphinxModuleProxy after a Gnosis Safe has been deployed.
  2. Deploy a Gnosis Safe and enable a SphinxModuleProxy in a single transaction.

We'll describe these in more detail below.

1. Deploy a SphinxModuleProxy for an existing Gnosis Safe

Anybody can call the SphinxModuleProxyFactory's deploySphinxModuleProxy function to deploy a new SphinxModuleProxy. After deploying the module, the SphinxModuleProxyFactory serves no further purpose; the Gnosis Safe owners can add the module by directly calling the Safe's enableModule function.

2. Deploy a Gnosis Safe and enable a SphinxModuleProxy in a single transaction

It must be possible to submit a single transaction that:

  1. Deploys a Gnosis Safe at a deterministic address
  2. Deploys a SphinxModuleProxy at a deterministic address
  3. Enables the SphinxModuleProxy within the Gnosis Safe

This process allows a third party (like Sphinx) to permissionlessly deploy and set up a Gnosis Safe on behalf of the Safe owners without requiring their signatures. If the Safe owners are confident that their Safe has been deployed correctly at a given address on one chain, then they can be confident that a Safe at the same address on any other chain has also been deployed correctly. To achieve this, the address of the Gnosis Safe must not rely on the deployer.

We can do this by calling the Gnosis Safe Proxy Factory's createProxyWithNonce function, which uses CREATE2. The initializer input parameter contains all the information necessary to set up the Gnosis Safe, including the Safe owner addresses, the signature threshold, and the SphinxModuleProxy info.

Since the initializer data determines the address of the Gnosis Safe, it cannot include the address of the Gnosis Safe since this would cause a circular dependency. Specifically, the circular dependency would occur because the initializer data would need to include the address of the Gnosis Safe, which is calculated based on the initializer data, which would need to include the address of the Gnosis Safe, etc. For this same reason, we cannot include the address of the SphinxModuleProxy in the initializer data since the module's address depends on the address of the Gnosis Safe.

To resolve this, the SphinxModuleProxyFactory includes functions for deploying and enabling a SphinxModuleProxy without using the address of the Gnosis Safe or the address of the SphinxModuleProxy as input parameters. The initializer data must include a MultiSend call that executes two function calls on the SphinxModuleProxyFactory: deploySphinxModuleProxyProxyFactoryFromSafe and enableSphinxModuleProxyFromSafe. More details on these functions are below. To see an example of this deployment process, see the initializeGnosisSafeWithModule function in the SphinxModuleProxy unit test file.

High-Level Invariants

  • It must be possible to deploy and enable a SphinxModuleProxy for a Gnosis Safe that already exists.
  • It must be possible for anybody to execute a single transaction that deploys a Gnosis Safe at a deterministic address, deploys a SphinxModuleProxy at a deterministic address, and enables the SphinxModuleProxy, as described in the previous section.
  • If the deployment strategy described in the previous section succeeds on one network, it must always succeed on another network (assuming that the appropriate factories have been deployed first). For example, this invariant would be violated if the following scenario is possible:
    1. The user deploys on chain 1 using the strategy described in the previous section.
    2. A malicious actor deploys a SphinxModuleProxy at the same CREATE2 address on chain 2.
    3. The user will not be able to deploy a Gnosis Safe at the same address on chain 2. It will revert because a SphinxModuleProxy already exists at the CREATE2 address.
  • The address of a SphinxModuleProxy must be calculated via CREATE2 using the following inputs:
    • The address of the SphinxModuleProxyFactory.
    • The address of the Gnosis Safe contract that the SphinxModuleProxy belongs to.
    • The address of the caller that deploys the SphinxModuleProxy through the SphinxModuleProxyFactory.
    • An arbitrary uint256 nonce.
  • All of the behavior described in this specification must apply to all Gnosis Safe contracts supported by Sphinx.

Function-Level Invariants

constructor()

  • Must deploy a SphinxModule contract at a CREATE2 address determined by the address of the SphinxModuleProxyFactory and a bytes32(0) salt.

function deploySphinxModuleProxy(address _safeProxy, uint256 _saltNonce) external returns (address sphinxModuleProxy)

  • Must revert if the input Gnosis Safe proxy is address(0).
  • Must revert if a contract already exists at the CREATE2 address.
  • A successful call must:
    • Deploy an EIP-1167 proxy at the correct CREATE2 address using the SphinxModule implementation deployed in the SphinxModuleProxyFactorys constructor.
    • Emit a SphinxModuleProxyDeployed event in the SphinxModuleProxyFactory.
    • Never succeed without successfully deploying the SphinxModule implementation.
    • Initialize the SphinxModuleProxy using the correct Gnosis Safe address.
    • Return the address of the SphinxModuleProxy.
  • A single caller must be able to deploy an arbitrary number of SphinxModuleProxy contracts by calling this function multiple times.

function deploySphinxModuleProxyFromSafe(uint256 _saltNonce) external

  • Must revert if a contract already exists at the CREATE2 address.
  • A successful call must:
    • Deploy an EIP-1167 proxy at the correct CREATE2 address, using the correct SphinxModule implementation deployed in the SphinxModuleProxyFactory's constructor.
    • Emit a SphinxModuleProxyDeployed event in the SphinxModuleProxyFactory.
    • Initialize the SphinxModuleProxy using the caller's address as the Gnosis Safe address.
  • A single caller must be able to deploy an arbitrary number of SphinxModuleProxys by calling this function multiple times.

function enableSphinxModuleProxyFromSafe(uint256 _saltNonce) external

  • Must revert if not delegatecalled.
  • A successful call must:
    • Must enable the correct SphinxModuleProxy as a module in the Gnosis Safe that triggered the delegatecall.
    • A single Gnosis Safe must be able to enable more than one SphinxModuleProxy by calling this function multiple times.

function computeSphinxModuleProxyAddress(address _safeProxy, address _caller, uint256 _saltNonce) external view returns (address);

  • Must return the correct CREATE2 address of a SphinxModuleProxy deployed by the SphinxModuleProxyFactory.

Assumptions

The SphinxModuleProxyFactory calls a couple of external contracts. We test that the interactions with these contracts work properly in the unit tests for the SphinxModuleProxyFactory, but we don't thoroughly test the internals of these external contracts. Instead, we assume that they're secure and have been thoroughly tested by their authors. These external contracts are:

  • Clones in OpenZeppelin's Contracts v4.9.3. This library deploys the SphinxModuleProxy contracts (via Clones.cloneDeterministic) and computes their addresses (via Clones.predictDeterministicAddress).
  • Gnosis Safe's enableModule function enables a SphinxModuleProxy within the user's Gnosis Safe.