Beispiel #1
0
    def can_bite(self, ilk: Ilk, urn: Urn) -> bool:
        """ Determine whether a vault can be liquidated

        Args:
            ilk: Collateral type
            urn: Identifies the vault holder or proxy
        """
        assert isinstance(ilk, Ilk)
        assert isinstance(urn, Urn)
        ilk = self.vat.ilk(ilk.name)
        urn = self.vat.urn(ilk, urn.address)
        rate = ilk.rate

        # Collateral value should be less than the product of our stablecoin debt and the debt multiplier
        safe = Ray(urn.ink) * ilk.spot >= Ray(urn.art) * rate
        if safe:
            return False

        # Ensure there's room in the litter box
        box: Rad = self.box()
        litter: Rad = self.litter()
        room: Rad = box - litter
        if litter >= box:
            logger.debug(f"biting {urn.address} would exceed maximum Dai out for liquidation")
            return False
        if room < ilk.dust:
            return False

        # Prevent null auction (ilk.dunk [Rad], ilk.rate [Ray], ilk.chop [Wad])
        assert self.chop(ilk) > Wad(0)  # ensure liquidations are enabled and this uses flipper instead of clipper
        dart: Wad = min(urn.art, Wad(min(self.dunk(ilk), room) / Rad(ilk.rate) / Rad(self.chop(ilk))))
        dink: Wad = min(urn.ink, urn.ink * dart / urn.art)

        return dart > Wad(0) and dink > Wad(0)
Beispiel #2
0
    def ilk(self, name: str) -> Ilk:
        assert isinstance(name, str)

        b32_ilk = Ilk(name).toBytes()
        (take, rate, ink, art) = self._contract.call().ilks(b32_ilk)

        return Ilk(name, Ray(take), Ray(rate), Wad(ink), Wad(art))
Beispiel #3
0
    def test_cage_keeper(self, mcd: DssDeployment, keeper: CageKeeper,
                         our_address: Address, other_address: Address):
        print_out("test_cage_keeper")
        ilks = keeper.get_ilks()
        urns = pytest.global_urns
        auctions = pytest.global_auctions

        for ilk in ilks:
            # Check if cage(ilk) called on all ilks
            assert mcd.end.tag(ilk) > Ray(0)

            # Check if flow(ilk) called on all ilks
            assert mcd.end.fix(ilk) > Ray(0)

        # All underwater urns present before ES have been skimmed
        for i in urns:
            urn = mcd.vat.urn(i.ilk, i.address)
            assert urn.art == Wad(0)

        # All auctions active before cage have been yanked
        for ilk in auctions["flips"].keys():
            for auction in auctions["flips"][ilk]:
                assert mcd.collaterals[ilk].flipper.bids(
                    auction.id).lot == Wad(0)

        for auction in auctions["flaps"]:
            assert mcd.flapper.bids(auction.id).lot == Rad(0)

        for auction in auctions["flops"]:
            assert mcd.flopper.bids(auction.id).lot == Wad(0)

        # Cage has been thawed (thaw() called)
        assert mcd.end.debt() != Rad(0)
Beispiel #4
0
    def can_bark(self, ilk: Ilk, urn: Urn, dog_hole: Rad, milk_hole: Rad,
                 chop: Wad) -> bool:
        # Typechecking intentionally omitted to improve performance
        rate = ilk.rate

        # Collateral value should be less than the product of our stablecoin debt and the debt multiplier
        safe = Ray(urn.ink) * ilk.spot >= Ray(urn.art) * rate
        if safe:
            return False

        # Ensure there's room in the dog.hole
        dog_dirt: Rad = self.mcd.dog.dog_dirt()
        dog_room: Rad = dog_hole - dog_dirt
        if dog_hole <= dog_dirt:
            return False

        # Ensure there's room in the collateral-specific hole
        milk_dirt: Rad = self.mcd.dog.dirt(ilk)
        milk_room: Rad = milk_hole - milk_dirt
        if milk_hole <= milk_dirt:
            return False

        # Prevent dusty partial liquidation
        room: Rad = min(dog_room, milk_room)
        dart: Wad = min(urn.art, Wad(room / Rad(ilk.rate) / Rad(chop)))
        if urn.art > dart:
            if Rad(urn.art - dart) * Rad(ilk.rate) < ilk.dust:
                return True
            elif Rad(dart) * Rad(ilk.rate) < ilk.dust:
                return False

        return True
