😃

ERC20のtransferFrom関数完全に理解した。

2023/03/30に公開

概要

ERC20に内蔵されているtransferFrom関数を使用するスマコンを作成している時に詰まってしまい、小一時間ChatGPTと会話して完全に理解したので復習を兼ねてこの記事で解説していきます。完全に理解して使いこなしたい人は要チェックです。

前提としてこのtransferFrom関数は第三者によるトークンの操作を許可する関数ですが、この関数を使用するには少しコツがいります。

とりあえずtransferFrom関数に触れる

transferFrom関数のコードチェック

function transferFrom(address from, address to, uint256 amount) external returns (bool);

上記はERC20のインターフェイスが定義されたIERC20内にあるtransferFrom関数です。一見するとこの関数をinterfaceを通してコントラクトに置き、第三者からの残高操作を実行したいEOA(アドレス)に叩かせることで難なく通りそうです 😃❗️

function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

上記はERC20のコードです。どうやらtransferFrom関数は_spendAllowanceという関数をラップしているようです。この_spendAllowanceは更に_approve(EOAの残高を第三者が操作することを許可する関数)をラップしているので、やはりtransferFromを叩けばEOAの残高を操作できそうです❗️

暗雲が立ち込める

_allowance構造体

mapping(address => mapping(address => uint256)) private _allowances;

_allowanceとはそのアドレスがいくらの(allowance)トークンの操作を誰(spender)に許可したかを保存している構造体です。どうやら先ほど登場した_spendAllowanceallowance関数を経由してコレを参照しているようです。

_spendAllowance関数

function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

今先ほどから幾度となく登場しているので、この辺りで_spendAllowanceを確認しておきましょう。
今先ほど説明した通りallowanceを叩いてるようです。このallowance_allowance構造体を参照してcurrentAllowanceに格納をしているようです。そして_approve関数で操作を許可されたトークン量(allowance)から送金する量(amount)をひいています。

......
ココで呼ばれてるapproveは更新作業をしているだけでは...?

ということは最初にapproveを叩いてからtransferFromを実行しなければ、一銭もトークンの操作を許可されていないのでエラーが発生してしまいます。

結論

transferFrom関数を叩く前にapprove関数を実行しよう

function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

よく確認すればownerには _msg.sender が格納されるので、EOA自らがこの関数を叩かなければいけなかったようです。
この関数の実行に署名させてから、コントラクト側での処理も署名させましょう!

今回は自らの失態と引き換えにコードチェックの重要性を学べたのでヨシとします。

Discussion