[SCH] AAVE Flash Loan & UniswapV2 Flash Swap

frenchkebab·2023년 5월 12일
0

sch

목록 보기
1/3

일반적으로 Flash Loan이나 Flash Swap의 매커니즘 자체는 복잡할 것이 없지만, 실제 코드를 구현하는 것은 또 다른 느낌이기에 해당 둘의 인터페이스를 간단하게 정리해보고자 한다.

얼핏 보기에는

  1. 돈달라고 호출
  2. callback 함수에서 fee를 붙여서 상환

하면 될 것 같지만,

막상 구현하려니 fee를 어떻게 계산해야 하나 조금 헷갈렸었다.

Reference

AAVE

UniswapV2 Flash Swap

AAVE Flash Swap

    /**
     * @dev allows smartcontracts to access the liquidity of the pool within one transaction,
     * as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
     * that must be kept into consideration. For further details please visit https://developers.aave.com
     * @param receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
     * @param assets the address of the principal reserve
     * @param amounts the amount requested for this flashloan
     * @param modes the flashloan borrow modes
     * @param params a bytes array to be sent to the flashloan executor
     * @param referralCode the referral code of the caller
     *
     */
    function flashLoan(
        address receiver,
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata modes,
        address onBehalfOf,
        bytes calldata params,
        uint16 referralCode
    ) external;

mode 설정

이자율에 대해 mode를 설정해 주어야 한다.

Test를 위해서는 그냥 0만 넣어도 무방하다.

fee

AAVEFlash Loan에서는 Receiver 함수를 호출해줄 때 이자를 같이 파라미터 값에 넣어서 알려준다.

interface IFlashLoanReceiver {
  /**
   * @notice Executes an operation after receiving the flash-borrowed assets
   * @dev Ensure that the contract can return the debt + premium, e.g., has
   *      enough funds to repay and has approved the Pool to pull the total amount
   * @param assets The addresses of the flash-borrowed assets
   * @param amounts The amounts of the flash-borrowed assets
   * @param premiums The fee of each flash-borrowed asset
   * @param initiator The address of the flashloan initiator
   * @param params The byte-encoded params passed when initiating the flashloan
   * @return True if the execution of the operation succeeds, false otherwise
   */
  function executeOperation(
    address[] calldata assets,
    uint256[] calldata amounts,
    uint256[] calldata premiums,
    address initiator,
    bytes calldata params
  ) external returns (bool);

  function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);

  function POOL() external view returns (IPool);
}

executeOperation라는 이름으로 콜백 함수를 정의해두면 premiums [] 배열에다가 넣어준다.

예시

    function getFlashLoan(address token, uint256 amount) external {
        address receiver = address(this);
        address[] memory assets = new address[](1);
        assets[0] = token;

        uint256[] memory amounts = new uint[](1);
        amounts[0] = amount;

        uint256[] memory modes = new uint[](1);
        modes[0] = 0;

        address onBehalfOf = address(this);

        bytes memory params = "";
        uint16 referralCode = 0;

        pool.flashLoan(receiver, assets, amounts, modes, onBehalfOf, params, referralCode);
    }

    function executeOperation(
        address[] memory assets,
        uint256[] memory amounts,
        uint256[] memory premiums,
        address initiator,
        bytes memory params
    ) public returns (bool) {
        for (uint256 i = 0; i < assets.length; i++) {
            uint256 amountOwing = amounts[i] + premiums[i];
            IERC20(assets[i]).approve(address(pool), amountOwing);
        }

        return true;
    }

이런 식으로 구현하면 된다.

Uniswap V2 Flash Swap

Uniswap V2Flash Swap에 대해서는 이 글에서 다루었다.

Token AToken B pair가 있을 때

  1. A를 빌려서 B로 상환 (30.08 bp)
    : 사실상 Swap
  2. A를 빌려서 A로 상환 (30 bp)

하는 2 가지의 방법이 있다.

A를 빌려서 B로 상환

0.3% (30 bp) 의 fee를 지불해야 한다.

뭐의 0.3%냐? 하는 부분이 살짝 헷갈릴 수 있는데, Swap이라고 생각하면 조금 단순하다.

x * y = k

CPMM 공식에서,
우리는 y를 빌리고 x로 갚는다고 생각해보자.

(x + 갚을X) * (y - 빌릴y) = k

이 공식을 만족하면 된다.

x는 원래 pool에 있던 xreserve,
y는 원래 pool에 있던 yreserve,
빌릴 y는 우리가 빌리고자 하는 y의 양이다.
k는 당연히 x * y이다.

모든 값은 고정되어 있고, 갚을 X의 양만 계산해내면 된다.
(이 때 여기서 fee는 포함되어있지 않다)

근데 이걸 그냥 Swap이라고 생각하면 이렇게도 볼 수 있다.

x를 주고 y를 받는 Swap이라고 생각해보자.

(x + 낼x) * (y - 받을y) = k

여기서 내가 줄 x의 양의 0.3%feepool에 내야한다.

(x + 0.997*낼x) * (y - 받을y) = k

이걸 다시 Flash Swap의 관점에서 생각하면 이렇다.

(x + 0.997*갚을x) * (y - 빌릴y) = k

그냥 Swap인데, 내가 빌릴 돈을 먼저 받고, x를 갚는 식이다.

Flash Swap의 단계

그럼 이제 이런 방식으로 구현된다.

  1. yy_out만큼 빌린다.
  2. x를 갚는다.
    (Uniswap V2 라이브러리getAmountsIn 함수를 이용하여, y_out만큼 받으려면, 얼마의 x를 내야하는지를 계산하면 fee가 포함된 상환액을 계산 가능)
    -> 이는 yy_out만큼 받는 swap을 하려면, 얼마만큼의 x를 내야 하는지의 계산하는 것과 동일하다.

사실 이렇게까지 설명할 필요 조차 없는 간단한 개념이지만, 막상 Flash Swap이라는 거창한 단어를 붙이고 구현하려니 Fee가 어떻게 계산이 되는지 조금 헷갈려서 정리를 해보았다.

A를 빌려서 A로 상환

이 경우에는 0.3008 % (30.09 bp)를 fee로 지불해야 한다.

왜 A를 빌려서 B로 갚는 경우와 수수료가 다른지에 대해서는 이 글에서 자세히 설명해 두었다.

fee의 계산

(x - x_out + 0.997*x_in) * y = k

하나만 뺐다가 넣으면 이 공식이 성립해야 한다.

즉, x_inx_out / 0.997과 같다.

이는 즉,

x_out * (1000 / 997)
x_out * (1 + 3/997)
x_in = x_out(원금) + x_out * 3/997(fee)

가 된다.

그냥 돈을 뺐다가 넣는 트랜잭션을 발생시키기 위해서는

x_out * 3/997 (0.003009027081)

만큼을 프로토콜에 지불해야 한다.

예시

https://solidity-by-example.org/defi/uniswap-v2-flash-swap/

이 링크에 자세히 나와있다.

uniswapV2Call 함수를 구현하고, swap함수를 호출하면 된다.

profile
Solidity에 대해 공부하고 있습니다.

0개의 댓글