Beispiel #5
0
    def test_should_identify_multi_step_opportunities(self, token1, token2,
                                                      token3, token4):
        # given
        conversion1 = Conversion(token1, token2, Ray.from_number(1.02),
                                 Wad.from_number(10000), 'met1')
        conversion2 = Conversion(token2, token3, Ray.from_number(1.03),
                                 Wad.from_number(10000), 'met2')
        conversion3 = Conversion(token3, token4, Ray.from_number(1.05),
                                 Wad.from_number(10000), 'met3')
        conversion4 = Conversion(token4, token1, Ray.from_number(1.07),
                                 Wad.from_number(10000), 'met4')
        conversions = [conversion1, conversion2, conversion3, conversion4]
        base_token = token1

        # when
        opportunities = OpportunityFinder(conversions).find_opportunities(
            base_token, Wad.from_number(100))

        # then
        assert len(opportunities) == 1
        assert len(opportunities[0].steps) == 4
        assert opportunities[0].steps[0].method == "met1"
        assert opportunities[0].steps[1].method == "met2"
        assert opportunities[0].steps[2].method == "met3"
        assert opportunities[0].steps[3].method == "met4"
Beispiel #6
0
    def validate_frob(self, ilk: Ilk, address: Address, dink: Wad, dart: Wad):
        """Helps diagnose `frob` transaction failures by asserting on `require` conditions in the contract"""
        def r(value, decimals=1):  # rounding function
            return round(float(value), decimals)

        def f(value, decimals=1):  # formatting function
            return f"{r(value):16,.{decimals}f}"

        assert isinstance(ilk, Ilk)
        assert isinstance(address, Address)
        assert isinstance(dink, Wad)
        assert isinstance(dart, Wad)

        assert self.live()  # system is live

        urn = self.urn(ilk, address)
        ilk = self.ilk(ilk.name)
        assert ilk.rate != Ray(0)  # ilk has been initialised

        ink = urn.ink + dink
        art = urn.art + dart
        ilk_art = ilk.art + dart

        logger.debug(
            f"System     | debt {f(self.debt())} | ceiling {f(self.line())}")
        logger.debug(
            f"Collateral | debt {f(Ray(ilk_art) * ilk.rate)} | ceiling {f(ilk.line)}"
        )

        dtab = Rad(ilk.rate * Ray(dart))
        tab = ilk.rate * art
        debt = self.debt() + dtab
        logger.debug(
            f"Frobbing     debt={r(ilk_art)}, ink={r(ink)}, dink={r(dink)}, dart={r(dart)}, "
            f"ilk.rate={r(ilk.rate,8)}, tab={r(tab)}, spot={r(ilk.spot, 4)}, debt={r(debt)}"
        )

        # either debt has decreased, or debt ceilings are not exceeded
        under_collateral_debt_ceiling = Rad(
            Ray(ilk_art) * ilk.rate) <= ilk.line
        under_system_debt_ceiling = debt < self.line()
        calm = dart <= Wad(0) or (under_collateral_debt_ceiling
                                  and under_system_debt_ceiling)

        # urn is either less risky than before, or it is safe
        safe = (dart <= Wad(0)
                and dink >= Wad(0)) or tab <= Ray(ink) * ilk.spot

        # urn has no debt, or a non-dusty amount
        neat = art == Wad(0) or Rad(tab) >= ilk.dust

        if not under_collateral_debt_ceiling:
            logger.warning("collateral debt ceiling would be exceeded")
        if not under_system_debt_ceiling:
            logger.warning("system debt ceiling would be exceeded")
        if not safe:
            logger.warning("urn would be unsafe")
        if not neat:
            logger.warning("debt would not exceed dust cutoff")
        assert calm and safe and neat
