0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

uniswap v3をfoundryでforkしてトークン交換してみる

Last updated at Posted at 2023-11-11

Foundry フォークテスト

まずはFoundry のfork テストをみてみましょう。こちらのyoutubeで分かりやすく解説されています。
https://www.youtube.com/watch?v=eKxJZgp9CTg
fork することで実際にデプロイされているコントラクトを使用できます。
forge testでテストスクリプトでやるときは引数に指定してあげます。

forge test --fork-url <Alchemyなどのmainnet PRC>  --match-path test/Weth.t.sol   -vvvv

もしくは unvil をつかってローカルにforkのテストネットを構築する場合は以下で試せます。

anvil  --fork-url <Alchemyなどのmainnet PRC> 

wethのフォークテスト

先ほどのリンクのyoutube ではdeposit だけでしたが approve, transferfrom なども試してみました。

pragma solidity ^0.8.18;

import "forge-std/Test.sol";
import "forge-std/console.sol";

interface IWETH {
    function balanceOf(address) external view returns (uint256);
    function allowance(address, address) external view returns (uint256);
    function deposit() external payable;
    function withdraw(uint256) external;
    function totalSupply() external returns (uint256);
    function approve(address, uint256) external returns (bool);
    function transfer(address, uint256) external returns (bool);
    function transferFrom(address, address, uint256) external returns (bool);
}

contract WethTest is Test {
    IWETH public weth;

    function setUp() public {
        weth = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    }

    function testWeth() public {
        uint256 mybalance;
        uint256 bob_balance;
        uint256 alice_balance;
        uint256 new_allowance;
        address mywallet = address(this);
        address bob = address(0x10000);
        address alice = address(0x2000);

        mybalance = weth.balanceOf(mywallet);
        console.log("My balance before", mybalance);

        weth.deposit{value: 10 ether}();
        mybalance = weth.balanceOf(mywallet);
        console.log("My balance after deposit", mybalance);

        weth.transfer(bob, 1 ether);
        mybalance = weth.balanceOf(mywallet);
        bob_balance = weth.balanceOf(bob);
        console.log("My balance after sending Bob 1 ether ", mybalance);
        console.log("bob's balance", bob_balance);
        console.log("approve Bob for 2 ether");

        weth.approve(bob, 2 ether);
        new_allowance = weth.allowance(mywallet, bob);
        console.log("Bob's allowance from my wallet ", new_allowance);
        bob_balance = weth.balanceOf(bob);
        console.log("bob's balance", bob_balance);
        console.log("Bob send Alice 2 ether instead of me");

        vm.prank(bob);
        weth.transferFrom(mywallet, alice, 2 ether);
        alice_balance = weth.balanceOf(alice);
        console.log("alice balance after transferfrom ", alice_balance);
        mybalance = weth.balanceOf(mywallet);
        console.log("My balance after transferfrom", mybalance);
        uint256 total_supply  = weth.totalSupply();
        console.log("Total Supply ", total_supply);

    }
}

結果はこんなかんじ

root@DESKTOP-N2O3OSL:~/weth# forge test --fork-url https://eth-mainnet.g.alchemy.com/v2/xxxxx  --match-path test/Weth.t.sol   -vvv
[⠆] Compiling...
[⠒] Compiling 1 files with 0.8.22
[⠢] Solc 0.8.22 finished in 684.08ms
Compiler run successful!

Running 1 test for test/Weth.t.sol:WethTest
[PASS] testWeth() (gas: 115853)
Logs:
  My balance before 0
  My balance after deposit 10000000000000000000
  My balance after sending Bob 1 ether  9000000000000000000
  bob's balance 1000000000000000000
  approve Bob for 2 ether
  Bob's allowance from my wallet  2000000000000000000
  bob's balance 1000000000000000000
  Bob send Alice 2 ether instead of me
  alice balance after transferfrom  2000000000000000000
  My balance after transferfrom 7000000000000000000
  Total Supply  3124229408761989594689998

Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.16s

Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
root@DESKTOP-N2O3OSL:~/weth#

uniswap v3でtokenを交換してみる。

uniscwap v3の調査

uniswap v3でtoken交換する関数はexactInputSingle(ExactInputSingleParams calldata)exactOutputSingle(ExactOutputSingleParams calldata)でした。

でも公式ドキュメントでは自分でexample.solを作ってそこからuniswapのコントラクトを呼んでいる。やっていることは 出金元のtokenのコントラクトでapprove(<uniswap address>, amout>してからuniswapのコントラクトの関数を呼んでいる。公式には実際のサンプルはないんですよね。以下のリンクは関数名は違うけどそのサンプルと同様にコントラクトを実際につくってやっている。Foundryのテストケースもある。
https://solidity-by-example.org/defi/uniswap-v3-swap/

なんでこんな面倒なことするんだろう。そのままexactInputSingle(ExactInputSingleParams calldata)exactOutputSingle(ExactOutputSingleParams calldata)を呼んだほうがよさそうなので、foundryのテストから呼ぶことにしました。

uniswap v3 のトークン交換テスト

以下が使用したトークン交換のテストです。

test/Uniswap.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;

import "forge-std/Test.sol";
import "forge-std/console.sol";

struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
}

struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
}

interface IUNI {
    function exactInputSingle(ExactInputSingleParams calldata) external payable returns (uint256 amountOut);
    function exactOutputSingle(ExactOutputSingleParams calldata) external payable returns (uint256 amountOut);
}


