ENS 컨트랙트 배포 중 기록
포스트
취소

ENS 컨트랙트 배포 중 기록


참고 Deploying ENS on a Private Chain

klaytn

클레이튼 엠버서더에서 진행하는 Core dev 프로젝트 중 ‘주소 라벨링’을 진행하고 있다.
이더리움의 ENS(Ethereum Name Service)를 Klaytn으로 개발하여 KIP를 등록하는 것이 이번 프로젝트의 목표이다.

klaytn



ENS 배포

1. 로컬 배포(완)

  • ollie님의 도움으로 hardhat 로컬 네트워크에는 컨트랙트 배포를 할 수 있었음.

    1


  • 버전에 따른 코드 파악이 부족했음.

    ENS가 버전에 따라 쓰이는 코드가 다르다는 것을 뒤늦게 알았지만 어떻게 적용해야하는지 파악이 어려웠다. (하지만 ollie님은 단 번에 파악하심 ㅋㅋ..)

    2

    • ensdomain 문서deps.sol 의 경우 모두 다 가져와서 한꺼번에 하게끔 되어있는데, 하나씩 해보면 된다는 말.

      3

    • ensdomain 컨트랙트가 버전업 되면서 코드가 약간 달라짐. 그래서 script/deploy.js를 고대로 가져다 쓰면 배포가 안됨.

      결국은 @ensdomains-contract 모듈 내의 resolver 부분(이쪽이 변경됨)을 ollie님이 알려주신 v0.0.8 버전으로 낮춰서 컴파일하고, deploy.js 를 살짝 변경해야 함.

      4



      • 그런데, 내가 hardhat을 잘 못해서 그런지 ollie님의 코드를 똑같이 쓰면 배포가 안됨.

        .deployed() 함수가 말을 듣지 않아 다른 함수 사용해서 배포

        • 예)
          • (기존 코드)

            await ens.deployed()

            ens.address // CA

            ⇒ ens.deployed() is not a function

          • (수정)

            await ens.waitForDeployment()

            ens.target // CA

          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
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          75
          76
          77
          78
          79
          80
          81
          82
          83
          84
          85
          86
          87
          88
          89
          90
          
            // deploy.js
            const { ethers } = require("hardhat");
            const namehash = require("eth-ens-namehash");
            const { makeAbi } = require("../makeABI");
                          
            const tld = "test";
            const labelhash = (label) => ethers.keccak256(ethers.toUtf8Bytes(label));
            const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
            const ZERO_HASH =
              "0x0000000000000000000000000000000000000000000000000000000000000000";
                          
            async function main() {
              const ENSRegistry = await ethers.getContractFactory("ENSRegistry");
              const FIFSRegistrar = await ethers.getContractFactory("FIFSRegistrar");
              const ReverseRegistrar = await ethers.getContractFactory("ReverseRegistrar");
              const PublicResolver = await ethers.getContractFactory("PublicResolver");
              const signers = await ethers.getSigners();
              const accounts = signers.map((s) => s.address);
                          
              // console.log(accounts);
                          
              let ensAddress;
              const ens = await ENSRegistry.deploy().then((res) => {
                ensAddress = res.target; // 이전에는 deployed() 이후에 .address로 컨트랙트 주소를 알 수 있었지만 target으로 변경함
                return res;
              });
              await ens.waitForDeployment(); // deployed() 요거 왜 안되지?? ㅠㅠ => 결국에는 waitForDeployment() 씀
              console.log("Registry at", ensAddress);
              await makeAbi(ensAddress, "ens");
                          
              let resolverAddress;
              const resolver = await PublicResolver.deploy(ensAddress, ZERO_ADDRESS).then(
                (res) => {
                  resolverAddress = res.target;
                  return res;
                }
              );
              await resolver.waitForDeployment();
              await setupResolver(ens, resolver, accounts[0]);
              console.log("Resolver at", resolverAddress);
              await makeAbi(resolverAddress, "resolver");
                          
              const registrar = await FIFSRegistrar.deploy(ens.target, namehash.hash(tld));
              await registrar.waitForDeployment();
              await setupRegistrar(ens, registrar);
              console.log("Registrar at", registrar.target);
              await makeAbi(registrar.target, "registrar");
                          
              const reverseRegistrar = await ReverseRegistrar.deploy(
                ens.target,
                resolver.target
              );
              await reverseRegistrar.waitForDeployment();
              await setupReverseRegistrar(ens, registrar, reverseRegistrar, accounts[0]);
              console.log("ReverseRegistrar at", reverseRegistrar.target);
              await makeAbi(reverseRegistrar.target, "reverseRegistrar");
            }
                          
            async function setupResolver(ens, resolver, account) {
              const resolverNode = namehash.hash("resolver");
              const resolverLabel = labelhash("resolver");
              await ens.setSubnodeOwner(ZERO_HASH, resolverLabel, account);
              await ens.setResolver(resolverNode, resolver.target);
              await resolver["setAddr(bytes32,address)"](resolverNode, resolver.target);
            }
                          
            async function setupRegistrar(ens, registrar) {
              await ens.setSubnodeOwner(ZERO_HASH, labelhash(tld), registrar.target);
            }
                          
            async function setupReverseRegistrar(
              ens,
              registrar,
              reverseRegistrar,
              account
            ) {
              await ens.setSubnodeOwner(ZERO_HASH, labelhash("reverse"), account);
              await ens.setSubnodeOwner(
                namehash.hash("reverse"),
                labelhash("addr"),
                reverseRegistrar.target
              );
            }
                          
            main()
              .then(() => process.exit(0))
              .catch((error) => {
                console.error(error);
                process.exit(1);
              });
          
  • hardhat이 낯설다?…

    이번 기회에 hardhat을 익혀보고 싶어서 해봤는데, 확실히 truffle과는 다른 너낌.. 배포하는데 꽤 애먹음.


