def exit(self, sender, usr, wad): require(wad <= Wad(2**255), "GemJoin/overflow") self.vat.slip(self.ilk_id, sender, Wad(0) - wad) require( self.gem.transferFrom(self.ADDRESS, usr, float(wad)), "GemJoin/failed-transfer", )
def kick(self, tab, lot, usr, kpr, now): require(tab > Rad(0), "Clipper/zero-tab") require(lot > Wad(0), "Clipper/zero-lot") require(bool(usr), "Clipper/zero-usr") require(self.kicks < (1 << 31) - 1, "Clipper/overflow") self.kicks += 1 sale_id = self.kicks self.active.append(sale_id) pip = self.spotter.ilks[self.ilk_id].pip val = pip.peek(now) self.sales[sale_id] = Sale( len(self.active) - 1, tab, lot, usr, now, Ray(val / Wad(self.spotter.par)) * self.buf, sale_id, ) if self.tip > Rad(0) or self.chip > Wad(0): self.vat.suck(self.vow.ADDRESS, kpr, self.tip + tab * Rad(self.chip))
def frob(self, i, u, v, w, dink, dart): urn = self.urns[i].get(u, Urn(u, Wad(0), Wad(0))) ilk = self.ilks[i] urn.ink += dink urn.art += dart ilk.Art += dart dtab = Rad(ilk.rate * dart) tab = Rad(ilk.rate * urn.art) self.debt += dtab require( dart <= Wad(0) or (Rad(ilk.Art * ilk.rate) <= ilk.line and self.debt <= self.Line), "Vat/ceiling-exceeded", ) require(dart <= Wad(0) <= dink or tab <= Rad(urn.ink * ilk.spot), "Vat/not-safe") require(urn.art == Wad(0) or tab >= ilk.dust, "Vat/dust") self.gem[i][v] -= dink w_dai = self.dai.get(w, Rad(0)) w_dai += dtab self.dai[w] = w_dai self.urns[i][u] = urn self.ilks[i] = ilk
def run_bidding_model(self, bid, ilk_id): if bid.tic != 0: return {"price": Wad(0)} for keeper in self.other_keepers: if self.vat.dai.get(keeper.ADDRESS, Rad(0)) >= bid.tab: return {"price": Wad(0)} return super().run_bidding_model(bid, ilk_id)
def close_vault(self, urn_id): ilk_id = self.urns[urn_id] dink = Wad(0) - self.vat.urns[ilk_id][urn_id]["ink"] dart = Wad(0) - self.vat.urns[ilk_id][urn_id]["art"] # Zero out the vault, freeing collateral self.vat.frob(ilk_id, urn_id, self.ADDRESS, self.ADDRESS, dink, dart) # Withdraw freed collateral self.gem_joins[ilk_id].exit(self.ADDRESS, self.ADDRESS, Wad(0) - dink) del self.urns[urn_id]
def __init__(self, vat, gem): self.ADDRESS = "flopper" self.vat = vat self.gem = gem self.bids = dict() self.kicks = 0 self.pad = Wad.from_number(1.5) self.beg = Wad.from_number(1.05) self.ttl = 3 * 60 * 60 self.tau = 2 * 24 * 60 * 60
def get_slippage(self, pair_id, in_token, in_amt, t): self.tick(t) out_token = next( filter(lambda x: x != in_token, self.pairs[pair_id].keys())) in_reserve = Wad.from_number(float(self.pairs[pair_id][in_token])) out_reserve = Wad.from_number(float(self.pairs[pair_id][out_token])) initial_rate = out_reserve / in_reserve k = in_reserve * out_reserve new_in_reserve = in_reserve + in_amt new_out_reserve = k / new_in_reserve new_rate = new_out_reserve / new_in_reserve return ( Rad(out_reserve - new_out_reserve), (new_rate - initial_rate) / initial_rate, )
def take(self, sale_id, amt, max_price, who, data, now, sender): require(self.sales.get(sale_id), "Clipper/not-running-auction") usr = self.sales[sale_id].usr tic = self.sales[sale_id].tic (done, price) = self.status(tic, self.sales[sale_id].top, now) require(not done, "Clipper/needs-reset") require(max_price >= price, "Clipper/too-expensive") lot = self.sales[sale_id].lot tab = self.sales[sale_id].tab lot_slice = min(lot, amt) owe = Rad(price * lot_slice) if owe > tab: owe = tab lot_slice = Wad(owe / Rad(price)) elif owe < tab and lot_slice < lot: dust = self.vat.ilks[self.ilk_id].dust if tab - owe < dust: require(tab > dust, "Clipper/no-partial-purchase") owe = tab - dust lot_slice = Wad(owe / Rad(price)) tab = tab - owe lot = lot - lot_slice self.vat.flux(self.ilk_id, self.ADDRESS, who, lot_slice) if len(data ) > 0 and who != self.vat.ADDRESS and who != self.dog.ADDRESS: who.clipperCall(sender, owe, lot_slice, data) self.vat.move(sender, self.vow.ADDRESS, owe) self.dog.digs(self.ilk_id, owe) if lot == Wad(0): self._remove(sale_id) elif tab == Rad(0): self.vat.flux(self.ilk_id, self.ADDRESS, usr, lot) self._remove(sale_id) else: self.sales[sale_id].tab = tab self.sales[sale_id].lot = lot return [owe, self.vat.urns]
def join(self, sender, usr, wad): require(wad >= Wad(0), "GemJoin/overflow") self.vat.slip(self.ilk_id, usr, wad) require( self.gem.transferFrom(sender, self.ADDRESS, float(wad)), "GemJoin/failed-transfer", )
def __init__(self, ilk_id): self.id = ilk_id self.Art = Wad(0) self.rate = Ray(0) self.spot = Ray(0) self.line = Rad(0) self.dust = Rad(0)
def open_max_vaults(self, actions): for ilk_id in self.ilks: vat_ilk = self.vat.ilks[ilk_id] if self.ilks[ilk_id].balanceOf( self.ADDRESS) > 0 and vat_ilk.spot > Ray(0): dink = Wad.from_number(self.ilks[ilk_id].balanceOf( self.ADDRESS)) dart = self.spot_paddings[ilk_id] * vat_ilk.spot * dink if dart > Wad(vat_ilk.dust) and Rad( dart * vat_ilk.rate) <= Rad(dink * vat_ilk.spot): actions.append({ "key": "OPEN_VAULT", "keeper": self, "handler": self.open_vault, "args": [ilk_id, dink, dart], "kwargs": {}, })
def bite(self, ilk_id, urn, now): # TODO: Remove `now` once better timekeeping solution is implemented rate = self.vat.ilks[ilk_id].rate spot = self.vat.ilks[ilk_id].spot dust = self.vat.ilks[ilk_id].dust ink = self.vat.urns[ilk_id][urn].ink art = self.vat.urns[ilk_id][urn].art require(spot > Ray(0) and Rad(ink * spot) < Rad(art * rate), "Cat/not-unsafe") milk = self.ilks[ilk_id] room = self.box - self.litter require(self.litter < self.box and room >= dust, "Cat/liquidation-limit-hit") dart = Wad.min(art, Wad(Rad.min(milk.dunk, room)) / Wad(rate) / milk.chop) dink = Wad.min(ink, ink * dart / art) require(dart > Wad(0) and dink > Wad(0), "Cat/null-auction") require( dart <= Wad.from_number(2**255) and dink <= Wad.from_number(2**255), "Cat/overflow", ) self.vat.grab(ilk_id, urn, self.ADDRESS, self.vow.ADDRESS, Wad(0) - dink, Wad(0) - dart) self.vow.fess(Rad(dart * rate), now) tab = Rad(dart * rate * milk.chop) self.litter += tab milk.flip.kick(urn, self.vow.ADDRESS, tab, dink, Rad(0), now)
def __init__(self, vat, cat, ilk_id): """""" self.ADDRESS = f"flipper-{ilk_id}" self.vat = vat self.cat = cat self.ilk_id = ilk_id self.beg = Wad(0) self.bids = {} self.kicks = 0 self.ttl = 0 self.tau = 0
def place_bid(self, bid_id, price, ilk_id, now): if price > Wad(0): flipper = self.flippers[ilk_id] bid = flipper.bids[bid_id] if bid.bid == bid.tab: # Dent phase our_lot = Wad(bid.bid / Rad(price)) if (our_lot * flipper.beg <= bid.lot and our_lot < bid.lot and self.vat.dai.get(self.ADDRESS) and self.vat.dai[self.ADDRESS] >= bid.bid): return { "key": "DENT", "keeper": self, "handler": flipper.dent, "args": [bid_id, self.ADDRESS, our_lot, bid.bid, now], "kwargs": {}, } else: # Tend phase our_bid = Rad.min(Rad(bid.lot) * price, bid.tab) if ((our_bid >= bid.bid * flipper.beg or our_bid == bid.tab) and our_bid > bid.bid and self.vat.dai.get(self.ADDRESS) and self.vat.dai[self.ADDRESS] >= (our_bid if self.ADDRESS != bid.guy else our_bid - bid.bid)): return { "key": "TEND", "keeper": self, "handler": flipper.tend, "args": [bid_id, self.ADDRESS, bid.lot, our_bid, now], "kwargs": {}, } return None
def redo(self, sale_id, kpr, now): usr = self.sales[sale_id].usr tic = self.sales[sale_id].tic top = self.sales[sale_id].top require(bool(usr), "Clipper/not-running-auction") (done, _) = self.status(tic, top, now) require(done, "Clipper/cannot-reset") tab = self.sales[sale_id].tab lot = self.sales[sale_id].lot self.sales[sale_id].tic = now pip = self.spotter.ilks[self.ilk_id].pip val = pip.peek(now) price = Ray(val / Wad(self.spotter.par)) self.sales[sale_id].top = price * self.buf if self.tip > Rad(0) or self.chip > Wad(0): dust = self.vat.ilks[self.ilk_id].dust if tab >= dust and Rad(lot * price) >= dust: self.vat.suck(self.vow.ADDRESS, kpr, self.tip + tab * Rad(self.chip))
def grab(self, i, u, v, w, dink, dart): urn = self.urns[i][u] ilk = self.ilks[i] urn.ink += dink urn.art += dart ilk.Art += dart dtab = Rad(ilk.rate * dart) if not self.gem[i].get(v): self.gem[i][v] = Wad(0) self.gem[i][v] -= dink if not self.sin.get(w): self.sin[w] = Rad(0) self.sin[w] -= dtab self.vice -= dtab
def __init__(self, vat, flapper, flopper): self.ADDRESS = "vow" self.vat = vat self.flapper = flapper self.flopper = flopper self.sin = {} self.Sin = Rad(0) self.Ash = Rad(0) self.wait = 0 self.dump = Wad(0) self.sump = Rad(0) self.bump = Rad(0) self.hump = Rad(0)
def run_bidding_model(self, bid, ilk_id): curr_price = Wad(bid.bid) / bid.lot if (bid.guy == self.ADDRESS or bid.lot == Wad(0) or curr_price > Wad( self.vat.ilks[ilk_id].spot * self.spotter.ilks[ilk_id].mat)): return {"price": Wad(0)} if bid.bid == Rad(0): return { "price": Wad.from_number(0.05) * Wad(bid.tab / Rad(bid.lot)) } return { "price": curr_price * (self.flippers[ilk_id].beg + Wad.from_number(random.uniform(0, 0.15))) }
def __init__(self, vat, spotter, dog, ilk_id): self.ADDRESS = f"clipper-{ilk_id}" self.vat = vat self.spotter = spotter self.dog = dog self.ilk_id = ilk_id self.buf = Ray.from_number(1) self.vow = None self.calc = None self.tail = 0 self.cusp = Ray(0) self.kicks = 0 self.active = [] self.sales = {} self.locked = 0 self.stopped = 0 self.chip = Wad(0) self.tip = Rad(0)
def bark(self, ilk_id, urn_id, kpr, now): ink = self.vat.urns[ilk_id][urn_id].ink art = self.vat.urns[ilk_id][urn_id].art milk = self.ilks[ilk_id] rate = self.vat.ilks[ilk_id].rate spot = self.vat.ilks[ilk_id].spot dust = self.vat.ilks[ilk_id].dust require(spot > Ray(0) and spot * ink < rate * art, "Dog/not-unsafe") room = min(self.Hole - self.Dirt, milk.hole - milk.dirt) require(room > Rad(0) and room >= dust, "Dog/liquidation-limit-hit") dart = min(art, Wad(room / Rad(rate)) / milk.chop) if Rad(rate * (art - dart)) < dust: # Q: What if art > room? # Resetting dart = art here can push past liq limit dart = art dink = ink * dart / art require(dink > Wad(0), "Dog/null-auction") require( dart <= Wad.from_number(2**255) and dink <= Wad.from_number(2**255), "Dog/overflow", ) self.vat.grab( ilk_id, urn_id, milk.clip.ADDRESS, self.vow.ADDRESS, Wad(0) - dink, Wad(0) - dart, ) due = Rad(rate * dart) self.vow.fess(due, now) tab = due * Rad(milk.chop) self.Dirt += tab self.ilks[ilk_id].dirt = milk.dirt + tab milk.clip.kick(tab, dink, urn_id, kpr, now) return [tab]
def poke(self, ilk_id, now): val = self.ilks[ilk_id].pip.peek(now) spot = Ray(val / Wad(self.par) / self.ilks[ilk_id].mat) self.vat.file_ilk(ilk_id, "spot", spot)
def peek(self, now): with open(self.price_feed_file) as price_feed_json: return Wad.from_number( # TODO: Constantize the "price_close" field here json.load(price_feed_json)[now]["price_close"])
def track_stat(state, action, _results): if action["key"] == "T_START": state["stats"]["gas_price_gwei"] = float( state["gas_oracle"].peek(state["t"]) * Wad.from_number(10 ** -9) )
def slip(self, ilk_id, usr, wad): usr_gem = self.gem[ilk_id].get(usr, Wad(0)) usr_gem += wad self.gem[ilk_id][usr] = usr_gem
def __init__(self, ilk_id): self.id = ilk_id self.chop = Wad(0) self.dunk = Rad(0)
# "09-05-2020", ]: timeframe_params = deepcopy(parameters) timeframe_params["Spotter"]["WETH"]["pip"] = PipLike( f"feeds/eth/{timeframe}.json") timeframe_params["GasOracle"][ "price_feed_file"] = f"feeds/gas/{timeframe}.json" timeframe_params["Uniswap"]["pairs"][ "0xa478c2975ab1ea89e8196811f51a7b7ade33eb11"][ "path"] = f"feeds/eth_dai_liquidity/{timeframe}.json" swept_params = sweep( { # "Clipper.WETH.chip": [Wad.from_number(0.001), Wad.from_number(0.01), Wad.from_number(0.1)], # "Clipper.WETH.tip": [Rad.from_number(100), Rad.from_number(500), Rad.from_number(1000)], "Clipper.WETH.chip": [Wad.from_number(0.001)], "Clipper.WETH.tip": [Rad.from_number(1000)], }, timeframe_params, ) DutchAuctionsSims = [ DutchAuctionsExperiment( contracts, keepers, sort_actions, ilk_ids, Token, stat_trackers, params, ) for params in swept_params
def flux(self, ilk_id, src, dst, wad): self.gem[ilk_id][src] -= wad dst_gem = self.gem[ilk_id].get(dst, Wad(0)) dst_gem += wad self.gem[ilk_id][dst] = dst_gem
def peek(self, now): with open(self.price_feed_file) as price_feed_json: # TODO: Constantize the "avgGasDay" field here # TODO: Format gas feed file to get rid of "data" return Wad.from_number( json.load(price_feed_json)["data"][now]["avgGasDay"])
} sort_actions = lambda _: random.random() ilk_ids = ["WETH"] stat_trackers = [ ilk_price("WETH"), num_new_bites(), num_bids_placed(), keeper_gem_balances(), num_active_bids(), auction_debt(), ] parameters = { "Cat": { "box": Rad(15000000000000000000000000000000000000000000000000000), "WETH": { "chop": Wad(1130000000000000000), "dunk": Rad(50000000000000000000000000000000000000000000000000), }, }, "Flipper": { "WETH": { "beg": Wad(1050000000000000000), "ttl": 18, "tau": 288, } }, "Keepers": { "NaiveVaultKeeper": { "amount": 500, "get_params":
num_sales_taken(), auction_debt(), ilk_price("WETH"), gas_price_gwei(), avg_time_to_liquidation("WETH"), ] parameters = { "Abacus": { "tau": 72 }, "Clipper": { "WETH": { "buf": Ray.from_number(1.05), "tail": 72, "cusp": Ray.from_number(0.5), "chip": Wad.from_number(0.08), "tip": Rad.from_number(1000), } }, "Dog": { "Hole": Rad(15000000000000000000000000000000000000000000000000000), "WETH": { "chop": Wad.from_number(1.13), "hole": Rad(15000000000000000000000000000000000000000000000000000), }, }, "Keepers": { "NaiveVaultKeeper": { "amount": 50, "get_params":