これに書いてあるcreate2によるデプロイをやってみました。
Foundryを使用します。
- create2はコントラクトからしか呼び出せない
- create2を呼び出すファクトリーコントラクトがまず必要
- ファクトリーコントラクトを呼び出して、実際のコントラクトをデプロイする
異なるチェーンで同じコントラクトアドレスを使うためには、、、
- 通常のデプロイの場合には
nonce
を同じにしたいといけない。 -
create2
の場合はファクトリーコントラクトのアドレスが異なるチェーンで同じでないといけない。
とりあえずやってみます。
ファクトリーコントラクトFactory
。
https://github.com/miguelmota/solidity-create2-example
create2のところはassemblyとなっています。solidityでは書けないようです。
pragma solidity ^0.8.13;
contract Factory {
event Deployed(address addr, uint256 salt);
function deploy(bytes memory code, uint256 salt) public {
address addr;
assembly {
addr := create2(0, add(code, 0x20), mload(code), salt)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
emit Deployed(addr, salt);
}
}
anvil
でテストネット起動
anvil
$RPC
にはhttp://127.0.0.1:8545
, $SEC1
にはプライベートキーを入れてFactory.sol
をデプロイします。ここであとで使用するためコントラクトアドレスをメモしておきます。
forge create --rpc-url $RPC --private-key $SEC1 src/Factory.sol:Factory
実際にデプロイするコントラクトはこちら。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;
import {ERC721} from "solmate/tokens/ERC721.sol";
import {Strings} from "openzeppelin-contracts/utils/Strings.sol";
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
error MintPriceNotPaid();
error MaxSupply();
error NonExistentTokenURI();
error WithdrawTransfer();
event NewMinted(uint256 tokenId);
contract NFT is ERC721, Ownable {
using Strings for uint256;
string public baseURI;
uint256 public currentTokenId;
uint256 public constant TOTAL_SUPPLY = 10_000;
uint256 public constant MINT_PRICE = 0.08 ether;
constructor(
string memory _name,
string memory _symbol,
string memory _baseURI
) ERC721(_name, _symbol) Ownable(msg.sender) {
baseURI = _baseURI;
}
function mintTo(address recipient) public payable returns (uint256) {
if(msg.value !=MINT_PRICE) {
revert MintPriceNotPaid();
}
uint256 newTokenId = ++currentTokenId;
if (newTokenId > TOTAL_SUPPLY) {
revert MaxSupply();
}
_safeMint(recipient, newTokenId);
emit NewMinted(newTokenId);
return newTokenId;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if(ownerOf(tokenId) == address(0)) {
revert NonExistentTokenURI();
}
return bytes(baseURI).length > 0
? string(abi.encodePacked(baseURI, tokenId.toString()))
:"";
}
function withdrawPayments(address payable payee) external onlyOwner {
uint256 balance = address(this).balance;
(bool transferTx, ) = payee.call{value: balance}("");
if(!transferTx) {
revert WithdrawTransfer();
}
}
}
Foundryではデプロイのスクリプトをsolidityかけるので、以下のとおり
-
DETERMINISTIC_CREATE2_FACTORY
にファクトリコントラクトのfactory
のアドレスをいれます。 - 実際にデプロイするコントラクトのコンストラクターに引数がある場合は
encodePacked
でコントラクトのバイナリと共に実際に使用する引数をいれる必要がある。 - このデプロイスクリプトが汎用的に記述できるかどうかは要検証。(引数の数が可変、引数の型の可変など)
- 事前にアドレスを計算してログに出すようにしてある。デプロイする前に実施することでアドレスを事前に計算できる。
import {Script} from "forge-std/Script.sol";
import {NFT} from "src/NFT.sol";
import {Factory} from "src/Factory.sol";
import "forge-std/console.sol";
contract DeterministicDeploy is Script {
// 先ほどの`Factory`コントラクトのアドレスを入れます。
address internal constant DETERMINISTIC_CREATE2_FACTORY = 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9;
NFT nft;
function run() public {
uint salt;
salt=3;
vm.broadcast();
//ここからネットワーク
Factory fact = Factory(DETERMINISTIC_CREATE2_FACTORY);
bytes memory creationBytecode = type(NFT).creationCode;
// デプロイするコントラクトのconstructorに引数が必要な場合も加える。
// 引数がない場合はinitCode= type(NFT).creationCode;でよい
bytes memory initCode = abi.encodePacked(creationBytecode, abi.encode("NFT contract","NFT","http://mynft.org"));
// ここはアドレス計算してログに出してるだけ
// この部分を使う事で事前にアドレスを計算できる
bytes memory bytecode = abi.encodePacked(bytes1(0xff),address(DETERMINISTIC_CREATE2_FACTORY),salt,keccak256(initCode));
address newaddr = address(uint160(uint(keccak256(bytecode))));
console.logAddress(newaddr);
// デプロイ
fact.deploy(initCode, salt);
}
}
スクリプト実行します ($SEC1
はプライベートキーをいれてます。)
forge script script/NFT.s.sol:DeterministicDeploy --rpc-url $RPC --broadcast --private-key $SEC1
実行すると以下のログがでます。
== Logs ==
の下がデプロイされるコントラクトのアドレスになります。(事前に計算したアドレス)
root@DESKTOP-N2O3OSL:~/nft# forge script script/NFT.s.sol:DeterministicDeploy --rpc-url $RPC --broadcast --private-key $SEC1
[⠰] Compiling...
No files changed, compilation skipped
Script ran successfully.
== Logs ==
0xeb1d23b5cf6da2cefD99bF4d6AC0430b7A0BDBeF
EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 31337.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
For more information, please see https://eips.ethereum.org/EIPS/eip-3855
## Setting up 1 EVM.
==========================
Chain 31337
Estimated gas price: 3.102308024 gwei
Estimated total gas used for script: 1812436
Estimated amount required: 0.005622734745786464 ETH
==========================
###
Finding wallets for all the necessary addresses...
##
Sending transactions [0 - 0].
⠁ [00:00:00] [##############################################################################################################################################################] 1/1 txes (0.0s)
Transactions saved to: /root/nft/broadcast/NFT.s.sol/31337/run-latest.json
Sensitive values saved to: /root/nft/cache/NFT.s.sol/31337/run-latest.json
##
Waiting for receipts.
⠉ [00:00:00] [##########################################################################################################################################################] 1/1 receipts (0.0s)
##### anvil-hardhat
✅ [Success]Hash: 0x8ddf14a9cce21184d4af5a0bf3cc1a870928f73b2b1c8ca971708d5ad95fe377
Block: 25
Paid: 0.003996530526966366 ETH (1312582 gas * 3.044785413 gwei)
Transactions saved to: /root/nft/broadcast/NFT.s.sol/31337/run-latest.json
Sensitive values saved to: /root/nft/cache/NFT.s.sol/31337/run-latest.json
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Total Paid: 0.003996530526966366 ETH (1312582 gas * avg 3.044785413 gwei)
Transactions saved to: /root/nft/broadcast/NFT.s.sol/31337/run-latest.json
Sensitive values saved to: /root/nft/cache/NFT.s.sol/31337/run-latest.json
また Factory.sol
では event Deployed(address addr, uint256 salt);
を送出してるので以下でもアドレスを確認できます。data
のの前半部分がコントラクトアドレスです。
root@DESKTOP-N2O3OSL:~/nft# cast logs
- address: 0xeb1d23b5cf6da2cefD99bF4d6AC0430b7A0BDBeF
blockHash: 0x305fe3c367d386d35f9518fc74447f0546fea3b5189bf458177ea6302acb3d79
blockNumber: 25
data: 0x
logIndex: 0
removed: false
topics: [
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0
0x0000000000000000000000000000000000000000000000000000000000000000
0x000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc9
]
transactionHash: 0x8ddf14a9cce21184d4af5a0bf3cc1a870928f73b2b1c8ca971708d5ad95fe377
transactionIndex: 0
- address: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
blockHash: 0x305fe3c367d386d35f9518fc74447f0546fea3b5189bf458177ea6302acb3d79
blockNumber: 25
data: 0x000000000000000000000000eb1d23b5cf6da2cefd99bf4d6ac0430b7a0bdbef0000000000000000000000000000000000000000000000000000000000000003
logIndex: 1
removed: false
topics: [
0xb03c53b28e78a88e31607a27e1fa48234dce28d5d9d9ec7b295aeb02e674a1e1
]
transactionHash: 0x8ddf14a9cce21184d4af5a0bf3cc1a870928f73b2b1c8ca971708d5ad95fe377
transactionIndex: 0
実際のデプロイしたNFTコントラクトでミントできました。
root@DESKTOP-N2O3OSL:~/nft# cast send $NFT_CON "mintTo(address)" $ADR2 --private-key $SEC1 --value "0.08 ether"
blockHash 0x785a3714d77f343dcfbc4469f344065fb2fb29d2984b948c86d90517ee1b1c59
blockNumber 26
contractAddress
cumulativeGasUsed 94376
effectiveGasPrice 3039677108
gasUsed 94376
logs [{"address":"0xeb1d23b5cf6da2cefd99bf4d6ac0430b7a0bdbef","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x","blockHash":"0x785a3714d77f343dcfbc4469f344065fb2fb29d2984b948c86d90517ee1b1c59","blockNumber":"0x1a","transactionHash":"0xf808721c61db2b2a9301e3dc6c5adff354ba6007f3f211428a7a5eb48e208d07","transactionIndex":"0x0","logIndex":"0x0","transactionLogIndex":"0x0","removed":false},{"address":"0xeb1d23b5cf6da2cefd99bf4d6ac0430b7a0bdbef","topics":["0x0f7088101dc997e74a352357ee3a53389b5957179a421a220c5258546478fe47"],"data":"0x0000000000000000000000000000000000000000000000000000000000000001","blockHash":"0x785a3714d77f343dcfbc4469f344065fb2fb29d2984b948c86d90517ee1b1c59","blockNumber":"0x1a","transactionHash":"0xf808721c61db2b2a9301e3dc6c5adff354ba6007f3f211428a7a5eb48e208d07","transactionIndex":"0x0","logIndex":"0x1","transactionLogIndex":"0x1","removed":false}]
logsBloom 0x00000000000000000002000000000020000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000040000000000000000000000000008000000000000000000040000000000800000000000000800020000000000000000000800000000000000000000000010000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082000000000000000000000000000000000000000000000000000060000000000000000000000000000000000001000000000000000800000000000000
root
status 1
transactionHash 0xf808721c61db2b2a9301e3dc6c5adff354ba6007f3f211428a7a5eb48e208d07
transactionIndex 0
type 2
root@DESKTOP-N2O3OSL:~/nft#
anvilのDefault CREATE2 Deployer
Foundry
に同梱してあるテストネットツールanvil
をみてみたら、
Create2
を使用するDefault CREATE2 Deployer
ってコントラクトが0x4e59b44847b379578588920ca78fbf26c0b4956c
既に入っているのだけど、使い方がわからなかった。
そのコントラクトはこれらしいんだけど、わからなかった。
https://github.com/Arachnid/deterministic-deployment-proxy/tree/master
わかったらアップデートします。
他参考サイト