3MIKAN
DeFi直コン

直コンでV2 LPを解除する前に|removeLiquidityの引数と確認順

V2型RouterのremoveLiquidityを直接呼ぶ前に、LP残高・allowance・reserve・totalSupply・minimum amounts・deadline・シミュレーション・receiptと回収不能条件を確認する順番を解説します。

3MIKANのブランドキャラクターが直コンでV2 LPを解除する前にを表す流動性タンク、交換経路、報酬区画、保管庫を分けて確認する場面

V2型LPを解除すると、LP tokenをburnし、Pairが保有する2種類のtokenを持分に応じて受け取ります。ただし、walletにLPがない、別versionのRouterを見ている、minimum amountsや期限が合わない、token transfer自体が失敗する、といった条件では実行できません。

この記事では、Uniswap V2 Router02のremoveLiquidityを公開sourceの例に使います。LPの所在 → contractとABI → 受取見込み → allowance → minimum amountsとdeadline → シミュレーション → receiptと最終balanceの順に確認します。直コンによる資金回収やRug pull回避を保証するものではありません。

LPの所在確認、受取見込みとminimum amounts、シミュレーション、receiptと最終balanceへ進むV2 LP解除フロー

解除する前にLPの所在を確認する

同じ「流動性position」でも、解除方法は所有stateで変わります。

読み取れたstate 何を持っているか 次の入口
PairのbalanceOf(user) > 0 wallet内のV2 LP token V2 Routerのremove系function候補
MasterChef等のuserInfo(pid,user).amount > 0 farmへstakedしたV2 LP farmのwithdraw条件を確認し、LPをwalletへ戻す
NFT owner / positionが確認できる V3型position Position Manager系contractを確認
どれも確認できない chain、account、version、移転履歴が違う可能性 transaction履歴とaddressを再照合

MasterChefのpool IDはdeposit記録から_pidを照合する方法で確認します。stakedしたままのLPをV2 Routerへ渡すことはできません。

chain・Router・Pair・tokenを固定する

次を一つの確認メモへまとめます。

  1. network名とchain ID
  2. signing accountとLP owner
  3. project公式情報にある対象versionのRouter address
  4. Routerが参照するFactory address
  5. Pair address、token0()token1()
  6. token A / Bのofficial addressとdecimals()
  7. Router、Factory、Pair、tokenのverified source
  8. proxyなら現在のimplementation、admin、ABI

V2互換を名乗っていても、functionやfee-on-transfer対応、permit、proxy構成が違う場合があります。contract verificationはsource照合の入口であり、安全証明ではありません。

直コン共通のaddress・proxy・ABI確認はverified contractとシミュレーションの確認順も参照してください。

removeLiquidityの7引数を読む

Uniswap V2 Router02のERC-20同士の関数は次の形です。

function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) public returns (uint amountA, uint amountB);
引数 意味 確認する値
tokenA / tokenB Pairを構成する2 token Pairのtoken0 / token1とofficial address
liquidity burnするLPのraw amount LP decimals()balanceOf(user)
amountAMin / amountBMin 受取量の下限 reserve、total supply、token仕様、quote時block
to 2 tokenの受取先 意図したaccount / contract
deadline 実行を許す期限 最新block timestampと意図した有効時間

Router02では、PairのLP tokenをRouterからPairへ移し、Pairのburn(to)を呼びます。返り値のtoken順をtokenA / tokenBへ対応させ、各minimumと比較します。

片方がnative tokenならremoveLiquidityETH系、fee-on-transfer tokenなら対応variantが用意される場合があります。名前が似ていてもsignature、返り値、処理が違うため、対象RouterのsourceとABIを基準にします。

LP残高とallowanceをraw amountで読む

通常のremoveLiquidityでは、PairというERC-20 contractでRouterへのallowanceが必要です。

Pair.balanceOf(user)                 >= liquidity
Pair.allowance(user, Router)         >= liquidity
transaction.to                       = Router
approve transaction.to               = Pair
approve argument spender             = Router

LP tokenの表示桁を18と決め打ちせず、Pairのdecimals()を読みます。unlimited approveを既定にせず、解除量、既存allowance、token固有のapprove動作を確認します。

removeLiquidityWithPermit系は、LP allowanceの代わりにpermit署名を使うvariantです。署名だから安全、gas表示がないから無料、とは判断できません。domain、chain ID、verifying contract、owner、spender、value、nonce、deadlineと、対象Pairがpermitを実装しているかを確認します。

reserveとtotal supplyから受取見込みを読む

単純化したV2 Pairでは、持分比率は次の形で確認できます。

share        = burnするLP / total LP supply
amountAQuote = reserveA × share
amountBQuote = reserveB × share

固定例を使います。

state
reserve A / B 1,000 A / 500 B
total LP supply 1,000 LP
burnするLP 100 LP
単純な受取見込み 100 A / 50 B

token Aが6 decimals、token Bが18 decimalsならraw amountはそれぞれ10000000050000000000000000000です。

これは確認用の基準値です。実行前のswapでreservesが変わる、feeがPairへ蓄積する、tokenがtransfer feeやrebasingを持つ、protocol固有処理がある場合は結果が変わります。Pairの現在stateと対象Router sourceを読みます。

minimum amountsは受取下限として作る

amountAMin / amountBMinを0にすると、そのtoken側の受取下限を外します。

