⛳
SafeERC20について
1. Overview
ガイドのターゲット
- SafeERC20とは何か、使うことのメリット
- SafeERC20のコード解説
- コードの参考
必要な知識
- Solidityの簡単な文法
2. SafeERC20とは何か、使うことのメリット
SafeERC20はコントラクト内から安全にERC20トークンのtransfer関数を呼び出せるライブラリです。使うことのメリットは2つあります。1つ目は、transfer関数を呼び出すトークンアドレスがコントラクトアドレスか確かめられることです。extcodesize関数でアドレスのコードの長さを調べて確かめます。2つ目は、transfer関数の戻り値を受け取ることができることです。今のERC20トークンには戻り値がありますが、昔のERC20トークンには戻り値がありませんでした。そこで、call関数を使うことでbool値を受け取り、transfer関数が実行できたか確認できます。
3. SafeERC20のコード解説
OpenZeppelinのgithubより
SafeERC20.solのコード
Address.solのコード
今回はsafeTransferがどうやって処理されていくかコードを見ていきます。
library SafeERC20 {
// addressのライブラリを使用します。
using Address for address;
// 使い方の例
// address.safeTransfer(address to, uint256 value)
function safeTransfer(
IERC20 token, // トークンアドレス(コントラクトアドレス)
address to, // 送り先
uint256 value // ERC20トークンの送金額
) internal {
// エンコードしてbytesの型にする
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
それではSafeERC20ライブラリの下にある_callOptionalReturn関数を見てみましょう。
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
// transferの情報をdataにしてaddressライブラリのfunctionCall関数を呼ぶ
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
// 戻り値のデータをデコードし、bool値を読み取る
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
次にaddressライブラリを見ていきます。引数の数が異なるfaunctionCall関数が2つあり順番に呼ばれています。
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
// エラー文を追加
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
// etherの送金額を設定
// ERC20トークンを送るためここは0
return functionCallWithValue(target, data, 0, errorMessage);
}
その次はfunctionCallWithValue関数です。この中で外部コントラクトを呼び出すcall関数を使っています。
function functionCallWithValue(
address target, // トークンアドレス(コントラクトアドレス)
bytes memory data, // transferの情報
uint256 value, // etherの金額、ERC20トークンの場合は0
string memory errorMessage // エラー文
) internal returns (bytes memory) {
// このコントラクトのetherの保有額を確認、ERC20トークンの場合は関係ない
require(address(this).balance >= value, "Address: insufficient balance for call");
// targetのトークンアドレスはコントラクトアドレスでなければならない
// extcodesize関数を使用して調べている
require(isContract(target), "Address: call to non-contract");
// call関数で外部のコントラクトの呼び出し
// call関数はデータの参照はできないが戻り値でbool値を受け取れる
(bool success, bytes memory returndata) = target.call{value: value}(data);
// successならreturndataを返し、それ以外ならrevertしてエラー文を返す
return verifyCallResult(success, returndata, errorMessage);
}
3. コードの参考
以下、説明を簡略化しますがAddressライブラリのfunctionCallWithValue関数で呼ばれた関数です。
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// extcodesize関数で該当のアドレスのコードの長さを調べられる
// EOAまたはまだ割り当てられていない、もしくはなんらかの理由でコードが存在しない場合0になる
assembly {
size := extcodesize(account)
}
return size > 0;
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata; // そのまま返す
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// returndataのメモリーの内容をエラー文にして返す
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
Discussion