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)
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))
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)
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
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"
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
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
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"
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)
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
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)
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
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)
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()
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))
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))
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)
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})")
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
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()")
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()")
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)
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)
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)})")
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
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
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)
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
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)