Beispiel #7
0
def max_dart(mcd: DssDeployment, collateral: Collateral,
             our_address: Address) -> Wad:
    assert isinstance(mcd, DssDeployment)
    assert isinstance(collateral, Collateral)
    assert isinstance(our_address, Address)

    urn = mcd.vat.urn(collateral.ilk, our_address)
    ilk = mcd.vat.ilk(collateral.ilk.name)

    # change in art = (collateral balance * collateral price with safety margin) - CDP's stablecoin debt
    dart = urn.ink * ilk.spot - Wad(Ray(urn.art) * ilk.rate)

    # change in debt must also take the rate into account
    dart = dart * Wad(Ray.from_number(1) / ilk.rate)

    # prevent the change in debt from exceeding the collateral debt ceiling
    if (Rad(urn.art) + Rad(dart)) >= ilk.line:
        print("max_dart is avoiding collateral debt ceiling")
        dart = Wad(ilk.line - Rad(urn.art))

    # prevent the change in debt from exceeding the total debt ceiling
    debt = mcd.vat.debt() + Rad(ilk.rate * dart)
    line = Rad(ilk.line)
    if (debt + Rad(dart)) >= line:
        print("max_dart is avoiding total debt ceiling")
        dart = Wad(debt - Rad(urn.art))

    assert dart > Wad(0)
    return dart
Beispiel #8
0
    def test_should_ignore_irrelevant_conversions(self, token1, token2, token3,
                                                  token4):
        # given
        conversion1 = Conversion(token1, token2, Ray.from_number(1.02),
                                 Wad.from_number(10000), 'met1')
        conversion2 = Conversion(token2, token1, Ray.from_number(1.03),
                                 Wad.from_number(10000), 'met2')
        conversion3 = Conversion(token1, token3, Ray.from_number(1.04),
                                 Wad.from_number(10000), 'met3')
        conversion4 = Conversion(token1, token4, Ray.from_number(1.07),
                                 Wad.from_number(10000), 'met4')
        conversion5 = Conversion(token2, token4, Ray.from_number(1.08),
                                 Wad.from_number(10000), 'met5')
        conversions = [
            conversion1, conversion2, conversion3, conversion4, conversion5
        ]
        base_token = token1

        # when
        opportunities = OpportunityFinder(conversions).find_opportunities(
            base_token, Wad.from_number(100))

        # then
        assert len(opportunities) == 1
        assert len(opportunities[0].steps) == 2
        assert opportunities[0].steps[0].method == "met1"
        assert opportunities[0].steps[1].method == "met2"
Beispiel #9
0
    def test_should_adjust_amounts_based_on_max_source_amount(
            self, token1, token2, token3, token4):
        # given
        conversion1 = Conversion(token1, token2, Ray.from_number(2.0),
                                 Wad.from_number(10000), 'met1')
        conversion2 = Conversion(token2, token3, Ray.from_number(1.6),
                                 Wad.from_number(10000), 'met2')
        conversion3 = Conversion(token3, token4, Ray.from_number(1.2),
                                 Wad.from_number(100), 'met3')
        conversion4 = Conversion(token4, token1, Ray.from_number(1.1),
                                 Wad.from_number(10000), 'met4')
        conversions = [conversion1, conversion4, conversion3, conversion2]
        base_token = token1

        # when
        opportunities = OpportunityFinder(conversions).find_opportunities(
            base_token, Wad.from_number(100))

        # then
        assert len(opportunities) == 1
        assert len(opportunities[0].steps) == 4
        assert opportunities[0].steps[0].method == "met1"
        assert opportunities[0].steps[0].source_amount == Wad.from_number(
            31.25)
        assert opportunities[0].steps[0].target_amount == Wad.from_number(62.5)
        assert opportunities[0].steps[1].method == "met2"
        assert opportunities[0].steps[1].source_amount == Wad.from_number(62.5)
        assert opportunities[0].steps[1].target_amount == Wad.from_number(100)
        assert opportunities[0].steps[2].method == "met3"
        assert opportunities[0].steps[2].source_amount == Wad.from_number(100)
        assert opportunities[0].steps[2].target_amount == Wad.from_number(120)
        assert opportunities[0].steps[3].method == "met4"
        assert opportunities[0].steps[3].source_amount == Wad.from_number(120)
        assert opportunities[0].steps[3].target_amount == Wad.from_number(132)
