ERC-3156 플래시 론 표준 (Flash Loans)

Final · 금고·DeFi · 2020-11-15 작성

담보 없이 토큰을 빌려 같은 거래 안에서 갚는 플래시 론의 함수와 실행 절차를 하나로 정합니다. 빌려주는 컨트랙트와 빌리는 컨트랙트가 서로 다른 구현이라도 같은 방식으로 맞물리게 합니다.

왜 필요한가

플래시 론은 담보 없이 토큰을 빌리되 같은 거래가 끝나기 전에 반드시 갚아야 하는 대출입니다. 이 표준이 나오기 전에는 이 기능을 먼저 만든 프로토콜마다 인터페이스와 사용 방식이 달랐습니다. 어떤 곳은 빌려준 쪽이 원금과 수수료를 가져가고 어떤 곳은 빌린 쪽이 직접 돌려줘야 합니다. 빌린 토큰과 다른 토큰으로 갚게 해 주는 곳도 있고 매매와 예치와 플래시 론을 하나의 진입점으로 묶은 곳과 따로 둔 곳도 있습니다. 자기 토큰을 수수료 없이 얼마든지 찍어 빌려주는 곳도 있습니다. 원문은 이런 차이가 앞으로 더 커질 것으로 보았고 여러 플래시 론을 연동하려는 개발자는 그만큼 기술 부채를 떠안게 됩니다. 이 표준은 빌려주는 쪽과 빌리는 쪽이 지켜야 할 함수와 절차를 하나로 정해 그 비용을 줄입니다.

심층 설명

두 역할과 콜백 구조

이 표준은 플래시 론을 두 컨트랙트가 콜백으로 맞물리는 과정으로 정의합니다. 빌려주는 쪽을 LENDER, 빌리는 쪽을 RECEIVER라고 부릅니다. LENDER는 IERC3156FlashLender 인터페이스를, RECEIVER는 IERC3156FlashBorrower 인터페이스를 반드시 구현해야 합니다. LENDER의 flashLoan이 토큰을 보낸 뒤 RECEIVER의 onFlashLoan을 호출하고 RECEIVER는 그 함수 안에서 빌린 토큰으로 할 일을 한 뒤 갚을 준비를 합니다. 거래가 끝나기 전에 원금과 수수료가 돌아오지 않으면 거래 전체가 되돌려집니다. flashLoan을 호출하는 주체와 토큰을 받는 receiver를 따로 둘 수 있도록 receiver를 인자로 받습니다. 여러 토큰을 한 컨트랙트에서 빌려주는 기존 관행에 맞춰 token 인자도 두 함수 모두에 들어갑니다.

maxFlashLoan과 flashFee의 규칙

maxFlashLoan(token)은 그 토큰으로 빌릴 수 있는 최대 금액을 반환해야 합니다. 지원하지 않는 토큰이면 되돌리지 말고 0을 반환해야 합니다. flashFee(token, amount)는 amount만큼 빌릴 때 원금에 더해 내야 할 수수료를 반환해야 합니다. 지원하지 않는 토큰이면 반드시 되돌려야 합니다. 두 함수가 다르게 동작하는 이유를 원문은 이렇게 설명합니다. 지원하지 않는 토큰에 수수료 숫자를 돌려주면 그 값 자체가 틀린 정보가 되므로 되돌리는 것이 맞다는 것입니다. 참고 구현에서는 fee 변수를 1이 0.01%가 되도록 두고 amount * fee / 10000으로 수수료를 계산합니다.

flashLoan이 지켜야 할 순서

flashLoan(receiver, token, amount, data)은 다음을 모두 지켜야 합니다. 첫째, 콜백 전에 amount만큼의 token을 receiver에 먼저 보냅니다. 둘째, receiver의 onFlashLoan을 호출하되 initiator 자리에 msg.sender를 넣습니다. 셋째, 받은 token, amount, data는 고치지 않고 그대로 넘깁니다. 넷째, fee 인자는 flashFee(token, amount)와 같은 값이어야 합니다. 다섯째, onFlashLoan의 반환값이 keccak256("ERC3156FlashBorrower.onFlashLoan")과 같은지 확인하고 다르면 되돌립니다. 여섯째, 콜백이 끝나면 receiver에게서 amount + fee만큼의 token을 가져오고 실패하면 되돌립니다. 모두 성공하면 true를 반환합니다.

onFlashLoan에서 RECEIVER가 할 일

RECEIVER는 onFlashLoan(initiator, token, amount, fee, data)를 구현합니다. 거래가 되돌려지지 않으려면 이 함수가 끝나기 전에 msg.sender인 LENDER가 amount + fee만큼의 token을 가져갈 수 있도록 승인해 두어야 합니다. 성공하면 keccak256("ERC3156FlashBorrower.onFlashLoan") 해시를 반환해야 합니다. 참고 구현 FlashBorrower는 msg.sender가 알고 있는 lender인지, initiator가 자기 자신인지 먼저 확인하고 data를 abi.decode로 풀어 어떤 작업을 할지 분기합니다. 대출을 시작하는 flashBorrow 함수는 flashFee로 수수료를 미리 구하고 기존 allowance에 amount + fee를 더해 approve한 뒤 flashLoan을 호출합니다.

왜 LENDER가 가져가는 방식인가

원금과 수수료를 RECEIVER가 LENDER에 보내는 방식(push) 대신 LENDER가 transferFrom으로 가져오는 방식(pull)을 택했습니다. LENDER에 transferFrom을 쓰는 다른 기능이 함께 있을 때 플래시 론 동안 그 기능을 잠그지 않아도 되기 때문입니다. push 방식도 가능하지만 LENDER의 다른 기능까지 모두 transfer 기반으로 바꿔야 합니다. 원문은 pull 구조가 더 단순하고 널리 쓰인다는 이유로 이를 골랐습니다. 같은 맥락에서 initiator, amount, fee를 data 안에 넣지 않고 별도 인자로 넘깁니다. 호출자가 data에 넣어 보내면 RECEIVER가 그 값이 맞는지 따로 검증할 장치가 필요해지므로 권장하지 않습니다. 함수 이름 flashLoan과 onFlashLoan은 다른 함수와 겹치기 어렵고 뜻이 분명해서 골랐습니다.

참고 구현: 플래시 민트와 플래시 론

원문은 두 가지 LENDER 참고 구현을 제공합니다. FlashMinter는 ERC20을 확장해 자기 토큰을 빌려줍니다. maxFlashLoan은 type(uint256).max - totalSupply()를 반환합니다. flashLoan은 receiver에 amount를 _mint한 뒤 콜백을 호출하고 allowance가 amount + fee 이상인지 확인한 뒤 그만큼 _burn합니다. token이 자기 주소가 아니면 되돌립니다. FlashLender는 supportedTokens 목록에 있는 ERC20 토큰을 보유분에서 빌려줍니다. transfer로 보내고 콜백 뒤 transferFrom으로 amount + fee를 회수합니다. maxFlashLoan은 지원 토큰이면 자기 잔액을, 아니면 0을 반환합니다. 두 구현 모두 CALLBACK_SUCCESS 상수와 콜백 반환값을 비교해 다르면 되돌립니다. 이 표준은 Final 상태로 확정되었고 기존 호환성 문제는 확인되지 않았습니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs