token.safeTransfer(borrower, amount);
target.functionCall(data);
다음과 같이 토큰을 전송하고 callback 함수를 부른다.
뭐가 문제일까?
pool
컨트랙트에다가 대출금을 보낸다target
을 대출할 token contract로 설정해놓고,bytes memory data = abi.encodeWithSignature("approve(address,uint256)", address(this), amount);
얘를 보내게 하면?
flash loan 돈은 하나도 안빠져나갔으므로, 상환에 대한 require
문을 통과한 뒤 트랜잭션이 완료되고 나서,
transferFrom
으로 돈을 빼나가면 된다.
require(to != address(this))
무조건 Flash Loan이 일어나면 대출 컨트랙트에서 자금이 나갔다 들어오게끔 하면 된다.
AAVE의 Flash Loan와 Uniswap V2의 Flash Swap 모두 receiver
함수에 대한 interface를 지정해 두었다.
처음에는 그냥 편의를 위한 것이라고 생각하였으나,,,
이러한 공격에 대한 Prevention이다.
function depositETH() external payable {
balances[msg.sender] += msg.value;
}
function flashLoanETH(uint256 amount) external {
uint256 balanceBefore = address(this).balance;
require(balanceBefore >= amount, "Requested amount is greater than Vault balance");
IFlashLoanEtherReceiver(msg.sender).callBack{value: amount}();
require(address(this).balance >= balanceBefore, "Need to pay back the loan");
}
Flash Loan을 받자마자 callback
에 deposit
을 넣어주면 무한으로 balance
mapping을 펌핑시킬 수 있다.
(정당하게 withdraw
가 가능해짐)
deposit
함수는 mapping
을 쓰는데,
require(address(this).balance >= balanceBefore, "Need to pay back the loan");
flash loan에 대한 상환의 체크는 mapping
을 사용하지 않기 때문이다.
reentrancy gaurd처럼 lock variable을 걸어서,
flashLoan
함수 실행 중에는 deposit
함수 자체를 호출할 수 없도록 할 수 있을 것 같다.
balances
mapping 값이 flash loan 전후로 바뀌지 않았는지를 체크 (deposit이 불가하도록)할 수 있다.
지금 상황에서는 사실상 deposit 함수 호출을 불가하게끔 하는 것이 최선인 것 같다.