Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updated TWCloneFactory with guarded salt #187

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions script/superchain/DeployTWCloneFactory.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
pragma solidity ^0.8.20;

import {Script} from "forge-std/Script.sol";
import "lib/forge-std/src/console.sol";
import {TWCloneFactory} from "src/TWCloneFactory.sol";
import {SuperChainInterop} from "src/module/token/crosschain/SuperChainInterop.sol";

interface ICreateX {

function deployCreate2(bytes32 salt, bytes memory initCode) external returns (address newContract);

}

contract DeployTWCloneFactoryScript is Script {

address createX = 0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed;

function deployDeterministic(bytes32 salt, bytes memory creationCode) public returns (address) {
address deployedAddress;

// Deploy using CREATE2
assembly {
deployedAddress := create2(0, add(creationCode, 0x20), mload(creationCode), salt)
}

require(deployedAddress != address(0), "CREATE2 failed");

return deployedAddress;
}

function run() external {
uint256 testPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(testPrivateKey);

bytes32 salt;
bytes memory initCode = abi.encodePacked(type(TWCloneFactory).creationCode);

address twCloneFactory = ICreateX(createX).deployCreate2(salt, initCode);
console.log("TWCloneFactory deployed: ", twCloneFactory);

vm.stopBroadcast();
}

}
45 changes: 45 additions & 0 deletions src/TWCloneFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import {LibClone} from "@solady/utils/LibClone.sol";

contract TWCloneFactory {

/// @dev Emitted when a proxy is deployed.
event ProxyDeployed(address indexed implementation, address proxy, address indexed deployer);

/// @dev Deploys a proxy that points to the given implementation.
function deployProxyByImplementation(address implementation, bytes memory data, bytes32 salt)
public
returns (address deployedProxy)
{
bytes32 saltHash = _guard(salt, data);
deployedProxy = LibClone.cloneDeterministic(implementation, saltHash);

emit ProxyDeployed(implementation, deployedProxy, msg.sender);

if (data.length > 0) {
// slither-disable-next-line unused-return
(bool success,) = deployedProxy.call(data);
require(success, "TWCloneFactory: proxy deployment failed");
}
}

function _guard(bytes32 salt, bytes memory data) internal view returns (bytes32) {
// 01 if cross chain deployment is allowed
// 00 if cross chain deployment is not allowed
bool allowCrossChainDeployment = bytes1(salt[0]) == hex"01";
bool encodeDataIntoSalt = bytes1(salt[1]) == hex"01";

if (allowCrossChainDeployment && encodeDataIntoSalt) {
return keccak256(abi.encode(salt, data));
} else if (allowCrossChainDeployment && !encodeDataIntoSalt) {
return salt;
} else if (!allowCrossChainDeployment && encodeDataIntoSalt) {
return keccak256(abi.encode(salt, block.chainid, data));
} else {
return keccak256(abi.encode(salt, block.chainid));
}
}

}
136 changes: 136 additions & 0 deletions test/TWCloneFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import "../src/TWCloneFactory.sol";
import {LibClone} from "@solady/utils/LibClone.sol";
import "forge-std/Test.sol";

contract MockImplementation {

uint256 public value;

function initialize(uint256 _value) public {
value = _value;
}

}

