티스토리 뷰

Solidity/DeFi

[Uniswap] V2 FlashSwap 예제

piatoss 2024. 2. 22. 10:16

⛓️ 시리즈

2024.01.30 - [Solidity/DeFi] - [Uniswap] V2 Core - UniswapV2ERC20

2024.01.31 - [Solidity/DeFi] - [Uniswap] V2 Core - UniswapV2Factory

2024.01.31 - [Solidity/DeFi] - [Uniswap] V2 Core - UniswapV2Pair

2024.02.05 - [Solidity/DeFi] - [Uniswap] V2 Core 보충 자료 - 백서 읽기

2024.02.20 - [Solidity/DeFi] - [Uniswap] V2 Router

2024.02.21 - [Solidity/DeFi] - [Uniswap] V2 Oracle 예제


🦄 FlashSwap

 플래시 스왑은 자산을 소유하고 있지 않은 상태에서도 유동성 풀로부터 자산을 빌려 사용할 수 있도록 Uniswap V2에서 새롭게 도입된 기능입니다. 자산을 빌리는 것이 가능해졌지만, 빌린 자산은 반드시 동일한 트랜잭션 안에서 빌린 자산과 상응하는 가치의 자산 + 수수료로 유동성 풀에 상환되어야만 합니다. 그래서 'flash'라는 이름이 붙여졌습니다.

 

 플래시 스왑은 어떤 식으로 동작되는 걸까요? 우선 UniswapV2Pair 컨트랙트의 swap 함수를 살펴봅시다.

function swap(
    uint amount0Out,
    uint amount1Out,
    address to,
    bytes calldata data
) external lock {
    require(
        amount0Out > 0 || amount1Out > 0,
        "UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT"
    );
    (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
    require(
        amount0Out < _reserve0 && amount1Out < _reserve1,
        "UniswapV2: INSUFFICIENT_LIQUIDITY"
    );

    uint balance0;
    uint balance1;
    {
        address _token0 = token0;
        address _token1 = token1;
        require(to != _token0 && to != _token1, "UniswapV2: INVALID_TO");
        if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
        if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
        if (data.length > 0)
            IUniswapV2Callee(to).uniswapV2Call(
                msg.sender,
                amount0Out,
                amount1Out,
                data
            );
        balance0 = IERC20(_token0).balanceOf(address(this));
        balance1 = IERC20(_token1).balanceOf(address(this));
    }
    uint amount0In = balance0 > _reserve0 - amount0Out
        ? balance0 - (_reserve0 - amount0Out)
        : 0;
    uint amount1In = balance1 > _reserve1 - amount1Out
        ? balance1 - (_reserve1 - amount1Out)
        : 0;
    require(
        amount0In > 0 || amount1In > 0,
        "UniswapV2: INSUFFICIENT_INPUT_AMOUNT"
    );
    {
        uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
        uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
        require(
            balance0Adjusted.mul(balance1Adjusted) >=
                uint(_reserve0).mul(_reserve1).mul(1000 ** 2),
            "UniswapV2: K"
        );
    }

    _update(balance0, balance1, _reserve0, _reserve1);
    emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}

 swap 함수는 파라미터 amountXOut 값이 0보다 크면 묻지도 따지지도 않고 일단 자금을 보내줍니다. 따라서 자금을 가지고 있지 않더라도 swap 함수를 다이렉트로 호출하여  amountXOut 만큼의 자금을 빌릴 수 있습니다. 

if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);

 그러나 이렇게 자금을 빌리기만 할 수 있다면 함수가 종료될 때 상수 K를 검사하는 과정에서 트랜잭션이 revert 되기 때문에 가스비만 낭비하게 됩니다.

 

 빌린 자금으로 무언가 추가적인 동작을 하고 싶다면 data를 추가해야 합니다. data의 길이가 0보다 크다면 swap 함수는 to를 IUniswapV2Callee 인터페이스로 감싸고 인터페이스의 uniswapV2Call 함수를 호출합니다. 즉, IUniswapV2Callee 인터페이스를 구현한 to 컨트랙트의 함수를 호출하여 추가적인 로직을 실행하는 것입니다. 이 함수 내에서 빌린 자산을 어떤 식으로든 사용할 수 있습니다. 그리고 uniswapV2Call 함수가 종료되기 직전에 빌린 자산과 수수료를 UniswapV2Pair 컨트랙트로 전송하기만 하면 됩니다. 주의할 점은 to가 IUniswapV2Callee 인터페이스를 구현한 컨트랙트여야 한다는 것입니다.

