Skip to content

Commit

Permalink
feat: deploy proxy factory and socket safe
Browse files Browse the repository at this point in the history
  • Loading branch information
ameeshaagrawal committed Oct 17, 2024
1 parent ec100e3 commit 5965e59
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;

import "../libraries/RescueFundsLib.sol";
import "../../libraries/RescueFundsLib.sol";

import "../utils/AccessControl.sol";
import {RESCUE_ROLE} from "../utils/AccessRoles.sol";
import "../../utils/AccessControl.sol";
import {RESCUE_ROLE} from "../../utils/AccessRoles.sol";
import "solady/utils/LibSort.sol";

import {ISafe, Enum} from "./SafeL2.sol";
Expand Down
File renamed without changes.
23 changes: 23 additions & 0 deletions contracts/utils/multisig/proxies/IProxyCreationCallback.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
import "./SafeProxy.sol";

/**
* @title IProxyCreationCallback
* @dev An interface for a contract that implements a callback function to be executed after the creation of a proxy instance.
*/
interface IProxyCreationCallback {
/**
* @dev Function to be called after the creation of a SafeProxy instance.
* @param proxy The newly created SafeProxy instance.
* @param _singleton The address of the singleton contract used to create the proxy.
* @param initializer The initializer function call data.
* @param saltNonce The nonce used to generate the salt for the proxy deployment.
*/
function proxyCreated(
SafeProxy proxy,
address _singleton,
bytes calldata initializer,
uint256 saltNonce
) external;
}
63 changes: 63 additions & 0 deletions contracts/utils/multisig/proxies/SafeProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

/**
* @title IProxy - Helper interface to access the singleton address of the Proxy on-chain.
* @author Richard Meissner - @rmeissner
*/
interface IProxy {
function masterCopy() external view returns (address);
}

/**
* @title SafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract.
* @author Stefan George - <[email protected]>
* @author Richard Meissner - <[email protected]>
*/
contract SafeProxy {
// Singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.
// To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`
address internal singleton;

/**
* @notice Constructor function sets address of singleton contract.
* @param _singleton Singleton address.
*/
constructor(address _singleton) {
require(_singleton != address(0), "Invalid singleton address provided");
singleton = _singleton;
}

/// @dev Fallback function forwards all transactions and returns all received return data.
fallback() external payable {
// solhint-disable-next-line no-inline-assembly
assembly {
let _singleton := and(
sload(0),
0xffffffffffffffffffffffffffffffffffffffff
)
// 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
if eq(
calldataload(0),
0xa619486e00000000000000000000000000000000000000000000000000000000
) {
mstore(0, _singleton)
return(0, 0x20)
}
calldatacopy(0, 0, calldatasize())
let success := delegatecall(
gas(),
_singleton,
0,
calldatasize(),
0,
0
)
returndatacopy(0, 0, returndatasize())
if eq(success, 0) {
revert(0, returndatasize())
}
return(0, returndatasize())
}
}
}
163 changes: 163 additions & 0 deletions contracts/utils/multisig/proxies/SafeProxyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

import "./SafeProxy.sol";
import "./IProxyCreationCallback.sol";

/**
* @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 - @Georgi87
*/
contract SafeProxyFactory {
event ProxyCreation(SafeProxy indexed proxy, address singleton);

/// @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(SafeProxy).creationCode;
}

/**
* @notice Internal method to create a new proxy contract using CREATE2. Optionally executes an initializer call to a new proxy.
* @param _singleton Address of singleton contract. Must be deployed at the time of execution.
* @param initializer (Optional) 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.
* @return proxy Address of the new proxy contract.
*/
function deployProxy(
address _singleton,
bytes memory initializer,
bytes32 salt
) internal returns (SafeProxy proxy) {
require(isContract(_singleton), "Singleton contract not deployed");

bytes memory deploymentData = abi.encodePacked(
type(SafeProxy).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)
}
}
}
}

