def check_eta(self): """ Cast spells that meet their schedule. First, the local database is updated with spells that have been scheduled between the last block reviewed and the most recent block receieved. Next, it simply traverses through each spell address, checking if its schedule has been reached/passed. If it has, it attempts to `cast` the spell. """ blockNumber = self.web3.eth.blockNumber now = self.web3.eth.getBlock(blockNumber).timestamp self.logger.info(f'Checking scheduled spells on block {blockNumber}') self.database.update_db_etas(blockNumber) etas = self.database.db.get(doc_id=3)["upcoming_etas"] yays = list(etas.keys()) for yay in yays: if etas[yay] <= now: spell = DSSSpell(self.web3, Address(yay)) if spell.done() == False: receipt = spell.cast().transact(gas_price=self.gas_price()) if receipt.successful == True: del etas[yay] else: del etas[yay] self.database.db.update({'upcoming_etas': etas}, doc_ids=[3])
def check_schedule(self, spell: DSSSpell, yay: str): """ Schedules spells that haven't been scheduled nor casted """ if is_contract_at(self.web3, Address(yay)): # Functional with DSSSpells but not DSSpells (not compatiable with DSPause) if spell.done() == False and self.database.get_eta_inUnix( spell) == 0: self.logger.info(f'Scheduling spell ({yay})') spell.schedule().transact(gas_price=self.gas_price())
def check_hat(self): """ Ensures the Hat is on the proposal (spell, EOA, multisig, etc) with the most approval. First, the local database is updated with proposal addresses (yays) that have been `etched` in DSChief between the last block reviewed and the most recent block receieved. Next, it simply traverses through each address, checking if its approval has surpased the current Hat. If it has, it will `lift` the hat. If the current or new hat hasn't been casted nor plotted in the pause, it will `schedule` the spell """ blockNumber = self.web3.eth.blockNumber self.logger.info(f'Checking Hat on block {blockNumber}') self.database.update_db_yays(blockNumber) yays = self.database.db.get(doc_id=2)["yays"] hat = self.dss.ds_chief.get_hat().address hatApprovals = self.dss.ds_chief.get_approvals(hat) contender, highestApprovals = hat, hatApprovals for yay in yays: contenderApprovals = self.dss.ds_chief.get_approvals(yay) if contenderApprovals > highestApprovals: contender = yay highestApprovals = contenderApprovals if contender != hat: self.logger.info(f'Lifting hat') self.logger.info(f'Old hat ({hat}) with Approvals {hatApprovals}') self.logger.info( f'New hat ({contender}) with Approvals {highestApprovals}') self.dss.ds_chief.lift( Address(contender)).transact(gas_price=self.gas_price) else: self.logger.info( f'Current hat ({hat}) with Approvals {hatApprovals}') # Read the hat; either is equivalent to the contender or old hat hatNew = self.dss.ds_chief.get_hat().address if hatNew != hat: self.logger.info(f'Confirmed ({contender}) now has the hat') spell = DSSSpell(self.web3, Address(hatNew)) if is_contract_at( self.web3, Address(hatNew)) else None # Schedules spells that haven't been scheduled nor casted if spell is not None: # Functional with DSSSpells but not DSSpells (not compatiable with DSPause) if spell.done() == False and self.database.get_eta_inUnix( spell) == 0: self.logger.info(f'Scheduling spell ({yay})') spell.schedule().transact(gas_price=self.gas_price) else: self.logger.warning( f'Spell is an EOA or 0x0, so keeper will not attempt to call schedule()' )
def get_etas(self, yays, blockNumber: int): """ Get all upcoming etas """ etas = {} for yay in yays: #Check if yay is an address to an EOA or a contract if is_contract_at(self.web3, Address(yay)): spell = DSSSpell(self.web3, Address(yay)) eta = self.get_eta_inUnix(spell) if (eta > 0) and (spell.done() == False): etas[spell.address.address] = eta return etas
def check_hat(self): """ Ensures the Hat is on the proposal (spell, EOA, multisig, etc) with the most approval. First, the local database is updated with proposal addresses (yays) that have been `etched` in DSChief between the last block reviewed and the most recent block receieved. Next, it simply traverses through each address, checking if its approval has surpased the current Hat. If it has, it will `lift` the hat. If the current or new hat hasn't been casted nor plotted in the pause, it will `schedule` the spell """ blockNumber = self.web3.eth.blockNumber self.logger.info(f'Checking Hat on block {blockNumber}') self.database.update_db_yays(blockNumber) yays = self.database.db.get(doc_id=2)["yays"] hat = self.dss.ds_chief.get_hat().address hatApprovals = self.dss.ds_chief.get_approvals(hat) contender, highestApprovals = hat, hatApprovals for yay in yays: contenderApprovals = self.dss.ds_chief.get_approvals(yay) if contenderApprovals > highestApprovals: contender = yay highestApprovals = contenderApprovals if contender != hat: self.logger.info(f'Lifting hat') self.logger.info(f'Old hat ({hat}) with Approvals {hatApprovals}') self.logger.info( f'New hat ({contender}) with Approvals {highestApprovals}') self.dss.ds_chief.lift( Address(contender)).transact(gas_price=self.gas_price()) spell = DSSSpell(self.web3, Address(contender)) elif hat != "0x0000000000000000000000000000000000000000": self.logger.info( f'Current hat ({hat}) with Approvals {hatApprovals}') spell = DSSSpell(self.web3, Address(hat)) else: return True self.check_schedule(spell, yay) return True
def test_check_eta(self, mcd: DssDeployment, keeper: ChiefKeeper): print_out("test_check_eta") keeper.initial_query() etas = keeper.database.db.get(doc_id=3)['upcoming_etas'] hat = mcd.ds_chief.get_hat() verify([hat.address], etas, 1) keeper.check_eta() # Confirm that the spell was casted and that the database was updated assert DSSSpell(mcd.web3, Address(hat)).done() == True etas = keeper.database.db.get(doc_id=3)['upcoming_etas'] verify([], etas, 0)
def test_setup(self, mcd: DssDeployment, our_address: Address, guy_address: Address): print_out("test_setup") # Give 1000 MKR to our_address amount = Wad.from_number(1000) mint_mkr(mcd.mkr, our_address, amount) assert mcd.mkr.balance_of(our_address) == amount #Give 2000 MKR to guy_address guyAmount = Wad.from_number(2000) mint_mkr(mcd.mkr, guy_address, guyAmount) assert mcd.mkr.balance_of(guy_address) == guyAmount # Lock MKR in DS-Chief assert mcd.mkr.approve( mcd.ds_chief.address).transact(from_address=our_address) assert mcd.mkr.approve( mcd.ds_chief.address).transact(from_address=guy_address) assert mcd.ds_chief.lock(amount).transact(from_address=our_address) assert mcd.ds_chief.lock(guyAmount).transact(from_address=guy_address) # Deploy spell self.spell = DSSSpell.deploy(mcd.web3, mcd.pause.address, mcd.vat.address) # Vote 1000 mkr on our address and guy_address # Vote 2000 mkr on global spell address assert mcd.ds_chief.vote_yays( [our_address.address, guy_address.address]).transact(from_address=our_address) assert mcd.ds_chief.vote_yays([self.spell.address.address ]).transact(from_address=guy_address) # At this point there are two yays in the chief, one to our_address and the other to the spell address pytest.global_spell = self.spell
def test_check_hat(self, mcd: DssDeployment, keeper: ChiefKeeper, guy_address: Address): print_out("test_check_hat") # Confirm the hat with the most approval is unchanged oldHat = mcd.ds_chief.get_hat() keeper.check_hat() newHat = mcd.ds_chief.get_hat() assert oldHat.address == newHat.address # Move the 2000 MKR vote from the last spell in test_database.py to new spell self.spell = DSSSpell.deploy(mcd.web3, mcd.pause.address, mcd.vat.address) assert mcd.ds_chief.vote_yays([self.spell.address.address ]).transact(from_address=guy_address) keeper.check_hat() # Confirm that the hat has been lifted newerHat = mcd.ds_chief.get_hat() assert newerHat.address == self.spell.address.address # Confirm that the spell was scheduled assert self.spell.eta() != 0
def get_eta_inUnix(self, spell: DSSSpell) -> int: eta = spell.eta() etaInUnix = eta.replace(tzinfo=timezone.utc).timestamp() return etaInUnix