Skip to content

Commit

Permalink
Merge pull request #29 from manifoldxyz/create2-deploy
Browse files Browse the repository at this point in the history
Create2 deploy
  • Loading branch information
wwhchung authored May 31, 2024
2 parents 487dbb9 + 9de8797 commit 95a7f95
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 0 deletions.
70 changes: 70 additions & 0 deletions contracts/proxy/DeploymentProxy.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
object "DeploymentProxy" {
// deployment code
code {
let size := datasize("runtime")
datacopy(0, dataoffset("runtime"), size)
return(0, size)
}
object "runtime" {
// deployed code
code {
// Calldata encoded is:
// nonce (bytes32), extensionArray (address[]), adminArray (address[]), bytecode
// nonce is 32 bytes at location 0
// extensionArray starts at location 32, and is of length 64 + length*32 (first byte is array data offset, second is array data length)
// adminArray starts at 64 + extensionArrayLength*32, and is of length 64 + length*32 (first byte is array data offset, second is array data length)
if iszero(eq(32, calldataload(32))) { revert(0, 0) }
// Load array data
let extensionArrayLength := calldataload(64)
let adminArrayOffset := add(96, mul(extensionArrayLength, 32))
if iszero(eq(32, calldataload(adminArrayOffset))) { revert(0, 0) }
let adminArrayLength := calldataload(add(32, adminArrayOffset))
// Compute bytecode offset
let offset := add(160, mul(add(extensionArrayLength, adminArrayLength), 32))
// Copy nonce + extensionArray + adminArray into position 0
calldatacopy(0, 0, offset)
let salt := keccak256(0, offset)
// Copy bytecode without nonce + extensionArray + adminArray into position 0
calldatacopy(0, offset, sub(calldatasize(), offset))
// Create2, using the bytecode stored in memory from the prior line
let result := create2(callvalue(), 0, sub(calldatasize(), offset), salt)
if iszero(result) { revert(0, 0) }
// Store function selector for registerExtension(address,string)
for { let i } lt(i, extensionArrayLength) { i := add(i, 1) } {
mstore(0, 0x3071a0f9)
// Store the extension
calldatacopy(32, add(96, mul(i, 32)), 32)
// Store empty string
mstore(64, 64)
mstore(96, 0)
let extensionRegisterResult := call(gas(), result, 0, 28, 100, 0, 0)
if iszero(extensionRegisterResult) { revert(0, 0) }
}
// Store function selector for approveAdmin(address)
let adminArrayDataOffset := add(64, adminArrayOffset)
for { let i } lt(i, adminArrayLength) { i := add(i, 1) } {
mstore(0, 0x6d73e669)
// Store the admin address
calldatacopy(32, add(adminArrayDataOffset, mul(i, 32)), 32)
// Store empty string
mstore(64, 64)
mstore(96, 0)
let approveAdminResult := call(gas(), result, 0, 28, 36, 0, 0)
if iszero(approveAdminResult) { revert(0, 0) }
}
// Store function selector for transferOwnership(address)
mstore(0, 0xf2fde38b)
// Store the caller
mstore(32, caller())
// Call transferOwnership(caller())
let transfer := call(gas(), result, 0, 28, 36, 0, 0)
if iszero(transfer) { revert(0, 0) }
// Store address
mstore(0, result)
// Emit event
let signatureHash := 0x4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b511
log1(0, 32, signatureHash)
return(12, 20)
}
}
}
23 changes: 23 additions & 0 deletions script/DeployDeploymentProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import "../contracts/ERC721CreatorImplementation.sol";

contract DeployDeploymentProxy is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
// Salt is: 0x6d616e69666f6c6420636f6e7472616374206465706c6f7965722070726f7879
// Hex of "manifold contract deployer proxy"
bytes memory bytecode = abi.decode(vm.parseJson(vm.readFile("out/DeploymentProxy.yul/DeploymentProxy.json"), "$.bytecode.object"), (bytes));
bytes memory initcode = abi.encodePacked(bytes32(0x6d616e69666f6c6420636f6e7472616374206465706c6f7965722070726f7879), bytecode);
(bool success, bytes memory data) = address(0x4e59b44847b379578588920cA78FbF26c0B4956C).call(initcode);
require(success, "DeploymentProxy deployment failed");
address deployedAddress = address(uint160(bytes20(data)));
console.logString("Deployed to:");
console.logAddress( deployedAddress);
vm.stopBroadcast();
}
}
175 changes: 175 additions & 0 deletions test/proxy/DeploymentProxy.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {ERC721CreatorImplementation} from "creator-core/ERC721CreatorImplementation.sol";
import {ERC721TokenURIExtension} from "../creator/erc721/extensions/ERC721TokenURIExtension.sol";
import {Test} from "forge-std/Test.sol";
import {Proxy} from "openzeppelin/proxy/Proxy.sol";
import {Address} from "openzeppelin/utils/Address.sol";
import {StorageSlot} from "openzeppelin/utils/StorageSlot.sol";
import "./lib/YulDeployer.sol";