if (data.length > 0)
      IUniswapV2Callee(to).uniswapV2Call(
          msg.sender,
          amount0Out,
          amount1Out,
          data
	  );

 여기까지 대략적인 동작 과정을 알아보았으니 예제를 통해 더 자세히 다뤄보도록 하겠습니다.


✨ SimpleFlashSwap.sol

 본 예제는 다음 예제를 예제를 참고했습니다.

전체 코드

더보기
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "../interfaces/IUniswapV2Callee.sol";
import "../interfaces/IUniswapV2Pair.sol";
import "../interfaces/IERC20.sol";

contract SimpleFlashSwap is IUniswapV2Callee {
    error OnlyOwner();
    error InvalidPair();
    error InvalidToken();
    error InvalidAmount();

    address public owner;

    IUniswapV2Pair public immutable pair;
    address public immutable token0;
    address public immutable token1;

    constructor(IUniswapV2Pair _pair) {
        owner = msg.sender;
        pair = _pair;
        token0 = _pair.token0();
        token1 = _pair.token1();
    }

    function withdraw(address token, uint256 amount) external returns (bool) {
        if (msg.sender != owner) {
            revert OnlyOwner();
        }

        if (token != token0 && token != token1) {
            revert InvalidToken();
        }

        return IERC20(token).transfer(owner, amount);
    }

    function flashSwap(
        address tokenBorrow,
        address tokenRepay,
        uint256 amount
    ) external {
        if (tokenBorrow != token0 && tokenBorrow != token1) {
            revert InvalidToken();
        }

        if (tokenRepay != token0 && tokenRepay != token1) {
            revert InvalidToken();
        }

        if (amount == 0) {
            revert InvalidAmount();
        }

        (uint amount0Out, uint amount1Out) = tokenBorrow == token0
            ? (amount, uint(0))
            : (uint(0), amount);

        bytes memory data = encodeData(tokenBorrow, tokenRepay, amount);
        pair.swap(amount0Out, amount1Out, address(this), data);
    }

    function uniswapV2Call(
        address sender,
        uint amount0,
        uint amount1,
        bytes calldata data
    ) external override {
        if (msg.sender != address(pair) || sender != address(this)) {
            revert InvalidPair();
        }

        (address tokenBorrow, address tokenRepay, uint256 amount) = decodeData(
            data
        );

        if (tokenBorrow != token0 && tokenBorrow != token1) {
            revert InvalidToken();
        }

        if (tokenRepay != token0 && tokenRepay != token1) {
            revert InvalidToken();
        }

        if (amount != amount0 && amount != amount1) {
            revert InvalidAmount();
        }

        uint repayAmount;

        if (tokenBorrow == tokenRepay) {
            repayAmount = repayWithSameToken(amount);
        } else {
            repayAmount = repayWithDifferentToken(tokenBorrow, amount);
        }

        IERC20(tokenRepay).transfer(address(pair), repayAmount);
    }

    function encodeData(
        address tokenBorrow,
        address tokenRepay,
        uint256 amount
    ) public pure returns (bytes memory) {
        return abi.encode(tokenBorrow, tokenRepay, amount);
    }

    function decodeData(
        bytes calldata data
    )
        public
        pure
        returns (address tokenBorrow, address tokenRepay, uint256 amount)
    {
        return abi.decode(data, (address, address, uint256));
    }
    // about 0.3009% fee
    function repayWithSameToken(
        uint256 amount
    ) public pure returns (uint256 repayAmount) {
        repayAmount = (amount * 1000) / 997 + 1;
    }

    function repayWithDifferentToken(
        address tokenBorrow,
        uint256 amount
    ) public view returns (uint256 repayAmount) {
        (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
        (uint256 amount0Out, uint256 amount1Out) = tokenBorrow == token0
            ? (amount, uint256(0))
            : (uint256(0), amount);

        uint numerator;
        uint denominator;

        if (amount0Out > 0) {
            numerator = amount0Out * reserve1 * 1000;
            denominator = (reserve0 - amount0Out) * 997;
        } else {
            numerator = amount1Out * reserve0 * 1000;
            denominator = (reserve1 - amount1Out) * 997;
        }

        repayAmount = (numerator / denominator) + 1;
    }

    receive() external payable {}
}

포인트

1. flashSwap 함수는 빌릴 토큰 주소와 상환할 토큰 주소를 구분하고 빌릴 자산의 양을 지정합니다. 플래시 스왑을 통해 빌린 자산은 동일한 유형의 자산으로 상환해도 되고 쌍을 이룬 자산으로 상환하는 것도 가능합니다.

 

2. uniswapV2Call 함수는 IUniswapV2Callee 인터페이스를 구현하기 위해 필요한 유일한 함수입니다. 여기서는 단순히 빌린 자산을 상환하는 로직만을 구현해 놓았습니다.

 

3. uniswapV2Call 함수로 전달되는 data는 encodeData 함수로 인코딩하고, 함수 내부에서 decodeData 함수로 디코딩하여 사용합니다.

 

4. 빌린 자산과 동일한 자산으로 상환할 때와 다른 자산으로 상환할 때 상환할 금액을 구하는 함수는 별도로 구현하였습니다. 동일한 자산인 경우에는 빌린 양 + 수수료를 반환하면 그만이지만, 다른 자산으로 상환할 때는 상수 K를 기반으로 상환할 양을 계산해야 합니다. 상환 금액에 +1은 정밀도 문제 때문에 추가하였습니다.


✨ SimpleFlashSwapTest

 플래시 스왑이 잘 동작하는지 테스트를 통해 확인해 보겠습니다.

전체 코드

더보기
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import "../src/interfaces/IUniswapV2Pair.sol";
import "../src/examples/SimpleFlashSwap.sol";
import "../src/test/ERC20.sol";
import "../src/UniswapV2Router01.sol";
import "../src/UniswapV2Factory.sol";
import "../src/UniswapV2Pair.sol";
import "../src/libraries/UniswapV2Library.sol";

contract SimpleFlashSwapTest is Test {
    UniswapV2Router01 public router;
    UniswapV2Factory public factory;
    UniswapV2Pair public pair;
    UniswapV2ERC20WithMint public token0;
    UniswapV2ERC20WithMint public token1;
    SimpleFlashSwap public flashSwap;

    uint public constant INIT_SUPPLY = 1000000e18;

    function setUp() public {
        token0 = new UniswapV2ERC20WithMint();
        token1 = new UniswapV2ERC20WithMint();

        (token0, token1) = token0 < token1
            ? (token0, token1)
            : (token1, token0);

        token0.mint(address(this), INIT_SUPPLY);
        token1.mint(address(this), INIT_SUPPLY);

        factory = new UniswapV2Factory(address(this));
        router = new UniswapV2Router01(address(factory), address(0));
        pair = UniswapV2Pair(
            factory.createPair(address(token0), address(token1))
        );

        vm.label(address(token0), "token0");
        vm.label(address(token1), "token1");
        vm.label(address(factory), "factory");
        vm.label(address(router), "router");
        vm.label(address(pair), "pair");

        token0.approve(address(router), type(uint256).max);
        token1.approve(address(router), type(uint256).max);

        (uint amountA, uint amountB, uint liquidity) = router.addLiquidity(
            address(token0),
            address(token1),
            INIT_SUPPLY / 2,
            INIT_SUPPLY / 2,
            INIT_SUPPLY / 2,
            INIT_SUPPLY / 2,
            address(this),
            block.timestamp
        );

        assertEq(amountA, INIT_SUPPLY / 2);
        assertEq(amountB, INIT_SUPPLY / 2);
        assertEq(liquidity, INIT_SUPPLY / 2 - 1000);

        flashSwap = new SimpleFlashSwap(IUniswapV2Pair(address(pair)));

        vm.label(address(flashSwap), "flashSwap");

        token0.mint(address(flashSwap), INIT_SUPPLY);
        token1.mint(address(flashSwap), INIT_SUPPLY);
    }

    // ===== Repay with same token0 =====
    function test_FlashSwapToken0ToToken1RepayWithToken0() public {
        address tokenBorrow = address(token0);
        address tokenRepay = address(token0);
        uint256 amount = 100000e18;
        uint256 repayAmount = flashSwap.repayWithSameToken(amount);
        uint256 fee = repayAmount - amount;

        uint256 flashToken0Before = token0.balanceOf(address(flashSwap));
        uint256 pairToken0Before = token0.balanceOf(address(pair));

        flashSwap.flashSwap(tokenBorrow, tokenRepay, amount);

        uint256 flashToken0After = token0.balanceOf(address(flashSwap));
        uint256 pairToken0After = token0.balanceOf(address(pair));

        assertEq(flashToken0After, flashToken0Before - fee);
        assertEq(pairToken0After, pairToken0Before + fee);
    }

    // ===== Repay with same token1 =====
    function test_FlashSwapToken1ToToken0RepayWithToken1() public {
        address tokenBorrow = address(token1);
        address tokenRepay = address(token1);
        uint256 amount = 100000e18;
        uint256 repayAmount = flashSwap.repayWithSameToken(amount);
        uint256 fee = repayAmount - amount;

        uint256 flashToken1Before = token1.balanceOf(address(flashSwap));
        uint256 pairToken1Before = token1.balanceOf(address(pair));

        flashSwap.flashSwap(tokenBorrow, tokenRepay, amount);

        uint256 flashToken1After = token1.balanceOf(address(flashSwap));
        uint256 pairToken1After = token1.balanceOf(address(pair));

        assertEq(flashToken1After, flashToken1Before - fee);
        assertEq(pairToken1After, pairToken1Before + fee);
    }

    // ===== Repay with different token1 =====
    function test_FlashSwapToken0ToToken1RepayWithToken1() public {
        address tokenBorrow = address(token0);
        address tokenRepay = address(token1);
        uint256 amount = 100000e18;
        uint256 repayAmount = flashSwap.repayWithDifferentToken(
            tokenBorrow,
            amount
        );

        uint256 flashToken0Before = token0.balanceOf(address(flashSwap));
        uint256 flashToken1Before = token1.balanceOf(address(flashSwap));
        uint256 pairToken0Before = token0.balanceOf(address(pair));
        uint256 pairToken1Before = token1.balanceOf(address(pair));

        flashSwap.flashSwap(tokenBorrow, tokenRepay, amount);

        uint256 flashToken0After = token0.balanceOf(address(flashSwap));
        uint256 flashToken1After = token1.balanceOf(address(flashSwap));
        uint256 pairToken0After = token0.balanceOf(address(pair));
        uint256 pairToken1After = token1.balanceOf(address(pair));

        assertEq(flashToken0After, flashToken0Before + amount);
        assertEq(flashToken1After, flashToken1Before - repayAmount);
        assertEq(pairToken0After, pairToken0Before - amount);
        assertEq(pairToken1After, pairToken1Before + repayAmount);
    }

    // ===== Repay with different token0 =====
    function test_FlashSwapToken1ToToken0RepayWithToken0() public {
        address tokenBorrow = address(token1);
        address tokenRepay = address(token0);
        uint256 amount = 100000e18;
        uint256 repayAmount = flashSwap.repayWithDifferentToken(
            tokenBorrow,
            amount
        );

        uint256 flashToken0Before = token0.balanceOf(address(flashSwap));
        uint256 flashToken1Before = token1.balanceOf(address(flashSwap));
        uint256 pairToken0Before = token0.balanceOf(address(pair));
        uint256 pairToken1Before = token1.balanceOf(address(pair));

        flashSwap.flashSwap(tokenBorrow, tokenRepay, amount);

        uint256 flashToken0After = token0.balanceOf(address(flashSwap));
        uint256 flashToken1After = token1.balanceOf(address(flashSwap));
        uint256 pairToken0After = token0.balanceOf(address(pair));
        uint256 pairToken1After = token1.balanceOf(address(pair));

        assertEq(flashToken0After, flashToken0Before - repayAmount);
        assertEq(flashToken1After, flashToken1Before + amount);
        assertEq(pairToken0After, pairToken0Before + repayAmount);
        assertEq(pairToken1After, pairToken1Before - amount);
    }
}

초기 설정

  1. 두 개의 ERC-20 토큰 컨트랙트 token0과 token1을 생성하고 100만 개의 토큰을 공급합니다.
  2. Uniswap V2 팩토리, 라우터, 토큰 페어의 유동성 풀 컨트랙트를 차례로 생성합니다.
  3. 라우터에게 최대 수량의 토큰 사용을 허용하고 각각 50만 개의 토큰을 유동성 풀에 예치합니다.
  4. 앞서 생성한 페어 컨트랙트를 입력으로 플래시 스왑 컨트랙트를 생성합니다.
  5. 플래시 스왑 컨트랙트에 각각의 토큰을 100만 개씩 공급합니다.

A를 빌리고 A로 상환하는 경우

  1. token0 10만 개를 유동성 풀에서 빌리고자 합니다. 이때 상환되어야 할 금액은 대략 token0 100300.903개입니다.
  2. 플래시 스왑을 실행하기 전에 flashSwap 컨트랙트와 pair 컨트랙트의 token0 잔액을 확인합니다.
  3. 플래시 스왑을 실행합니다.
  4. 플래시 스왑을 실행하고 난 뒤의 flashSwap 컨트랙트의 token0 잔액은 플래시 스왑을 실행하기 전에서 수수료를 제외한 값이어야 합니다.
  5. 플래시 스왑을 실행하고 난 뒤의 pair 컨트랙트의 token0 잔액은 플래시 스왑을 실행하기 전에서 수수료를 더한 값이어야 합니다.
미션 1: 유동성 풀의 초기 token0의 수량 x와 token1의 수량 y의 곱 k (x * y = k)를 유지하라

빌리고자 하는 token0의 수량을 a, 상환되어야 하는 token0의 수량을 b라고 했을 때
(x - a + b) * y = k를 만족하는 b는 a 이상의 값이면 됩니다. 빌린 a를 그대로 상환하면 될 것 같군요?

그러나! 수수료를 간과해서는 안됩니다. 수수료는 최종적으로 상환되는 수량 c에서 0.3% 공제됩니다.
따라서 b >= c - 0.003 * c가 됩니다.

식을 정리하여 최종적으로 상환해야 할 금액 c를 기준으로 정리하면 c =< b + (3/997) * b가 됩니다.

자금을 빌린 사용자 입장에서 실제 수수료는 0.3009%가 지불되어야 한다는 것을 확인할 수 있습니다.

A를 빌리고 B로 상환하는 경우

  1. token0 10만 개를 유동성 풀에서 빌리고자 합니다. 이때 상환되어야 할 금액은 대략 token1 125376.129개입니다.
  2. 플래시 스왑을 실행하기 전에 flashSwap 컨트랙트와 pair 컨트랙트의 token0, token1 잔액을 확인합니다.
  3. 플래시 스왑을 실행합니다.
  4. 플래시 스왑을 실행하고 난 뒤의 flashSwap 컨트랙트의 token0 잔액은 플래시 스왑을 실행하기 전에서 빌린 자금을 더한 값이어야 합니다.
  5. 플래시 스왑을 실행하고 난 뒤의 flashSwap 컨트랙트의 token1 잔액은 플래시 스왑을 실행하기 전에서 상환된 금액을 제외한 값이어야 합니다.
  6. 플래시 스왑을 실행하고 난 뒤의 pair 컨트랙트의 token0 잔액은 플래시 스왑을 실행하기 전에서 빌려준 자금을 제외한 값이어야 합니다.
  7. 플래시 스왑을 실행하고 난 뒤의 pair 컨트랙트의 token1 잔액은 플래시 스왑을 실행하기 전에서 상환된 금액을 더한 값이어야 합니다.
미션 2: A를 빌리고 B로 상환하는 것은 B를 A로 스왑하는 것과 마찬가지이다

빌리고자 하는 token0의 수량을 a, 상환되어야 하는 token1의 수량을 b라고 했을 때
(x - a) * (y + b) = k 여야 합니다. 이 때 b를 기준으로 식을 정리하면 b = (a * y) / (x - a) 입니다.

여기서 동일한 토큰을 상환할 때와 마찬가지로 수수료를 고려해줘야 합니다.
수수료는 최종적으로 상환되는 수량 c에서 0.3% 공제됩니다. 따라서 b >= c - 0.003 * c가 됩니다.

식을 정리하여 최종적으로 상환해야 할 금액 c를 기준으로 정리하면 c =< b + (3/997) * b가 됩니다.

테스트 결과

$ forge test --mc SimpleFlashSwapTest -vvvv
[⠃] Compiling...
No files changed, compilation skipped

Running 4 tests for test/SimpleFlashSwap.t.sol:SimpleFlashSwapTest
[PASS] test_FlashSwapToken0ToToken1RepayWithToken0() (gas: 68307)
Traces:
  [68307] SimpleFlashSwapTest::test_FlashSwapToken0ToToken1RepayWithToken0()
    ├─ [609] flashSwap::repayWithSameToken(100000000000000000000000 [1e23]) [staticcall]
    │   └─ ← 100300902708124373119359 [1.003e23]
    ├─ [2564] token0::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1000000000000000000000000 [1e24]
    ├─ [2564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500000000000000000000000 [5e23]
    ├─ [49143] flashSwap::flashSwap(token0: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], token0: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 100000000000000000000000 [1e23])
    │   ├─ [44780] pair::swap(100000000000000000000000 [1e23], 0, flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0x0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   ├─ [8730] token0::transfer(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 100000000000000000000000 [1e23])
    │   │   │   ├─ emit Transfer(from: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], value: 100000000000000000000000 [1e23])
    │   │   │   └─ ← true
    │   │   ├─ [5218] flashSwap::uniswapV2Call(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 100000000000000000000000 [1e23], 0, 0x0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   │   ├─ [3130] token0::transfer(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], 100300902708124373119359 [1.003e23])
    │   │   │   │   ├─ emit Transfer(from: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], to: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], value: 100300902708124373119359 [1.003e23])
    │   │   │   │   └─ ← true
    │   │   │   └─ ← ()
    │   │   ├─ [564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 500300902708124373119359 [5.003e23]
    │   │   ├─ [2564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 500000000000000000000000 [5e23]
    │   │   ├─ emit Sync(reserve0: 500300902708124373119359 [5.003e23], reserve1: 500000000000000000000000 [5e23])
    │   │   ├─ emit Swap(sender: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], amount0In: 100300902708124373119359 [1.003e23], amount1In: 0, amount0Out: 100000000000000000000000 [1e23], amount1Out: 0, to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1])
    │   │   └─ ← ()
    │   └─ ← ()
    ├─ [564] token0::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 999699097291875626880641 [9.996e23]
    ├─ [564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500300902708124373119359 [5.003e23]
    └─ ← ()

[PASS] test_FlashSwapToken0ToToken1RepayWithToken1() (gas: 86156)
Traces:
  [86156] SimpleFlashSwapTest::test_FlashSwapToken0ToToken1RepayWithToken1()
    ├─ [6730] flashSwap::repayWithDifferentToken(token0: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 100000000000000000000000 [1e23]) [staticcall]
    │   ├─ [2526] pair::getReserves() [staticcall]
    │   │   └─ ← 500000000000000000000000 [5e23], 500000000000000000000000 [5e23], 1
    │   └─ ← 125376128385155466399198 [1.253e23]
    ├─ [2564] token0::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1000000000000000000000000 [1e24]
    ├─ [2564] token1::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1000000000000000000000000 [1e24]
    ├─ [2564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500000000000000000000000 [5e23]
    ├─ [2564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500000000000000000000000 [5e23]
    ├─ [47417] flashSwap::flashSwap(token0: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], token1: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 100000000000000000000000 [1e23])
    │   ├─ [45504] pair::swap(100000000000000000000000 [1e23], 0, flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0x0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   ├─ [8730] token0::transfer(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 100000000000000000000000 [1e23])
    │   │   │   ├─ emit Transfer(from: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], value: 100000000000000000000000 [1e23])
    │   │   │   └─ ← true
    │   │   ├─ [12431] flashSwap::uniswapV2Call(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 100000000000000000000000 [1e23], 0, 0x0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   │   ├─ [526] pair::getReserves() [staticcall]
    │   │   │   │   └─ ← 500000000000000000000000 [5e23], 500000000000000000000000 [5e23], 1
    │   │   │   ├─ [8730] token1::transfer(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], 125376128385155466399198 [1.253e23])
    │   │   │   │   ├─ emit Transfer(from: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], to: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], value: 125376128385155466399198 [1.253e23])
    │   │   │   │   └─ ← true
    │   │   │   └─ ← ()
    │   │   ├─ [564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 400000000000000000000000 [4e23]
    │   │   ├─ [564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 625376128385155466399198 [6.253e23]
    │   │   ├─ emit Sync(reserve0: 400000000000000000000000 [4e23], reserve1: 625376128385155466399198 [6.253e23])
    │   │   ├─ emit Swap(sender: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], amount0In: 0, amount1In: 125376128385155466399198 [1.253e23], amount0Out: 100000000000000000000000 [1e23], amount1Out: 0, to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1])
    │   │   └─ ← ()
    │   └─ ← ()
    ├─ [564] token0::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1100000000000000000000000 [1.1e24]
    ├─ [564] token1::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 874623871614844533600802 [8.746e23]
    ├─ [564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 400000000000000000000000 [4e23]
    ├─ [564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 625376128385155466399198 [6.253e23]
    └─ ← ()

[PASS] test_FlashSwapToken1ToToken0RepayWithToken0() (gas: 86201)
Traces:
  [86201] SimpleFlashSwapTest::test_FlashSwapToken1ToToken0RepayWithToken0()
    ├─ [6730] flashSwap::repayWithDifferentToken(token1: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 100000000000000000000000 [1e23]) [staticcall]
    │   ├─ [2526] pair::getReserves() [staticcall]
    │   │   └─ ← 500000000000000000000000 [5e23], 500000000000000000000000 [5e23], 1
    │   └─ ← 125376128385155466399198 [1.253e23]
    ├─ [2564] token0::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1000000000000000000000000 [1e24]
    ├─ [2564] token1::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1000000000000000000000000 [1e24]
    ├─ [2564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500000000000000000000000 [5e23]
    ├─ [2564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500000000000000000000000 [5e23]
    ├─ [47441] flashSwap::flashSwap(token1: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], token0: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 100000000000000000000000 [1e23])
    │   ├─ [45518] pair::swap(0, 100000000000000000000000 [1e23], flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   ├─ [8730] token1::transfer(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 100000000000000000000000 [1e23])
    │   │   │   ├─ emit Transfer(from: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], value: 100000000000000000000000 [1e23])
    │   │   │   └─ ← true
    │   │   ├─ [12445] flashSwap::uniswapV2Call(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0, 100000000000000000000000 [1e23], 0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   │   ├─ [526] pair::getReserves() [staticcall]
    │   │   │   │   └─ ← 500000000000000000000000 [5e23], 500000000000000000000000 [5e23], 1
    │   │   │   ├─ [8730] token0::transfer(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], 125376128385155466399198 [1.253e23])
    │   │   │   │   ├─ emit Transfer(from: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], to: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], value: 125376128385155466399198 [1.253e23])
    │   │   │   │   └─ ← true
    │   │   │   └─ ← ()
    │   │   ├─ [564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 625376128385155466399198 [6.253e23]
    │   │   ├─ [564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 400000000000000000000000 [4e23]
    │   │   ├─ emit Sync(reserve0: 625376128385155466399198 [6.253e23], reserve1: 400000000000000000000000 [4e23])
    │   │   ├─ emit Swap(sender: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], amount0In: 125376128385155466399198 [1.253e23], amount1In: 0, amount0Out: 0, amount1Out: 100000000000000000000000 [1e23], to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1])
    │   │   └─ ← ()
    │   └─ ← ()
    ├─ [564] token0::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 874623871614844533600802 [8.746e23]
    ├─ [564] token1::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1100000000000000000000000 [1.1e24]
    ├─ [564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 625376128385155466399198 [6.253e23]
    ├─ [564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 400000000000000000000000 [4e23]
    └─ ← ()

[PASS] test_FlashSwapToken1ToToken0RepayWithToken1() (gas: 68542)
Traces:
  [68542] SimpleFlashSwapTest::test_FlashSwapToken1ToToken0RepayWithToken1()
    ├─ [609] flashSwap::repayWithSameToken(100000000000000000000000 [1e23]) [staticcall]
    │   └─ ← 100300902708124373119359 [1.003e23]
    ├─ [2564] token1::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 1000000000000000000000000 [1e24]
    ├─ [2564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500000000000000000000000 [5e23]
    ├─ [49389] flashSwap::flashSwap(token1: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], token1: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 100000000000000000000000 [1e23])
    │   ├─ [44916] pair::swap(0, 100000000000000000000000 [1e23], flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   ├─ [8730] token1::transfer(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 100000000000000000000000 [1e23])
    │   │   │   ├─ emit Transfer(from: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], value: 100000000000000000000000 [1e23])
    │   │   │   └─ ← true
    │   │   ├─ [5332] flashSwap::uniswapV2Call(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0, 100000000000000000000000 [1e23], 0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000000000000000152d02c7e14af6800000)
    │   │   │   ├─ [3130] token1::transfer(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], 100300902708124373119359 [1.003e23])
    │   │   │   │   ├─ emit Transfer(from: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], to: pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14], value: 100300902708124373119359 [1.003e23])
    │   │   │   │   └─ ← true
    │   │   │   └─ ← ()
    │   │   ├─ [2564] token0::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 500000000000000000000000 [5e23]
    │   │   ├─ [564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   │   │   └─ ← 500300902708124373119359 [5.003e23]
    │   │   ├─ emit Sync(reserve0: 500000000000000000000000 [5e23], reserve1: 500300902708124373119359 [5.003e23])
    │   │   ├─ emit Swap(sender: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], amount0In: 0, amount1In: 100300902708124373119359 [1.003e23], amount0Out: 0, amount1Out: 100000000000000000000000 [1e23], to: flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1])
    │   │   └─ ← ()
    │   └─ ← ()
    ├─ [564] token1::balanceOf(flashSwap: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) [staticcall]
    │   └─ ← 999699097291875626880641 [9.996e23]
    ├─ [564] token1::balanceOf(pair: [0x33036a2Af35b56B6e6DFD986D533fC748BC1Af14]) [staticcall]
    │   └─ ← 500300902708124373119359 [5.003e23]
    └─ ← ()