Beispiel #10
0
def max_dart(mcd: DssDeployment, collateral: Collateral, our_address: Address) -> Wad:
    """Determines how much stablecoin should be reserved in an `urn` to make it as poorly collateralized as
    possible, such that a small change to the collateral price could trip the liquidation ratio."""
    assert isinstance(mcd, DssDeployment)
    assert isinstance(collateral, Collateral)
    assert isinstance(our_address, Address)

    urn = mcd.vat.urn(collateral.ilk, our_address)
    ilk = mcd.vat.ilk(collateral.ilk.name)

    # change in art = (collateral balance * collateral price with safety margin) - CDP's stablecoin debt
    dart = urn.ink * ilk.spot - Wad(Ray(urn.art) * ilk.rate)

    # change in debt must also take the rate into account
    dart = dart * Wad(Ray.from_number(1) / ilk.rate)

    # prevent the change in debt from exceeding the collateral debt ceiling
    if (Rad(urn.art) + Rad(dart)) >= ilk.line:
        print("max_dart is avoiding collateral debt ceiling")
        dart = Wad(ilk.line - Rad(urn.art))

    # prevent the change in debt from exceeding the total debt ceiling
    debt = mcd.vat.debt() + Rad(ilk.rate * dart)
    line = Rad(ilk.line)
    if (debt + Rad(dart)) >= line:
        print("max_dart is avoiding total debt ceiling")
        dart = Wad(debt - Rad(urn.art))

    assert dart > Wad(0)
    return dart
Beispiel #11
0
    def test_should_take_if_model_price_updated(self, kick):
        # given
        (model, model_factory) = models(self.keeper, kick)
        (needs_redo, price, initial_lot, initial_tab) = self.clipper.status(kick)

        # when initial model price is too low
        bad_price = price - Ray.from_number(30)
        self.simulate_model_bid(model, bad_price)
        self.keeper.check_all_auctions()
        self.keeper.check_for_bids()
        wait_for_other_threads()

        # then ensure no bid was submitted
        (needs_redo, price, lot, tab) = self.clipper.status(kick)
        assert lot == initial_lot
        assert tab == initial_tab

        # when model price becomes appropriate
        good_price = price + Ray.from_number(30)
        self.simulate_model_bid(model, good_price)
        self.keeper.check_all_auctions()
        self.keeper.check_for_bids()
        wait_for_other_threads()

        # then ensure our bid was submitted
        our_take: Clipper.TakeLog = self.last_log()
        assert our_take.id == kick
        assert our_take.price <= good_price
        # and that the auction finished
        (needs_redo, price, lot, tab) = self.clipper.status(kick)
        assert not needs_redo
        assert lot == Wad(0) or tab == Rad(0)
Beispiel #12
0
    def get_underwater_urns(self, ilks: List) -> List[Urn]:
        """ With all urns every frobbed, compile and return a list urns that are under-collateralized up to 100%  """

        underwater_urns = []

        for ilk in ilks:

            urn_history = UrnHistory(self.web3, self.dss, ilk,
                                     self.deployment_block,
                                     self.arguments.vulcanize_endpoint,
                                     self.arguments.vulcanize_key)

            urns = urn_history.get_urns()

            self.logger.info(f'Collected {len(urns)} from {ilk}')

            i = 0
            for urn in urns.values():
                urn.ilk = self.dss.vat.ilk(urn.ilk.name)
                mat = self.dss.spotter.mat(urn.ilk)
                usdDebt = Ray(urn.art) * urn.ilk.rate
                usdCollateral = Ray(urn.ink) * urn.ilk.spot * mat
                # Check if underwater ->  urn.art * ilk.rate > urn.ink * ilk.spot * spotter.mat[ilk]
                if usdDebt > usdCollateral:
                    underwater_urns.append(urn)
                i += 1

                if i % 100 == 0:
                    self.logger.info(f'Processed {i} urns of {ilk.name}')

        return underwater_urns
Beispiel #13
0
def reserve_dai(mcd: DssDeployment,
                c: Collateral,
                usr: Address,
                amount: Wad,
                extra_collateral=Wad.from_number(1)):
    assert isinstance(mcd, DssDeployment)
    assert isinstance(c, Collateral)
    assert isinstance(usr, Address)
    assert isinstance(amount, Wad)
    assert amount > Wad(0)

    # Determine how much collateral is needed
    ilk = mcd.vat.ilk(c.ilk.name)
    rate = ilk.rate  # Ray
    spot = ilk.spot  # Ray
    assert rate >= Ray.from_number(1)
    collateral_required = Wad(
        (Ray(amount) / spot) * rate) * extra_collateral + Wad(1)
    print(
        f'collateral_required for {str(amount)} dai is {str(collateral_required)}'
    )

    wrap_eth(mcd, usr, collateral_required)
    c.approve(usr)
    assert c.adapter.join(usr, collateral_required).transact(from_address=usr)
    assert mcd.vat.frob(c.ilk, usr, collateral_required,
                        amount).transact(from_address=usr)
    assert mcd.vat.urn(c.ilk, usr).art >= Wad(amount)
