示例#1
0
class BiteKeeper:
    """Keeper to bite undercollateralized cups."""

    logger = logging.getLogger('bite-all-keeper')

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='bite-keeper')
        parser.add_argument(
            "--rpc-host",
            type=str,
            default="http://localhost:8545",
            help="JSON-RPC endpoint URI with port " +
                "(default: `http://localhost:8545')"
        )
        parser.add_argument(
            "--rpc-timeout",
            help="JSON-RPC timeout (in seconds, default: 10)",
            default=10,
            type=int
        )
        parser.add_argument(
            "--eth-from",
            help="Ethereum account from which to send transactions",
            required=True,
            type=str
        )
        parser.add_argument(
            "--eth-key",
            type=str,
            nargs='*',
            help="Ethereum private key(s) to use " +
                "(e.g. 'key_file=/path/to/keystore.json," +
                "pass_file=/path/to/passphrase.txt')"
        )
        parser.add_argument(
            "--tub-address",
            help="Ethereum address of the Tub contract",
            required=True,
            type=str
        )
        parser.add_argument(
            "--graphql-url",
            type=str,
            default="https://sai-mainnet.makerfoundation.com/v1",
            help="GraphQL URL " +
                "(default: `https://sai-mainnet.makerfoundation.com/v1')"
        )
        parser.add_argument(
            "--bitecdps-address",
            help="Ethereum address of the BiteCdps contract",
            type=str
        )
        parser.add_argument(
            "--top",
            help="Quickly process N top bites, (default: 500)",
            default=500,
            type=int
        )
        parser.add_argument(
            "--chunks",
            help="Process top bites in chunks of N, (default: 100)",
            default=100,
            type=int
        )
        parser.add_argument(
            "--debug",
            help="Enable debug output",
            dest='debug',
            action='store_true'
        )
        self.arguments = parser.parse_args(args)

        # Configure connection to the chain
        provider = HTTPProvider(
            endpoint_uri=self.arguments.rpc_host,
            request_kwargs={'timeout': self.arguments.rpc_timeout}
        )
        self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else Web3(provider)
        self.web3.eth.defaultAccount = self.arguments.eth_from
        self.our_address = Address(self.arguments.eth_from)
        register_keys(self.web3, self.arguments.eth_key)
        self.tub = Tub(
            web3=self.web3, address=Address(self.arguments.tub_address)
        )
        self.vox = Vox(web3=self.web3, address=self.tub.vox())
        self.top = self.arguments.top
        self.chunks = self.arguments.chunks

        if self.arguments.bitecdps_address and self.arguments.graphql_url:
            self.use_bitecdps = True
            self.bitecdps = BiteCdps(
                web3=self.web3, address=Address(self.arguments.bitecdps_address)
            )
            self.graphql_url = self.arguments.graphql_url
        else:
            self.use_bitecdps = False

        logging.basicConfig(
            format='%(asctime)-15s %(levelname)-8s %(message)s',
            level=(logging.DEBUG if self.arguments.debug else logging.INFO)
        )

    def main(self):
        with Lifecycle(self.web3) as lifecycle:
            self.lifecycle = lifecycle
            lifecycle.on_block(self.check_all_cups)

    def check_all_cups(self):
        if self.tub._contract.functions.off().call():
            self.logger.info('Single Collateral Dai has been Caged')
            self.logger.info('Starting to bite all cups in the tub contract')

            # Read some things that wont change across cups
            axe = self.tub.axe() # Liquidation penalty [RAY] Fixed at 1 RAY at cage
            par = self.vox.par() # Dai Targe Price     [RAY] Typically 1 RAY
            tag = self.tub.tag() # Ref/Oracle price    [RAY] Fixed at shutdown

            if self.use_bitecdps:
                self.call_bitecdps()
            else:
                for cup_id in range(self.tub.cupi()):
                    self.check_cup(cup_id+1, axe, par, tag)

            self.lifecycle.terminate()
        else:
            self.logger.info('Single Collateral Dai live')

    def check_cup(self, cup_id, axe: Ray, par: Ray, tag: Ray):
        cup = self.tub.cups(cup_id)
        rue = Ray(self.tub.tab(cup_id)) # Amount of Debt[RAY]

        # Amount owed in SKR, including liquidation penalty
        # var owe = rdiv(rmul(rmul(rue, axe), vox.par()), tag());
        owe = ((rue * axe) * par) / tag

        # Bite cups with owe over a threshold that haven't been bitten before
        if owe > Ray.from_number(0) and cup.art != Wad.from_number(0):
            self.logger.info(
                f'Bite cup {cup_id} with owe of {owe} and ink of {cup.ink}'
            )
            self.tub.bite(cup_id).transact(gas_price=self.gas_price())

    def call_bitecdps(self):
        self.logger.info(f'Will bite top {self.top} CDPs')
        # cdps = [i+1 for i in range(self.tub.cupi())]
        cdps = self.get_cdps()
        self.logger.info(f'found {len(cdps)} CDPs')
        for i in range(0, len(cdps), self.chunks):
            chunk = cdps[i:i+self.chunks]
            self.logger.info(f'BiteCdps.bite({chunk})')
            self.bitecdps.bite(chunk).transact(gas_price=self.gas_price())

    def get_cdps(self):
        client = GraphQLClient(self.graphql_url)
        result = client.execute(QUERY.replace('XXX', f'{self.top}'))
        data = json.loads(result)
        cdps = []
        for cdp in data["data"]["allCups"]["nodes"]:
            cdps.append(cdp["id"])
        return cdps

    def gas_price(self):
        """ IncreasingGasPrice """
        GWEI = 1000000000

        return IncreasingGasPrice(initial_price=5*GWEI,
                                  increase_by=10*GWEI,
                                  every_secs=60,
                                  max_price=300*GWEI)
