일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 컨트렉트 동일한 함수이름 호출
- Vue
- 러스트 기초
- multicall
- 프록시배포구조
- ambiguous function description
- ethers
- rust 기초
- 컨트렉트 배포 자동화
- 머신러닝기초
- 체인의정석
- 스마트컨트렉트 예약어 함수이름 중복
- 스마트컨트렉트테스트
- ethers v6
- git rebase
- vue기초
- 러스트 기초 학습
- 티스토리챌린지
- SBT표준
- 오블완
- ethers websocket
- 스마트컨트렉트 함수이름 중복 호출
- 스마트 컨트렉트 함수이름 중복
- Vue.js
- 스마트컨트렉트프록시
- chainlink 설명
- ethers typescript
- nest.js설명
- 러스트기초
- ethers type
- Today
- Total
체인의정석
Solidity Create와 Create2 본문
여러개의 컨트렉트를 만드는 contractFactory 컨트렉트를 만들어야 하는 상황이 생겼다.
이런경우 사용가능한 함수는 create1,2,3가 있다. 이번엔 create1과 2에 대해 정리하려고한다.
출처 : https://docs.openzeppelin.com/cli/2.8/deploying-with-create2
1. Create
create의 경우 solidity에서 new Contract를 쓰면 자연스럽게 생성되는 것으로 sender주소와 nonce값을 가지고 컨트렉트 주소를 뽑아낸다.
new_address = hash(sender, nonce)
https://docs.soliditylang.org/en/latest/control-structures.html#creating-contracts-via-new
create1을 써서 컨트렉트를 배포하는 코드는 다음과 같다.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
uint public x;
constructor(uint a) payable {
x = a;
}
}
contract C {
D d = new D(4); // will be executed as part of C's constructor
function createD(uint arg) public {
D newD = new D(arg);
newD.x();
}
function createAndEndowD(uint arg, uint amount) public payable {
// Send ether along with the creation
D newD = new D{value: amount}(arg);
newD.x();
}
}
2. Create2
create2의 경우
- 0xFF, a constant that prevents collisions with CREATE
- The sender’s own address
- A salt (an arbitrary value provided by the sender)
- The to-be-deployed contract’s bytecode
new_address = hash(0xFF, sender, salt, bytecode)
다음과 같이 사용자가 직접 salt값을 지정해준다. 예를들어 유니스왑v2의 contractFactory의 경우 동일 풀에 대한 컨트레트 주소가 중복 생성되는것을 막아야하기 때문에 각 토큰의 주소를 salt값으로 넣어주어서 동일 토큰에대한 풀 주소 배포를 방지해준다. 따라서 만약 고유 설정값이 있는 컨트렉트를 배포하는 경우에는 해당 값을 salt로 넣어주면되고 고유하지 않다면 그냥 create를 써주면된다.
Create2를 배포하는 solidity코드는 다음과 같다. 만약 입력값을 빼고 싶다면 getCreationBytecode에서 bytecode만 사용하면 된다.
입력값을 뺀 후 나중에 initialize를 하는 방법또한 가능하다.
function getCreationBytecode(uint256 a, uint256 b, uint256 c, uint256 d) public pure returns (bytes memory) {
bytes memory bytecode = type(ContractName).creationCode;
return abi.encodePacked(bytecode, abi.encode(a, b, c, d));
}
function _createVote(uint256 a, uint256 b, uint256 topN, c d) internal returns (address voteAddress) {
bytes memory bytecode = getCreationBytecode(a, b, c, d);
bytes32 salt =
keccak256(
abi.encodePacked(
a,
b,
c,
d
)
);
assembly {
dappAddress := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
return dappAddress;
}
하지만 여기에서는 입력값이 필요했고 여기에 대한 2가지 해결방법을 생각해냈다.
1. initialize함수에 따로 빼기
생성자 형태로 초기화가 필요한 함수를 생성자에 넣는 대신 따로 initialize함수로 빼서 실행을 한다.
해당 방법의 경우 나중에 만약 생성자를 다시 실행시켜야 할 경우에 유용할 수도 있으며 배포 과정이 2단계가 되지만 생성자에 대한 관리가 조금 덜 햇갈릴것 같아서 해당 방식을 개인적으로는 좋게 생각한다.
2. constructor 의 input값을 같이 바이트코드로 인코딩하여 실행하기
https://it-timehacker.tistory.com/340
먼저 참고한 부분은 uniswapV2의 Factory였다.
create2에 대한 설명은 위의 포스팅에 있기 때문에 생략하도록 하겠다.
해당 글을 보니
abi.encodePacked(bytecode, abi.encode(arg1, arg2))
이런식으로 input값을 create2의 바이트코드에 넣을값과 함께 인코딩하면 된다고 한다.
그 이유는
bytes memory bytecode = type(컨트렉트이름).creationCode;
해당 바이트코드를 도출하는 코드에서 쓰는 creationCode가 생성자 함수를 포함해서 바이트코드를 도출하는 것이기 때문에 input값을 같이 인코딩해서 넣으면 생성자에 input값을 넣은것처럼 되기 때문이다.