Beispiel #14
0
def create_debt(web3: Web3, mcd: DssDeployment, our_address: Address, deployment_address: Address):
    assert isinstance(web3, Web3)
    assert isinstance(mcd, DssDeployment)
    assert isinstance(our_address, Address)
    assert isinstance(deployment_address, Address)

    # Create a vault
    collateral = mcd.collaterals['ETH-A']
    ilk = collateral.ilk
    wrap_eth(mcd, deployment_address, Wad.from_number(1))
    collateral.approve(deployment_address)
    assert collateral.adapter.join(deployment_address, Wad.from_number(1)).transact(
        from_address=deployment_address)
    frob(mcd, collateral, deployment_address, dink=Wad.from_number(1), dart=Wad(0))
    dart = max_dart(mcd, collateral, deployment_address) - Wad(1)
    frob(mcd, collateral, deployment_address, dink=Wad(0), dart=dart)
    assert not mcd.cat.can_bite(ilk, mcd.vat.urn(collateral.ilk, deployment_address))

    # Undercollateralize by dropping the spot price, and then bite the vault
    to_price = Wad(Web3.toInt(collateral.pip.read())) / Wad.from_number(2)
    set_collateral_price(mcd, collateral, to_price)
    urn = mcd.vat.urn(collateral.ilk, deployment_address)
    assert urn.ink is not None and urn.art is not None
    assert ilk.spot is not None
    safe = Ray(urn.art) * mcd.vat.ilk(ilk.name).rate <= Ray(urn.ink) * ilk.spot
    assert not safe
    assert mcd.cat.can_bite(collateral.ilk, urn)
    assert mcd.cat.bite(collateral.ilk, urn).transact()
    flip_kick = collateral.flipper.kicks()

    # Generate some Dai, bid on and win the flip auction without covering all the debt
    wrap_eth(mcd, our_address, Wad.from_number(10))
    collateral.approve(our_address)
    assert collateral.adapter.join(our_address, Wad.from_number(10)).transact(from_address=our_address)
    web3.eth.defaultAccount = our_address.address
    frob(mcd, collateral, our_address, dink=Wad.from_number(10), dart=Wad.from_number(200))
    collateral.flipper.approve(mcd.vat.address, approval_function=hope_directly())
    current_bid = collateral.flipper.bids(flip_kick)
    urn = mcd.vat.urn(collateral.ilk, our_address)
    assert Rad(urn.art) > current_bid.tab
    bid = Rad.from_number(6)
    TestFlipper.tend(collateral.flipper, flip_kick, our_address, current_bid.lot, bid)
    mcd.vat.can(our_address, collateral.flipper.address)
    wait(mcd, our_address, collateral.flipper.ttl()+1)
    assert collateral.flipper.deal(flip_kick).transact()

    # Raise debt from the queue (note that vow.wait is 0 on our testchain)
    bites = mcd.cat.past_bites(100)
    for bite in bites:
        era_bite = bite.era(web3)
        assert era_bite > int(datetime.now().timestamp()) - 120
        assert mcd.vow.sin_of(era_bite) > Rad(0)
        assert mcd.vow.flog(era_bite).transact()
        assert mcd.vow.sin_of(era_bite) == Rad(0)
    # Cancel out surplus and debt
    dai_vow = mcd.vat.dai(mcd.vow.address)
    assert dai_vow <= mcd.vow.woe()
    assert mcd.vow.heal(dai_vow).transact()
    assert mcd.vow.woe() >= mcd.vow.sump()
Beispiel #15
0
 def test_ilk(self, mcd):
     assert mcd.vat.ilk('XXX') == Ilk('XXX',
                                      rate=Ray(0),
                                      ink=Wad(0),
                                      art=Wad(0),
                                      spot=Ray(0),
                                      line=Rad(0),
                                      dust=Rad(0))
