class TestRebalance(TestVatDai): def create_keeper(self, mocker, dai_target="all"): # Create a keeper mocker.patch("web3.net.Net.peer_count", return_value=1) self.keeper = AuctionKeeper(args=args( f"--eth-from {self.keeper_address} " f"--type flip --ilk ETH-C --bid-only " f"--vat-dai-target {dai_target} " f"--return-gem-interval 3 " f"--model ./bogus-model.sh"), web3=self.web3) assert self.web3.eth.defaultAccount == self.keeper_address.address self.web3 = self.keeper.web3 self.mcd = self.keeper.mcd assert self.keeper.auctions # Changed the collateral to ETH-C because our testchain didn't have dust set for ETH-A or ETH-B self.collateral = self.keeper.collateral self.collateral.approve(self.keeper_address) self.thread = threading.Thread(target=self.keeper.main, daemon=True) self.thread.start() return self.keeper def shutdown_keeper(self): self.keeper.shutdown( ) # HACK: Lifecycle doesn't invoke this as expected self.keeper.lifecycle.terminate("unit test completed") self.thread.join() # HACK: Lifecycle leaks threads; this needs to be fixed in pymaker import ctypes while threading.active_count() > 1: for thread in threading.enumerate(): if thread is not threading.current_thread(): print(f"Attempting to kill thread {thread}") sysexit = ctypes.py_object( SystemExit ) # Creates a C pointer to a Python "SystemExit" exception ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread.ident), sysexit) time.sleep(1) # Ensure we don't leak threads, which would break wait_for_other_threads() later on assert threading.active_count() == 1 assert self.get_dai_vat_balance() == Wad(0) @pytest.mark.timeout(60) def test_balance_added_after_startup(self, mocker): try: # given gem balances after starting keeper token_balance_before = self.get_dai_token_balance() self.create_keeper(mocker) time.sleep(6) # wait for keeper to join everything on startup vat_balance_before = self.get_dai_vat_balance() assert self.get_dai_token_balance() == Wad(0) assert vat_balance_before == Wad(0) # when adding Dai purchase_dai(Wad.from_number(77), self.keeper_address) assert self.get_dai_token_balance() == Wad.from_number(77) # and pretending there's a bid which requires Dai reservoir = Reservoir(self.keeper.vat.dai(self.keeper_address)) assert self.keeper.check_bid_cost(id=1, cost=Rad.from_number(20), reservoir=reservoir) # then ensure all Dai is joined assert self.get_dai_token_balance() == Wad(0) assert self.get_dai_vat_balance() == Wad.from_number(77) # when adding more Dai and pretending there's a bid we cannot cover purchase_dai(Wad.from_number(23), self.keeper_address) assert self.get_dai_token_balance() == Wad.from_number(23) reservoir = Reservoir(self.keeper.vat.dai(self.keeper_address)) assert not self.keeper.check_bid_cost( id=2, cost=Rad(Wad.from_number(120)), reservoir=reservoir) # then ensure the added Dai was joined anyway assert self.get_dai_token_balance() == Wad(0) assert self.get_dai_vat_balance() == Wad.from_number(100) finally: self.shutdown_keeper() self.give_away_dai() @pytest.mark.timeout(600) def test_fixed_dai_target(self, mocker): try: # given a keeper configured to maintained a fixed amount of Dai target = Wad.from_number(100) purchase_dai(target * 2, self.keeper_address) assert self.get_dai_token_balance() == Wad.from_number(200) self.create_keeper(mocker, target) time.sleep(6) # wait for keeper to join 100 on startup vat_balance_before = self.get_dai_vat_balance() assert vat_balance_before == target # when spending Dai assert self.keeper.dai_join.exit(self.keeper_address, Wad.from_number(22)).transact() assert self.get_dai_vat_balance() == Wad.from_number(78) # and pretending there's a bid which requires more Dai reservoir = Reservoir(self.keeper.vat.dai(self.keeper_address)) assert self.keeper.check_bid_cost(id=3, cost=Rad.from_number(79), reservoir=reservoir) # then ensure Dai was joined up to the target assert self.get_dai_vat_balance() == target # when pretending there's a bid which we have plenty of Dai to cover reservoir = Reservoir(self.keeper.vat.dai(self.keeper_address)) assert self.keeper.check_bid_cost(id=4, cost=Rad(Wad.from_number(1)), reservoir=reservoir) # then ensure Dai levels haven't changed assert self.get_dai_vat_balance() == target finally: self.shutdown_keeper() @pytest.mark.timeout(30) def test_collateral_removal(self, mocker): try: # given a keeper configured to return all collateral upon rebalance token_balance_before = self.get_gem_token_balance() vat_balance_before = self.get_gem_vat_balance() self.create_keeper(mocker) time.sleep(6) # wait for keeper to startup assert self.get_gem_token_balance() == token_balance_before assert self.get_gem_vat_balance() == vat_balance_before # when some ETH was wrapped and joined wrap_eth(self.mcd, self.keeper_address, Wad.from_number(1.53)) token_balance = self.get_gem_token_balance() assert token_balance > Wad(0) self.collateral.adapter.join(self.keeper_address, token_balance).transact() assert self.get_gem_vat_balance( ) == vat_balance_before + token_balance # then wait to ensure collateral was exited automatically time.sleep(4) assert self.get_gem_vat_balance() == Wad(0) assert self.get_gem_token_balance( ) == token_balance_before + Wad.from_number(1.53) finally: self.shutdown_keeper()
class TestSwapCollateralSlippage(TestSAFEEngineSystemCoin): def create_keeper(self, mocker, system_coin_target="all"): # Create a keeper mocker.patch("web3.net.Net.peer_count", return_value=1) self.keeper = AuctionKeeper(args=args( f"--eth-from {self.keeper_address} " f"--type collateral --collateral-type ETH-B --bid-only " f"--safe-engine-system-coin-target {system_coin_target} " f"--return-collateral-interval 3 " f"--swap-collateral " f"--max-swap-slippage 0.00001 " f"--model ./bogus-model.sh"), web3=self.web3) assert self.web3.eth.defaultAccount == self.keeper_address.address self.web3 = self.keeper.web3 self.geb = self.keeper.geb assert self.keeper.auctions # Changed the collateral to ETH-C because our testchain didn't have dust set for ETH-A or ETH-B self.collateral = self.keeper.collateral self.collateral.approve(self.keeper_address) self.thread = threading.Thread(target=self.keeper.main, daemon=True) self.thread.start() return self.keeper def shutdown_keeper(self): self.keeper.shutdown( ) # HACK: Lifecycle doesn't invoke this as expected self.keeper.lifecycle.terminate("unit test completed") self.thread.join() # HACK: Lifecycle leaks threads; this needs to be fixed in pyflex import ctypes while threading.active_count() > 1: for thread in threading.enumerate(): if thread is not threading.current_thread(): print(f"Attempting to kill thread {thread}") sysexit = ctypes.py_object( SystemExit ) # Creates a C pointer to a Python "SystemExit" exception ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread.ident), sysexit) time.sleep(1) # Ensure we don't leak threads, which would break wait_for_other_threads() later on assert threading.active_count() == 1 assert self.get_system_coin_safe_engine_balance() == Wad(0) @pytest.mark.timeout(30) def test_swap_collateral(self, mocker): try: # Starting collateral balances token_balance_before = self.get_collateral_token_balance() safe_engine_balance_before = self.get_collateral_safe_engine_balance( ) # Start keeper self.create_keeper(mocker) time.sleep(6) # wait for keeper to startup # Collateral balances are unchanged after keeper startup assert self.get_collateral_token_balance() == token_balance_before assert self.get_collateral_safe_engine_balance( ) == safe_engine_balance_before # Keeper's starting syscoin balance syscoin_balance_before = self.geb.system_coin.balance_of( self.keeper_address) # when some ETH was wrapped and joined wrap_eth(self.geb, self.keeper_address, Wad.from_number(1.53)) token_balance = self.get_collateral_token_balance() assert token_balance > Wad(0) self.collateral.adapter.join(self.keeper_address, token_balance).transact() # then wait to ensure collateral was exited but not swapped due to slippage time.sleep(4) # collateral exited assert self.get_collateral_safe_engine_balance() == Wad(0) # collateral withdrawn to ETH assert self.get_collateral_token_balance() == Wad(0) # ETH not swapped for syscoin assert not self.geb.system_coin.balance_of( self.keeper_address) > syscoin_balance_before finally: self.shutdown_keeper() self.give_away_system_coin()