Skip to content

Commit

Permalink
Stable 7248 update cctpv2 deploy using create2 (#16)
Browse files Browse the repository at this point in the history
As per
[STABLE-7248](https://circlepay.atlassian.net/browse/STABLE-7248?atlOrigin=eyJpIjoiMGI5ZDI3OTc2ZWZhNDIzYjg5NTQ2YzQ5YzViYTgzMzgiLCJwIjoiaiJ9):

- Created new `ProxyFactory` that needs to be deployed traditionally via
CREATE
 - Added deploy script and tests for `ProxyFactory`.

## NOTE

This PR does not yet integrate v1/v2 deployment scripts. The v2
contracts will need to be updated to use an initialize pattern.

[STABLE-7248]:
https://circlepay.atlassian.net/browse/STABLE-7248?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
epoon-circle authored Sep 18, 2024
1 parent 6f02d5d commit 6d7d118
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 2 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ simulate-deploy:
deploy:
forge script scripts/v1/deploy.s.sol:DeployScript --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast

simulate-deploy-proxy-factory:
forge script scripts/DeployProxyFactory.s.sol:DeployProxyFactoryScript --rpc-url ${RPC_URL} --sender ${SENDER}

deploy-proxy-factory:
forge script scripts/DeployProxyFactory.s.sol:DeployProxyFactoryScript --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast

simulate-deployv2:
forge script scripts/v2/1_deploy.s.sol:DeployV2Script --rpc-url ${RPC_URL} --sender ${SENDER}

Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,26 @@ The contracts are deployed using [Forge Scripts](https://book.getfoundry.sh/tuto

### V2

The contracts are deployed using [Forge Scripts](https://book.getfoundry.sh/tutorials/solidity-scripting). The script is located in [scripts/v2/1_deploy.s.sol](/scripts/v2/1_deploy.s.sol). Follow the below steps to deploy the contracts:
Deploy Create2Factory first if not yet deployed.

1. Add the below environment variables to your [env](.env) file
<ol type="a">
<li>

Add the below environment variable to your [env](.env) file:
- `CREATE2_FACTORY_DEPLOYER_KEY`</li>
<li>

Run `make simulate-deploy-create2-factory RPC_URL=<RPC_URL> SENDER=<SENDER>` to perform a dry run.</li>
<li>

Run
```make deploy-create2-factory RPC_URL=<RPC_URL> SENDER=<SENDER>```
to deploy the Create2Factory.</li>
</ol>

The contracts are deployed via `CREATE2` through Create2Factory. Follow the below steps to deploy the contracts:

1. Replace the environment variables in your [env](.env) file with the following:

- `MESSAGE_TRANSMITTER_DEPLOYER_KEY`
- `TOKEN_MESSENGER_DEPLOYER_KEY`
Expand All @@ -116,6 +133,7 @@ The contracts are deployed using [Forge Scripts](https://book.getfoundry.sh/tuto
- `DOMAIN`
- `REMOTE_DOMAIN`
- `BURN_LIMIT_PER_MESSAGE`
- `CREATE2_FACTORY_ADDRESS`

In addition, to link the remote bridge, one of two steps needs to be followed:

Expand Down
43 changes: 43 additions & 0 deletions scripts/DeployCreate2Factory.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024, Circle Internet Financial Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity 0.7.6;

import "forge-std/Script.sol";
import {Create2Factory} from "../src/v2/Create2Factory.sol";

contract DeployCreate2FactoryScript is Script {
uint256 private create2FactoryDeployerKey;

Create2Factory private create2Factory;

function deployCreate2Factory(
uint256 deployerKey
) internal returns (Create2Factory _create2Factory) {
vm.startBroadcast(deployerKey);

_create2Factory = new Create2Factory();

vm.stopBroadcast();
}

function setUp() public {
create2FactoryDeployerKey = vm.envUint("CREATE2_FACTORY_DEPLOYER_KEY");
}

function run() public {
create2Factory = deployCreate2Factory(create2FactoryDeployerKey);
}
}
75 changes: 75 additions & 0 deletions src/v2/Create2Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2024, Circle Internet Financial Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity 0.7.6;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {Ownable} from "../roles/Ownable.sol";

/**
* @title Create2Factory
* @notice Contract used for deterministic contract deployments across chains.
*/
contract Create2Factory is Ownable {
/**
* @notice Deploys the contract.
* @param amount Amount of native token to seed the deployment
* @param salt A unique identifier
* @param bytecode The contract bytecode to deploy
* @return addr The deployed address
*/
function deploy(
uint256 amount,
bytes32 salt,
bytes calldata bytecode
) external payable onlyOwner returns (address addr) {
// Deploy deterministically
addr = Create2.deploy(amount, salt, bytecode);
}

/**
* @notice Deploys the contract and calls into it.
* @param amount Amount of native token to seed the deployment
* @param salt A unique identifier
* @param bytecode The contract bytecode to deploy
* @param data The data to call the implementation with
* @return addr The deployed address
*/
function deployAndCall(
uint256 amount,
bytes32 salt,
bytes calldata bytecode,
bytes calldata data
) external payable onlyOwner returns (address addr) {
// Deploy deterministically
addr = Create2.deploy(amount, salt, bytecode);

Address.functionCall(addr, data);
}

/**
* @notice A helper function for predicting a deterministic address.
* @param salt The unique identifier
* @param bytecodeHash The keccak256 hash of the deployment bytecode.
* @return addr The deterministic address
*/
function computeAddress(
bytes32 salt,
bytes32 bytecodeHash
) external view returns (address addr) {
addr = Create2.computeAddress(salt, bytecodeHash);
}
}
30 changes: 30 additions & 0 deletions test/mocks/MockInitializableImplementation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity 0.7.6;

import {Initializable} from "@openzeppelin/contracts/proxy/Initializable.sol";

contract MockInitializableImplementation is Initializable {
address public addr;
uint256 public num;

function initialize(address _addr, uint256 _num) external initializer {
addr = _addr;
num = _num;
}
}
106 changes: 106 additions & 0 deletions test/v2/Create2Factory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity 0.7.6;
pragma abicoder v2;

import {Create2Factory} from "../../src/v2/Create2Factory.sol";
import {MockInitializableImplementation} from "../mocks/MockInitializableImplementation.sol";
import {UpgradeableProxy} from "@openzeppelin/contracts/proxy/UpgradeableProxy.sol";
import {Test} from "forge-std/Test.sol";

contract Create2FactoryTest is Test {
Create2Factory private create2Factory;
MockInitializableImplementation private impl;

event Upgraded(address indexed implementation);

function setUp() public {
create2Factory = new Create2Factory();
impl = new MockInitializableImplementation();
}

function test_SetUpState() public {
// Check owners
assertEq(create2Factory.owner(), address(this));
}

function testDeploy(address addr, uint256 num, bytes32 salt) public {
// Construct initializer
bytes memory initializer = abi.encodeWithSelector(
MockInitializableImplementation.initialize.selector,
addr,
num
);
// Construct bytecode
bytes memory bytecode = abi.encodePacked(
type(UpgradeableProxy).creationCode,
abi.encode(address(impl), initializer)
);
// Deploy proxy
address expectedAddr = create2Factory.computeAddress(
salt,
keccak256(bytecode)
);
address proxyAddr = create2Factory.deploy(0, salt, bytecode);

// Verify deterministic
assertEq(proxyAddr, expectedAddr);
// Check initialized vars
assertEq(MockInitializableImplementation(proxyAddr).addr(), addr);
assertEq(MockInitializableImplementation(proxyAddr).num(), num);
}

function testDeployAndCall(
address addr,
uint256 num,
uint256 amount,
bytes32 salt
) public {
// Construct initializer
bytes memory initializer = abi.encodeWithSelector(
MockInitializableImplementation.initialize.selector,
addr,
num
);
// Construct bytecode
bytes memory bytecode = abi.encodePacked(
type(UpgradeableProxy).creationCode,
abi.encode(address(impl), "")
);
// Deploy proxy
address expectedAddr = create2Factory.computeAddress(
salt,
keccak256(bytecode)
);
vm.deal(address(this), amount);
address proxyAddr = create2Factory.deployAndCall{value: amount}(
amount,
salt,
bytecode,
initializer
);

// Verify deterministic
assertEq(proxyAddr, expectedAddr);
// Check initialized vars
assertEq(MockInitializableImplementation(proxyAddr).addr(), addr);
assertEq(MockInitializableImplementation(proxyAddr).num(), num);
// Verify balance
assertEq(proxyAddr.balance, amount);
}
}

0 comments on commit 6d7d118

Please sign in to comment.