Exemple #1
0
                    type=int, default="180")
parser.add_argument("-q", "--quiet", help="only output warnings",
                    action='store_true')
parser.add_argument('cdps', metavar='CDP', type=int, nargs='+',
                    help='CDP id(s) to check')
args = parser.parse_args()

web3 = Web3(HTTPProvider(endpoint_uri="https://mainnet.infura.io/metamask"))

tub = Tub(web3=web3, address=Address('0x448a5065aebb8e423f0896e6c5d525c040f59af3'))
minimum_ratio=Ray.from_number(args.warnratio / 100)
requirements_satisfied=True

for cup_id in args.cdps:
    cup = tub.cups(cup_id)
    pro = tub.ink(cup_id)*tub.tag()
    tab = tub.tab(cup_id)
    if not args.quiet:
        print(f'CDP #{cup_id}')
        print(f'  Owner {cup.lad}')
        print(f'  Deposited {float(cup.ink):.8} PETH')
        print(f'  Debt {float(tab):.8} DAI')
    if tab > Wad(0):
        current_ratio = Ray(pro / tab)
        if not args.quiet:
            print(f'  Current Ratio {float(current_ratio):.2%}')
        is_undercollateralized = (current_ratio < minimum_ratio)
        if is_undercollateralized:
            print(f'CDP #{cup_id} is {float(current_ratio):.2%} which is less than {float(minimum_ratio):.2%}')
            requirements_satisfied=False
Exemple #2
0
class CdpKeeper:
    """Keeper to actively manage open CDPs."""

    logger = logging.getLogger('cdp-keeper')

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='cdp-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("--tub-address",
                            help="Ethereum address of the Tub contract",
                            required=True,
                            type=str)
        parser.add_argument(
            "--min-margin",
            help=
            "Margin between the liquidation ratio and the top-up threshold",
            type=float,
            required=True)
        parser.add_argument(
            "--top-up-margin",
            help="Margin between the liquidation ratio and the top-up target",
            type=float,
            required=True)
        parser.add_argument("--max-sai", type=float, required=True)
        parser.add_argument("--avg-sai", type=float, required=True)
        parser.add_argument("--gas-price",
                            help="Gas price in Wei (default: node default)",
                            default=0,
                            type=int)
        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"http://{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)
        self.tub = Tub(web3=self.web3,
                       address=Address(self.arguments.tub_address))
        self.sai = ERC20Token(web3=self.web3, address=self.tub.sai())

        self.liquidation_ratio = self.tub.mat()
        self.minimum_ratio = self.liquidation_ratio + Ray.from_number(
            self.arguments.min_margin)
        self.target_ratio = self.liquidation_ratio + Ray.from_number(
            self.arguments.top_up_margin)
        self.max_sai = Wad.from_number(self.arguments.max_sai)
        self.avg_sai = Wad.from_number(self.arguments.avg_sai)

        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_startup(self.startup)
            lifecycle.on_block(self.check_all_cups)

    def startup(self):
        self.approve()

    def approve(self):
        self.tub.approve(directly(gas_price=self.gas_price()))

    def check_all_cups(self):
        for cup in self.our_cups():
            self.check_cup(cup.cup_id)

    def check_cup(self, cup_id: int):
        assert (isinstance(cup_id, int))

        # If cup is undercollateralized and the amount of SAI we are holding is more than `--max-sai`
        # then we wipe some debt first so our balance reaches `--avg-sai`. Bear in mind that it is
        # possible that we pay all out debt this way and our SAI balance will still be higher
        # than `--max-sai`.
        if self.is_undercollateralized(cup_id) and self.sai.balance_of(
                self.our_address) > self.max_sai:
            amount_of_sai_to_wipe = self.calculate_sai_wipe()
            if amount_of_sai_to_wipe > Wad(0):
                self.tub.wipe(
                    cup_id,
                    amount_of_sai_to_wipe).transact(gas_price=self.gas_price())

        # If cup is still undercollateralized, calculate the amount of SKR needed to top it up so
        # the collateralization level reaches `--top-up-margin`. If we have enough ETH, exchange
        # in to SKR and then top-up the cup.
        if self.is_undercollateralized(cup_id):
            top_up_amount = self.calculate_skr_top_up(cup_id)
            if top_up_amount <= eth_balance(self.web3, self.our_address):
                # TODO we do not always join with the same amount as the one we lock!
                self.tub.join(top_up_amount).transact(
                    gas_price=self.gas_price())
                self.tub.lock(
                    cup_id, top_up_amount).transact(gas_price=self.gas_price())
            else:
                self.logger.info(
                    f"Cannot top-up as our balance is less than {top_up_amount} ETH."
                )

    def our_cups(self):
        for cup_id in range(1, self.tub.cupi() + 1):
            cup = self.tub.cups(cup_id)
            if cup.lad == self.our_address:
                yield cup

    def is_undercollateralized(self, cup_id) -> bool:
        pro = self.tub.ink(cup_id) * self.tub.tag()
        tab = self.tub.tab(cup_id)
        if tab > Wad(0):
            current_ratio = Ray(pro / tab)
            # Prints the Current CDP Ratio and the Minimum Ratio specified under --min-margin
            print(f'Current Ratio {current_ratio}')
            print(f'Minimum Ratio {self.minimum_ratio}')
            return current_ratio < self.minimum_ratio
        else:
            return False

    def calculate_sai_wipe(self) -> Wad:
        """Calculates the amount of SAI that can be wiped.

        Calculates the amount of SAI than can be wiped in order to bring the SAI holdings
        to `--avg-sai`.
        """
        return Wad.max(
            self.sai.balance_of(self.our_address) - self.avg_sai, Wad(0))

    def calculate_skr_top_up(self, cup_id) -> Wad:
        """Calculates the required top-up in SKR.

        Calculates the required top-up in SKR in order to bring the collateralization level
        of the cup to `--target-ratio`.
        """
        pro = self.tub.ink(cup_id) * self.tub.tag()
        tab = self.tub.tab(cup_id)
        if tab > Wad(0):
            current_ratio = Ray(pro / tab)
            return Wad.max(
                tab * (Wad(self.target_ratio - current_ratio) /
                       Wad.from_number(self.tub.tag())), Wad(0))
        else:
            return Wad(0)

    def gas_price(self):
        if self.arguments.gas_price > 0:
            return FixedGasPrice(self.arguments.gas_price)
        else:
            return DefaultGasPrice()