DeFiにみる2020年のERC-20実装
はじめに
2020年8月、年初よりにわかに盛り上がっていた分散型金融DeFiがイーサリアム全体を巻き込み大きな話題となりました。DAI、Compound、Uniswap、そしてSushi、スマートコントラクトで自動的に執行され増えていくトークンに熱狂したCryptoユーザーの方も多かったのではないかと思います。本記事では、DeFi関連のERC-20を中心に実装を追い、ERC-20からの変更点、工夫点を解説します。
また、DeFiコントラクトにおけるapprove問題として後編記事を公開しています。
免責事項ですが、本文中に言及するプロダクトについて一切推奨するものではありません。
ERC-20とは
ERC-20とは2015年にイーサリアムの発案者であるVitalik氏を中心に定義されたイーサリアム上で独自のファンジブルトークン(FT)、つまり通貨やポイントを発行するための標準仕様です。ERC-20ではFTの総発行数の管理、残高の確認、譲渡、譲渡の委任を実現しています。技術的には以下のインターフェースを備えたスマートコントラクトのことを指します。
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
現在のイーサリアムにおいて、FTのデファクトはERC-20であり、DeFiの世界においてもそれはかわりません。本記事で言及するトークンは特記がない限りERC-20を実装しています。
USDT
Tether USD(USDT)は、Tether社が米ドルとの裏付けをし発行しているステーブルトークンです。USDTの実装は、Solidityのバージョンは、v0.4.18とこの記事で言及するトークンとしては最も古く、EtherscanやUniswapのPoolを見ても人気第一位のトークンと言えます。
今日のDeFiプロダクトの多くはその根底となる信頼性をステーブルトークンに大きく依存しています。USDTと後述のDAIの安定性がDeFiを下支えしているといっても過言ではないでしょう。
USDTはBlackList機能を有しています。コントラクトオーナーは、BlackListに指定したアカウントを追加することができます。BlackListに追加されたアカウントは、トークンの凍結、つまり譲渡が制限され、オーナーによるトークンの没収が可能になります。賛否あると思いますが、企業が運営するステーブルトークンであるため必要な機能と考えられます。
mapping (address => bool) public isBlackListed;
function addBlackList (address _evilUser) public onlyOwner {
isBlackListed[_evilUser] = true;
AddedBlackList(_evilUser);
}
function removeBlackList (address _clearedUser) public onlyOwner {
isBlackListed[_clearedUser] = false;
RemovedBlackList(_clearedUser);
}
function destroyBlackFunds (address _blackListedUser) public onlyOwner {
require(isBlackListed[_blackListedUser]);
uint dirtyFunds = balanceOf(_blackListedUser);
balances[_blackListedUser] = 0;
_totalSupply -= dirtyFunds;
DestroyedBlackFunds(_blackListedUser, dirtyFunds);
}
Solidityバージョンが古く、BlackListという名前を含めて、「一度デプロイしてしまうと一切の変更ができない」というスマートコントラクトの性質を体現するようなトークンです。
DAI
DAIはMaker DAOが開発した価値を米ドルに固定することを目標としたステーブルトークンです。複数の暗号通貨を担保にし価格を安定させていると言われています。
DAIについて特筆すべき点は、2019年にコントラクトとエコシステムのバージョンアップを行っており、旧DAIはSAIと呼ばれる別トークンとなりました。Crypto界隈だとアップデートのことをForkと呼んで忌避したりお祭りしたりする傾向がありますが、DAIはForkと呼ばれずバージョンアップと呼ばれていたので不思議なものです。
前述のUSDTと打って変わってDAIはバージョンアップのおかげでSolidityのバージョンは比較的あたらしいv0.5.12でビルドされています。また、mint以外に特権ユーザーは存在しない作りになっており、USDTとDAI、米ドルステーブルトークンとして双対を成しています。
DAI は非常にシンプルな実装ですが、特筆すべき機能としてpermit関数があります。permit関数は、署名を用いることでallowance値をメタトランザクションとして他アカウントに実行させることができます。
interface Permit {
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
}
この permit関数はERC-2612として標準化を目指しており、後編記事でパターンとして詳しく解説します。
COMP
COMPは、レンディングプラットフォームCompoundに、トークンを貸し付けることで得られるガバナンストークンです。発行上限数を10,000,000と定められています。
内部のトークン保有量には、Solidityで一般的なuint256ではなくuint96が使われています。uint256は 256 bit の保存領域を持ち、77桁もの数字を扱えます。一方、uint96では96 bitの保存領域を持ち、28桁の数字を扱えます。28桁であれば十分すぎるほど大きな数字と感じますが、暗号通貨では法定通貨と違い、DeFi の大きな要素の一つである「利息」では小数点以下の数字が重要であり小数点以下に大きな桁数を使うことになります。COMP の場合、発行上限数が10桁であり、Decimals(小数点以下の桁数)が18桁ですので、合わせて28桁、uint96 でちょうど扱うことができます。
/// @notice EIP-20 token decimals for this token
uint8 public constant decimals = 18;
/// @notice Total number of tokens in circulation
uint public constant totalSupply = 10000000e18; // 10 million Comp
/// @notice Allowance amounts on behalf of others
mapping (address => mapping (address => uint96)) internal allowances;
/// @notice Official record of token balances for each account
mapping (address => uint96) internal balances;
発行するトークンに上限を定める場合、様々な要因によって決定することになりますが、gas代視点から見たCOMPの上限設計は非常に参考になるのではないでしょうか? その他、投票機能であるdelegate、delegateBySig機能などを兼ね揃えており、COMPはガバナンストークン開発において必読のコントラクトと言えるでしょう。
OpenZeppelin
OpenZeppelinは、Solidityにおける数多くの参考実装をオープンソースとして公開しています。OpenZeppelinを採用していることでその部分のコード監査をスキップでき、独自部分に集中したコード監査を受けることができるそうです。もちろん、ERC-20も非常に便利に作られており、発行する際には採用を検討すべきでしょう。最近のOpenZeppelinでは、参考実装以上に、Solidityで不便なところを補う薄いOSレイヤーを作っているように感じます。
一点注意すべきなのは、SafeERC20です。safeTransferFromという関数を実装していますが、送信先がコントラクトであるかどうかをチェックし、後編で解説するonReserved Hookを呼び出すような処理は入っていません。コントラクトからの呼び出し時に、revertを避け、呼び出し元でエラーハンドリング存在するようです。yToken等で使われており、いくつもの処理を1トランザクションで行う DeFi を意識した実装のようです。
他にも increaseAllowance / decreaseAllowance というallowance値を増減させる実装を入れています。ERC-20の議論時にコメントされていましたが、思い入れのある機能なのかもしれません。これに限らずapprove周りがERC-20の最大の課題と言えそうです。
SUSHI
SUSHIはSushiswapへの流動性供給者へのリワードとして配布されるガバナンストークンです。本来フォーク元のUniswapとUNIを取り上げるべきかと思いましたが、UNIに特筆することもないのでSUSHIを取り上げます。
ERC-20としてのベースはOpenZeppelinの物をそのまま利用し、ガバナンス部分は前述のCompoundをそのまま利用しています。Sushiswapのエコシステム自体もUniswapのコピーであるため、新たなものを生み出しているわけではないと批判されがちなSushiswapではあります。ただ、私個人としては、マーケティング方法や的確に既存プロダクトのメリットを吸収しデメリットを解消するスマートさに感銘を受けます。
今後のイーサリアムのプロダクトは、既存プロダクトのデメリットを解消し、よりメリットのある形をユーザーに素早く提供することで進化していくのではないかと思います。それがすべてをパブリックに動かすブロックチェーンならではの原動力となっていくのではと考えています。
(最後に確認して気付いたんですが、COMPからのガバナンスコピーに実装ミスがあるように思います。別途解説かな)
中締め
ERC-20自体を実現することは非常にシンプルなコードで実現できます。100行に満たず実現することができるでしょう。シンプルが故に多くのプロジェクトに採用され、プロジェクトごとに独自の機能が加わり、それがEIPやOpenZeppelinによって標準化されていく、イーサリアムのエコシステムを体現する規格であると感じます。
書いているうちに長くなったので前編後編にわけました。続編では、approve問題という視点でパターン化し、2020年以降のデファクトとなる実装を探っていきます。
投げERC-20歓迎しています。
rmanzoku.eth (0xd868711BD9a2C6F1548F5f4737f71DA67d821090)
投げトークンの送り先はこちら。 rmanzoku.eth (0xd868711BD9a2C6F1548F5f4737f71DA67d821090)