Remix로 contract 만들고 배포하기

2023. 7. 11. 16:18Blockchain/Smart contract

728x90
반응형

EVM 계열 블록체인에 smart contract를 배포하기 위해 로직을 작성하는 IDE는 아마 web IDE인 Remix를 가장 많이 사용할 것이다. 이번 포스팅에는 Remix를 사용해서 solidity contract의 compile 및 deploy 하는 방법을 공유하려 한다.

Solidity sample code

Remix에서 기본적으로 제공하는 1_Storage.sol 코드를 사용하여 진행해 볼것이다. remix에 처음 접속하면 "File explorer" 탭에 default_workspace 안에 해당 컨트랙트 예제가 존재한다. 간단하게 코드 구성만 확인해보고 넘어간다.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.2 <0.9.0;

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 * @custom:dev-run-script ./scripts/deploy_with_ethers.ts
 */
contract Storage {

    uint256 number;

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public {
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}
  • pragma solidity >=0.8.2 <0.9.0;
    • Solidity에는 가장 먼저 compiler 버전을 명시해야 한다. 배포한 solidity contract에 사용될 solc의 정확한 버전을 명시해야 하며, 이 예제에서는 0.8.2이상 0.9.0미만 버전의 compiler에 대응될 수 있다는 뜻이다.
  • contract Storage { ... }
    • 한 solidity contract에는 전체 contract 범위를 표현하는 contract keyword를 통해 name을 선언해야 한다. 해당 contract에서는 없지만 만약 상속 받아야 하는 import contract가 있을 시 is 를 통해 외부 contract를 선언해 준다(e.g. contract Storage is ERC20 { ... }). 마찬가지로 해당 contract는 가지고 있지 않지만 contract 실행 시 initialize가 필요하다면contract keyword 내에constructor()를 통해 로직을 정의한다.
  • uint256 number;
    • uint256 타입의 number라는 명칭의 state 저장소를 선언한다.
  • function store(uint256 num) public { ... }
    • store명칭의 함수를 정의하여 state 변경을 실행한다. public keyword를 통해 외부에서도 접근 가능함을 정의한다.
  • function retrieve() public view returns (uint256) { ... }
    • retrieve 명칭의 함수를 정의하여 저장된 state를 조회한다. public keyword를 통해 외부에서도 접근 가능함을 정의하며 view keyword로 조회 함수임을 정의한다. Return값은 uint256 단일 변수이다.

앞서 확인한 것처럼 store를 통해 숫자를 저장하고 retireve를 통해 저장된 숫자를 조회하는 단순한 contract이다. Solidity를 작성하기 위한 세부적인 문법은 기회가 되면 정리를 해볼것이다.

cf) Remixd

번외로, default_workspace처럼 IDE 내부에 directory를 설정하여 contract를 작성할 수도 있지만 로컬 디렉토리 내에 있는 contract를 가져와서 개발하는 기능을 Remix에서 제공한다. Remixd라는 daemon을 사용하면 구현 가능하다. Remixd는 npm을 통해 설치할 수 있다.

$ npm install -g @remix-project/remixd

Global로 설치한 remixd를 cli를 통해서 구동할 수 있다. -s flag를 통해 연결할 directory를 설정한다.

$ remixd -s ./[SOLIDITY_DIR]

Remixd를 동작 시킨 후에 Remix에서 connect를 수행한다.

위 그림에서처럼 "connect to localhost"를 선택하면 아래와 같이 remixd와 연결을 진행하겠냐 하는 alert가 확인된다.

Alert 정보를 확인하고 연결을 진행한다.

이후에 위 예시처럼 로컬 디렉토리 상의 contract를 remix에서 사용할 수 있다.

Compile

Compiler 탭으로 들어가면 선택한 sol 파일에 대한 compile을 수행할 수 있다. 파란색 compile 버튼을 클릭하면 작성한 contract 코드에 대해 compile을 수행한다.

실제 compile 완료 시의 화면이다. 완료하면 즉각적으로 custom IPFS에 publish하거나 Swarm에 publish 할 수 있다.