Beispiel #16
0
    def ilk(self, name: str) -> Ilk:
        assert isinstance(name, str)

        b32_ilk = Ilk(name).toBytes()
        (art, rate, spot, line, dust) = self._contract.functions.ilks(b32_ilk).call()

        # We could get "ink" from the urn, but caller must provide an address.
        return Ilk(name, rate=Ray(rate), ink=Wad(0), art=Wad(art), spot=Ray(spot), line=Rad(line), dust=Rad(dust))
Beispiel #17
0
    def test_mat(self, mcd):
        val = Ray(mcd.collaterals['ETH-A'].pip.read_as_int())

        ilk = mcd.vat.ilk('ETH-A')
        par = mcd.spotter.par()
        mat = mcd.spotter.mat(ilk)

        assert mat == (Ray(val * 10 ** 9) / par) / (ilk.spot)
Beispiel #18
0
 def __init__(self, otc: SimpleMarket, order: Order):
     self.otc = otc
     self.order = order
     super().__init__(source_token=order.buy_token,
                      target_token=order.pay_token,
                      rate=Ray(order.pay_amount) / Ray(order.buy_amount),
                      max_source_amount=order.buy_amount,
                      method=f"opc.take({self.order.order_id})")
Beispiel #19
0
def is_cdp_safe(ilk: Ilk, urn: Urn) -> bool:
    assert isinstance(urn, Urn)
    assert urn.art is not None
    assert ilk.rate is not None
    assert urn.ink is not None
    assert ilk.spot is not None

    #print(f'art={urn.art} * rate={ilk.rate} <=? ink={urn.ink} * spot={ilk.spot}')
    return (Ray(urn.art) * ilk.rate) <= Ray(urn.ink) * ilk.spot
Beispiel #20
0
 def __init__(self, tub: Tub, tap: Tap):
     self.tub = tub
     self.tap = tap
     super().__init__(source_token=self.tub.sai(),
                      target_token=self.tub.skr(),
                      rate=(Ray.from_number(1) /
                            Ray(tap.ask(Wad.from_number(1)))),
                      max_source_amount=self.bustable_amount_in_sai(tap),
                      method="tub.bust()")
Beispiel #21
0
 def __init__(self, tub: Tub):
     self.tub = tub
     super().__init__(
         source_token=self.tub.gem(),
         target_token=self.tub.skr(),
         rate=(Ray.from_number(1) / Ray(tub.ask(Wad.from_number(1)))),
         max_source_amount=Wad.from_number(
             1000000),  #1 mio ETH = infinity ;)
         method="tub.join()")
Beispiel #22
0
    def test_mold_tax_and_tax(self, deployment: Deployment):
        # given
        assert deployment.tub.tax() == Ray.from_number(1)

        # when
        deployment.tub.mold_tax(Ray(1000000000000000020000000000)).transact()

        # then
        assert deployment.tub.tax() == Ray(1000000000000000020000000000)
Beispiel #23
0
    def test_mold_mat_and_mat(self, deployment: Deployment):
        # given
        assert deployment.tub.mat() == Ray.from_number(1)

        # when
        deployment.tub.mold_mat(Ray.from_number(1.5)).transact()

        # then
        assert deployment.tub.mat() == Ray.from_number(1.5)
Beispiel #24
0
    def test_ilk(self, mcd):
        assert mcd.vat.ilk('XXX') == Ilk('XXX',
                                         rate=Ray(0), ink=Wad(0), art=Wad(0), spot=Ray(0), line=Rad(0), dust=Rad(0))

        ilk = mcd.collaterals["ETH-C"].ilk
        assert ilk.line == Rad.from_number(1000000)
        assert ilk.dust == Rad.from_number(20)

        representation = repr(ilk)
        assert "ETH-C" in representation
    def __init__(self, exchange: zrx.ZrxExchange, order: zrx.Order):
        self.exchange = exchange
        self.order = order

        super().__init__(source_token=order.buy_token,
                         target_token=order.pay_token,
                         rate=Ray(order.pay_amount) / Ray(order.buy_amount),
                         max_source_amount=order.buy_amount -
                         self.exchange.get_unavailable_buy_amount(self.order),
                         method=f"zrx.fill_order({hash(self.order)})")
