def _find_contract(address: Any) -> Any: if address is None: return address = _resolve_address(address) if address in _contract_map: return _contract_map[address] if "chainid" in CONFIG.active_network: try: from brownie.network.contract import Contract return Contract(address) except ValueError: pass
def load_contract(token_name_or_address: str): if token_name_or_address.lower() == "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": # USDC does weird things to get their implementation # TODO: don't hard code this!!! impl = Contract("0xB7277a6e95992041568D9391D09d0122023778A2") proxy = Contract("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") contract = Contract.from_abi(proxy._name, proxy.address, impl.abi) return contract if token_name_or_address.lower() in ["eth", ZERO_ADDRESS, ETH_ADDRESS.lower()]: # just use weth for eth token_name_or_address = "weth" elif token_name_or_address.lower() == "ankreth": # TODO: find a tokenlist with this on it token_name_or_address = "0xe95a203b1a91a908f9b9ce46459d101078c2c3cb" elif token_name_or_address.lower() == "obtc": # boring DAO btc # TODO: find a tokenlist with this on it token_name_or_address = "0x8064d9Ae6cDf087b1bcd5BDf3531bD5d8C537a68" # TODO: why was this erroring at the top level imports? from brownie.network.web3 import _resolve_address # this raises a ValueError if this is not an address or ENS name address = _resolve_address(token_name_or_address) contract = Contract(address) # TODO: we shouldn't need from_explorer, but i'm seeing weird things were DAI loads as IWETH9 if ( contract._name == "IWETH9" and address != "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" ): warn(f"Reloading contract supposedly named IWETH9: {address}") contract = Contract.from_explorer(address) # check if this is a proxy contract # TODO: theres other ways to have an impl too. usdc has one that uses storage impl = None if hasattr(contract, "implementation"): impl = Contract(contract.implementation.call()) elif hasattr(contract, "target"): impl = Contract(contract.target.call()) if impl: contract = Contract.from_abi(contract._name, address, impl.abi) return contract
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_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 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