-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from manifoldxyz/create2-deploy
Create2 deploy
- Loading branch information
Showing
4 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |