def test_get_or_create(ArgobytesFactory): contract_a = get_or_create(accounts[0], ArgobytesFactory) contract_b = get_or_create(accounts[0], ArgobytesFactory) assert contract_a == contract_b
def argobytes_multicall(ArgobytesMulticall): return get_or_create(accounts[0], ArgobytesMulticall)
def argobytes_authority(ArgobytesAuthority): return get_or_create(accounts[0], ArgobytesAuthority)
def onesplit_offchain_action(OneSplitOffchainAction): return get_or_create(accounts[0], OneSplitOffchainAction)
def weth9_action(Weth9Action): return get_or_create(accounts[0], Weth9Action)
def test_get_or_create(ArgobytesFactory19): contract_a = get_or_create(accounts[0], ArgobytesFactory19, salt="123") contract_b = get_or_create(accounts[0], ArgobytesFactory19, salt="123") assert contract_a.address == contract_b.address
def atomic_enter(): """Use a flash loan to deposit into leveraged cyy3crv position.""" # TODO: we need an account with private keys account = accounts.at(os.environ["LEVERAGE_ACCOUNT"]) print(f"Hello, {account}") min_3crv_to_claim = os.environ.get("MIN_3CRV_TO_CLAIM", 50) # deploy our contracts if necessary argobytes_factory = get_or_create(account, ArgobytesFactory) argobytes_flash_borrower = get_or_create(account, ArgobytesFlashBorrower) enter_cyy3crv_action = get_or_create(account, EnterCYY3CRVAction) # get the clone for the account argobytes_clone = get_or_clone(account, argobytes_factory, argobytes_flash_borrower) print(f"clone: {argobytes_clone}") assert account == argobytes_clone.owner(), "Wrong owner detected!" print("Preparing contracts...") # TODO: use multicall to get all the addresses? dai = lazy_contract(enter_cyy3crv_action.DAI()) usdc = lazy_contract(enter_cyy3crv_action.USDC()) usdt = lazy_contract(enter_cyy3crv_action.USDT()) threecrv = lazy_contract(enter_cyy3crv_action.THREE_CRV()) threecrv_pool = lazy_contract(enter_cyy3crv_action.THREE_CRV_POOL()) y3crv = lazy_contract(enter_cyy3crv_action.Y_THREE_CRV()) cyy3crv = lazy_contract(enter_cyy3crv_action.CY_Y_THREE_CRV()) fee_distribution = lazy_contract(enter_cyy3crv_action.THREE_CRV_FEE_DISTRIBUTION()) lender = DyDxFlashLender # use multiple workers to fetch the contracts # there will still be some to fetch, but this speeds things up some # this can take some time since solc/vyper may have to download # TODO: i think doing this in parallel might be confusiing things # poke_contracts([dai, usdc, usdt, threecrv, threecrv_pool, y3crv, cyy3crv, lender]) tokens = [dai, usdc, usdt, threecrv, y3crv, cyy3crv] balances = get_balances(account, tokens) print(f"{account} balances") print_token_balances(balances) claimable_3crv = get_claimable_3crv(account, fee_distribution, min_3crv_to_claim) # TODO: calculate/prompt for these min_3crv_mint_amount = 1 tip_3crv = 0 tip_address = _resolve_address( "tip.satoshiandkin.eth" ) # TODO: put this on a subdomain and uses an immutable min_cream_liquidity = 1000 enter_data = EnterData( dai=balances[dai], dai_flash_fee=None, usdc=balances[usdc], usdt=balances[usdt], min_3crv_mint_amount=min_3crv_mint_amount, threecrv=balances[threecrv], tip_3crv=tip_3crv, y3crv=balances[y3crv], min_cream_liquidity=min_cream_liquidity, sender=account, tip_address=tip_address, claim_3crv=claimable_3crv > min_3crv_to_claim, ) # TODO: do this properly. use virtualprice and yearn's price calculation print("warning! summed_balances is not actually priced in USD") summed_balances = ( enter_data.dai + enter_data.usdc + enter_data.usdt + enter_data.threecrv + enter_data.y3crv + claimable_3crv ) assert summed_balances > 100, "no coins" # TODO: figure out the actual max leverage, then prompt the user for it (though i dont see much reason not to go the full amount here) flash_loan_amount = int(summed_balances * 7.4) print(f"flash_loan_amount: {flash_loan_amount}") assert flash_loan_amount > 0, "no flash loan calculated" enter_data = enter_data._replace( dai_flash_fee=lender.flashFee(dai, flash_loan_amount) ) pprint(enter_data) extra_balances = {} if enter_data.claim_3crv: extra_balances[threecrv.address] = claimable_3crv approve(account, balances, extra_balances, argobytes_clone) # flashloan through the clone enter_tx = argobytes_clone.flashBorrow( lender, dai, flash_loan_amount, Action( enter_cyy3crv_action, CallType.DELEGATE, False, "enter", enter_data, ).tuple, ) print(f"enter success! {enter_tx.return_value}") enter_tx.info() num_events = len(enter_tx.events) print(f"num events: {num_events}") enter_return = to_int(enter_tx.return_value) print(f"return value: {enter_return}") assert enter_return > 0, "no cyy3ccrv returned!" print(f"clone ({argobytes_clone.address}) balances") balances = get_balances(argobytes_clone, tokens) print_token_balances(balances) # TODO: why is this not working? we return cyy3crv.balanceOf! # assert balances[cyy3crv] == enter_tx.return_value # TODO: make sure the clone has cyy3crv? print(f"account ({account}) balances") print_token_balances(get_balances(account, tokens))
def example_action_2(ExampleAction): return get_or_create(accounts[0], ExampleAction)
def enter_cyy3crv_action(EnterCYY3CRVAction): return get_or_create(accounts[0], EnterCYY3CRVAction)
def argobytes_trader(ArgobytesTrader): return get_or_create(accounts[0], ArgobytesTrader)
def curve_fi_action(CurveFiAction): return get_or_create(accounts[0], CurveFiAction)
def atomic_enter(account, min_3crv_to_claim): """Use a flash loan to deposit into leveraged cyy3crv position.""" logger.info(f"Hello, {account}") # deploy our contracts if necessary enter_cyy3crv_action = get_or_create(account, ArgobytesBrownieProject.EnterCYY3CRVAction) (argobytes_factory, argobytes_flash_borrower, argobytes_clone) = get_or_clone_flash_borrower(account) logger.info("clone: %s", argobytes_clone) assert account == argobytes_clone.owner(), "Wrong owner detected!" print("Preparing contracts...") # TODO: use multicall to get all the addresses? dai = lazy_contract(enter_cyy3crv_action.DAI()) usdc = lazy_contract(enter_cyy3crv_action.USDC()) usdt = lazy_contract(enter_cyy3crv_action.USDT()) threecrv = lazy_contract(enter_cyy3crv_action.THREE_CRV()) threecrv_pool = lazy_contract(enter_cyy3crv_action.THREE_CRV_POOL()) y3crv = lazy_contract(enter_cyy3crv_action.Y_THREE_CRV()) cyy3crv = lazy_contract(enter_cyy3crv_action.CY_Y_THREE_CRV()) fee_distribution = lazy_contract(enter_cyy3crv_action.THREE_CRV_FEE_DISTRIBUTION()) lender = DyDxFlashLender assert threecrv_pool.coins(0) == dai.address assert threecrv_pool.coins(1) == usdc.address assert threecrv_pool.coins(2) == usdt.address # TODO: assert threecrv_pool.coins(3) == revert tokens = [dai, usdc, usdt, threecrv, y3crv, cyy3crv] # use multiple workers to fetch the contracts # there will still be some to fetch, but this speeds things up some # this can take some time since solc/vyper may have to download # TODO: i think doing this in parallel might be confusiing things poke_contracts(tokens + [threecrv_pool, lender]) balances = get_balances(account, tokens) print(f"{account} balances") print_token_balances(balances) claimable_3crv = get_claimable_3crv(account, fee_distribution, min_3crv_to_claim) # TODO: calculate/prompt for these min_3crv_mint_amount = 1 tip_3crv = 0 min_cream_liquidity = 1000 enter_data = EnterData( dai=balances[dai], dai_flash_fee=None, usdc=balances[usdc], usdt=balances[usdt], min_3crv_mint_amount=min_3crv_mint_amount, threecrv=balances[threecrv], tip_3crv=tip_3crv, y3crv=balances[y3crv], min_cream_liquidity=min_cream_liquidity, sender=account, claim_3crv=claimable_3crv > min_3crv_to_claim, ) # TODO: do this properly. use virtualprice and yearn's price calculation print("warning! summed_balances is not actually priced in USD") summed_balances = Decimal( enter_data.dai + enter_data.usdc + enter_data.usdt + enter_data.threecrv + enter_data.y3crv + claimable_3crv ) print(f"summed_balances: {summed_balances}") assert summed_balances > 100, "no coins" # TODO: figure out the actual max leverage, then prompt the user for it (though i dont see much reason not to go the full amount here) # TODO: if they have already done a flash loan once, we might be able to do a larger amount flash_loan_amount = int(summed_balances * Decimal(7.4)) print(f"flash_loan_amount: {flash_loan_amount}") assert flash_loan_amount > 0, "no flash loan calculated" enter_data = enter_data._replace(dai_flash_fee=lender.flashFee(dai, flash_loan_amount)) extra_balances = {} if enter_data.claim_3crv: extra_balances[threecrv.address] = claimable_3crv safe_token_approve(account, balances, argobytes_clone, extra_balances) # flashloan through the clone pprint(enter_data) enter_tx = argobytes_clone.flashBorrow( lender, dai, flash_loan_amount, ArgobytesAction( enter_cyy3crv_action, ArgobytesActionCallType.DELEGATE, "enter", enter_data, ).tuple, ) print("enter success!") """ # TODO: this crashes ganache print(f"enter success! {enter_tx.return_value}") # TODO: this crashes ganache enter_tx.info() """ # num_events = len(enter_tx.events) # print(f"num events: {num_events}") """ # TODO: this crashes ganache enter_return = to_int(enter_tx.return_value) print(f"return value: {enter_return}") assert enter_return > 0, "no cyy3ccrv returned!" """ # TODO: what should we set this to? # TODO: this should be in the tests, not here print(f"enter_tx.gas_used: {enter_tx.gas_used}") assert enter_tx.gas_used < 1200000 print(f"clone ({argobytes_clone.address}) balances") balances = get_balances(argobytes_clone, tokens) print_token_balances(balances) print(f"account ({account}) balances") print_token_balances(get_balances(account, tokens))
def argobytes_proxy(ArgobytesProxy): # on mainnet we use the (bytes32) salt to generate custom addresses, but we dont need that in our tests= return get_or_create(accounts[0], ArgobytesProxy)
def argobytes_factory(ArgobytesFactory): return get_or_create(accounts[0], ArgobytesFactory)
def simple_exit(account): """Make a bunch of transactions to withdraw from a leveraged cyy3crv position.""" print(f"Hello, {account}") # TODO: flag for slippage amount. default 0.5% # TODO: use salts for the contracts once we figure out a way to store them. maybe 3box? # TODO: we only use this for the constants. don't waste gas deploying this on mainnet if it isn't needed exit_cyy3crv_action = get_or_create( account, ArgobytesBrownieProject.ExitCYY3CRVAction) print("Preparing contracts...") # TODO: use multicall to get all the addresses? # TODO: i want to use IERC20, but it lacks getters for the state variables dai = load_contract(exit_cyy3crv_action.DAI(), owner=account) usdc = load_contract(exit_cyy3crv_action.USDC(), owner=account) usdt = load_contract(exit_cyy3crv_action.USDT(), owner=account) threecrv = load_contract(exit_cyy3crv_action.THREE_CRV(), owner=account) threecrv_pool = load_contract(exit_cyy3crv_action.THREE_CRV_POOL(), owner=account, force=True) y3crv = load_contract(exit_cyy3crv_action.Y_THREE_CRV(), owner=account, force=True) cyy3crv = load_contract(exit_cyy3crv_action.CY_Y_THREE_CRV(), owner=account, force=True) cydai = load_contract(exit_cyy3crv_action.CY_DAI(), owner=account, force=True) cream = load_contract(exit_cyy3crv_action.CREAM(), owner=account, force=True) tokens = [dai, usdc, usdt, threecrv, y3crv, cyy3crv, cydai] poke_contracts(tokens + [threecrv_pool, cream]) # TODO: compare all these contracts with our own implementations start_balances = get_balances(account, tokens) print_token_balances(start_balances, f"{account} start balances") # approve 100%. if we approve borrowBalance, then we leave some dust behind since the approve transaction adds a block of interest # also, since this is a simple exit, we will probably have to run this again safe_token_approve(account, {dai: start_balances[dai]}, cydai) borrow_balance = cydai.borrowBalanceCurrent.call(account) print(f"cyDAI borrow_balance: {borrow_balance / 1e18}") # TODO: add a few blocks worth of interest just in case? # repay as much DAI as we can if start_balances[dai] == 0: # we do not have any DAI to repay. hopefully there is some headroom, or might take a lot of loops # TODO: click.confirm this? pass elif start_balances[dai] > borrow_balance: # we have more DAI than we need. repay the full balance # TODO: why do we need "from" here? repay_borrow_tx = cydai.repayBorrow(borrow_balance, {"from": account}) repay_borrow_tx.info() else: # we do not have enough DAI. repay what we can # TODO: skip this if its a small amount? # TODO: why do we need "from" here? repay_borrow_tx = cydai.repayBorrow(start_balances[dai], {"from": account}) repay_borrow_tx.info() # we need more DAI! # calculate how much cyy3crv we can safely withdraw (error, liquidity, shortfall) = cream.getHypotheticalAccountLiquidity( account, cydai, 0, borrow_balance) assert error == 0 assert shortfall == 0 print(f"liquidity: {liquidity}") # TODO: convert liquidity into cyy3crv. then leave some headroom # TODO: get 0.9 out of state y3crv_decimals = get_token_decimals(y3crv) cyy3crv_decimals = get_token_decimals(cyy3crv) # TODO: i think we should be able to use cream's price oracle for this # TODO: how do we get the 90% out of the contract? # TODO: does leaving headroom make sense? how much? add it in only if this isn't the last repayment? available_cyy3crv_in_usd = liquidity / Decimal(0.90) print(f"available_cyy3crv_in_usd: {available_cyy3crv_in_usd}") available_cyy3crv_in_3crv = available_cyy3crv_in_usd / ( Decimal(threecrv_pool.get_virtual_price()) / Decimal("1e18")) print(f"available_cyy3crv_in_3crv: {available_cyy3crv_in_3crv}") available_cyy3crv_in_y3crv = available_cyy3crv_in_3crv / ( Decimal(y3crv.getPricePerFullShare.call()) / Decimal("1e18")) print(f"available_cyy3crv_in_y3crv: {available_cyy3crv_in_y3crv}") one_cyy3crv_in_y3crv = Decimal( cyy3crv.exchangeRateCurrent.call()) / Decimal( 10**(18 + y3crv_decimals - cyy3crv_decimals)) print(f"one_cyy3crv_in_y3crv: {one_cyy3crv_in_y3crv}") # TODO: this calculation is wrong available_cyy3crv = available_cyy3crv_in_y3crv // one_cyy3crv_in_y3crv print(f"available_cyy3crv: {available_cyy3crv}") # TODO: why do we need "from" here? redeem_tx = cyy3crv.redeem(available_cyy3crv, {"from": account}) redeem_tx.info() # redeem returns 0 on success assert redeem_tx.return_value == 0 y3crv_balance = y3crv.balanceOf(account) y3crv.withdraw(y3crv_balance, {"from": account}) threecrv.balanceOf(account) end_balances = get_balances(account, tokens) print_token_balances(end_balances, f"{account} end balances") assert end_balances[threecrv] > start_balances[threecrv]
def exit_cyy3crv_action(ExitCYY3CRVAction): return get_or_create(accounts[0], ExitCYY3CRVAction)
def simple_enter(): """Make a bunch of transactions to deposit into leveraged cyy3crv position.""" # TODO: we need an account with private keys account = accounts.at(os.environ["LEVERAGE_ACCOUNT"]) print(f"Hello, {account}") min_3crv_to_claim = os.environ.get("MIN_3CRV_TO_CLAIM", 50) # deploy our contracts if necessary enter_cyy3crv_action = get_or_create(account, EnterCYY3CRVAction) print("Preparing contracts...") # TODO: use multicall to get all the addresses? dai = lazy_contract(enter_cyy3crv_action.DAI()) usdc = lazy_contract(enter_cyy3crv_action.USDC()) usdt = lazy_contract(enter_cyy3crv_action.USDT()) threecrv = lazy_contract(enter_cyy3crv_action.THREE_CRV()) threecrv_pool = lazy_contract(enter_cyy3crv_action.THREE_CRV_POOL()) y3crv = lazy_contract(enter_cyy3crv_action.Y_THREE_CRV()) cyy3crv = lazy_contract(enter_cyy3crv_action.CY_Y_THREE_CRV()) cydai = lazy_contract(enter_cyy3crv_action.CY_DAI()) fee_distribution = lazy_contract( enter_cyy3crv_action.THREE_CRV_FEE_DISTRIBUTION()) cream = lazy_contract(enter_cyy3crv_action.CREAM()) tokens = [dai, usdc, usdt, threecrv, y3crv, cyy3crv] balances = get_balances(account, tokens) print_token_balances(balances, f"{account} balances") claimable_3crv = get_claimable_3crv(account, fee_distribution, min_3crv_to_claim) # TODO: calculate/prompt for these min_3crv_mint_amount = 1 tip_3crv = 0 tip_address = _resolve_address( "tip.satoshiandkin.eth" ) # TODO: put this on a subdomain and uses an immutable min_cream_liquidity = 1000 # TODO: do this properly. use virtualprice and yearn's price calculation # print("warning! summed_balances is not actually priced in USD") # summed_balances = enter_data.dai + enter_data.usdc + enter_data.usdt + enter_data.threecrv + enter_data.y3crv + claimable_3crv # assert summed_balances > 100, "no coins" balances_for_3crv_pool = { dai: balances[dai], usdc: balances[usdc], usdt: balances[usdt], } approve(account, balances_for_3crv_pool, {}, threecrv_pool) threecrv_add_liquidity_tx = threecrv_pool.add_liquidity( [ balances_for_3crv_pool[dai], balances_for_3crv_pool[usdc], balances_for_3crv_pool[usdt], ], min_3crv_mint_amount, {"from": account}, ) threecrv_add_liquidity_tx.info() if claimable_3crv >= min_3crv_to_claim: claim_tx = fee_distribution.claim({"from": account}) claim_tx.info() # TODO: tip the developer in 3crv or ETH # deposit 3crv for y3crv balances_for_y3crv = get_balances(account, [threecrv]) print_token_balances(balances, f"{account} balances_for_y3crv:") approve(account, balances_for_y3crv, {}, y3crv) y3crv_deposit_tx = y3crv.deposit(balances_for_y3crv[threecrv], {"from": account}) y3crv_deposit_tx.info() # setup cream markets = [] if not cream.checkMembership(account, cyy3crv): markets.append(cyy3crv) # TODO: do we need this? is this just for borrows? # if not cream.checkMembership(account, cydai): # markets.append(cydai) if markets: enter_markets_tx = cream.enterMarkets(markets, {"from": account}) enter_markets_tx.info() else: print("CREAM markets already entered") # deposit y3crv for cyy3crv balances_for_cyy3crv = get_balances(account, [y3crv]) print_token_balances(balances_for_cyy3crv, f"{account} balances for cyy3crv:") approve(account, balances_for_cyy3crv, {}, cyy3crv) mint_tx = cyy3crv.mint(balances_for_cyy3crv[y3crv], {"from": account}) mint_tx.info() borrow_amount = int(balances_for_y3crv[threecrv] * 0.8) print(f"borrow_amount: {borrow_amount}") assert borrow_amount > 0 # TODO: this could be better, figute out how to properly calculate the maximum safe borrow balances_before_borrow = get_balances(account, [cyy3crv, y3crv]) print_token_balances(balances_before_borrow, f"{account} balances before borrow:") # TODO: we could use `borrow_amount` here ( cream_error, cream_liquidity, cream_shortfall, ) = cream.getHypotheticalAccountLiquidity(account, cydai, 0, 0) print(f"cream_error: {cream_error}") print(f"cream_liquidity: {cream_liquidity}") print(f"cream_shortfall: {cream_shortfall}") assert cream_error == 0, "cream error" assert cream_error == 0, "cream shortfall" assert cream_liquidity > borrow_amount, "no cream liquidity available for borrowing" borrow_tx = cydai.borrow(borrow_amount, {"from": account}) print(f"enter simple success! {borrow_tx.return_value}") borrow_tx.info() num_events = len(borrow_tx.events) print(f"num events: {num_events}") balances = get_balances(account, tokens) print_token_balances(balances, f"{account} balances") # borrow returns non-zero on error assert borrow_tx.return_value == 0, "error borrowing DAI!" # TODO: where should these balances be? assert balances[cyy3crv] > 0 assert balances[dai] == borrow_amount
def kyber_action(KyberAction): return get_or_create(accounts[0], KyberAction, constructor_args=[accounts[0]])
def main(): # os.makedirs(DEPLOY_DIR, exist_ok=True) print("account 0:", accounts[0]) # todo: automatic price to match mainnet default speed gas_price("20 gwei") argobytes_tip_address = web3.ens.resolve("tip.satoshiandkin.eth") print(f"argobytes_tip_address: {argobytes_tip_address}") argobytes_flash_borrower_arbitragers = [ accounts[0], accounts[1], accounts[2], accounts[3], accounts[4], ] starting_balance = accounts[0].balance() # TODO: docs for using ERADICATE2 # TODO: openzepplin helper uses bytes32, but gastoken uses uint256. salt = "" # deploy a dsproxy just to compare gas costs # TODO: whats the deploy cost of DSProxyFactory? ds_proxy_factory = ArgobytesInterfaces.DSProxyFactory(DSProxyFactoryAddress, accounts[5]) ds_proxy_factory.build() # deploy ArgobytesFactory # deploy ArgobytesFlashBorrower # clone ArgobytesFlashBorrower for accounts[0] (argobytes_factory, argobytes_flash_borrower, argobytes_clone) = get_or_clone_flash_borrower( accounts[0], ) # deploy ArgobytesAuthority argobytes_authority = get_or_create(accounts[0], ArgobytesBrownieProject.ArgobytesAuthority, salt=salt) # quick_save_contract(argobytes_authority) # TODO: setup auth for the proxy # for now, owner-only access works, but we need to allow a bot in to call atomicArbitrage # deploy the main contracts get_or_create(accounts[0], ArgobytesBrownieProject.ArgobytesMulticall) # deploy base actions argobytes_trader = get_or_create(accounts[0], ArgobytesBrownieProject.ArgobytesTrader) # deploy all the exchange actions get_or_create(accounts[0], ArgobytesBrownieProject.ExampleAction) # get_or_create(accounts[0], ArgobytesBrownieProject.OneSplitOffchainAction) kyber_action = get_or_create( accounts[0], ArgobytesBrownieProject.KyberAction, constructor_args=[argobytes_tip_address] ) get_or_create(accounts[0], ArgobytesBrownieProject.UniswapV1Action) get_or_create(accounts[0], ArgobytesBrownieProject.UniswapV2Action) # get_or_create(accounts[0], ArgobytesBrownieProject.ZrxV3Action) get_or_create(accounts[0], ArgobytesBrownieProject.Weth9Action) get_or_create(accounts[0], ArgobytesBrownieProject.CurveFiAction) # deploy leverage cyy3crv actions get_or_create(accounts[0], ArgobytesBrownieProject.EnterCYY3CRVAction) get_or_create(accounts[0], ArgobytesBrownieProject.ExitCYY3CRVAction) get_or_create(accounts[0], ArgobytesBrownieProject.EnterUnit3CRVAction) # get_or_create(accounts[0], ArgobytesBrownieProject.ExitUnit3CRVAction) bulk_actions = [ # allow bots to call argobytes_trader.atomicArbitrage # TODO: allow bots to flash loan from WETH10 and DyDx and Uniswap and any other wrappers that we trust # TODO: think about this more ( argobytes_authority, 0, # 0=CALL False, argobytes_authority.allow.encode_input( argobytes_flash_borrower_arbitragers, argobytes_trader, 0, # 0=CALL argobytes_trader.atomicArbitrage.signature, ), ), ] argobytes_clone.executeMany(bulk_actions) print("gas used by accounts[0]:", accounts[0].gas_used) ending_balance = accounts[0].balance() assert ending_balance < starting_balance print("ETH used by accounts[0]:", (starting_balance - ending_balance) / 1e18) """ # save all the addresses we might use, not just ones for own contracts quick_save("CurveFiBUSD", CurveFiBUSDAddress) quick_save("CurveFiCompound", CurveFiCompoundAddress) quick_save("CurveFiPAX", CurveFiPAXAddress) quick_save("CurveFiREN", CurveFiRENAddress) quick_save("CurveFiSUSDV2", CurveFiSUSDV2Address) quick_save("CurveFiTBTC", CurveFiTBTCAddress) quick_save("CurveFiUSDT", CurveFiUSDTAddress) quick_save("CurveFiY", CurveFiYAddress) quick_save("KollateralInvoker", KollateralInvokerAddress) quick_save("KyberNetworkProxy", KyberNetworkProxyAddress) quick_save("KyberRegisterWallet", KyberRegisterWalletAddress) quick_save("OneSplit", OneSplitAddress) quick_save("SynthetixAddressResolver", SynthetixAddressResolverAddress) quick_save("UniswapFactory", UniswapV1FactoryAddress) quick_save("UniswapV2Router", UniswapV2RouterAddress) quick_save("YearnWethVault", YearnWethVaultAddress) # TODO: this list is going to get long. use tokenlists.org instead quick_save("DAI", DAIAddress) quick_save("cDAI", cDAIAddress) quick_save("cUSDC", cUSDCAddress) quick_save("sUSD", ProxysUSDAddress) quick_save("USDC", USDCAddress) quick_save("COMP", COMPAddress) quick_save("AAVE", AAVEAddress) quick_save("LINK", LINKAddress) quick_save("MKR", MKRAddress) quick_save("SNX", SNXAddress) quick_save("WBTC", WBTCAddress) quick_save("YFI", YFIAddress) quick_save("WETH9", WETH9Address) quick_save("yvyCRV", YVYCRVAddress) """ # # give the argobytes_flash_borrower a bunch of coins. it will forward them when deploying the diamond # accounts[1].transfer(DevHardwareAddress, 50 * 1e18) # accounts[2].transfer(DevHardwareAddress, 50 * 1e18) # accounts[3].transfer(DevHardwareAddress, 50 * 1e18) # accounts[4].transfer(DevMetamaskAddress, 50 * 1e18) # make a clone vault w/ auth for accounts[5] and approve a bot to call atomicArbitrage. then print total gas starting_balance = accounts[5].balance() deploy_tx = argobytes_factory.createClone19( argobytes_flash_borrower.address, salt, {"from": accounts[5]}, ) argobytes_flash_borrower_clone_5 = ArgobytesBrownieProject.ArgobytesFlashBorrower.at( deploy_tx.return_value, accounts[5] ) bulk_actions = [ # allow bots to call argobytes_trader.atomicArbitrage # TODO: think about this more. the msg.sender might not be what we need ( argobytes_authority, 0, # 0=Call False, argobytes_authority.allow.encode_input( argobytes_flash_borrower_arbitragers, argobytes_trader, 1, # 1=delegatecall argobytes_trader.atomicArbitrage.signature, ), ), # TODO: gas_token.buyAndFree or gas_token.free depending on off-chain balance/price checks ] argobytes_flash_borrower_clone_5.executeMany(bulk_actions) ending_balance = accounts[5].balance() print( "ETH used by accounts[5] to deploy a proxy with auth:", (starting_balance - ending_balance) / 1e18, ) # make a clone for accounts[6]. then print total gas starting_balance = accounts[6].balance() # TODO: optionally free gas token deploy_tx = argobytes_factory.createClone19( argobytes_flash_borrower.address, salt, {"from": accounts[6]}, ) argobytes_flash_borrower_clone_6 = ArgobytesBrownieProject.ArgobytesFlashBorrower.at( deploy_tx.return_value, accounts[6] ) ending_balance = accounts[6].balance() print( "ETH used by accounts[6] to deploy a proxy:", (starting_balance - ending_balance) / 1e18, ) # make a clone for accounts[7]. then print total gas starting_balance = accounts[7].balance() deploy_tx = argobytes_factory.createClone19( argobytes_flash_borrower.address, salt, accounts[7], {"from": accounts[7]}, ) argobytes_flash_borrower_clone_7 = ArgobytesBrownieProject.ArgobytesFlashBorrower.at( deploy_tx.return_value, accounts[7] ) ending_balance = accounts[7].balance() print( "ETH used by accounts[7] to deploy a proxy:", (starting_balance - ending_balance) / 1e18, )
def uniswap_v2_action(UniswapV2Action): return get_or_create(accounts[0], UniswapV2Action)
def atomic_exit(): """Use a flash loan to withdraw from a leveraged cyy3crv position.""" load_dotenv(find_dotenv()) # TODO: we need an account with private keys account = accounts.at(os.environ["LEVERAGE_ACCOUNT"]) # TODO: prompt for slippage amount slippage = 0.1 # TODO: use salts for the contracts once we figure out a way to store them. maybe 3box? # deploy our contracts if necessary argobytes_factory = get_or_create(account, ArgobytesFactory) argobytes_flash_borrower = get_or_create(account, ArgobytesFlashBorrower) exit_cyy3crv_action = get_or_create(account, ExitCYY3CRVAction) # get the clone for the account argobytes_clone = get_or_clone(account, argobytes_factory, argobytes_flash_borrower) assert account == argobytes_clone.owner(), "Wrong owner detected!" print("Preparing contracts...") # TODO: use multicall to get all the addresses? # TODO: do we need all these for exit? or just enter? dai = lazy_contract(exit_cyy3crv_action.DAI()) usdc = lazy_contract(exit_cyy3crv_action.USDC()) usdt = lazy_contract(exit_cyy3crv_action.USDT()) threecrv = lazy_contract(exit_cyy3crv_action.THREE_CRV()) threecrv_pool = lazy_contract(exit_cyy3crv_action.THREE_CRV_POOL()) y3crv = lazy_contract(exit_cyy3crv_action.Y_THREE_CRV()) cyy3crv = lazy_contract(exit_cyy3crv_action.CY_Y_THREE_CRV()) fee_distribution = lazy_contract( exit_cyy3crv_action.THREE_CRV_FEE_DISTRIBUTION()) lender = DyDxFlashLender # use multiple workers to fetch the contracts # there will still be some to fetch, but this speeds things up some # this can take some time since solc/vyper may have to download # poke_contracts([dai, usdc, usdt, threecrv, threecrv_pool, y3crv, cyy3crv, lender]) # TODO: fetch other tokens? tokens = [cyy3crv] # TODO: calculate/prompt for these min_remove_liquidity_dai = 1 tip_dai = 0 tip_address = _resolve_address( "tip.satoshiandkin.eth" ) # TODO: put this on a subdomain and uses an immutable # TODO: this should be False in the default case exit_from_account = False # min_cream_liquidity = 1 if exit_from_account: exit_from = account exit_to = ZERO_ADDRESS balances = get_balances(exit_from, tokens) print(f"{exit_from} balances") raise NotImplementedError( "we need an approve so CY_DAI.repayBorrowBehalf is allowed") else: exit_from = ZERO_ADDRESS exit_to = account balances = get_balances(argobytes_clone, tokens) print(f"{argobytes_clone} balances") pprint(balances) # TODO: ii think this might not be right flash_loan_amount = int( exit_cyy3crv_action.calculateExit.call(exit_from) * (1 + slippage)) print(f"flash_loan_amount: {flash_loan_amount}") dai_flash_fee = lender.flashFee(dai, flash_loan_amount) exit_data = ExitData( min_remove_liquidity_dai=min_remove_liquidity_dai, tip_dai=tip_dai, dai_flash_fee=dai_flash_fee, tip_address=tip_address, exit_from=exit_from, # TODO: allow exiting to an arbitrary account exit_to=account, ) pprint(exit_data) extra_balances = {} approve(account, balances, extra_balances, argobytes_clone) # flashloan through the clone exit_tx = argobytes_clone.flashBorrow( lender, dai, flash_loan_amount, Action( exit_cyy3crv_action, CallType.DELEGATE, False, "exit", *exit_data, ).tuple, ) print("exit success!") exit_tx.info() num_events = len(enter_tx.events) print(f"num events: {num_events}") print("clone balances") pprint(get_balances(argobytes_clone, tokens)) print("account balances") pprint(get_balances(account, tokens))
def atomic_exit(account, tip_eth, tip_3crv): """Use a flash loan to withdraw from a leveraged cyy3crv position.""" # TODO: we need an account with private keys print(f"Hello, {account}") # 0.5 is 0.5% # slippage = ['small', 'medium', 'high', 'bad idea'] slippage_pct = 0.5 # TODO: this should be a click arg # TODO: use salts for the contracts once we figure out a way to store them. maybe 3box? # deploy our contracts if necessary argobytes_factory = get_or_create_factory(account) argobytes_flash_borrower = get_or_create_flash_borrower(account) exit_cyy3crv_action = get_or_create( account, ArgobytesBrownieProject.ExitCYY3CRVAction) # get the clone for the account argobytes_clone = get_or_clone(account, argobytes_factory, argobytes_flash_borrower) assert account == argobytes_clone.owner(), "Wrong owner detected!" print("Preparing contracts...") # TODO: use multicall to get all the addresses? # TODO: do we need all these for exit? or just enter? dai = lazy_contract(exit_cyy3crv_action.DAI(), account) usdc = lazy_contract(exit_cyy3crv_action.USDC(), account) usdt = lazy_contract(exit_cyy3crv_action.USDT(), account) threecrv = lazy_contract(exit_cyy3crv_action.THREE_CRV(), account) threecrv_pool = lazy_contract(exit_cyy3crv_action.THREE_CRV_POOL(), account, force=True) y3crv = lazy_contract(exit_cyy3crv_action.Y_THREE_CRV(), account) cyy3crv = lazy_contract(exit_cyy3crv_action.CY_Y_THREE_CRV(), account) cydai = lazy_contract(exit_cyy3crv_action.CY_DAI(), account) lender = DyDxFlashLender tokens = [dai, usdc, usdt, threecrv, y3crv, cyy3crv, cydai] # use multiple workers to fetch the contracts # there will still be some to fetch, but this speeds things up some # this can take some time since solc/vyper may have to download poke_contracts(tokens + [threecrv_pool, lender]) balances = get_balances(argobytes_clone, tokens) print(f"clone {argobytes_clone} balances") print_token_balances(balances) dai_borrowed = cydai.borrowBalanceCurrent.call(argobytes_clone) assert dai_borrowed > 0, "No DAI position to exit from" print(f"dai_borrowed: {dai_borrowed}") borrow_rate_per_block = cydai.borrowRatePerBlock.call() # TODO: how many blocks of interest should we add on? base this on gas speed? # TODO: is this right? i think it might be overshooting. i don't think it matters that much though interest_slippage_blocks = int((5 * 60) / get_average_block_time()) # https://www.geeksforgeeks.org/python-program-for-compound-interest/ flash_loan_amount = int(dai_borrowed * (pow( (1 + borrow_rate_per_block / 1e18), interest_slippage_blocks))) print(f"flash_loan_amount: {flash_loan_amount}") # safety check. make sure our interest doesn't add a giant amount assert flash_loan_amount <= dai_borrowed * (1 + slippage_pct / 100) flash_loan_fee = lender.flashFee(dai, flash_loan_amount) print(f"flash_loan_fee: {flash_loan_fee}") # TODO: safety check on the flash loan fee? # TODO: this is slightly high because we add 5 minutes of interest. we could spend gas subtracting out the unused slack, but i doubt its worthwhile max_3crv_burned = ( threecrv_pool.calc_token_amount( # dai, usdc, usdt [ flash_loan_amount + flash_loan_fee, 0, 0, ], # is_deposit False, ) * (1 + slippage_pct / 100)) exit_data = ExitData( dai_flash_fee=flash_loan_fee, max_3crv_burned=max_3crv_burned, tip_3crv=tip_3crv, sender=account, ) pprint(exit_data) safe_token_approve(account, balances, argobytes_clone) # flashloan through the clone exit_tx = argobytes_clone.flashBorrow( lender, dai, flash_loan_amount, ArgobytesAction( exit_cyy3crv_action, ArgobytesActionCallType.DELEGATE, False, "exit", *exit_data, ).tuple, { "value": tip_eth, }, ) print("exit success!") # exit_tx.info() # num_events = len(exit_tx.events) # print(f"num events: {num_events}") print(f"gas used: {exit_tx.gas_used}") print("clone balances") print_token_balances(get_balances(argobytes_clone, tokens)) print("account balances") print_token_balances(get_balances(account, tokens))