示例#2
0
class BiteKeeper:
    """Keeper to bite undercollateralized cups."""

    logger = logging.getLogger('bite-all-keeper')

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='bite-keeper')
        parser.add_argument("--rpc-host",
                            help="JSON-RPC host (default: `localhost')",
                            default="localhost",
                            type=str)
        parser.add_argument("--rpc-port",
                            help="JSON-RPC port (default: `8545')",
                            default=8545,
                            type=int)
        parser.add_argument("--rpc-timeout",
                            help="JSON-RPC timeout (in seconds, default: 10)",
                            default=10,
                            type=int)
        parser.add_argument(
            "--eth-from",
            help="Ethereum account from which to send transactions",
            required=True,
            type=str)
        parser.add_argument(
            "--eth-key",
            type=str,
            nargs='*',
            help=
            "Ethereum private key(s) to use (e.g. 'key_file=/path/to/keystore.json,pass_file=/path/to/passphrase.txt')"
        )
        parser.add_argument("--tub-address",
                            help="Ethereum address of the Tub contract",
                            required=True,
                            type=str)
        parser.add_argument("--debug",
                            help="Enable debug output",
                            dest='debug',
                            action='store_true')
        self.arguments = parser.parse_args(args)

        self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3(
            HTTPProvider(
                endpoint_uri=
                f"https://{self.arguments.rpc_host}:{self.arguments.rpc_port}",
                request_kwargs={"timeout": self.arguments.rpc_timeout}))
        self.web3.eth.defaultAccount = self.arguments.eth_from
        self.our_address = Address(self.arguments.eth_from)
        register_keys(self.web3, self.arguments.eth_key)
        self.tub = Tub(web3=self.web3,
                       address=Address(self.arguments.tub_address))
        self.vox = Vox(web3=self.web3, address=self.tub.vox())

        logging.basicConfig(
            format='%(asctime)-15s %(levelname)-8s %(message)s',
            level=(logging.DEBUG if self.arguments.debug else logging.INFO))

    def main(self):
        with Lifecycle(self.web3) as lifecycle:
            lifecycle.on_block(self.check_all_cups)

    def check_all_cups(self):
        if self.tub._contract.functions.off().call():
            self.logger.info('Single Collateral Dai has been Caged')
            self.logger.info('Starting to bite all cups in the tub contract')

            # Read some things that wont change across cups
            axe = self.tub.axe(
            )  # Liquidation penalty [RAY] Fixed at 1 RAY at cage
            par = self.vox.par()  # Dai Targe Price     [RAY] Typically 1 RAY
            tag = self.tub.tag()  # Ref/Oracle price    [RAY] Fixed at shutdown

            for cup_id in range(self.tub.cupi()):
                self.check_cup(cup_id + 1, axe, par, tag)
        else:
            self.logger.info('Single Collateral Dai live')

    def check_cup(self, cup_id, axe: Ray, par: Ray, tag: Ray):
        cup = self.tub.cups(cup_id)
        rue = Ray(self.tub.tab(cup_id))  # Amount of Debt[RAY]

        # Amount owed in SKR, including liquidation penalty
        # var owe = rdiv(rmul(rmul(rue, axe), vox.par()), tag());
        owe = ((rue * axe) * par) / tag

        # Bite cups with owe over a threshold that haven't been bitten before
        if owe > Ray.from_number(0) and cup.art != Wad.from_number(0):
            self.logger.info(
                f'Bite cup {cup_id} with owe of {owe} and ink of {cup.ink}')
            self.tub.bite(cup_id).transact(gas_price=self.gas_price())

    def gas_price(self):
        """ IncreasingGasPrice """
        GWEI = 1000000000

        return IncreasingGasPrice(initial_price=5 * GWEI,
                                  increase_by=10 * GWEI,
                                  every_secs=60,
                                  max_price=300 * GWEI)