Blockchain - ABI(Application Binary interface) 구현

김도영·2022년 8월 1일
0

ABI는 Application Binary Interface의 약자로, 런타임 시 바이너리 코드와 상호작용하기 위한 인터페이스이다. ABI는 바이너리 형태로 되어있는 스마트 컨트랙트가 어떤 인터페이스를 가지고 있는지 알려주는 역할을 한다.

ABI를 사용해 Contract ABI Call 하기

Remix에 코드를 작성하고 컴파일한다.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

contract helloWorld {
	function renderHelloWorld () public pure returns (string memory greeting) {
    	greeting = "Hello World!";
    }
}

컴파일 후 Compilation Details버튼을 눌러 컨트랙트 세부 정보를 확인해본다.

이 중 우리가 확인해볼 데이터는 ABI 이다.

ABI를 통해 컨트랙트 내 함수를 어떻게 사용하는지 확인할 수 있다. Web3.js에서는 web3.eth.Contract()에 ABI를 인자로 넣어 컨트랙트를 구현한다.

WEB3DEPLOY에서는 ABI와 Web3.js를 이용해 컨트랙트를 바로 배포할 수 있는 자바스크립트 코드이다.

여기서는 컨트랙트를 배포하고 트랜잭션을 보내는 코드를 제공한다.

이제 ABI를 복사하여 해당 컨트랙트를 사용하는 방법을 알아보자. 먼저 ABI 코드를 복사한 후 이전 실습에서 사용한 Ganache에 배포 후 다음과 같이 코드를 작성한다.

const express = require('express');
const app = express();
const port = 8080;
const Contract = require('web3-eth-contract');

async function helloWolrd() {
    try {
        const abi = [{
                "inputs": [],
                "name": "renderHelloWorld",
                "outputs": [
                    {
                        "internalType": "string",
                        "name": "greeting",
                        "type": "string"
                    }
                ],
                "stateMutability": "pure",
                "type": "function"
            }];
        const address = '0x56a268C4e9EC7A986A6ed1A6d0e79748E60be600';
        Contract.setProvider('http://127.0.0.1:7545');
        const contract = new Contract(abi, address);
        const result = await contract.methods.renderHelloWorld().call();
        console.log(result);
        return result;
    } catch (e) {
        console.log(e);
        return e;
    }
}

app.get('/helloworld', (req, res) => {
    helloWolrd().then( (result) => {
        res.send(result);
    })
})

app.listen(port, () => {
    console.log('Listening...');
});

web.eth.Contract()는 ABI와 계정 주소를 입력받고, 컨트랙트를 객체를 반환한다. 이 컨트랙트 객체는 자바스크립트 객체처럼 사용할 수 있고, 컨트랙트 내의 상태변수와 함수를 속성과 메서드처럼 사용할 수 있다.

const contract = new Contract(abi, address);

이렇게 만들어진 컨트랙트 객체에 있는 함수를 사용하기 위해서는 contract.methods.함수명().call()와 같이 사용할 수 있다. contract.methods.함수명()은 컨트랙트에 있는 함수를 실행하는 트랜잭션 객체를 생성한다. 그리고, contract.methods.함수명().call()은 트랜잭션을 실행한 결과를 Promise 형태로 반환한다.

Remix에서 ABI를 호출해보면 다음과 같다.

Remix에서도 Web3.js를 사용하여 GUI로 ABI Call을 할 수 있도록 지원한다.

바이트 코드를 사용해 배포해보기

이제는 바이트 코드를 받아와 노드를 통해 직접 배포하는 트랜잭션을 보내보려고 한다. Remix에서 ERC-20 기본 토큰 예제 코드를 가져온다.

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.10;

interface ERC20Interface {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transferFrom(address spender, address recipient, uint256 amount) external returns (bool);
   
    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Transfer(address indexed spender, address indexed from, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 oldAmount, uint256 amount);
}

contract SimpleToken is ERC20Interface {
    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) public _allowances;

    uint256 public _totalSupply;
    string public _name;
    string public _symbol;
    uint8 public _decimals;
   
    constructor(string memory getName, string memory getSymbol) {
        _name = getName;
        _symbol = getSymbol;
        _decimals = 18;
        _totalSupply = 100000000e18;
        _balances[msg.sender] = _totalSupply;
    }
   
    function name() public view returns (string memory) {
        return _name;
    }
   
    function symbol() public view returns (string memory) {
        return _symbol;
    }
   
    function decimals() public view returns (uint8) {
        return _decimals;
    }
   
    function totalSupply() external view virtual override returns (uint256) {
        return _totalSupply;
    }
   
    function balanceOf(address account) external view virtual override returns (uint256) {
        return _balances[account];
    }
   
    function transfer(address recipient, uint amount) public virtual override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }
   
    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowances[owner][spender];
    }
   
    function approve(address spender, uint amount) external virtual override returns (bool) {
        uint256 currentAllownace = _allowances[msg.sender][spender];
        require(currentAllownace >= amount, "ERC20: Transfer amount exceeds allowance");
        _approve(msg.sender, spender, currentAllownace, amount);
        return true;
    }
   
    function transferFrom(address sender, address recipient, uint256 amount) external virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        emit Transfer(msg.sender, sender, recipient, amount);
        uint256 currentAllowance = _allowances[sender][msg.sender];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, msg.sender, currentAllowance, currentAllowance - amount);
        return true;
    }
   
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;
    }
   
    function _approve(address owner, address spender, uint256 currentAmount, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        require(currentAmount == _allowances[owner][spender], "ERC20: invalid currentAmount");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, currentAmount, amount);
    }
}

컴파일 후 ABI 코드를 복사 한후 다음과 같이 코드를 수정한다.

const express =require('express');
const app = express();
const port = 8080;
const Contract = require('web3-eth-contract');

async function deploySimpleToken() {
    try {
        const abi = [
            {
                "inputs": [
                    {
                        "internalType": "string",
                        "name": "getName",
                        "type": "string"
                    },
                    {
                        "internalType": "string",
                        "name": "getSymbol",
                        "type": "string"
                    }
                ],
                "stateMutability": "nonpayable",
                "type": "constructor"
            },
            // 생략
        ];
        
        const byteCode = "0000"; // 바이트코드를 붙여넣는다.
        Contract.setProvider('http://127.0.0.1:7545');
        const contract = new Contract(abi);
        const receipt = await contract.deploy({data:"0x" + byteCode, arguments: ["ErcSimpleToken", "EST"]}).send({from:"배포연결된 주소", gas: 2000000, gasPrice:'1000000000000'});
        console.log(receipt);
        return receipt;
    } catch(e) {
        console.log(e);
        return e;
    }
}

app.get('/deploy', (req, res) => {
    deploySimpleToken().then((result) => {
        res.send(result);
    })
})

app.listen(port, () => {
	console.log('Listening...');
});

서버를 실행한 후 Deploy가 성공하면 Ganache 블록에 올라간 주소를 찾을 수 있다.

이 주소를 Remix에서 At Address를 통해 불러올 수 있다.

profile
Blockchain Developer

0개의 댓글