Test result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.09ms
 
Ran 1 test suites: 4 tests passed, 0 failed, 0 skipped (4 total tests)

플래시 스왑 수수료가 다르다?

A를 빌리고 B로 상환하는 경우

 이 경우는 0.3009%가 아닌, 0.3%에 가까운 수치라고 합니다. 왜 더 저렴한가에 대해서는 자세한 내용을 찾아볼 수 없지만 B -> A 스왑이라고 봤을 때, 상수 k를 기준으로 상대적으로 가치가 떨어지는 B의 반환량이 많아짐으로 인해 오차가 조정되었다고 볼 수 있을까요? 제가 수학 관련 전공이 아니라 더 깊이 있는 분석은 어렵네요;

A를 빌리고 A로 상환하는 경우

 수수료는 0.3009%라고 합니다.

 

'Solidity > DeFi' 카테고리의 다른 글

[Uniswap] V2 Oracle 예제  (0) 2024.02.21
[Uniswap] V2 Router  (0) 2024.02.20
[Uniswap] V2 Core 보충 자료 - 백서 읽기  (0) 2024.02.05
[Uniswap] V2 Core - UniswapV2Pair  (0) 2024.01.31
[Uniswap] V2 Core - UniswapV2Factory  (1) 2024.01.31
최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday
글 보관함