【Solidity/web3.js】USDTを扱う際の注意点
以下2点の注意が必要みたいです。
久しぶりに実装で詰まったのでさくっと記事にしました。。
approve の実行
要点
allowed != 0
の時に再度approveしたい時は、一度approve(_spender, 0)
する必要がある。
繰り返しapprove(_spender, 10)
などと実行することはできなく、間で必ずapprove(_spender, 0)
を挟んだり、transfer
してallowed
を 0 にする必要がある。
詳細
USDTのapprove
関数の中身は以下の通り。
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}
require
のコメントに書いてある通りで、race conditionを軽減するためにapprove(_spender, 0)
する必要がある。
具体的に発生しうる問題は以下の通り。
require
の中身を読みやすくすると以下の通り。
require((_value == 0) || (allowed[msg.sender][_spender] == 0));
やりがちなミスと対策
ミス
- front側で approve -> transferFrom を呼び出すコードを実装
- ユーザがapproveだけで離脱してしまい、transferFrom が呼び出されなかった
- 再度このユーザがapproveを呼び出そうとすると、
allowed != 0
のため、require
にはじかれる
対策
-
allowance
を呼び出して返り値 != 0
の場合は、approve(_spender, 0)
を呼び出す
Contractから呼び出す際はSafeERC20を使う
要点
Contract中でUSDTをtransferFrom
などする際は、SafeERC20
を使う必要がある
例
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract Test {
using SafeERC20 for IERC20;
function transfer(address _to, uint256 _amount) public {
IERC20 token = IERC20(/* USDT address */);
token.safeTransferFrom(msg.sender, _to, _amount);
}
}
詳細
IERC20のtransferFrom
は返り値がbool
型
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
USDTのtransferFrom
は返り値がない
// Forward ERC20 methods to upgraded contract if this one is deprecated
function transferFrom(
address _from,
address _to,
uint256 _value
) public whenNotPaused {
require(!isBlackListed[_from]);
if (deprecated) {
return
UpgradedStandardToken(upgradedAddress).transferFromByLegacy(
msg.sender,
_from,
_to,
_value
);
} else {
return super.transferFrom(_from, _to, _value);
}
}
上記違いにより、IERC20をそのまま使うことはできないが、SafeERC20を挟むことで正常に呼び出せる。(浅い理解)
あるいは、USDTだけにフォーカスするのであれば、IERC20.solを使わずに自前でinterfaceを定義してもよさそう。
SafeERC20を使わないと。。
コンパイル・デプロイはできる。
SafeERC20を使っていない状態のContractの関数をweb3.js経由・Metamaskで呼び出そうとすると、警告が表示され、実行しても失敗する。
参考
Wrappers around ERC20 operations that throw on failure (when the token contract returns false). Tokens that return no value (and instead revert or throw on failure) are also supported, non-reverting calls are assumed to be successful. To use this library you can add a using SafeERC20 for ERC20; statement to your contract, which allows you to call the safe operations as token.safeTransfer(…), etc.
さいごに
Twitterの方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!
また、個人開発したdAppsの解説記事もありますので、良かったらそちらもご覧ください!
Discussion