interface IWETH {
    function balanceOf(address) external view returns (uint256);
    function allowance(address, address) external view returns (uint256);
    function deposit() external payable;
    function withdraw(uint256) external;
    function totalSupply() external returns (uint256);
    function approve(address, uint256) external returns (bool);
    function transfer(address, uint256) external returns (bool);
    function transferFrom(address, address, uint256) external returns (bool);
}

interface IDAI {
    function balanceOf(address) external view returns (uint256);
}


address constant WETH_ADDR=address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
address constant DAI_ADDR=address(0x6B175474E89094C44Da98b954EedeAC495271d0F);
address constant UNI_ADDR=address(0xE592427A0AEce92De3Edee1F18E0157C05861564);


contract UniswapTest is Test {
    IWETH public weth;
    IDAI public dai;
    IUNI public uni;

    function setUp() public {
        weth = IWETH(WETH_ADDR);
        dai = IDAI(DAI_ADDR);
        uni = IUNI(UNI_ADDR);
    }

    function testExactInputSingle() public {
        uint256 mybalance;
        uint256 dai_balance;
        uint256 amountOut;
        address mywallet = address(this);
        weth.deposit{value: 10 ether}();
        mybalance = weth.balanceOf(mywallet);
        console.log("My balance after deposit", mybalance);
        dai_balance=dai.balanceOf(mywallet);
        console.log("Dai Balance", dai_balance);        
        console.log("Approve Uni to spend 1 ether");  
        weth.approve(UNI_ADDR, 1 ether);
        console.log("Swap 1 ether to Dai with exactInputSingle function");
        console.logBytes4(uni.exactInputSingle.selector); // check selector for debug
        amountOut=uni.exactInputSingle(ExactInputSingleParams(WETH_ADDR,DAI_ADDR,3000,mywallet,block.timestamp + 100,1 ether,0,0));
        console.log("Amount Out", amountOut);
        dai_balance=dai.balanceOf(mywallet);
        console.log("Dai Balance (daller)", dai_balance/ (1 ether ));

    }

    function testExactOutputSingle() public {
        uint256 mybalance;
        uint256 dai_balance;
        uint256 amountOut;
        address mywallet = address(this);
        weth.deposit{value: 10 ether}();
        mybalance = weth.balanceOf(mywallet);
        console.log("My balance after deposit", mybalance);
        dai_balance=dai.balanceOf(mywallet);
        console.log("Dai Balance", dai_balance);
        console.log("Approve Uni to spend 5 ether");
        weth.approve(UNI_ADDR, 5 ether);
        console.log("Swap ether to 10000$ Dai with exactOutputSingle function");
        amountOut=uni.exactOutputSingle(ExactOutputSingleParams(WETH_ADDR,DAI_ADDR,3000,mywallet,block.timestamp + 100, 10000 ether,5 ether,0));
        console.log("Amount Out", amountOut);
        dai_balance=dai.balanceOf(mywallet);
        console.log("Dai Balance ", dai_balance/(1e16));
        mybalance = weth.balanceOf(mywallet);
        console.log("My ether balance", mybalance);
    }

}
実行結果
root@DESKTOP-N2O3OSL:~/weth# forge test --fork-url https://eth-mainnet.g.alchemy.com/v2/xxxxx  --match-path test/Uniswap.t.sol   -vvv
[⠆] Compiling...
[⠢] Compiling 1 files with 0.8.22
[⠆] Solc 0.8.22 finished in 739.49ms
Compiler run successful!

Running 2 tests for test/Uniswap.t.sol:UniswapTest
[PASS] testExactInputSingle() (gas: 145252)
Logs:
  0x414bf389
  My balance after deposit 10000000000000000000
  Dai Balance 0
  Approve Uni to spend 1 ether
  Swap 1 ether to Dai with exactInputSingle function
  Amount Out 2048503466360154622352
  Dai Balance (daller) 2048

[PASS] testExactOutputSingle() (gas: 178964)
Logs:
  0x414bf389
  My balance after deposit 10000000000000000000
  Dai Balance 0
  Approve Uni to spend 5 ether
  Swap ether to 10000$ Dai with exactOutputSingle function
  Amount Out 4882008713537451517
  Dai Balance  10000
  My ether balance 5117991286462548483

Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.71s

Ran 1 test suites: 2 tests passed, 0 failed, 0 skipped (2 total tests)
testExactInputSingle() の説明

1. weth.approve(UNI_ADDR, 1 ether); で uniswapに 1etherの使用許可を出します。
2. uni.exactInputSingle(... にて実際のtoken交換を行います。
3. 1etherを最大量のDAIに交換します。

testExactInputSingle() の説明

1. weth.approve(UNI_ADDR, 5 ether); で uniswapに 5etherの使用許可を出します。
2. uni.exactOutputSingle(... にて実際のtoken交換を行います。
3. 許容量(5 ether) の範囲内で10,000 DAIに交換します。許容量が足りない場合はエラーになります。

######はまったとこと注意点

  • exactInputSingle(..の呼び出しselector が正しいかどうかをconsole.logBytes4(uni.exactInputSingle.selector); で確認した。実際のセレクターのIDはetherscanのところで確認できる。
    ethescan-selector.PNG
  • ExactInputSingleParams と ExactOutputSingleParamsfeeはきっちり3000 (0.3%)にしないと失敗した。これが時期による変動値なのかも不明です。
  • 当然だけど ExactOutputSingleParamsamountInMaximumに必要とされるトークン以下を指定するとエラーになります。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?