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를 통해 불러올 수 있다.