일반적으로 Flash Loan이나 Flash Swap의 매커니즘 자체는 복잡할 것이 없지만, 실제 코드를 구현하는 것은 또 다른 느낌이기에 해당 둘의 인터페이스를 간단하게 정리해보고자 한다.
얼핏 보기에는
fee
를 붙여서 상환하면 될 것 같지만,
막상 구현하려니 fee
를 어떻게 계산해야 하나 조금 헷갈렸었다.
/**
* @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를 설정해 주어야 한다.
Test를 위해서는 그냥 0
만 넣어도 무방하다.
AAVE의 Flash 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에 대해서는 이 글에서 다루었다.
Token A
⎯ Token B
pair가 있을 때
30.08 bp
)30 bp
)하는 2 가지의 방법이 있다.
0.3%
(30 bp
) 의 fee를 지불해야 한다.
뭐의 0.3%냐? 하는 부분이 살짝 헷갈릴 수 있는데, Swap이라고 생각하면 조금 단순하다.
x * y = k
의 CPMM 공식에서,
우리는 y를 빌리고 x로 갚는다고 생각해보자.
(x + 갚을X) * (y - 빌릴y) = k
이 공식을 만족하면 된다.
x
는 원래 pool에 있던 x
의 reserve,
y
는 원래 pool에 있던 y
의 reserve,
빌릴 y
는 우리가 빌리고자 하는 y
의 양이다.
k
는 당연히 x
* y
이다.
모든 값은 고정되어 있고, 갚을 X
의 양만 계산해내면 된다.
(이 때 여기서 fee는 포함되어있지 않다)
근데 이걸 그냥 Swap이라고 생각하면 이렇게도 볼 수 있다.
x
를 주고 y
를 받는 Swap이라고 생각해보자.
(x + 낼x) * (y - 받을y) = k
여기서 내가 줄 x의 양의 0.3%
는 fee로 pool에 내야한다.
(x + 0.997*낼x) * (y - 받을y) = k
이걸 다시 Flash Swap의 관점에서 생각하면 이렇다.
(x + 0.997*갚을x) * (y - 빌릴y) = k
그냥 Swap인데, 내가 빌릴 돈을 먼저 받고, x를 갚는 식이다.
그럼 이제 이런 방식으로 구현된다.
y
를 y_out
만큼 빌린다.x
를 갚는다.getAmountsIn
함수를 이용하여, y_out
만큼 받으려면, 얼마의 x
를 내야하는지를 계산하면 fee
가 포함된 상환액을 계산 가능)y
를 y_out
만큼 받는 swap을 하려면, 얼마만큼의 x
를 내야 하는지의 계산하는 것과 동일하다.사실 이렇게까지 설명할 필요 조차 없는 간단한 개념이지만, 막상 Flash Swap이라는 거창한 단어를 붙이고 구현하려니 Fee가 어떻게 계산이 되는지 조금 헷갈려서 정리를 해보았다.
이 경우에는 0.3008 %
(30.09 bp
)를 fee로 지불해야 한다.
왜 A를 빌려서 B로 갚는 경우와 수수료가 다른지에 대해서는 이 글에서 자세히 설명해 두었다.
(x - x_out + 0.997*x_in) * y = k
하나만 뺐다가 넣으면 이 공식이 성립해야 한다.
즉, x_in
은 x_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
함수를 호출하면 된다.