2. 클레이튼 바오밥 배포(완)

  • 이제 klaytn-baobab 에서 배포를 하려하니 다음과 같은 오류가 뜸.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      ProviderError: evm: execution reverted
          at HttpProvider.request (/Users/instant/Desktop/ens_test/node_modules/hardhat/src/internal/core/providers/http.ts:88:21)
          at processTicksAndRejections (node:internal/process/task_queues:96:5)
          at HardhatEthersProvider.estimateGas (/Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/internal/hardhat-ethers-provider.ts:237:27)
          at /Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:235:35
          at async Promise.all (index 0)
          at HardhatEthersSigner._sendUncheckedTransaction (/Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:256:7)
          at HardhatEthersSigner.sendTransaction (/Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:125:18)
          at send (/Users/instant/Desktop/ens_test/node_modules/ethers/src.ts/contract/contract.ts:299:20)
          at Proxy.setResolver (/Users/instant/Desktop/ens_test/node_modules/ethers/src.ts/contract/contract.ts:338:16)
          at setupResolver (/Users/instant/Desktop/ens_test/scripts/deploy.js:65:3)
    
    • 시도 1) hardhat 설정을 내가 잘못했나?

      deploy.js 에서 account를 불러올 때 truffle과 달리 다음과 같이 불러오는데 이게 로컬 네트워크에서만 적용되는 줄 알았는데, 검색해보니 config 설정을 하면 되는 거 였음.

      1
      2
      3
      4
      5
      
        // deploy.js
        ...
        const signers = await ethers.getSigners();
        const accounts = signers.map((s) => s.address);
        ...
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      
        // hardhat.config.js
        require("@nomicfoundation/hardhat-toolbox");
        const path = require("path");
        require("dotenv").config({ path: path.resolve(__dirname, "./.env") });
        const { KLAYTN_URL, PRIVATE_KEY } = process.env;
              
        /** @type import('hardhat/config').HardhatUserConfig */
        module.exports = {
          solidity: "0.8.19",
          networks: {
            klaytn: {
              url: KLAYTN_URL || "",
              accounts: [PRIVATE_KEY],
            },
          },
        };
      

      하지만 계속 되는 에러.

    • 시도 2) PublicResolver 구조를 한번 더 파악해보자.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        ProviderError: evm: execution reverted
            at HttpProvider.request (/Users/instant/Desktop/ens_test/node_modules/hardhat/src/internal/core/providers/http.ts:88:21)
            at processTicksAndRejections (node:internal/process/task_queues:96:5)
            at HardhatEthersProvider.estimateGas (/Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/internal/hardhat-ethers-provider.ts:237:27)
            at /Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:235:35
            at async Promise.all (index 0)
            at HardhatEthersSigner._sendUncheckedTransaction (/Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:256:7)
            at HardhatEthersSigner.sendTransaction (/Users/instant/Desktop/ens_test/node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:125:18)
            at send (/Users/instant/Desktop/ens_test/node_modules/ethers/src.ts/contract/contract.ts:299:20)
            at Proxy.setResolver (/Users/instant/Desktop/ens_test/node_modules/ethers/src.ts/contract/contract.ts:338:16)
            at setupResolver (/Users/instant/Desktop/ens_test/scripts/deploy.js:65:3)
      

      위의 에러를 보면 PublicResolver 를 배포할 때 ENSRegistry 에 있는 setResolver 함수에서 자꾸 거절함.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
        // ENSRegistry.sol
        ...
        function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public virtual override authorised(node) returns(bytes32) {
          bytes32 subnode = keccak256(abi.encodePacked(node, label));
          _setOwner(subnode, owner);
          emit NewOwner(node, label, owner);
          return subnode;
        }
              
        function setResolver(bytes32 node, address resolver) public virtual override authorised(node) {
          emit NewResolver(node, resolver);
          records[node].resolver = resolver;
        }
        ...
      

      함수 내에 require 등의 제어가 없으니 authorised 문제인가…?

      하지만 scope를 확인하면, setSubnodeOwner 는 잘 된다.

      5


  • 시도 3) 네트워크 차이..?

    ‘당연히 로컬 네트워크는 빠를 수 밖에 없다. 배포 시에 셋팅이 문제라면 컨트랙트가 배포되는 속도와 TPS에 따른 차이가 존재하려나?’ 라는 생각을 하게 됐다.

    아직까지 hardhat을 잘 쓰는게 아니기 때문에 배포 따로, 셋팅 따로 하는 로직으로 바꿔 버렸다.

    결과는?

    • ✅ 배포
      • 배포 스크립트

        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
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        
          const { ethers } = require("hardhat");
          const { makeAbi } = require("../makeABI");
                    
          async function main() {
            const ENSRegistry = await ethers.getContractFactory("ENSRegistry");
            const FIFSRegistrar = await ethers.getContractFactory("FIFSRegistrar");
            const ReverseRegistrar = await ethers.getContractFactory("ReverseRegistrar");
            const PublicResolver = await ethers.getContractFactory("PublicResolver");
            const signers = await ethers.getSigners();
            const accounts = signers.map((s) => s.address);
                    
            let ensAddress;
            const ens = await ENSRegistry.deploy().then((res) => {
              ensAddress = res.target; // 이전에는 deployed() 이후에 .address로 컨트랙트 주소를 알 수 있었지만 target으로 변경됨
              return res;
            });
            await ens.waitForDeployment(); // deployed() 요거 왜 안되지?? ㅠㅠ => hardhat 버전이 업데이트 되면서 다른 것 써야함
            console.log("Registry at", ensAddress);
            await makeAbi(ensAddress, "ens");
                    
            let resolverAddress;
            const resolver = await PublicResolver.deploy(ensAddress, ZERO_ADDRESS).then(
              (res) => {
                resolverAddress = res.target;
                return res;
              }
            );
            await resolver.waitForDeployment();
            console.log("Resolver at", resolverAddress);
            await makeAbi(resolverAddress, "resolver");
                    
            const registrar = await FIFSRegistrar.deploy(ens.target, namehash.hash(tld));
            await registrar.waitForDeployment();
            console.log("Registrar at", registrar.target);
            await makeAbi(registrar.target, "registrar");
                    
            const reverseRegistrar = await ReverseRegistrar.deploy(
              ens.target,
              resolver.target
            );
            await reverseRegistrar.waitForDeployment();
            console.log("ReverseRegistrar at", reverseRegistrar.target);
            await makeAbi(reverseRegistrar.target, "reverseRegistrar");
          }
                    
          main()
            .then(() => process.exit(0))
            .catch((error) => {
              console.error(error);
              process.exit(1);
            });
        

        6

    • ✅ 셋팅
      • 셋팅 스크립트

        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
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        
          const path = require("path");
          require("dotenv").config({ path: path.resolve(__dirname, "./.env") });
          const { KLAYTN_URL, PRIVATE_KEY, ADDRESS } = process.env;
                    
          const Caver = require("caver-js");
          const caver = new Caver(KLAYTN_URL);
                    
          const { ethers } = require("hardhat");
          const namehash = require("eth-ens-namehash");
                    
          const tld = "test";
          const labelhash = (label) => ethers.keccak256(ethers.toUtf8Bytes(label));
          const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
          const ZERO_HASH =
            "0x0000000000000000000000000000000000000000000000000000000000000000";
                    
          // Contract
          const ensData = require("./abis/ens.json");
          const ens = new caver.klay.Contract(ensData.abi, ensData.address).methods;
                    
          const registrarData = require("./abis/registrar.json");
          const registrar = new caver.klay.Contract(
            registrarData.abi,
            registrarData.address
          ).methods;
                    
          const resolverData = require("./abis/resolver.json");
          const resolver = new caver.klay.Contract(resolverData.abi, resolverData.address)
            .methods;
                    
          const reverseRegistrarData = require("./abis/reverseRegistrar.json");
          const reverseRegistrar = new caver.klay.Contract(
            reverseRegistrarData.abi,
            reverseRegistrarData.address
          ).methods;
                    
          const setting = async () => {
            const resolverNode = namehash.hash("resolver");
            const resolverLabel = labelhash("resolver");
                    
            console.log("setSubnodeOwner를 셋팅합니다.");
            const setSubnodeOwner = await ens
              .setSubnodeOwner(ZERO_HASH, resolverLabel, ADDRESS)
              .encodeABI();
            await SendTransaction(setSubnodeOwner, ensData.address);
                    
            console.log(" ");
            console.log("setResolver를 셋팅합니다.");
            const setResolver = await ens
              .setResolver(resolverNode, resolverData.address)
              .encodeABI();
            await SendTransaction(setResolver, ensData.address);
                    
            console.log(" ");
            console.log("setAddr를 셋팅합니다.");
            const setAddr = await resolver
              .setAddr(resolverNode, resolverData.address)
              .encodeABI();
            await SendTransaction(setAddr, resolverData.address);
                    
            console.log(" ");
            console.log("Registrar를 셋팅합니다.");
            const setupRegistrar = await ens
              .setSubnodeOwner(ZERO_HASH, labelhash(tld), registrarData.address)
              .encodeABI();
            await SendTransaction(setupRegistrar, ensData.address);
                    
            console.log(" ");
            console.log("ReverseRegistrar를 셋팅합니다.");
            const reverseSubnodeOwner = await ens
              .setSubnodeOwner(ZERO_HASH, labelhash("reverse"), ADDRESS)
              .encodeABI();
            await SendTransaction(reverseSubnodeOwner, ensData.address);
            const reverseSubnodeOwnerCa = await ens
              .setSubnodeOwner(
                namehash.hash("reverse"),
                labelhash("addr"),
                reverseRegistrarData.address
              )
              .encodeABI();
            await SendTransaction(reverseSubnodeOwnerCa, ensData.address);
          };
                    
          const SendTransaction = async (data, to, estimate) => {
            await caver.klay.accounts
              .signTransaction(
                {
                  from: ADDRESS,
                  to: to,
                  gasPrice: await caver.klay.getGasPrice(),
                  gas: 500000,
                  data: data,
                },
                PRIVATE_KEY
              )
              .then(async (Tx) => {
                await caver.klay
                  .sendSignedTransaction(Tx.rawTransaction)
                  .then((hash, err) => {
                    if (err) {
                      console.log(err);
                    } else {
                      console.log("success!");
                    }
                  });
              });
          };
                    
          setting();
        

        7

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.