def test_get_or_clone(): account = accounts[0] argobytes_factory = get_or_create_factory(account) argobytes_proxy = get_or_create_flash_borrower(account) argobytes_clone = get_or_clone(account, argobytes_factory, argobytes_proxy) print(argobytes_clone)
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 argobytes_flash_clone(argobytes_factory, argobytes_flash_borrower): # on mainnet we use the (bytes32) salt to generate custom addresses, but we dont need that in our tests return get_or_clone(accounts[0], argobytes_factory, argobytes_flash_borrower)
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 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))