class TestAuctionKeeperCollateralFlashSwap(TransactionIgnoringTest): def teardown_method(self, test_method): pass def setup_class(self): """ I'm excluding initialization of a specific collateral perchance we use multiple collaterals to improve test speeds. This prevents us from instantiating the keeper as a class member. """ self.web3 = get_web3() self.geb = get_geb(self.web3) self.keeper_address = get_keeper_address(self.web3) self.collateral = self.geb.collaterals['ETH-B'] self.min_auction = self.collateral.collateral_auction_house.auctions_started( ) + 1 self.keeper = AuctionKeeper(args=args( f"--eth-from {self.keeper_address.address} " f"--type collateral " f"--flash-swap " f"--from-block {self.geb.starting_block_number} " f"--min-auction {self.min_auction} " f"--model ../models/collateral_model.sh " f"--collateral-type {self.collateral.collateral_type.name}"), web3=self.geb.web3) self.keeper.approve() #flash-swap should disable rebalance assert self.keeper.rebalance_system_coin() == Wad(0) assert isinstance(self.keeper.gas_price, DynamicGasPrice) self.default_gas_price = self.keeper.gas_price.get_gas_price(0) @staticmethod def collateral_balance(address: Address, c: Collateral) -> Wad: assert (isinstance(address, Address)) assert (isinstance(c, Collateral)) return Wad(c.collateral.balance_of(address)) @staticmethod def buy_collateral( collateral_auction_house: FixedDiscountCollateralAuctionHouse, id: int, address: Address, bid_amount: Wad): assert (isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse)) assert (isinstance(id, int)) assert (isinstance(bid_amount, Wad)) current_bid = collateral_auction_house.bids(id) assert current_bid.auction_deadline > datetime.now().timestamp() assert bid_amount <= Wad(current_bid.amount_to_raise) assert collateral_auction_house.buy_collateral( id, bid_amount).transact(from_address=address) @staticmethod def buy_collateral_with_system_coin( geb: GfDeployment, c: Collateral, collateral_auction_house: FixedDiscountCollateralAuctionHouse, id: int, address: Address, bid_amount: Wad): assert (isinstance(geb, GfDeployment)) assert (isinstance(c, Collateral)) assert (isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse)) assert (isinstance(id, int)) assert (isinstance(bid_amount, Wad)) collateral_auction_house.approve( collateral_auction_house.safe_engine(), approval_function=approve_safe_modification_directly( from_address=address)) previous_bid = collateral_auction_house.bids(id) c.approve(address) reserve_system_coin(geb, c, address, bid_amount, extra_collateral=Wad.from_number(2)) TestAuctionKeeperCollateralFlashSwap.buy_collateral( collateral_auction_house, id, address, bid_amount) def simulate_model_bid(self, geb: GfDeployment, c: Collateral, model: object, gas_price: Optional[int] = None): assert (isinstance(geb, GfDeployment)) assert (isinstance(c, Collateral)) assert (isinstance(gas_price, int)) or gas_price is None collateral_auction_house = c.collateral_auction_house initial_bid = collateral_auction_house.bids(model.id) assert initial_bid.amount_to_sell > Wad(0) our_bid = Wad.from_number(500) * initial_bid.amount_to_sell reserve_system_coin(geb, c, self.keeper_address, our_bid, extra_collateral=Wad.from_number(2)) simulate_model_output(model=model, price=Wad.from_number(500), gas_price=gas_price) def test_collateral_auction_house_address(self): assert self.keeper.collateral_auction_house.address == self.collateral.collateral_auction_house.address def test_flash_proxy_settle_auction(self, c: Collateral, web3, geb, auction_id, other_address): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return set_collateral_price(geb, c, Wad.from_number(100)) eth_before = self.web3.eth.getBalance(self.keeper_address.address) # when self.keeper.check_all_auctions() wait_for_other_threads() assert self.web3.eth.getBalance( self.keeper_address.address) > eth_before current_status = collateral_auction_house.bids(auction_id) assert current_status.raised_amount == Rad(0) assert current_status.sold_amount == Wad(0) assert current_status.amount_to_raise == Rad(0) assert current_status.amount_to_sell == Wad(0) assert current_status.auction_deadline == 0 assert current_status.raised_amount == Rad(0) def test_flash_proxy_liquidate_and_settle_auction(self, c: Collateral, web3, geb, auction_id, other_address): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return set_collateral_price(geb, c, Wad.from_number(100)) eth_before = self.web3.eth.getBalance(self.keeper_address.address) auctions_started = collateral_auction_house.auctions_started() # when critical_safe = create_critical_safe(geb, c, bid_size, other_address) self.keeper.check_safes() wait_for_other_threads() assert self.web3.eth.getBalance( self.keeper_address.address) > eth_before assert collateral_auction_house.auctions_started( ) == auctions_started + 1 auction_status = collateral_auction_house.bids(auctions_started + 1) assert auction_status.raised_amount == Rad(0) assert auction_status.sold_amount == Wad(0) assert auction_status.amount_to_raise == Rad(0) assert auction_status.amount_to_sell == Wad(0) assert auction_status.auction_deadline == 0 assert auction_status.raised_amount == Rad(0)
class TestAuctionKeeperFixedDiscountCollateralAuctionHouse(TransactionIgnoringTest): def teardown_method(self, test_method): pass def setup_class(self): """ I'm excluding initialization of a specific collateral perchance we use multiple collaterals to improve test speeds. This prevents us from instantiating the keeper as a class member. """ self.web3 = get_web3() self.geb = get_geb(self.web3) self.keeper_address = get_keeper_address(self.web3) self.collateral = self.geb.collaterals['ETH-B'] self.min_auction = self.collateral.collateral_auction_house.auctions_started() + 1 self.keeper = AuctionKeeper(args=args(f"--eth-from {self.keeper_address.address} " f"--type collateral " f"--from-block 200 " f"--min-auction {self.min_auction} " f"--model ../models/collateral_model.sh " f"--collateral-type {self.collateral.collateral_type.name}"), web3=self.geb.web3) self.keeper.approve() assert isinstance(self.keeper.gas_price, DynamicGasPrice) self.default_gas_price = self.keeper.gas_price.get_gas_price(0) @staticmethod def collateral_balance(address: Address, c: Collateral) -> Wad: assert (isinstance(address, Address)) assert (isinstance(c, Collateral)) return Wad(c.collateral.balance_of(address)) @staticmethod def buy_collateral(collateral_auction_house: FixedDiscountCollateralAuctionHouse, id: int, address: Address, bid_amount: Wad): assert (isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse)) assert (isinstance(id, int)) assert (isinstance(bid_amount, Wad)) current_bid = collateral_auction_house.bids(id) assert current_bid.auction_deadline > datetime.now().timestamp() assert bid_amount <= Wad(current_bid.amount_to_raise) assert collateral_auction_house.buy_collateral(id, bid_amount).transact(from_address=address) @staticmethod def buy_collateral_with_system_coin(geb: GfDeployment, c: Collateral, collateral_auction_house: FixedDiscountCollateralAuctionHouse, id: int, address: Address, bid_amount: Wad): assert (isinstance(geb, GfDeployment)) assert (isinstance(c, Collateral)) assert (isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse)) assert (isinstance(id, int)) assert (isinstance(bid_amount, Wad)) collateral_auction_house.approve(collateral_auction_house.safe_engine(), approval_function=approve_safe_modification_directly(from_address=address)) previous_bid = collateral_auction_house.bids(id) c.approve(address) reserve_system_coin(geb, c, address, bid_amount, extra_collateral=Wad.from_number(2)) TestAuctionKeeperFixedDiscountCollateralAuctionHouse.buy_collateral(collateral_auction_house, id, address, bid_amount) def simulate_model_bid(self, geb: GfDeployment, c: Collateral, model: object, gas_price: Optional[int] = None): assert (isinstance(geb, GfDeployment)) assert (isinstance(c, Collateral)) assert (isinstance(gas_price, int)) or gas_price is None collateral_auction_house = c.collateral_auction_house initial_bid = collateral_auction_house.bids(model.id) assert initial_bid.amount_to_sell > Wad(0) our_bid = Wad.from_number(500) * initial_bid.amount_to_sell reserve_system_coin(geb, c, self.keeper_address, our_bid, extra_collateral=Wad.from_number(2)) simulate_model_output(model=model, price=Wad.from_number(500), gas_price=gas_price) def test_collateral_auction_house_address(self): """ Sanity check ensures the keeper fixture is looking at the correct collateral """ assert self.keeper.collateral_auction_house.address == self.collateral.collateral_auction_house.address def test_should_start_a_new_model_and_provide_it_with_info_on_auction_start(self, auction_id, other_address): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return (model, model_factory) = models(self.keeper, auction_id) # when self.keeper.check_all_auctions() wait_for_other_threads() initial_bid = self.collateral.collateral_auction_house.bids(auction_id) # then model_factory.create_model.assert_called_once_with(Parameters(collateral_auction_house=collateral_auction_house.address, surplus_auction_house=None, debt_auction_house=None, id=auction_id)) # and status = model.send_status.call_args[0][0] assert status.id == auction_id assert status.collateral_auction_house == collateral_auction_house.address assert status.surplus_auction_house is None assert status.debt_auction_house is None assert status.amount_to_sell == initial_bid.amount_to_sell assert status.amount_to_raise == initial_bid.amount_to_raise assert status.block_time > 0 assert status.auction_deadline < status.block_time + collateral_auction_house.total_auction_length() + 1 # cleanup TestAuctionKeeperFixedDiscountCollateralAuctionHouse.buy_collateral_with_system_coin(self.geb, self.collateral, collateral_auction_house, auction_id, other_address, Wad.from_number(30)) def test_should_provide_model_with_updated_info_after_our_partial_bid(self, auction_id): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return (model, model_factory) = models(self.keeper, auction_id) # when self.keeper.check_all_auctions() wait_for_other_threads() initial_status = collateral_auction_house.bids(model.id) # then assert model.send_status.call_count == 1 # when bidding less than the full amount our_balance = Wad(initial_status.amount_to_raise) / Wad.from_number(2) reserve_system_coin(self.geb, self.collateral, self.keeper_address, our_balance) assert initial_status.amount_to_raise != Rad(0) assert self.geb.safe_engine.coin_balance(self.keeper_address) > Rad(0) # Make our balance lte half of the auction size half_amount_to_raise = initial_status.amount_to_raise / Rad.from_number(2) if self.geb.safe_engine.coin_balance(self.keeper_address) >= half_amount_to_raise: burn_amount = self.geb.safe_engine.coin_balance(self.keeper_address) - half_amount_to_raise assert burn_amount < self.geb.safe_engine.coin_balance(self.keeper_address) self.geb.safe_engine.transfer_internal_coins(self.keeper_address, Address("0x0000000000000000000000000000000000000000"), burn_amount).transact() assert self.geb.safe_engine.coin_balance(self.keeper_address) <= half_amount_to_raise assert self.geb.safe_engine.coin_balance(self.keeper_address) > Rad(0) simulate_model_output(model=model, price=None) self.keeper.check_for_bids() # and checking auction status and sending auction status to model self.keeper.check_all_auctions() wait_for_other_threads() # and self.keeper.check_all_auctions() wait_for_other_threads() # then assert model.send_status.call_count > 1 # ensure our bid was processed current_status = collateral_auction_house.bids(model.id) assert current_status.amount_to_raise == initial_status.amount_to_raise assert current_status.amount_to_sell == initial_status.amount_to_sell assert current_status.auction_deadline == initial_status.auction_deadline assert current_status.raised_amount == Rad(our_balance) # and the last status sent to our model reflects our bid status = model.send_status.call_args[0][0] assert status.id == auction_id assert status.collateral_auction_house == collateral_auction_house.address assert status.surplus_auction_house is None assert status.debt_auction_house is None assert status.amount_to_sell == initial_status.amount_to_sell assert status.amount_to_raise == initial_status.amount_to_raise assert status.raised_amount == Rad(our_balance) assert status.auction_deadline == initial_status.auction_deadline # and auction is still active final_status = collateral_auction_house.bids(model.id) assert final_status.amount_to_raise == initial_status.amount_to_raise assert final_status.amount_to_sell == initial_status.amount_to_sell assert final_status.auction_deadline == initial_status.auction_deadline assert final_status.raised_amount == Rad(our_balance) #cleanup our_balance = Wad(initial_status.amount_to_raise) + Wad(1) reserve_system_coin(self.geb, self.collateral, self.keeper_address, our_balance) assert self.geb.safe_engine.coin_balance(self.keeper_address) >= initial_status.amount_to_raise simulate_model_output(model=model, price=None) self.keeper.check_for_bids() self.keeper.check_all_auctions() wait_for_other_threads() # ensure auction has been deleted current_status = collateral_auction_house.bids(model.id) assert current_status.raised_amount == Rad(0) assert current_status.sold_amount == Wad(0) assert current_status.amount_to_raise == Rad(0) assert current_status.amount_to_sell == Wad(0) assert current_status.auction_deadline == 0 assert current_status.raised_amount == Rad(0) def test_auction_deleted_after_our_full_bid(self, auction_id): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return (model, model_factory) = models(self.keeper, auction_id) # when self.keeper.check_all_auctions() wait_for_other_threads() initial_status = collateral_auction_house.bids(model.id) # then assert model.send_status.call_count == 1 # when bidding the full amount our_bid = Wad(initial_status.amount_to_raise) + Wad(1) reserve_system_coin(self.geb, self.collateral, self.keeper_address, our_bid, Wad.from_number(2)) assert self.geb.safe_engine.coin_balance(self.keeper_address) >= initial_status.amount_to_raise simulate_model_output(model=model, price=None) self.keeper.check_for_bids() # and checking auction status and sending auction status to model self.keeper.check_all_auctions() wait_for_other_threads() # and self.keeper.check_all_auctions() wait_for_other_threads() # ensure our bid was processed and auction has been deleted current_status = collateral_auction_house.bids(model.id) assert current_status.raised_amount == Rad(0) assert current_status.sold_amount == Wad(0) assert current_status.amount_to_raise == Rad(0) assert current_status.amount_to_sell == Wad(0) assert current_status.auction_deadline == 0 assert current_status.raised_amount == Rad(0) def test_should_provide_model_with_updated_info_after_somebody_else_partial_bids(self, auction_id, other_address): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return (model, model_factory) = models(self.keeper, auction_id) # when self.keeper.check_all_auctions() wait_for_other_threads() # then assert model.send_status.call_count == 1 # when collateral_auction_house.approve(collateral_auction_house.safe_engine(), approval_function=approve_safe_modification_directly(from_address=other_address)) previous_bid = collateral_auction_house.bids(auction_id) new_bid_amount = Wad.from_number(30) self.buy_collateral_with_system_coin(self.geb, self.collateral, collateral_auction_house, model.id, other_address, new_bid_amount) # and self.keeper.check_all_auctions() wait_for_other_threads() # then assert model.send_status.call_count > 1 # and status = model.send_status.call_args[0][0] assert status.id == auction_id assert status.collateral_auction_house == collateral_auction_house.address assert status.surplus_auction_house is None assert status.debt_auction_house is None assert status.raised_amount == Rad(new_bid_amount) assert status.amount_to_sell == previous_bid.amount_to_sell assert status.amount_to_raise == previous_bid.amount_to_raise assert status.block_time > 0 assert status.auction_deadline > status.block_time def test_should_not_do_anything_if_no_output_from_model(self): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return previous_block_number = self.web3.eth.blockNumber # when # [no output from model] # and self.keeper.check_all_auctions() wait_for_other_threads() # then assert self.web3.eth.blockNumber == previous_block_number @pytest.mark.skip("failing after adding rebalance_system_coin() in check_bids") def test_should_increase_gas_price_of_pending_transactions_if_model_increases_gas_price(self, auction_id): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return (model, model_factory) = models(self.keeper, auction_id) # when bid_price = Wad.from_number(20.0) reserve_system_coin(self.geb, self.collateral, self.keeper_address, bid_price * bid_size * 2, Wad.from_number(2)) self.keeper.rebalance_system_coin() simulate_model_output(model=model, price=bid_price, gas_price=10) # and self.start_ignoring_transactions() # and self.keeper.check_all_auctions() self.keeper.check_for_bids() # and self.end_ignoring_transactions() # and simulate_model_output(model=model, price=bid_price, gas_price=15) # and self.keeper.check_for_bids() wait_for_other_threads() # then #assert collateral_auction_house.bids(auction_id).raised_amount == Rad(bid_price * bid_size) assert self.web3.eth.getBlock('latest', full_transactions=True).transactions[0].gasPrice == 15 def test_should_obey_gas_price_provided_by_the_model(self, auction_id): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return (model, model_factory) = models(self.keeper, auction_id) # when self.simulate_model_bid(self.geb, self.collateral, model, gas_price=175000) # and self.keeper.check_all_auctions() self.keeper.check_for_bids() wait_for_other_threads() # then assert self.web3.eth.getBlock('latest', full_transactions=True).transactions[0].gasPrice == 175000 def test_should_use_default_gas_price_if_not_provided_by_the_model(self, auction_id): # given collateral_auction_house = self.collateral.collateral_auction_house if not isinstance(collateral_auction_house, FixedDiscountCollateralAuctionHouse): return (model, model_factory) = models(self.keeper, auction_id) # when self.simulate_model_bid(self.geb, self.collateral, model) # and self.keeper.check_all_auctions() self.keeper.check_for_bids() wait_for_other_threads() # then assert self.web3.eth.getBlock('latest', full_transactions=True).transactions[0].gasPrice == \ self.default_gas_price @classmethod def teardown_class(cls): pop_debt_and_settle_debt(get_web3(), get_geb(get_web3()), past_blocks=1200, require_settle_debt=True) cls.cleanup_debt(get_web3(), get_geb(get_web3()), get_other_address(get_web3())) @classmethod def cleanup_debt(cls, web3, geb, address): # Cancel out debt unqueued_unauctioned_debt = geb.accounting_engine.unqueued_unauctioned_debt() total_on_auction_debt = geb.accounting_engine.total_on_auction_debt() system_coin_needed = unqueued_unauctioned_debt + total_on_auction_debt #system_coin_needed = geb.safe_engine.debt_balance(geb.accounting_engine.address) if system_coin_needed == Rad(0): return # Need to add Wad(1) when going from Rad to Wad reserve_system_coin(geb, geb.collaterals['ETH-A'], get_our_address(web3), Wad(system_coin_needed) + Wad(1)) assert geb.safe_engine.coin_balance(get_our_address(web3)) >= system_coin_needed # transfer system coin to accounting engine geb.safe_engine.transfer_internal_coins(get_our_address(web3), geb.accounting_engine.address, system_coin_needed).transact(from_address=get_our_address(web3)) system_coin_accounting_engine = geb.safe_engine.coin_balance(geb.accounting_engine.address) assert system_coin_accounting_engine >= system_coin_needed assert geb.accounting_engine.settle_debt(unqueued_unauctioned_debt).transact() assert geb.accounting_engine.unqueued_unauctioned_debt() == Rad(0) assert geb.accounting_engine.total_queued_debt() == Rad(0) if geb.accounting_engine.total_on_auction_debt() > Rad(0): geb.accounting_engine.cancel_auctioned_debt_with_surplus(total_on_auction_debt).transact() assert geb.accounting_engine.total_on_auction_debt() == Rad(0) assert geb.safe_engine.debt_balance(geb.accounting_engine.address) == Rad(0)