우선 대략적인 내용은 아래 글을 참고하면 될 것 같습니다.
(다만, 정글넷이라는 테스트넷에서 실험을 바탕으로 쓰여진 글이므로 사용량이 정확한건 아닙니다.)
https://steemit.com/eos/@leordev/eos-ram-and-bandwith-analysis-airdropping-steps-on-junglenet
어제 EOS 메인넷에서 최초로 ADD 토큰이 에어드랍 되었는데, 이 상황을 예로 들어 RAM 사용량을 좀 더 정확하게 계산해보겠습니다.
(CPU, Net은 필요한만큼 Delegate 해뒀다가 나중에 Undelegate 하면 되므로 여기서는 다루지 않겠습니다)
글 작성 편의상 cleos 의 -u 옵션은 생략하였습니다.
Contract 용량
$ cleos set contract eosadddddddd build/contracts/eosio.token
(ADD 토큰 발행 계정은 eosadddddddd 입니다)
EOS 토큰 생성(create) 및 발급(issue)에 사용한 eosio.token 을 별다른 수정없이 그대로 가져쓴다고 가정하면, set contract 시 차지하는 RAM 은 Contract를 컴파일 한 WASM 파일 사이즈의 10배입니다.
(libraries/chain/include/eosio/chain/config.hpp 에 정의된 setcode_ram_bytes_multiplier 값이 10입니다.)
WASM 파일은 18965 byte이고 RAM은 10배인 약 185 KB 를 차지합니다.
토큰 생성
$ cleos push action eosadddddddd create '["eosadddddddd", "10000000000.0000 ADD"]' -p eosadddddddd
ADD 토큰의 생성 및 발행량은
$ cleos get table eosadddddddd ADD stat
명령으로 확인할 수 있으며,
총 100억개를 생성하여 100억개를 발행하였음을 알 수 있습니다.
ADD 외에도 AD 라는 토큰도 100억개를 발행했네요.
토큰 생성에는 264 byte의 RAM을 사용합니다.
struct currency_stats {
asset supply;
asset max_supply;
account_name issuer;
uint64_t primary_key()const { return supply.symbol.name(); }
};
위는 stat 테이블의 레코드 정의이며 asset(16 byte) + asset(16 byte) + account_name(8 byte) 의 data 와 asset.symbol(8 byte) 을 key로 가지는 multi-index table을 아래와 같이 정의하여 사용합니다.
typedef eosio::multi_index<N(stat), currency_stats> stats;
stats statstable( _self, sym.name() );
statstable.emplace( _self, [&]( auto& s ) {
s.supply.symbol = maximum_supply.symbol;
s.max_supply = maximum_supply;
s.issuer = issuer;
});
최초 emplace 시에는 table을 생성하여 record를 추가하고, 이후부터 emplace 시에는 record만 추가합니다.
(모든 record를 erase 하는 경우에는 table도 삭제됩니다)
토큰 생성의 경우 table size(112byte) + key에 의한 size(112byte) + 레코드 data size(40byte)가 되어 총 264byte를 차지하게 됩니다.
이후에 토큰을 추가 생성하는 경우 레코드만 추가되므로 40byte 씩 RAM을 차지합니다.
토큰 발행(전송)
지금까지는 RAM 사용량이 얼마 되지 않는데요, 문제는 지금부터 입니다.
$ cleos push action eosadddddddd issue '["<대상 account>", "x.xxxx ADD", ""]' -p eosadddddddd
issue 액션을 호출하면 우선 issuer(여기서는 eosadddddddd 계정) 에게 issue 하고자 하는 토큰 개수를 balance에 더하고 (add_balance() 호출) issuer가 <대상 account>
에게 transfer 하게 됩니다.
transfer 액션은 보내는 사람의 balance에서 토큰 개수를 빼고 (sub_balance() 호출), 받는 사람의 balance에 토큰 개수를 더합니다 (add_balance() 호출).
issuer가 자신에게 issue 하는 경우 transfer 액션은 호출되지 않습니다.
여기서 add_balance() 를 호출하는 곳을 보면 ram_payer가 issuer가 됩니다.
즉, 토큰 balance 정보를 가지고 있는 accounts table
을 <대상 account>
가 소유하지만 (scope), 그에 필요한 RAM은 ram_payer (여기서는 eosadddddddd) 의 RAM에서 빠져나갑니다. (토큰도 나눠주는데 좀 불합리 한 것 같죠? ㅋㅋ)
struct account {
asset balance;
uint64_t primary_key()const { return balance.symbol.name(); }
};
RAM을 차지하는 size는 table size(112byte) + key에 의한 size(112byte) + 레코드 data size(16byte)가 되어 총 240byte입니다.
제네시스 스냅샷 기준 163930 개의 계정에 모두 issue를 하게 되면 163930 x 240byte ≈ 37.5206MB 를 소모하게 됩니다.
이는 최초 에어드랍 시에만 해당하는 내용이며, 이후 토큰을 받은 사람들이 transfer를 하기 시작하면 table의 modify() 가 불리면서 issuer는 자신의 RAM에서 빠져나갔던 일정 부분을 돌려받게 됩니다.
(이 내용은 좀 복잡하기 때문에 저 아래에서 다루겠습니다)
ADD 토큰은 EOS 제네시스 스냅샷 기준으로 EOS : ADD = 1 : 0.5 비율로 에어드랍 하였습니다.
$ cleos get table eosadddddddd b1 accounts
명령으로 b1에게 5천만개가 있는 것을 보고 1 : 0.5 비율임을 알았습니다.
163930개의 모든 account에게 에어드랍을 한다면 총 5억개의 ADD를 발행(전송) 할 것이고 eosadddddddd 계정은 나머지 95억개의 ADD를 보유하게 되겠죠.
ADD 토큰이 실제로 어떤 과정을 통해 분배되었는지 살펴보면,
$ cleos -u http://br.eosrio.io:8080 get actions eosadddddddd --full 22 20
eosadddddddd 계정의 action을 22번째부터 20개이후 만큼 가져오는 명령인데, 보시면 자신에게 100억개를 모두 issue 한 것을 알 수 있습니다.
$ cleos -u http://br.eosrio.io:8080 get actions eosadddddddd --full 72 10
$ cleos -u http://br.eosrio.io:8080 get actions eosadddddddd --full 172691 10
이후 72번째 action에서 b1 에게 5000만개 transfer를 시작으로 172696번째 action에서 마지막 transfer를 함으로 에어드랍이 완료된 것을 알 수 있습니다. (총 3시간 8분 정도 걸렸네요)
마지막 transfer 한 ADD 개수가 50개 인 것을 보아 EOS 100개 이상 account에게만 에어드랍 한 것을 알 수 있습니다.
이 글 최상단에 링크되어 있는 글에 따르면 제네시스 스냅샷 기준으로 EOS 100개 이상을 보유한 account는 총 84979개 이므로,
총 16만 계정에 에어드랍 하는 것보다 절반의 RAM을 절약할 수 있습니다.
여기서 한 가지 특이한 것이 에어드랍을 issue 액션으로 하지 않고, 자신에게 전체토큰을 issue 한 뒤 transfer 액션을 이용했다는 점인데 어차피 issue가 내부적으로 transfer를 호출하기 때문에 cpu 사용량을 줄이기 위함이 아닌가 합니다.
그럼 여기서 의문이 드는 것이 32만 계정에 에어드랍을 하게되면 64MB를 넘어가게 되는데, contract가 사용할 수 있는 RAM은 64MB로 제한되어 있는 현재 상황에서 에어드랍을 어떻게 해야할까요?
https://github.com/EOSIO/eos/issues/4285
여기에 질문을 올려놨는데 답변을 해줄 지 모르겠네요 ㅎㅎ
Plactal의 Eric song 님께서 지적해주셔서 확인해보니, multi-index table의 용량은 execution memory (64MB로 제한) 과는 별개입니다. 즉, RAM을 구매한만큼 multi-index table 용량은 사용할 수 있기 때문에 32만 계정 이상에게 에어드랍도 가능합니다.
부연 설명
위에 언급했던, 에어드랍 받은 사람들이 transfer를 하게 되면 토큰 발급자(issuer)가 일부 RAM을 돌려받는 부분에 대한 설명입니다.
transfer 시 table에 대한 modify()의 payer는 다음과 같습니다.
account name of the payer for the Storage usage of the updated row; a value of 0 indicates that the payer of the modified row is the same as the existing payer.
transfer 시 보내는 사람의 RAM payer는 보내는 사람이지만 받는 사람의 RAM payer는 0 (받는 사람의 table을 생성해준 사람) 입니다.
modify()에 관한 몇 가지 예를 들면,
1. ADD를 분배받은 A가 ADD를 분배받은 B에게 일정량을 보내는 경우
- A의 ADD
accounts table
은 eosadddddddd RAM을 사용 중입니다. (payer : eosadddddddd) - B의 ADD
accounts table
도 eosadddddddd RAM을 사용 중입니다. (payer : eosadddddddd) - A가 B에게 ADD를 일정량 보내면 각각의
accounts table
에 modify가 일어납니다. - A의 ADD account table에 대해 A가 payer가 되면서 table size를 제외한 만큼(112+16 byte) RAM을 A가 사용하게 됩니다.
- eosadddddddd 는 이제 A의 ADD acccount table의 table size만 가지게 되고 128 byte를 A로부터 돌려받게 됩니다.
- B의 ADD
accounts table
은 payer가 0 (즉, 첫 payer인 eosadddddddd)이므로 RAM의 변화는 없습니다. - 결과적으로 eosadddddddd 는 128 byte의 여유가 생기고 A는 128 byte를 더 사용하게 됩니다.
2. ADD를 분배받은 A가 ADD를 받지 못한 C에게 일정량을 보내는 경우
- eosadddddddd는 앞에 1의 경우와 같습니다. 따라서 A에게서 128 byte를 돌려받게 됩니다.
- A는 앞에 1의 경우와 같이 128 byte를 더 사용하게 됩니다.
- 그런데 C에게는 ADD
accounts table
이 없었기 때문에 토큰을 보내는 A의 RAM을 소모하여 C의 ADDaccounts table
이 생성됩니다. - 결과적으로 eosadddddddd 는 128 byte의 여유가 생기고 A는 128+240 = 368 byte를 더 사용하게 됩니다.
- 여기서 C가 다른 누군가에게 ADD 토큰을 transfer 해야 A는 그나마 128 byte를 돌려받을 수 있고, C가 ADD 토큰을 모두 사용하여 balance가 0이 되어야 A는 나머지 112 byte까지 돌려받을 수 있습니다.
좀 어렵죠? ㅠㅠ
이 내용은 ADD 토큰 뿐만 아니라 EOS 에도 해당됩니다.
첫 메인넷 때는 eosio 계정이 EOS 토큰을 issue 해주었기 때문에 16만여개의 accounts table
을 eosio의 RAM을 이용해서 생성했지만 이후에 EOS를 transfer 하게 되면 128 byte 또는 368 byte 만큼 추가로 사용하게 됩니다.
새로 계정을 생성 (A 계정이라 칭함) 해서 사용하는 경우, A 계정의 accounts table
만큼 RAM을 아끼고 싶다면 A 계정에게 EOS를 송금해주기 전에 A 계정의 RAM을 소량 팔아버립니다. 그러면 A계정의 accounts table
이 eosio.ram 계정의 RAM을 사용해서 생성됩니다. (꼼수라고 해야할까.. 그래봐야 240 byte ~ 112 byte 세이브 하는 정도. RAM 판매 수수료는 최소 0.0001 EOS. 약간의 이득이긴 한데 ㅋ)
앞으로 EOS 메인넷에서 수많은 에어드랍(또는 ERC20 -> EOS토큰 스왑)이 있을텐데, 위의 table 생성 시 소모되는 RAM 을 고려하여 꼭 필요한 송금만 하는 것이 RAM을 아껴쓸 수 있는 방법입니다.
소스코드 분석과 여러 번의 테스트를 거치면서 확인한 수치들인데 혹시 잘못된 부분이 있다면 지적 부탁 드립니다.
(jjangjjangman 태그 사용시 댓글을 남깁니다.)
호출에 감사드립니다! 즐거운 스티밋하세요!
와 램과 관련된 상세한 분석 감사합니다~!! 👍👍👍
개발자 텔방에서 @raindays 님 덕분에 알게된 내용이 많네요. ^^
구체적인 설명감사합니당^^
도움이 되었으면 좋겠네요^^
eos 에어드랍 받을일은 없지만 어떤식으로 이루어지는지 잘 배우고 갑니다.
댓글 감사합니다^^
복잡하군요. dApp을 운영하는 입장에서는 RAM 비용도 상당하겠군요.
어플리케이션이 많이 개발되면 램 사용량이 늘어나겠군요. 코드는 모르지만 흥미롭네요^^