이더리움 네트워크를 이용하는 것은 비용이 든다. 바로 가스비
(Gas Fee, 이하 가스비)다. 블록체인 네트워크를 가만히 관찰하다보면 세상에 공짜가 없다는 것을 몸소 느끼게 된다. 일상 생활에서도 내가 지금 이 자리에서 숨을 쉬고 있게 하는데는 여러 비용이 들고 있을 것이다. 당장 오늘 먹은 컵라면도 타국의 이름 모를 농부가 수확한 밀로 만든 가루로 만들어져 전해져왔을 것이다. 농부를 있게 하는 것도 사회적으로 많은 비용이 지불되었다는 거을 알기는 힘들지 않다.
사담이 길어졌지만 어쨌든 블록체인에서 무언가를 하는데는 모두 비용이 든다. 이더리움 네트워크에서는 적어도 보는 것은 공짜다. 현실에서 유리창 너머 DP된 상품들을 보는 것은 자유인 것처럼 말이다. 그 외에 쓰고, 수정하고, 지우는 것까지 모두 비용이 든다.
그래서 고민하게 된다. 스마트 컨트랙트를 쓰고 있는 순간에도, 어떻게 내 스마트 컨트랙트가 가스비를 덜 소모하며 배포가 될 수 있을지에 대해서 말이다. 그리고 배포가 된 이후에 사람들이 쓸 때에 드는 가스비도 덜 소모되게 해야 한다.
기존에 내가 컴퓨터공학을 배우고 있을때에는 '메모리사용을 최소화해야한다', '실행되는 명령이 적어야 한다'는 불문율이 있었다. 하지만 컴퓨터 메모리는 크기가 더 작아지면서 저장하는 공간은 커지며 현재 스마트폰에 이르렀다. CPU도 많은 발전이 있어 왠만한 복잡한 명령도 과거에 비해 매우 빠르게 처리되고 있다. 물론 몸에 베인 습관은 어쩔 수 없지만, 이제는 결과만 만들면 된다는 관점에 치우쳐 있었다.
하지만 스마트 컨트랙트는 다시 예전으로 회귀하게 만들었다. 다시 명령의 수행 빈도와 메모리 차지 공간에 대해서 고민하게 만든다. 왜냐하면 모든 게 비용이기 때문이다.
현재 스마트 컨트랙트에서 다른 컨트랙트에 함수를 사용한다고 해보자.
이번에는 간단하게 숫자를 저장하고 변경하는 두 함수를 제공하는 컨트랙트 Anything
을 만들어봤다. 이것을 이전 TruffleTutorial
컨트랙트에 Anything
이 제공하는 함수를, 실행하는 함수를 만들어주었다.
[Anything.sol]
//SPDX-License-Identifier: UNLICENSED
pragma solidity >= 0.4.22 <= 0.9.0;
contract Anything{
uint number;
constructor() {
number = 0;
}
function setNumber(uint _number) public returns(uint) {
number = _number;
return number;
}
function getNumber() public view returns(uint){
return number;
}
}
[TruffleTutorial.sol]
//SPDX-License-Identifier: UNLICENSE
pragma solidity >= 0.4.22 < 0.9.0;
import "./Anything.sol";
contract TruffleTutorial {
address public owner = msg.sender;
string public message;
Anything public anything;
constructor() {
message = "Hello World";
anything = new Anything();
}
modifier ownerOnly(){
require(
msg.sender == owner,
"This function is restrictied to the contarcts's owner"
);
_;
}
function setMessage(string memory _message)
public
ownerOnly
returns(string memory)
{
require(bytes(_message).length > 0);
message = _message;
return message;
}
// 추가된 함수들
function getOtherNumber() public view returns(uint) {
return anything.getNumber();
}
function SetOtherNumber(uint _number) public returns(uint){
anything.setNumber(_number);
return anything.getNumber();
}
}
일단 이것이 두 컨트랙트를 연결하는 기본조건이다. TruffleTutorial
에 Anything
솔리디티 파일을 import 해오면 해당 컨트랙트를 사용할 수 있다. 그리고 TurffleTutorial
의 생성자를 보면 anything
식별자에 새로운 Anything 컨트랙트의 객체를 생성하여 할당한다. 마치 자바의 클래스처럼 사용되는 느낌이다.
위 파일을 그냥 한 파일에 합쳐버리자. 이렇게도 가능하다.
[TruffleTutorial.sol]
//SPDX-License-Identifier: UNLICENSE
pragma solidity >= 0.4.22 < 0.9.0;
// Anything 컨트랙트
contract Anything{
uint number;
constructor() {
number = 0;
}
function setNumber(uint _number) public returns(uint) {
number = _number;
return number;
}
function getNumber() public view returns(uint){
return number;
}
}
// TruffleTutorial 컨트랙트
contract TruffleTutorial {
address public owner = msg.sender;
string public message;
Anything public anything;
constructor() {
message = "Hello World";
anything = new Anything();
}
modifier ownerOnly(){
require(
msg.sender == owner,
"This function is restrictied to the contarcts's owner"
);
_;
}
function setMessage(string memory _message)
public
ownerOnly
returns(string memory)
{
require(bytes(_message).length > 0);
message = _message;
return message;
}
// 추가된 함수들
function getOtherNumber() public view returns(uint) {
return anything.getNumber();
}
function SetOtherNumber(uint _number) public returns(uint){
anything.setNumber(_number);
return anything.getNumber();
}
}
직접 한 파일에 컨트랙트를 두 개 만들어주어 TruffleTutorial
컨트랙트에서 동일하게 객체를 생성하여 사용해주었다.
첫 번째 경우의 Anything
컨트랙트 파일은 동일한데, TruffleTutorial
컨트랙트는 다음과 같이 수정된다.
[TruffleTutorial.sol]
//SPDX-License-Identifier: UNLICENSE
pragma solidity >= 0.4.22 < 0.9.0;
import "./Anything.sol";
contract TruffleTutorial {
address public owner = msg.sender;
string public message;
Anything public anything;
constructor() {
message = "Hello World";
anything = Anything([배포된 컨트랙트의 주소]);
}
modifier ownerOnly(){
require(
msg.sender == owner,
"This function is restrictied to the contarcts's owner"
);
_;
}
function setMessage(string memory _message)
public
ownerOnly
returns(string memory)
{
require(bytes(_message).length > 0);
message = _message;
return message;
}
function getOtherNumber() public view returns(uint) {
return anything.getNumber();
}
function SetOtherNumber(uint _number) public returns(uint){
anything.setNumber(_number);
return anything.getNumber();
}
}
구분 | 배포 비용 | SetOtherNumber(5) 비용 | setMessage("Hi, there!") 비용 |
---|---|---|---|
1번 케이스 | 0.01840777 ETH | 48379 wei | 33211 wei |
2번 케이스 | 0.01804746 ETH | 48379 wei | 33211 wei |
3번 케이스 | 0.01662174 ETH | 48401 wei | 33211 wei |
TruffleTutorial
내부 함수인 setMessage()
를 실행시키는 것은 모두 똑같은 비용이 들었다.
하지만 배포와 외부 컨트랙트의 함수인 SetOtherNumber()
를 실행시킬 때, 3번 케이스의 경우가 유의미한 차이를 보였다. 우선 배포 비용은 다른 케이스에 비해 많이 적다. 이는 해당 컨트랙트의 자체 크기가 줄어들어서 그렇다고 생각한다. 하지만 SetOtherNumber()
함수가 배포된 다른 컨트랙트의 함수를 실행하고 있기 때문에 다른 케이스에 비해 함수 실행 트랜잭션 비용이 더 든다.
하나 또 특기할 사항은 컨트랙트를 내부 파일로 옮겨온 것 뿐인데 1번 케이스와 2번 케이스의 배포 비용이 약간 차이가 나는 것이다.
주의할 점 : 현재는 로컬 테스트넷에 테스트를 해봤다. 실제 메인넷에서는 컨트랙트 배포나 트랜잭션이 메인넷을 이용하려는 수요 정도에 따라 가스비가 더 나올 수 있다.
일단은 3번 케이스의 경우 확실히 유의미한 차이가 나는 것을 확인했다.
이번 상황 가정의 경우는 고정비와 변동비를 생각해볼 필요가 있다. 배포 비용을 적게 쓴 이후, 다른 컨트랙트의 함수를 이용하는 비용은 유저들이 지불하게 한다는 경우 하나. 배포 비용을 많이 쓴 이후, 내부의 다른 컨트랙트를 이용하여 외부 함수를 이용할 때의 비용을 없애는 경우 하나.
사실 후자를 추천하는 편이다. 결국 장기적으로 변동비는 고정비를 넘어서게 되어있다.
1번 케이스와 2번 케이스의 경우는 왜 배포 비용에서 약간의 차이가 나는지 확인해야될 것 같다. 각각의 파일로 존재할 때와, 한 파일에 합쳐졌을 때 어떤 차이가 있는지 알아보려고 한다. 만약 2번 케이스가 항상 옳다면, 작업은 따로 하더라도, 배포될 때는 한 파일로 합쳐주는 게 비용을 아끼는 방안이 아닌가?? 중요한 사항이다.