예제 #1
0
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)
예제 #4
0
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
예제 #7
0
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))
예제 #13
0
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)
예제 #14
0
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)
예제 #17
0
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]])
예제 #19
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)
예제 #21
0
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))