ERC-7399 플래시론 표준 인터페이스 (⚡ Flash Loans ⚡)
Review · 금고·DeFi · 2023-07-25 작성
한 트랜잭션 안에서 빌리고 바로 갚는 플래시론의 요청, 콜백, 상환 절차를 하나의 인터페이스로 통일합니다. 차입자는 코드 수정 없이 여러 대출자를 바꿔 쓸 수 있습니다.
왜 필요한가
플래시론(flash loan)은 담보 없이 큰 금액을 빌린 뒤 같은 트랜잭션 안에서 수수료와 함께 갚아야 하는 대출입니다. 지금은 이 기능을 제공하는 곳마다 방식이 제각각입니다. 대출을 시작하는 함수의 문법이 다르고 돈을 받는 주소와 되호출(콜백)을 받는 주소를 나눌 수 있는 곳과 없는 곳이 있으며 갚는 방식도 대출자가 차입자에게서 끌어가는 곳과 차입자가 직접 보내야 하는 곳으로 갈립니다. 대출자가 자기 토큰을 즉석에서 찍어 빌려주는 플래시 민팅(flash minting)의 취급도 다릅니다. 그래서 차입자는 대출자마다 연동 코드를 따로 만들어야 하고 생태계가 커질수록 호환성을 유지하는 비용이 늘어납니다. 이 표준은 대출자와 차입자 양쪽에 필요한 유연성을 하나의 인터페이스로 묶어 차입자가 코드 수정 없이 대출자를 자유롭게 바꿔 쓸 수 있게 합니다.
구현해야 하는 것
function(address, address, IERC20, uint256, uint256, bytes memory) external returns (bytes memory) callback;: 콜백 수신자가 구현해야 하는 함수 형태입니다. 개시자, 상환 수신자, 자산, 금액, 수수료, 데이터를 받고 임의의 데이터를 반환합니다. 이름은 자유이며 오버로드할 수 있습니다.
심층 설명
네 가지 역할의 분리
이 표준에는 역할이 넷 나옵니다. 개시자(initiator)는 대출자(lender)의 flash를 호출하는 주체로, 자산과 금액, 대출 수신자, 콜백 수신자와 콜백 함수를 정합니다. 대출 수신자(loan receiver)는 빌린 자산을 받는 주소이고 콜백 수신자(callback receiver)는 대출자가 자산을 보낸 뒤 호출하는 함수가 있는 주소입니다. 상환 수신자(payment receiver)는 대출자가 정하며 원금과 수수료를 받을 주소입니다. 이렇게 자산이 오가는 주소와 코드를 실행하는 주소를 분리해 제어 흐름과 자산 흐름을 독립적으로 설계할 수 있습니다. 대출 수신자 주소는 콜백 인자로 넘기지 않는데, 대개 콜백 수신자와 같고 다를 때는 개시자가 data에 넣어 전달할 수 있기 때문입니다.
조회 함수는 리버트하지 않습니다
maxFlashLoan은 해당 자산으로 빌릴 수 있는 최대 금액을 반환해야 하며 리버트해서는 안 됩니다. 그 자산의 대출이 불가능하면 0을 반환해야 합니다. flashFee는 amount만큼 빌릴 때의 수수료를 반환해야 하며 역시 리버트해서는 안 되고 불가능한 대출이면 type(uint256).max를 반환해야 합니다. 불가능한 경우에도 리버트 대신 숫자를 돌려주는 이유는, 차입자가 리버트 처리 없이 여러 대출자를 조회해 조건을 비교하고 정렬할 수 있게 하기 위해서입니다. 수수료는 0으로 정할 수 있고 어떤 자산을 지원할지는 대출자가 스스로 정하며 모든 자산을 지원할 수도 있습니다.
flash 함수가 지켜야 하는 규칙
flash는 콜백을 실행하기 전에 amount만큼의 asset을 대출 수신자에게 전송해야 합니다. 콜백에는 msg.sender를 initiator로 넣어야 하고 전달받은 asset, amount, data를 고치지 않고 그대로 넘겨야 합니다. 콜백의 fee 인자는 flashFee(asset, amount)와 같아야 합니다. 콜백이 끝나기 전에 상환 수신자의 asset 잔고가 콜백 시작 시점보다 amount + fee만큼 늘어나야 하며 그렇지 않으면 리버트해야 합니다. flash의 반환값은 콜백의 반환값과 같아야 합니다. initiator와 amount를 data에 섞지 않고 별도 인자로 넘기는 이유는, data에 넣으면 수신자가 그 값이 진짜인지 검증할 별도 장치가 필요해지기 때문입니다.
콜백은 주소 20바이트와 시그니처 4바이트로 지정합니다
flash의 callback 인자는 콜백 수신자 주소 20바이트와 콜백 함수 시그니처 4바이트를 붙인 24바이트로 인코딩됩니다. 콜백 함수는 이름을 자유롭게 정할 수 있고 오버로드도 가능하지만 인자 구성은 (initiator, paymentReceiver, asset, amount, fee, data)이고 반환형은 bytes memory여야 합니다. 호출할 함수 자체를 지정할 수 있으므로, data 안에 어떤 동작을 할지 라우팅 정보를 넣지 않고도 콜백 수신자가 용도별로 다른 함수를 둘 수 있습니다. 콜백의 반환값은 flash를 거쳐 개시자에게 그대로 전달되므로, 수신자가 개시자에게 임의의 정보를 돌려줄 수도 있습니다.
상환은 푸시 방식입니다
상환은 차입자 쪽이 원금과 수수료를 상환 수신자에게 보내는 푸시(push) 방식입니다. 대출자가 차입자에게서 돈을 끌어가는 풀(pull) 방식이 더 흔하지만 푸시 방식은 자산 흐름과 제어 흐름을 분리할 수 있습니다. 풀 방식만 구현할 수 있는 대출자는 이 표준의 외부 인터페이스를 제공하면서 내부적으로는 풀 방식으로 유동성을 가져오는 간단한 래퍼 컨트랙트를 둘 수 있습니다. 함수 이름을 flash로 정한 것은 동사로서 의미가 분명하고 다른 함수와 충돌할 가능성이 낮으며 대출자가 보유한 자산을 빌려주는 경우와 즉석에서 발행하는 플래시 민팅의 경우를 모두 포괄하기 때문입니다.
ERC-3156의 후속이며 아직 검토 단계입니다
이 표준은 기존 플래시론 표준인 ERC-3156의 후속입니다. 직접적인 하위 호환은 없지만 ERC-3156 대출자에게서 유동성을 가져와 이 표준의 외부 인터페이스를 제공하는 래퍼 컨트랙트는 만들기 쉽다고 원문은 설명합니다. 따라서 기존 ERC-3156 기반 대출자도 큰 비용 없이 이 표준을 따르는 차입자를 받을 수 있습니다. 원문 상태는 Review로, 아직 검토 중이어서 내용이 바뀔 수 있는 제안입니다. 실제 구현 시점에는 최신 원문을 확인하는 것이 안전합니다.
쓸 때 주의할 점
- 콜백 인자를 그대로 믿지 않기: 콜백 인자는 무조건 믿을 수 없습니다. initiator, asset, amount는 콜백 호출자가 거짓이면 실제로 일어나지 않은 거래를 가리킬 수 있고 fee는 틀리거나 잘못 계산됐을 수 있으며 data는 조작됐을 수 있습니다. initiator, asset, amount, fee를 믿으려면 콜백 호출자가 검증된 대출자 화이트리스트에 있는지 확인하고 data까지 믿으려면 추가로 initiator가 신뢰하는 주소 그룹에 속하는지 확인해야 합니다.
- 자동 상환의 위험: 인자로 받은 amount와 fee를 그대로 갚는 수신자는 콜백 안에 initiator와 대출자가 신뢰할 수 있는지 검증하는 장치를 넣어야 합니다. 대안으로, 플래시론이 시작됐고 어떤 amount와 fee를 기대하는지 상태 변수로 기록하는 권한 있는 함수를 두거나, loanReceiver가 amount를 실제로 받았는지 확인하고 자체 기준으로 수수료가 적절한지 판단한 뒤 아니면 리버트할 수 있습니다.
- 공급량 기반 오라클 조작: 플래시 민팅이 가능한 자산은 공급량을 쉽게 조작할 수 있습니다. 그 자산의 공급량을 참고하는 오라클은 플래시 민팅된 수량을 제외하거나, 시간 평균을 낸 데이터를 만들거나, 변동하는 공급량에 대한 다른 대책을 찾아야 합니다.
- 산술 오버플로와 언더플로: 대출자가 한 트랜잭션에서 플래시 민팅할 수 있는 양에 한도를 두지 않으면 누구나 2^256-1만큼의 자산을 찍어낼 수 있습니다. 이런 금액을 받게 되는 쪽의 프로토콜은 오버플로 보호를 바이트코드에 심어 주는 컴파일러를 쓰거나 명시적인 검사를 두어 컨트랙트가 이를 감당할 수 있게 해야 합니다.
- 금고 고갈 공격: 플래시 민팅을 같은 플랫폼의 사업 기능과 결합하면 의도치 않은 결과가 생기기 쉽습니다. 원문 예시에서는 자기 자산을 플래시 민팅해 주는 컨트랙트가 사용자의 소각 요청 시 제3자에게서 차입하는 구조일 때, 공격자가 대량으로 플래시 민팅한 뒤 상환을 일으켜 그 컨트랙트를 차입 한도까지 몰아넣고 제3자의 금리를 올려 담보 부족 상태로 만든 다음 청산해 이익을 얻을 수 있습니다.