Skip to content

Commit

Permalink
Merge pull request #468 from safe-global/feat/improve-proxy-factory
Browse files Browse the repository at this point in the history
1.4.0: Improve `ProxyFactory`
  • Loading branch information
mmv08 authored Jan 12, 2023
2 parents c2d0f04 + 8abdcdd commit 5a91e2c
Show file tree
Hide file tree
Showing 8 changed files with 3,399 additions and 2,975 deletions.
24 changes: 19 additions & 5 deletions benchmark/GnosisSafe.Proxy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,32 @@ import { buildSafeTransaction } from "../src/utils/execution";
import { benchmark } from "./utils/setup"
import { getFactory } from "../test/utils/setup";

const testTarget = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"

benchmark("Proxy", [{
name: "creation",
prepare: async (contracts,_,nonce) => {
const factory = contracts.additions.factory
const data = factory.interface.encodeFunctionData("createProxy", [testTarget, "0x"])
// We're cheating and passing the factory address as a singleton address to bypass a check that singleton contract exists
const data = factory.interface.encodeFunctionData("createProxyWithNonce", [factory.address, "0x", 0])
return buildSafeTransaction({ to: factory.address, data, safeTxGas: 1000000, nonce })
},
fixture: async () => {
return {
factory: await getFactory(),
}
}
}])

benchmark("Proxy", [{
name: "chain specific creation",
prepare: async (contracts,_,nonce) => {
const factory = contracts.additions.factory
// We're cheating and passing the factory address as a singleton address to bypass a check that singleton contract exists
const data = factory.interface.encodeFunctionData("createChainSpecificProxyWithNonce", [factory.address, "0x", 0])
return buildSafeTransaction({ to: factory.address, data, safeTxGas: 1000000, nonce })
},
fixture: async () => {
return {
factory: await getFactory()
factory: await getFactory(),
}
}
}])
}])
118 changes: 62 additions & 56 deletions contracts/proxies/GnosisSafeProxyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,79 @@ pragma solidity >=0.7.0 <0.9.0;
import "./GnosisSafeProxy.sol";
import "./IProxyCreationCallback.sol";

/// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
/// @title Proxy Factory - Allows to create a new proxy contract and execute a message call to the new proxy within one transaction.
/// @author Stefan George - <[email protected]>
contract GnosisSafeProxyFactory {
event ProxyCreation(GnosisSafeProxy proxy, address singleton);

/// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
/// @param singleton Address of singleton contract.
/// @param data Payload for message call sent to new proxy contract.
function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) {
proxy = new GnosisSafeProxy(singleton);
if (data.length > 0)
// solhint-disable-next-line no-inline-assembly
assembly {
if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) {
revert(0, 0)
}
}
emit ProxyCreation(proxy, singleton);
}

/// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed.
function proxyRuntimeCode() public pure returns (bytes memory) {
return type(GnosisSafeProxy).runtimeCode;
}

/// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.
function proxyCreationCode() public pure returns (bytes memory) {
return type(GnosisSafeProxy).creationCode;
}

/// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer.
/// @dev Allows to create a new proxy contract using CREATE2. Optionally executes an initializer call to a new proxy.
/// This method is only meant as an utility to be called from other methods
/// @param _singleton Address of singleton contract.
/// @param initializer Payload for message call sent to new proxy contract.
/// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
function deployProxyWithNonce(
/// @param _singleton Address of singleton contract. Must be deployed at the time of execution.
/// @param initializer Payload for a message call to be sent to a new proxy contract.
/// @param salt Create2 salt to use for calculating the address of the new proxy contract.
function deployProxy(
address _singleton,
bytes memory initializer,
uint256 saltNonce
bytes32 salt
) internal returns (GnosisSafeProxy proxy) {
// If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it
bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
require(isContract(_singleton), "Singleton contract not deployed");

bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton)));
// solhint-disable-next-line no-inline-assembly
assembly {
proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
}
require(address(proxy) != address(0), "Create2 call failed");

if (initializer.length > 0) {
// solhint-disable-next-line no-inline-assembly
assembly {
if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) {
revert(0, 0)
}
}
}
}

/// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
/// @param _singleton Address of singleton contract.
/// @param initializer Payload for message call sent to new proxy contract.
/// @dev Allows to create a new proxy contract and execute a message call to the new proxy within one transaction.
/// @param _singleton Address of singleton contract. Must be deployed at the time of execution.
/// @param initializer Payload for a message call to be sent to a new proxy contract.
/// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
function createProxyWithNonce(
address _singleton,
bytes memory initializer,
uint256 saltNonce
) public returns (GnosisSafeProxy proxy) {
proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);
if (initializer.length > 0)
// solhint-disable-next-line no-inline-assembly
assembly {
if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) {
revert(0, 0)
}
}
// If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it
bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
proxy = deployProxy(_singleton, initializer, salt);
emit ProxyCreation(proxy, _singleton);
}

/// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction
/// @param _singleton Address of singleton contract.
/// @param initializer Payload for message call sent to new proxy contract.
/// @dev Allows to create a new proxy contract that should exist only on 1 network (e.g. specific governance or admin accounts)
/// by including the chain id in the create2 salt. Such proxies cannot be created on other networks by replaying the transaction.
/// @param _singleton Address of singleton contract. Must be deployed at the time of execution.
/// @param initializer Payload for a message call to be sent to a new proxy contract.
/// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
function createChainSpecificProxyWithNonce(
address _singleton,
bytes memory initializer,
uint256 saltNonce
) public returns (GnosisSafeProxy proxy) {
// If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it
bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce, getChainId()));
proxy = deployProxy(_singleton, initializer, salt);
emit ProxyCreation(proxy, _singleton);
}

/// @dev Allows to create a new proxy contract, execute a message call to the new proxy and call a specified callback within one transaction
/// @param _singleton Address of singleton contract. Must be deployed at the time of execution.
/// @param initializer Payload for a message call to be sent to a new proxy contract.
/// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
/// @param callback Callback that will be invoked after the new proxy contract has been successfully deployed and initialized.
function createProxyWithCallback(
Expand All @@ -90,18 +90,24 @@ contract GnosisSafeProxyFactory {
if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce);
}

/// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce`
/// This method is only meant for address calculation purpose when you use an initializer that would revert,
/// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory.
/// @param _singleton Address of singleton contract.
/// @param initializer Payload for message call sent to new proxy contract.
/// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
function calculateCreateProxyWithNonceAddress(
address _singleton,
bytes calldata initializer,
uint256 saltNonce
) external returns (GnosisSafeProxy proxy) {
proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);
revert(string(abi.encodePacked(proxy)));
/// @dev Returns true if `account` is a contract.
/// @param account The address being queried
function isContract(address account) internal view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly {
size := extcodesize(account)
}
return size > 0;
}

/// @dev Returns the chain id used by this contract.
function getChainId() public view returns (uint256) {
uint256 id;
// solhint-disable-next-line no-inline-assembly
assembly {
id := chainid()
}
return id;
}
}
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
"devDependencies": {
"@gnosis.pm/mock-contract": "^4.0.0",
"@gnosis.pm/safe-singleton-factory": "^1.0.3",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-ethers": "2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.0",
"@nomiclabs/hardhat-waffle": "^2.0.0",
"@nomiclabs/hardhat-waffle": "2.0.1",
"@openzeppelin/contracts": "^3.4.0",
"@types/chai": "^4.2.14",
"@types/mocha": "^8.2.0",
Expand All @@ -66,21 +66,21 @@
"eslint-plugin-no-only-tests": "^2.4.0",
"eslint-plugin-prettier": "^3.1.4",
"ethereum-waffle": "^3.3.0",
"ethers": "^5.1.4",
"ethers": "5.4.0",
"hardhat": "^2.2.1",
"hardhat-deploy": "0.9.2",
"husky": "^5.1.3",
"prettier": "^2.1.2",
"prettier-plugin-solidity": "^1.0.0-alpha.60",
"prettier-plugin-solidity": "1.0.0-beta.10",
"solc": "0.7.6",
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solhint": "3.3.4",
"solhint-plugin-prettier": "0.0.5",
"solidity-coverage": "^0.7.17",
"ts-node": "^9.1.1",
"typescript": "^4.2.4",
"yargs": "^16.1.1"
},
"peerDependencies": {
"ethers": "^5.1.4"
"ethers": "5.4.0"
}
}
11 changes: 10 additions & 1 deletion src/utils/proxies.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers, Contract } from "ethers"
import { ethers, Contract, BigNumberish } from "ethers"

export const calculateProxyAddress = async (factory: Contract, singleton: string, inititalizer: string, nonce: number | string) => {
const deploymentCode = ethers.utils.solidityPack(["bytes", "uint256"], [await factory.proxyCreationCode(), singleton])
Expand All @@ -15,4 +15,13 @@ export const calculateProxyAddressWithCallback = async (factory: Contract, singl
[nonce, callback]
)
return calculateProxyAddress(factory, singleton, inititalizer, saltNonceWithCallback)
}

export const calculateChainSpecificProxyAddress = async (factory: Contract, singleton: string, inititalizer: string, nonce: number | string, chainId: BigNumberish) => {
const deploymentCode = ethers.utils.solidityPack(["bytes", "uint256"], [await factory.proxyCreationCode(), singleton])
const salt = ethers.utils.solidityKeccak256(
["bytes32", "uint256", "uint256"],
[ethers.utils.solidityKeccak256(["bytes"], [inititalizer]), nonce, chainId]
)
return ethers.utils.getCreate2Address(factory.address, salt, ethers.utils.keccak256(deploymentCode))
}
Loading

0 comments on commit 5a91e2c

Please sign in to comment.