자측 하단의 "Compilation Details"를 클릭하면 compile한 상세 결과를 확인할 수 있다. 여기 params 중 deploy에 가장 중요한 것은 ABIbytecode이다.

ABI

ABI(Application Binary Interface)는 외부에서 contract interaction을 위해 해당 contract의 interface 형식을 표현한 것이다. 1_Storage.sol contract를 compile한 후 확인되는 ABI는 아래와 같다.

[
    {
        "inputs": [],
        "name": "retrieve",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "num",
                "type": "uint256"
            }
        ],
        "name": "store",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
]

각 function 별로 JSON 형태로 정의되며, 확인되는 field의 specification을 간단히 정리한다.

  • name
    각 function의 이름을 정의한다. 1_Storage.solstore, retrieve 두개의 function을 가지고 있다.
  • type
    function의 타입을 정의한다. 일반적인 기능 구현은 "function", constructor는 "contructor", Eth receive function은 "receive", default로 실행되거나 ETH를 전송할 때 실행되는 "fallback"으로 구성된다. 1_Storage.sol의 경우 execute/query의 function만 가지고 있으므로, store, retrieve 모두 "function"으로 설정된다.
  • inputs
    function이 입력받는 object를 정의한다.
    • name: parameter의 이름
    • type: parameter의 타입
    • internalType: Solidity 내부에서 사용되는 타입, 만약 입력 타입이 contract에 정의된 struct라면 아래와 같이 표시 될 수 있다.
{ 
  "internalType": "struct Storage.StructName", 
  "name": "structExample", 
  "type": "tuple" 
}
  • outputs
    function 수행 시 return되는 object를 정의한다. 내부 필드는 위 inputs와 동일하다.
  • stateMutability
    해당 function의 state 변경 가능성을 정의한다. stateMutability에서 확인 가능한 옵션은 아래와 같다.
    • pure: state를 변경할 수 없고, state의 값을 read 할 수 도 없다.
    • view: state를 변경할 수 없지만, state의 값을 read 할 수 있다.
    • payable: 해당 function이 ETH를 수령할 수 있다.
    • nonpayable: 해당 function이 ETH를 수령할 수 없다.

Bytecode

ABI가 contract와의 interaction을 위해 사용된다면, bytecode는 실제 EVM에서 동작하도록 solidity가 변환뙨 정보이다. Bytecode는 모두 "opcode"로 분류되어 EVM 상에서 로직으로 동작한다. 1_Storage.sol을 compile한 후 확인되는 bytecode는 아래와 같다.

{
    "functionDebugData": {},
    "generatedSources": [],
    "linkReferences": {},
    "object": "6080604052348015...",
    "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE ...",
    "sourceMap": "199:356:0:-:0;;;;;;;;;;;;;;;;;;;"
}

앞선 Ethereum gas에서도 언급되었지만, 실제 contract가 ethereum에 등록되는 object와 이 코드가 어떤 기능을 수행하는지 확인 할 수 있는 opcodes가 중요하다. opcodes는 bytecode를 byte 단위로 정해진 기능을 뜻한다. 각 opcode의 기능이 정의된 예시를 아래 표에서 살펴본다. OP code는 여기서 확인할 수 있다.

OP code Name Minimum gas Description
60 PUSH1 3 Place 1 byte item on stack
52 MSTORE 3 Save word to memory
34 CALLVALUE 2 Get deposited value by the instruction/transaction responsible for this execution

위에서 확인되는 object는 60/80/60/40/52/34/...로 구성되어 있고 "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE ...와 대응이 일치하는 것을 확인 가능하다.

Contract 배포 시에는 object만 ethereum으로 전달되면 된다.

Setting env & Deploy

Deploy 탭으로 들어가면 위와 같은 화면을 확인할 수 있다.

Environment

가장 먼저 Remix를 이용해 contract를 배포할 환경을 선택할 수 있다. Ethereum fork 버전 별로 Remix VM을 설정하여 간편하게 테스트를 진행 할 수 있다. 또한 "Injected Provider - MetaMask"를 통해 metamask extension을 이용하여 지갑에 연결한 외부 EVM에 배포할 수 있으며 "WalletConnect"로 다른 지갑을 사용할 수도 있다. 지갑을 이용하여 ethereum mainnet에 contract를 배포한다면 지갑에 gas fee가 존재해야 한다. 또한 여러 버전의 provider를 제공해서, 각 provider를 이용하여 contract를 배포할 수도 있다.