先ほどのquoteへ「1%の差を許容する」という仮定だけを置くと、次の計算です。

amountAMin = 100 × (1 - 0.01) = 99 A
amountBMin =  50 × (1 - 0.01) = 49.5 B

1%は推奨値ではありません。許容範囲を広げればrevertしにくくなる一方、少ない受取量を受け入れる幅も広がります。quoteのblock、reserves、tokenのtransfer仕様、市場変動を確認し、raw unitsへ変換します。

slippageを上げて不明なtoken制限を解決しようとせず、Price impact・slippage・MEVの違いも分けて確認してください。

deadlineは古い入力を止める条件

Router02はdeadline < block.timestampならrevertします。Explorerへ遠い未来のtimestampを貼るのではなく、対象chainの最新block timestampへ意図した有効時間を加えます。

  • Unix秒か
  • 対象chainのblock timestampを基準にしたか
  • quoteや確認内容を有効とみなせる時間か
  • 署名直前にまだ期限内か

期限を延ばすことは、minimum amountsやcontractの安全性を改善しません。

固定例で成功・失敗を比較する

固定したV2 stateで100 LPを解除すると、単純計算では100 A / 50 Bです。minimumが99 A / 49.5 B、LP allowanceが100 LP、期限内なら、入力条件を満たします。

次の条件は別々に止めます。

変更した条件 判定
Pair allowanceが0 RouterがLPを移せず失敗
walletのLP balanceが99、解除量が100 LP不足
amountAMinが101 A 見込み100 Aが下限未満
deadlineが最新blockと同じか過去 期限切れ
Pairのtoken addressが確認メモと違う 対象contract不一致として中止

この成功判定は、その入力とstateを使ったV2型の計算結果です。実transactionのreceiptでも、deployed contractの安全性証明でもありません。

シミュレーションでrevertと入力を先に確認する

対象accountをfromに指定し、同じRouter address、calldata、value、block条件でeth_callします。

確認するものは次の通りです。

  • RPCのchain IDとblock number
  • Router addressとcurrent ABI
  • decodeしたremoveLiquidityの7引数
  • LP balance / allowance、reserves、total supply
  • token A / Bの受取見込みとminimum amounts
  • resultまたはrevert data

シミュレーション成功後にもreserves、allowance、deadline、proxy implementationは変わりえます。成功は本番実行や資金回収の保証ではありません。

署名前・receipt・final stateを別々に見る

署名前はnetwork、account、transaction to、function、7引数、native tokenのvalue、spending cap、warningを照合します。walletがcalldataをdecodeできない場合は先へ進まず、別のdecoderとverified ABIで確認します。

送信後は次の順に確認します。

  1. receiptのstatus、block number、transaction hash
  2. transaction toとinput calldata
  3. Pair addressが発行したBurnTransfer等のlogs
  4. walletのLP balance減少
  5. recipientのtoken A / B balance増加
  6. Pair reservesと残allowance

receipt成功だけ、eventだけ、画面表示だけでは証拠が足りません。receipt・calldata・logs・stateの確認順で発行元addressも含めて確認します。

解除できない条件を分ける

front-endが停止していても、contractが動作し、正しいLPをwalletが所有し、Router / Pairの条件を満たせば、公開interfaceから状態を確認できる場合はあります。しかし次の状態では、直コンでも回収を保証できません。

  • LPがfarmやvaultへ移っており、walletが所有していない
  • withdraw経路がない、paused、role制限、lock期間がある
  • proxy implementationやRouterが悪意あるcodeへ変わった
  • Pair reservesがdrainされ、LPに対応する資産がない
  • tokenがblacklist、transfer停止、fee、malicious callbackを持つ
  • Pair / Router / token sourceが未検証で、ABIを安全に特定できない
  • LP token自体が別Pairまたは偽物
  • gas tokenがなく、transactionを送れない

ethereum.orgのsmart contract securityが示すように、access controlやemergency stopなどの実装条件はcontractごとに異なります。追加approveや不明な署名を急がず、chain、address、source、transaction履歴、receipt、allowanceを保存して状況を分けます。

症状別チェック表

症状 最初に読む値 次の確認
LPが0 Pair balanceOf(user) chain、account、farm userInfo、NFT position
allowance不足 Pair allowance(user, Router) spender、解除量、permit variant
minimum系revert reserves、total supply、quote block decimals、token順、transfer fee
transfer失敗 token source、paused / blacklist Pair balance、Router variant、revert data
receipt成功だが残高が違う logsの発行元とrecipient token fee、別account、最終state
sourceが違う official address、proxy implementation 書き込みを止め、ABIを推測しない

最終チェックリスト

  • wallet内V2 LP、staked LP、V3 NFT positionを区別した
  • chain、Router、Factory、Pair、token A / Bを公式情報で照合した
  • verified source、proxy、implementation、ABI、adminを確認した
  • LP balance、decimals、allowance、reserves、total supplyを読んだ
  • 受取見込みとminimum amountsをraw unitsで記録した
  • minimum amountsを0、deadlineを任意の遠い値にしていない
  • unlimited approveを既定にしていない
  • シミュレーション、署名、receipt、logs、最終balanceを分けた
  • front-end停止やRug pull時の回収を保証していない

V2 LPを作る側はaddLiquidityの引数と確認順、LPをfarmから戻す前のpool ID確認はMasterChefの_pidを照合する方法へ進んでください。

確認した一次情報