contract ProxyMock is Proxy {
constructor(address impl) {
assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = impl;
(bool success, ) = impl.delegatecall(abi.encodeWithSignature("initialize(string,string)", "Test Proxy", "TP"));
require(success, "Initialization failed");
}

/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

/**
* @dev Returns the current implementation address.
*/
function implementation() public view returns (address) {
return _implementation();
}

function _implementation() internal override view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
}

interface DeploymentProxy {}

contract DeploymentProxyTest is Test {
DeploymentProxy proxy;
ProxyMock proxyMock;
address implementation;

YulDeployer yulDeployer = new YulDeployer();
address creator = address(0xC12EA7012);
address admin1 = address(0x01234);
address admin2 = address(0x56789);

function setUp() public virtual {
proxy = DeploymentProxy(yulDeployer.deployContract("out/DeploymentProxy.yul/DeploymentProxy.json"));
implementation = address(new ERC721CreatorImplementation());
}

function testDeploy() public {
vm.prank(creator);
bytes32 nonce = 0x0;
address[] memory extensions = new address[](0);
address[] memory admins = new address[](0);
bytes memory bytecode = abi.encodePacked(nonce, abi.encode(extensions), abi.encode(admins), vm.getCode("DeploymentProxy.t.sol:ProxyMock"), abi.encode(implementation));
(bool success, bytes memory data) = address(proxy).call(bytecode);
assertTrue(success);
address deployedAddress = address(uint160(bytes20(data)));
address contractOwner = ERC721CreatorImplementation(deployedAddress).owner();
assertEq(creator, contractOwner);
vm.stopPrank();
}

function testDeployWithExtension() public {
bytes32 nonce = 0x0;
ERC721TokenURIExtension extension1 = new ERC721TokenURIExtension(creator);
ERC721TokenURIExtension extension2 = new ERC721TokenURIExtension(creator);
address[] memory extensions = new address[](2);
address[] memory admins = new address[](0);
extensions[0] = address(extension1);
extensions[1] = address(extension2);
vm.prank(creator);
bytes memory bytecode = abi.encodePacked(nonce, abi.encode(extensions), abi.encode(admins), vm.getCode("DeploymentProxy.t.sol:ProxyMock"), abi.encode(implementation));
(bool success, bytes memory data) = address(proxy).call(bytecode);
assertTrue(success);
address deployedAddress = address(uint160(bytes20(data)));
address contractOwner = ERC721CreatorImplementation(deployedAddress).owner();
assertEq(creator, contractOwner);
address[] memory registeredExtensions = ERC721CreatorImplementation(deployedAddress).getExtensions();
assertEq(2, registeredExtensions.length);
assertEq(address(extension1), registeredExtensions[0]);
assertEq(address(extension2), registeredExtensions[1]);
vm.stopPrank();
}

function testDeployWithAdmins() public {
bytes32 nonce = 0x0;
address[] memory extensions = new address[](0);
address[] memory admins = new address[](2);
admins[0] = admin1;
admins[1] = admin2;
vm.prank(creator);
bytes memory bytecode = abi.encodePacked(nonce, abi.encode(extensions), abi.encode(admins), vm.getCode("DeploymentProxy.t.sol:ProxyMock"), abi.encode(implementation));
(bool success, bytes memory data) = address(proxy).call(bytecode);
assertTrue(success);
address deployedAddress = address(uint160(bytes20(data)));
address contractOwner = ERC721CreatorImplementation(deployedAddress).owner();
assertEq(creator, contractOwner);
address[] memory registeredAdmins = ERC721CreatorImplementation(deployedAddress).getAdmins();
assertEq(2, registeredAdmins.length);
assertEq(admin1, registeredAdmins[0]);
assertEq(admin2, registeredAdmins[1]);
vm.stopPrank();
}

function testDeployWithExtensionAndAdmin() public {
bytes32 nonce = 0x0;
ERC721TokenURIExtension extension = new ERC721TokenURIExtension(creator);
address[] memory extensions = new address[](1);
address[] memory admins = new address[](1);
extensions[0] = address(extension);
admins[0] = admin1;
vm.prank(creator);
bytes memory bytecode = abi.encodePacked(nonce, abi.encode(extensions), abi.encode(admins), vm.getCode("DeploymentProxy.t.sol:ProxyMock"), abi.encode(implementation));
(bool success, bytes memory data) = address(proxy).call(bytecode);
assertTrue(success);
address deployedAddress = address(uint160(bytes20(data)));
address contractOwner = ERC721CreatorImplementation(deployedAddress).owner();
assertEq(creator, contractOwner);
address[] memory registeredExtensions = ERC721CreatorImplementation(deployedAddress).getExtensions();
assertEq(1, registeredExtensions.length);
assertEq(address(extension), registeredExtensions[0]);
address[] memory registeredAdmins = ERC721CreatorImplementation(deployedAddress).getAdmins();
assertEq(1, registeredAdmins.length);
assertEq(admin1, registeredAdmins[0]);
vm.stopPrank();
}

function testRedeploy() public {
vm.prank(creator);
bytes32 nonce = 0x0;
address[] memory extensions = new address[](0);
address[] memory admins = new address[](0);
bytes memory bytecode = abi.encodePacked(nonce, abi.encode(extensions), abi.encode(admins), vm.getCode("DeploymentProxy.t.sol:ProxyMock"), abi.encode(implementation));
(bool success, bytes memory data) = address(proxy).call(bytecode);
assertTrue(success);
address deployedAddress = address(uint160(bytes20(data)));
address contractOwner = ERC721CreatorImplementation(deployedAddress).owner();
assertEq(creator, contractOwner);

// Attempt to redeploy
(success, data) = address(proxy).call(bytecode);
assertFalse(success);

// Attempt to redeploy with same nonce but different extensions
ERC721TokenURIExtension extension = new ERC721TokenURIExtension(creator);
extensions = new address[](1);
extensions[0] = address(extension);
bytecode = abi.encodePacked(nonce, abi.encode(extensions), abi.encode(admins), vm.getCode("DeploymentProxy.t.sol:ProxyMock"), abi.encode(implementation));
(success, data) = address(proxy).call(bytecode);
assertTrue(success);
address deployedAddressWithExtensions = address(uint160(bytes20(data)));
assertNotEq(deployedAddress, deployedAddressWithExtensions);


// Attempt to redeploy with same nonce but different admins
extensions = new address[](0);
admins = new address[](1);
admins[0] = admin1;
bytecode = abi.encodePacked(nonce, abi.encode(extensions), abi.encode(admins), vm.getCode("DeploymentProxy.t.sol:ProxyMock"), abi.encode(implementation));
(success, data) = address(proxy).call(bytecode);
assertTrue(success);
address deployedAddressWithAdmins = address(uint160(bytes20(data)));
assertNotEq(deployedAddress, deployedAddressWithAdmins);
assertNotEq(deployedAddressWithExtensions, deployedAddressWithAdmins);
vm.stopPrank();
}
}
29 changes: 29 additions & 0 deletions test/proxy/lib/YulDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Test.sol";

contract YulDeployer is Test {
///@notice Compiles a Yul contract and returns the address that the contract was deployed to
///@notice If deployment fails, an error will be thrown
///@param fileName - The file name of json for the compiled Yul contract. Usually located in the out directory
///@return deployedAddress - The address that the contract was deployed to
function deployContract(string memory fileName) public returns (address) {
bytes memory bytecode = abi.decode(vm.parseJson(vm.readFile(fileName), "$.bytecode.object"), (bytes));

///@notice deploy the bytecode with the create instruction
address deployedAddress;
assembly {
deployedAddress := create(0, add(bytecode, 0x20), mload(bytecode))
}

///@notice check that the deployment was successful
require(
deployedAddress != address(0),
"YulDeployer could not deploy contract"
);

///@notice return the address that the contract was deployed to
return deployedAddress;
}
}

0 comments on commit 95a7f95

Please sign in to comment.