로컬호스트를 이용하거나 직접적으로 JSON RPC에 연결하여 배포한다면 "Custom - External Http Provider"를 선택하여 URL를 입력해서 사용할 수도 있다. 연결 시 주의할 점은 external provider 선택 시 나타나는 가이드와 같이 geth 동작 시에 --http.corsdomain 옵션을 주어 Remix에서 접근할 수 있도록 cross orgin을 설정해 주어야 한다. 실제 로컬호스트에 연결을 하면 위와 같이 environment 아래에 networkid도 geth 동작 시 설정한 1317로 확인이 되며 geth의 account가 자동으로 설정된다.

Account

Environment를 설정했다면 contract 배포 주체인 account도 설정 가능하다. VM을 선택했다면 VM 자체에서 제공하는 account를 사용해서 배포할 수 있으며, MetaMask 등 지갑을 사용한다면 사용하기로 설정한 account가 자동으로 선택된다. External HTTP provider를 선택하여도 자동으로 선택된다.

Gas limit

Remix에서 contract 배포나 execute를 위한 tx의 gas limit을 설정한다.

Value

Value 필드는 transaction 전송 시에 함께 보낼 contract deposit amount를 설정한다. 이는 gas랑은 전혀 상관없이 function이 payable 타입일 시 수령할 수 있는 목적의 amount이다.

Deploy

Contract 배포를 위한 준비 단계는 모두 완료되었다. 이제 배포할 contract를 선택하여 노란색 deploy 버튼을 눌러주면 environment EVM에 배포가 된다.

위 그림은 Remix VM(Merge) 버전에 배포한 결과이다. 빨간색 박스 안에서 배포한 결과를 확인할 수 있다. from은 배포를 진행한 주체인 account이며, 화면 좌측의 account가 기존 100ether의 balance에서 99.999..874325ether로 gas fee만큼 사라진 것을 확인할 수 있다.

Contract가 배포 되었으면 contract address가 생성된다. 좌측 Deployed Contracts 항목에서 방금 배포한 contract의 주소를 0xD91...39138처럼 확인할 수 있다. 이렇게 배포된 상태에서 remix를 이용하여 execute transaction을 전송하거나 state query를 진행할 수 있다.

Run transaction & Query

Deploy된 contract의 toggle 버튼을 누르면 배포한 contract를 컨트롤 할 수 있는 항목들이 확인된다. Transaction을 전송하여 state를 변경할 수 있는 function은 노란색으로, query를 할 수 있는 function은 파란색으로 확인되니 구분하기 쉬울 것이다.

Send tx

입력할 수 있는 uint256 내의 숫자를 선택하여 노란색 store 버튼을 누르면 EVM에 tx를 전송한다. 테스트로 17을 전송한다. 하단 콘솔창에서 확인되는 것처럼 tx send가 성공했다면 contract 내의 로직에 맞게 state 변경을 진행했을 것이다.

Query

입력한 state 변경 값을 확인하기 위해 파란색 retrieve 버튼을 누르면 입력되어 있는 값을 확인할 수 있다. 이전에 tx를 전송했던 것처럼 17이 retrieve 버튼 아래에 확인된다.

Conclusion

Solidity 개발을 위한 IDE인 Remix의 사용방법을 알아보았다. Remix는 ethereum smart contract의 시작부터 끝까지 모두 동작시킬 수 있는 멋진 IDE이다. Text editor, compile, deploy, execute까지 만능이다. 테스트 용도 뿐만 아니라 실제 상용환경으로도 배포가 가능하기 때문에 정말 다재다능한 IDE라 생각된다. Ethereum solidity 기반 dApp이 주류가 된 이유 중에 하나도 이 편리한 IDE 덕분이라 생각한다.

참고
https://docs.soliditylang.org

728x90
반응형