Beispiel #26
0
    def bid_available(
        self, id: int, our_price: Wad, available_dai: Rad
    ) -> Tuple[Optional[Wad], Optional[Transact], Optional[Rad]]:
        assert isinstance(id, int)
        assert isinstance(our_price, Wad)

        # Handle case where model supplied a price before keeper removed it from active auction collection
        (needs_redo, auction_price, lot, tab) = self.clipper.status(id)
        if needs_redo or auction_price == Ray(0) or lot == Wad(0):
            self.logger.debug(
                f"auction {id} is no longer available for taking")
            return None, None, None

        our_lot = lot
        if Ray(our_price) >= auction_price:

            if Wad(available_dai) > Wad(
                    0):  # TODO: Perhaps compare it with some dust amount?
                # Calculate how much of the lot we can afford with Dai available, don't bid for more than that
                lot_we_can_afford: Wad = Wad(available_dai /
                                             Rad(auction_price))
                if lot_we_can_afford < lot:
                    self.logger.debug(
                        f"with {available_dai} Dai we can afford to bid on {float(lot_we_can_afford)} "
                        f"out of {float(lot)} at {float(auction_price)} on auction {id}"
                    )
                    our_lot = lot_we_can_afford

            if our_lot <= self.min_lot:
                self.logger.debug(
                    f"our lot {our_lot} less than configured minimum {self.min_lot} for auction {id}"
                )
                # even if we won't take, return cost of full lot at our_price to flag Dai starvation and rebalance Dai
                return None, None, Rad(lot) * Rad(our_price)

            if not self.debt_exceeds_chost(our_lot, auction_price, lot, tab):
                self.logger.debug(
                    f"slice {our_lot} won't cover enough debt to clear the chop*dust floor"
                )
                # again, return cost of full lot to flag Dai starvation and rebalance Dai
                return None, None, Rad(lot) * Rad(our_price)

            self.logger.debug(
                f"taking {our_lot} from auction {id} at {auction_price}")
            # TODO: consider making pymaker enforce this
            self.clipper.validate_take(id, Wad(our_lot), auction_price)
            our_cost = Rad(our_lot) * auction_price
            return Wad(our_price), self.clipper.take(id, Wad(our_lot),
                                                     auction_price), our_cost
        else:
            self.logger.debug(
                f"auction {id} price is {auction_price}; cannot take at {our_price}"
            )
            return None, None, None
Beispiel #27
0
    def bid(self, id: int, price: Wad) -> Tuple[Optional[Wad], Optional[Transact], Optional[Rad]]:
        assert isinstance(id, int)
        assert isinstance(price, Wad)

        bid = self.flopper.bids(id)
        our_lot = bid.bid / Rad(price)

        if Ray(our_lot) * self.beg <= Ray(bid.lot) and our_lot < Rad(bid.lot):
            return price, self.flopper.dent(id, Wad(our_lot), bid.bid), bid.bid
        else:
            return None, None, None
Beispiel #28
0
    def test_safe(self, deployment: Deployment):
        # given
        deployment.tub.mold_mat(Ray.from_number(1.5)).transact()
        deployment.tub.mold_axe(Ray.from_number(1.2)).transact()
        DSValue(web3=deployment.web3, address=deployment.tub.pip()).poke_with_int(Wad.from_number(250).value).transact()

        # when
        deployment.tub.open().transact()

        # then
        assert deployment.tub.safe(1)
Beispiel #29
0
 def __init__(self, log, sender):
     args = log['args']
     self.id = args['id']
     self.max = Ray(args['max'])         # Max bid price specified
     self.price = Ray(args['price'])     # Calculated bid price
     self.owe = Rad(args['owe'])         # Dai needed to satisfy the calculated bid price
     self.tab = Rad(args['tab'])         # Remaining debt
     self.lot = Wad(args['lot'])         # Remaining lot
     self.usr = Address(args['usr'])     # Liquidated vault
     self.block = log['blockNumber']
     self.tx_hash = log['transactionHash'].hex()
     self.sender = sender
Beispiel #30
0
    def test_should_calculate_total_rate(self, token1, token2):
        # given
        step1 = Conversion(token1, token2, Ray.from_number(1.01),
                           Wad.from_number(1000), 'met1')
        step2 = Conversion(token2, token1, Ray.from_number(1.02),
                           Wad.from_number(1000), 'met2')

        # when
        sequence = Sequence([step1, step2])

        # then
        assert sequence.total_rate() == Ray.from_number(1.0302)