contract TWCloneFactoryTest is Test {

TWCloneFactory factory;
MockImplementation implementation;

uint256 chainId1 = 1;
uint256 chainId2 = 2;

function setUp() public {
factory = new TWCloneFactory();
implementation = new MockImplementation();
}

// Test case 1: Cross chain deployment on - same address on two chains
function testCrossChainDeploymentOnSameAddress() public {
// Set up the salt with allowCrossChainDeployment = true, encodeDataIntoSalt = false
// salt[0] == hex"01", salt[1] == hex"00"
bytes32 salt = bytes32(abi.encodePacked(hex"01", hex"00", bytes30(0)));
bytes memory data = "";

// Save the current state
uint256 snapshotId = vm.snapshot();

// Set chain ID to chainId1 and deploy
vm.chainId(chainId1);
address proxy1 = factory.deployProxyByImplementation(address(implementation), data, salt);

// Revert to the snapshot to reset the state
vm.revertTo(snapshotId);

// Set chain ID to chainId2 and deploy
vm.chainId(chainId2);
address proxy2 = factory.deployProxyByImplementation(address(implementation), data, salt);

// Check that the proxies are the same
assertEq(proxy1, proxy2, "Proxies should be the same on different chains when cross chain deployment is on");
}

// Test case 2: Cross chain deployment off - different address on two chains
function testCrossChainDeploymentOffDifferentAddress() public {
// Set up the salt with allowCrossChainDeployment = false, encodeDataIntoSalt = false
// salt[0] == hex"00", salt[1] == hex"00"
bytes32 salt = bytes32(abi.encodePacked(hex"00", hex"00", bytes30(0)));
bytes memory data = "";

// Save the current state
uint256 snapshotId = vm.snapshot();

// Set chain ID to chainId1 and deploy
vm.chainId(chainId1);
address proxy1 = factory.deployProxyByImplementation(address(implementation), data, salt);

// Revert to the snapshot to reset the state
vm.revertTo(snapshotId);

// Set chain ID to chainId2 and deploy
vm.chainId(chainId2);
address proxy2 = factory.deployProxyByImplementation(address(implementation), data, salt);

// Check that the proxies are different
assertNotEq(
proxy1, proxy2, "Proxies should be different on different chains when cross chain deployment is off"
);
}

// Test case 3: Not encodeDataIntoSalt - same address with different init data
function testNotEncodeDataIntoSaltSameAddress() public {
// Set up the salt with allowCrossChainDeployment = true, encodeDataIntoSalt = false
// salt[0] == hex"01", salt[1] == hex"00"
bytes32 salt = bytes32(abi.encodePacked(hex"01", hex"00", bytes30(0)));

// Save the current state
uint256 snapshotId = vm.snapshot();

// Deploy with data1
vm.chainId(chainId1);
bytes memory data1 = abi.encodeWithSignature("initialize(uint256)", 42);
address proxy1 = factory.deployProxyByImplementation(address(implementation), data1, salt);

// Revert to the snapshot to reset the state
vm.revertTo(snapshotId);

// Deploy with data2
vm.chainId(chainId2);
bytes memory data2 = abi.encodeWithSignature("initialize(uint256)", 100);
address proxy2 = factory.deployProxyByImplementation(address(implementation), data2, salt);

// Check that the proxies are the same
assertEq(proxy1, proxy2, "Proxies should be the same when encodeDataIntoSalt is off");
}

// Test case 4: Encode Data into salt - different address with different init data
function testEncodeDataIntoSaltDifferentAddress() public {
// Set up the salt with allowCrossChainDeployment = true, encodeDataIntoSalt = true
// salt[0] == hex"01", salt[1] == hex"01"
bytes32 salt = bytes32(abi.encodePacked(hex"01", hex"01", bytes30(0)));

// Deploy with data1
bytes memory data1 = abi.encodeWithSignature("initialize(uint256)", 42);
address proxy1 = factory.deployProxyByImplementation(address(implementation), data1, salt);

// Deploy with data2
bytes memory data2 = abi.encodeWithSignature("initialize(uint256)", 100);
address proxy2 = factory.deployProxyByImplementation(address(implementation), data2, salt);

// Check that the proxies are different
assertNotEq(proxy1, proxy2, "Proxies should be different when encodeDataIntoSalt is on");

// Verify that both initializations took effect independently
MockImplementation proxyImpl1 = MockImplementation(proxy1);
uint256 value1 = proxyImpl1.value();
assertEq(value1, 42, "Value should be initialized to 42");

MockImplementation proxyImpl2 = MockImplementation(proxy2);
uint256 value2 = proxyImpl2.value();
assertEq(value2, 100, "Value should be initialized to 100");
}

}