Skip to content

Commit

Permalink
add a function for getting proxy code from solc artifacts
Browse files Browse the repository at this point in the history
  • Loading branch information
mmv08 committed Jan 11, 2023
1 parent 4cb2283 commit e8606e2
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 34 deletions.
45 changes: 20 additions & 25 deletions contracts/proxies/GnosisSafeProxyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ import "./IProxyCreationCallback.sol";
contract GnosisSafeProxyFactory {
event ProxyCreation(GnosisSafeProxy proxy, address 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;
Expand All @@ -22,8 +17,13 @@ contract GnosisSafeProxyFactory {
/// @dev Allows to create a new proxy contract using CREATE2.
/// This method is only meant as an utility to be called from other methods
/// @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, bytes32 salt) internal returns (GnosisSafeProxy proxy) {
function deployProxy(
address _singleton,
bytes memory initializer,
bytes32 salt
) internal returns (GnosisSafeProxy proxy) {
require(isContract(_singleton), "Singleton contract not deployed");

bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton)));
Expand All @@ -32,11 +32,20 @@ contract GnosisSafeProxyFactory {
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 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 message call sent to new proxy contract.
/// @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,
Expand All @@ -45,21 +54,14 @@ contract GnosisSafeProxyFactory {
) 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));
proxy = deployProxy(_singleton, salt);
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)
}
}
proxy = deployProxy(_singleton, initializer, salt);
emit ProxyCreation(proxy, _singleton);
}

/// @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 message call sent to new proxy contract.
/// @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,
Expand All @@ -68,20 +70,13 @@ contract GnosisSafeProxyFactory {
) 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, salt);
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)
}
}
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 message call sent to new proxy contract.
/// @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 Down
18 changes: 9 additions & 9 deletions test/factory/ProxyFactory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { expect } from "chai";
import hre, { deployments, waffle, ethers } from "hardhat";
import "@nomiclabs/hardhat-ethers";
import { deployContract, getFactory, getMock, getSafeWithOwners } from "../utils/setup";
import { deployContract, getFactory, getMock, getSafeWithOwners, getGnosisSafeProxyRuntimeCode } from "../utils/setup";
import { AddressZero } from "@ethersproject/constants";
import { BigNumber } from "ethers";
import { calculateChainSpecificProxyAddress, calculateProxyAddress, calculateProxyAddressWithCallback } from "../../src/utils/proxies";
import { getAddress } from "ethers/lib/utils";
import { chainId } from './../utils/encoding';

describe("ProxyFactory", async () => {
Expand Down Expand Up @@ -72,13 +71,14 @@ describe("ProxyFactory", async () => {
const proxyAddress = await calculateProxyAddress(factory, singleton.address, initCode, saltNonce)
await expect(
factory.createProxyWithNonce(singleton.address, initCode, saltNonce)
).to.emit(factory, "ProxyCreation").withArgs(proxyAddress, singleton.address)
).to.emit(factory, "ProxyCreation").withArgs(proxyAddress, singleton.address)
const proxy = singleton.attach(proxyAddress)

expect(await proxy.creator()).to.be.eq(AddressZero)
expect(await proxy.isInitialized()).to.be.eq(false)
expect(await proxy.masterCopy()).to.be.eq(singleton.address)
expect(await singleton.masterCopy()).to.be.eq(AddressZero)
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await factory.proxyRuntimeCode())
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await getGnosisSafeProxyRuntimeCode())
})

it('should emit event with initializing', async () => {
Expand All @@ -93,7 +93,7 @@ describe("ProxyFactory", async () => {
expect(await proxy.isInitialized()).to.be.eq(true)
expect(await proxy.masterCopy()).to.be.eq(singleton.address)
expect(await singleton.masterCopy()).to.be.eq(AddressZero)
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await factory.proxyRuntimeCode())
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await getGnosisSafeProxyRuntimeCode())
})

it('should not be able to deploy same proxy twice', async () => {
Expand Down Expand Up @@ -139,7 +139,7 @@ describe("ProxyFactory", async () => {
expect(await proxy.isInitialized()).to.be.eq(false)
expect(await proxy.masterCopy()).to.be.eq(singleton.address)
expect(await singleton.masterCopy()).to.be.eq(AddressZero)
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await factory.proxyRuntimeCode())
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await getGnosisSafeProxyRuntimeCode())
})

it('should emit event with initializing', async () => {
Expand All @@ -154,7 +154,7 @@ describe("ProxyFactory", async () => {
expect(await proxy.isInitialized()).to.be.eq(true)
expect(await proxy.masterCopy()).to.be.eq(singleton.address)
expect(await singleton.masterCopy()).to.be.eq(AddressZero)
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await factory.proxyRuntimeCode())
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await getGnosisSafeProxyRuntimeCode())
})

it('should deploy proxy to create2 address with chainid included in salt', async () => {
Expand All @@ -166,7 +166,7 @@ describe("ProxyFactory", async () => {

await factory.createChainSpecificProxyWithNonce(singleton.address, initCode, saltNonce)

expect(await provider.getCode(proxyAddress)).to.eq(await factory.proxyRuntimeCode())
expect(await provider.getCode(proxyAddress)).to.be.eq(await getGnosisSafeProxyRuntimeCode())
})

it('should not be able to deploy same proxy twice', async () => {
Expand Down Expand Up @@ -232,7 +232,7 @@ describe("ProxyFactory", async () => {
expect(await proxy.isInitialized()).to.be.eq(false)
expect(await proxy.masterCopy()).to.be.eq(singleton.address)
expect(await singleton.masterCopy()).to.be.eq(AddressZero)
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await factory.proxyRuntimeCode())
expect(await hre.ethers.provider.getCode(proxyAddress)).to.be.eq(await getGnosisSafeProxyRuntimeCode())
})
})
})
6 changes: 6 additions & 0 deletions test/utils/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ export const getCompatFallbackHandler = async () => {
return (await compatFallbackHandlerContract()).attach((await compatFallbackHandlerDeployment()).address);
}

export const getGnosisSafeProxyRuntimeCode = async () => {
const proxyArtifact = await hre.artifacts.readArtifact('GnosisSafeProxy');

return proxyArtifact.deployedBytecode;
}

export const compile = async (source: string) => {
const input = JSON.stringify({
'language': 'Solidity',
Expand Down

0 comments on commit e8606e2

Please sign in to comment.