/**
* @notice Deploys a new proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
* @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 (SafeProxy 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, initializer, salt);
emit ProxyCreation(proxy, _singleton);
}

/**
* @notice Deploys a new chain-specific proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
* @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 (SafeProxy 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);
}

/**
* @notice Deploy a new proxy with `_singleton` singleton and `saltNonce` salt.
* Optionally executes an initializer call to a new proxy and calls a specified callback address `callback`.
* @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(
address _singleton,
bytes memory initializer,
uint256 saltNonce,
IProxyCreationCallback callback
) public returns (SafeProxy proxy) {
uint256 saltNonceWithCallback = uint256(
keccak256(abi.encodePacked(saltNonce, callback))
);
proxy = createProxyWithNonce(
_singleton,
initializer,
saltNonceWithCallback
);
if (address(callback) != address(0))
callback.proxyCreated(proxy, _singleton, initializer, saltNonce);
}

/**
* @notice Returns true if `account` is a contract.
* @dev This function will return false if invoked during the constructor of a contract,
* as the code is not actually created until after the constructor finishes.
* @param account The address being queried
* @return True if `account` is a contract
*/
function isContract(address account) internal view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly {
size := extcodesize(account)
}
return size > 0;
}

/**
* @notice Returns the ID of the chain the contract is currently deployed on.
* @return The ID of the current chain as a uint256.
*/
function getChainId() public view returns (uint256) {
uint256 id;
// solhint-disable-next-line no-inline-assembly
assembly {
id := chainid()
}
return id;
}
}
73 changes: 70 additions & 3 deletions scripts/deploy/scripts/deploySocket.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Contract, constants } from "ethers";
import { Contract, Transaction, constants, utils } from "ethers";
import {
DeployParams,
getInstance,
Expand Down Expand Up @@ -46,15 +46,30 @@ export const deploySocket = async (
// safe wrapper deployment
const safe: Contract = await getOrDeploy(
"SafeL2",
"contracts/utils/SafeL2.sol",
"contracts/utils/multisig/SafeL2.sol",
[],
deployUtils
);
deployUtils.addresses["SafeL2"] = safe.address;

const safeProxyFactory: Contract = await getOrDeploy(
"SafeProxyFactory",
"contracts/utils/multisig/proxies/SafeProxyFactory.sol",
[],
deployUtils
);
deployUtils.addresses["SafeProxyFactory"] = safeProxyFactory.address;

const proxyAddress = await createSocketSafe(
safeProxyFactory,
safe.address,
[socketOwner]
);
deployUtils.addresses["SocketSafeProxy"] = proxyAddress;

const multisigWrapper: Contract = await getOrDeploy(
"MultiSigWrapper",
"contracts/utils/MultiSigWrapper.sol",
"contracts/utils/multisig/MultiSigWrapper.sol",
[socketOwner, deployUtils.addresses["SafeL2"]],
deployUtils
);
Expand Down Expand Up @@ -230,3 +245,55 @@ export const deploySocket = async (
deployedAddresses: deployUtils.addresses,
};
};

// Assuming you are in a contract context
async function createSocketSafe(safeProxyFactory, safeAddress, owners) {
const addressZero = "0x0000000000000000000000000000000000000000";
const functionSignature =
"setup(address[],uint256,address,bytes,address,address,uint256,address)";
const functionSelector = utils.id(functionSignature).slice(0, 10);

const encodedParameters = utils.defaultAbiCoder.encode(
[
"address[]",
"uint256",
"address",
"bytes",
"address",
"address",
"uint256",
"address",
],
[
owners,
owners.length,
addressZero,
"0x",
addressZero,
addressZero,
0,
addressZero,
]
);
const encodedData = functionSelector + encodedParameters.slice(2); // Remove '0x' from encodedParameters

const tx = await safeProxyFactory.createChainSpecificProxyWithNonce(
safeAddress,
encodedData,
0
);
const receipt = await tx.wait();

const safeSetupEvent = receipt.events?.find(
(event: Event) => event.event === "ProxyCreation"
);

if (safeSetupEvent) {
const proxy = safeSetupEvent.args.proxy;
console.log(`Safe proxy: ${proxy}`);
} else {
console.log(
"Safe proxy created event not found in the transaction receipt"
);
}
}
2 changes: 1 addition & 1 deletion test/MultiSigWrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity 0.8.19;

import {Test} from "forge-std/Test.sol";
import "../contracts/utils/MultiSigWrapper.sol";
import "../contracts/utils/multisig/MultiSigWrapper.sol";
import {MockSafe} from "../contracts/mocks/MockSafe.sol";

contract MultiSigWrapperTestHelper is MultiSigWrapper {
Expand Down

0 comments on commit 5965e59

Please sign in to comment.