def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser("chief-keeper") self.add_arguments(parser) parser.set_defaults(cageFacilitated=False) self.arguments = parser.parse_args(args) 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 register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) if self.arguments.dss_deployment_file: self.dss = DssDeployment.from_json( web3=self.web3, conf=open(self.arguments.dss_deployment_file, "r").read()) else: self.dss = DssDeployment.from_node(web3=self.web3) self.deployment_block = self.arguments.chief_deployment_block self.max_errors = self.arguments.max_errors self.errors = 0 self.confirmations = 0 logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO))
def cleanup_urn(mcd: DssDeployment, collateral: Collateral, address: Address): assert isinstance(mcd, DssDeployment) assert isinstance(collateral, Collateral) assert isinstance(address, Address) urn = mcd.vat.urn(collateral.ilk, address) ilk = mcd.vat.ilk(collateral.ilk.name) # If jug.drip has been called, we won't have sufficient dai to repay the CDP if ilk.rate > Ray.from_number(1): return # Repay borrowed Dai mcd.approve_dai(address) # Put all the user's Dai back into the vat if mcd.dai.balance_of(address) >= Wad(0): assert mcd.dai_adapter.join(address, mcd.dai.balance_of(address)).transact(from_address=address) # tab = Ray(urn.art) * ilk.rate # print(f'tab={str(tab)}, rate={str(ilk.rate)}, dai={str(mcd.vat.dai(address))}') if urn.art > Wad(0) and mcd.vat.dai(address) >= Rad(urn.art): frob(mcd, collateral, address, Wad(0), urn.art * -1) # Withdraw collateral collateral.approve(address) urn = mcd.vat.urn(collateral.ilk, address) # dink = Wad((Ray(urn.art) * ilk.rate) / ilk.spot) # print(f'dink={str(dink)}, ink={str(urn.ink)}') if urn.art == Wad(0) and urn.ink > Wad(0): frob(mcd, collateral, address, urn.ink * -1, Wad(0)) assert collateral.adapter.exit(address, mcd.vat.gem(collateral.ilk, address)).transact(from_address=address)
def create_risky_cdp(mcd: DssDeployment, c: Collateral, collateral_amount: Wad, gal_address: Address, draw_dai=True) -> Urn: assert isinstance(mcd, DssDeployment) assert isinstance(c, Collateral) assert isinstance(gal_address, Address) # Ensure vault isn't already unsafe (if so, this shouldn't be called) urn = mcd.vat.urn(c.ilk, gal_address) assert is_cdp_safe(mcd.vat.ilk(c.ilk.name), urn) # Add collateral to gal vault if necessary c.approve(gal_address) token = Token(c.ilk.name, c.gem.address, c.adapter.dec()) print(f"collateral_amount={collateral_amount} ink={urn.ink}") dink = collateral_amount - urn.ink if dink > Wad(0): vat_balance = mcd.vat.gem(c.ilk, gal_address) balance = token.normalize_amount(c.gem.balance_of(gal_address)) print( f"before join: dink={dink} vat_balance={vat_balance} balance={balance} vat_gap={dink - vat_balance}" ) if vat_balance < dink: # handle dusty balances with non-18-decimal tokens vat_gap = dink - vat_balance + token.min_amount if balance < vat_gap: if c.ilk.name.startswith("ETH"): wrap_eth(mcd, gal_address, vat_gap) else: raise RuntimeError("Insufficient collateral balance") assert c.adapter.join(gal_address, token.unnormalize_amount(vat_gap)).transact( from_address=gal_address) vat_balance = mcd.vat.gem(c.ilk, gal_address) balance = token.normalize_amount(c.gem.balance_of(gal_address)) print( f"after join: dink={dink} vat_balance={vat_balance} balance={balance} vat_gap={dink - vat_balance}" ) assert vat_balance >= dink assert mcd.vat.frob(c.ilk, gal_address, dink, Wad(0)).transact(from_address=gal_address) urn = mcd.vat.urn(c.ilk, gal_address) # Put gal CDP at max possible debt dart = max_dart(mcd, c, gal_address) - Wad(1) if dart > Wad(0): print(f"Frobbing {c.ilk.name} with ink={urn.ink} and dart={dart}") assert mcd.vat.frob(c.ilk, gal_address, Wad(0), dart).transact(from_address=gal_address) # Draw our Dai, simulating the usual behavior urn = mcd.vat.urn(c.ilk, gal_address) if draw_dai and urn.art > Wad(0): mcd.approve_dai(gal_address) assert mcd.dai_adapter.exit(gal_address, urn.art).transact(from_address=gal_address) print(f"Exited {urn.art} Dai from urn") return urn
def __init__(self, args: list, **kwargs): self.network = args[0] endpoint_uri = f"https://parity0.{self.network}.makerfoundation.com:8545" self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3(HTTPProvider(endpoint_uri=endpoint_uri, request_kwargs={"timeout": 10})) self.our_address = self.web3.toChecksumAddress(args[1]) print(f"Account address = {self.our_address}") self.web3.eth.defaultAccount = self.our_address self.mcd = DssDeployment.from_node(self.web3) dsr_client = Dsr(mcd=self.mcd, owner=Address(self.web3.eth.defaultAccount)) if dsr_client.has_proxy(): print(f"Has DS-Proxy: True") proxy = dsr_client.get_proxy() human_readable_balance = "{:,}".format(dsr_client.get_balance(proxy.address).__float__()) print(f"DS-Proxy address = {proxy.address.address}") print(f"DS-Proxy balance = {human_readable_balance}") else: print(f"Has DS-Proxy: False")
def d(web3, our_address, keeper_address, gal_address): # dss = """ # {"MCD_MOM": "0xD3F7637ace900b381a0A6Bf878639E5BCa564B53", "MCD_VAT": "0x7dc681a82de642cf86cc05d6c938aA0021a6807F", "MCD_VOW": "0x68A75d6CaC097fFE5FC1e912EaDc8832bC1D8f36", "MCD_DRIP": "0x58B21768D30433481FbE87C2065Cc081eF982898", "MCD_PIT": "0x0D64bE75122D4Cb0F65776a973f3c03eb83177Ff", "MCD_CAT": "0x49A4177032a6cA5f419E764a154B6CE710418e6a", "MCD_FLAP": "0x90194dC5E16C203EE8debEc949130Ed60D97Af96", "MCD_FLOP": "0x27c232d542B3F9503c327Ff087582983E2086c61", "MCD_DAI": "0xdBA593D53D5C5AA91D73402DB3622d63463620Dd", "MCD_JOIN_DAI": "0xD2E5103366FEf1F6D387d0e11132Cef95ae3F8c8", "MCD_MOVE_DAI": "0x86a1f3308dA49e7b3C8e64a6702AD19f72Ca4aEB", "MCD_GOV": "0xD6be7670e94E88b28143F057944DAAA8900629AE", "COLLATERALS": ["WETH"], "WETH": "0x820f809525024513a5A45e74f5Cf36C67b98F2D7", "MCD_JOIN_WETH": "0xe058f7252743064ee497cF5Caa10F312B00691e9", "MCD_MOVE_WETH": "0x4dFcCEBCF5Ae068AE41503B93f7d4e8234087800", "MCD_FLIP_WETH": "0xec8e3dFdfD4665f19971B4ffE2a78b51095f349b", "MCD_SPOT_WETH": "0xc323B27F990C4AA3C9297Da8738d765Bac3ca8df", "PIP_WETH": "0xaCab49615c32e56a59097855a08d3653cAb0e473"} # """ # return DssDeployment.from_json(web3=web3, conf=dss) d = DssDeployment.deploy(web3=web3, debt_ceiling=Wad.from_number(100000000)) c = d.collaterals[0] assert d.pit.file_line(c.ilk, Wad.from_number(100000000)).transact() # Set collateral debt ceiling assert d.cat.file_lump(c.ilk, Wad.from_number(100)).transact() # Set liquidation Quantity of c at 100 # mint gem for cdp frob() by gal_address and our_address to draw dai assert c.gem.mint(Wad.from_number(2000000)).transact() assert c.gem.transfer(gal_address, Wad.from_number(1000000)).transact() # Require to join the adapter assert c.gem.approve(c.adapter.address).transact() assert c.gem.approve(c.adapter.address).transact(from_address=gal_address) # draw dai for our_address assert c.adapter.join(Urn(our_address), Wad.from_number(1000000)).transact() assert d.pit.frob(c.ilk, Wad.from_number(1000000), Wad.from_number(1000000)).transact() assert d.dai_move.move(our_address, keeper_address, Wad.from_number(10000)).transact() # mint MKR for the keeper assert d.mkr.mint(Wad.from_number(100)).transact() assert d.mkr.transfer(keeper_address, Wad.from_number(100)).transact() return d
def create_unsafe_cdp(mcd: DssDeployment, c: Collateral, collateral_amount: Wad, gal_address: Address, draw_dai=True) -> Urn: assert isinstance(mcd, DssDeployment) assert isinstance(c, Collateral) assert isinstance(gal_address, Address) # Ensure CDP isn't already unsafe (if so, this shouldn't be called) urn = mcd.vat.urn(c.ilk, gal_address) assert is_cdp_safe(mcd.vat.ilk(c.ilk.name), urn) # Add collateral to gal CDP if necessary c.approve(gal_address) dink = collateral_amount - urn.ink if dink > Wad(0): balance = c.gem.balance_of(gal_address) if balance < dink: wrap_eth(mcd, gal_address, dink - balance) assert c.adapter.join(gal_address, dink - balance).transact(from_address=gal_address) simulate_frob(mcd, c, gal_address, dink, Wad(0)) assert mcd.vat.frob(c.ilk, gal_address, dink, Wad(0)).transact(from_address=gal_address) # Put gal CDP at max possible debt dart = max_dart(mcd, c, gal_address) - Wad(1) simulate_frob(mcd, c, gal_address, Wad(0), dart) assert mcd.vat.frob(c.ilk, gal_address, Wad(0), dart).transact(from_address=gal_address) # Draw our Dai, simulating the usual behavior urn = mcd.vat.urn(c.ilk, gal_address) if draw_dai and urn.art > Wad(0): mcd.approve_dai(gal_address) assert mcd.dai_adapter.exit(gal_address, urn.art).transact(from_address=gal_address) print(f"Exited {urn.art} Dai from urn") # Manipulate price to make gal CDP underwater to_price = Wad(c.pip.read_as_int()) - Wad.from_number(1) set_collateral_price(mcd, c, to_price) # Ensure the CDP is unsafe assert not is_cdp_safe(mcd.vat.ilk(c.ilk.name), urn) return urn
def __init__(self, args: list, **kwargs): """Pass in arguements assign necessary variables/objects and instantiate other Classes""" parser = argparse.ArgumentParser("cage-keeper") self.add_arguments(parser=parser) parser.set_defaults(cageFacilitated=False) 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 register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) if self.arguments.dss_deployment_file: self.dss = DssDeployment.from_json( web3=self.web3, conf=open(self.arguments.dss_deployment_file, "r").read()) else: self.dss = DssDeployment.from_node(web3=self.web3) self.deployment_block = self.arguments.vat_deployment_block self.max_errors = self.arguments.max_errors self.errors = 0 self.cageFacilitated = self.arguments.cageFacilitated self.confirmations = 0 # Create gas strategy if self.arguments.ethgasstation_api_key: self.gas_price = DynamicGasPrice(self.arguments) else: self.gas_price = DefaultGasPrice() setup_logging(self.arguments)
def __init__(self, args, **kwargs): parser = argparse.ArgumentParser("dsrdemo") parser.add_argument( "--eth-from", type=str, required=True, help= "Ethereum address from which to send transactions; checksummed (e.g. '0x12AebC')" ) parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument( "--network", type=str, required=True, help= "Network that you're running the Keeper on (options, 'mainnet', 'kovan', 'testnet')" ) parser.add_argument("--rpc-port", type=int, default=8545, help="JSON-RPC port (default: `8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") 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')" ) 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 register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) # Instantiate the dss and dsr classes self.dss = DssDeployment.from_network(web3=self.web3, network=self.arguments.network) self.dsr = Dsr(self.dss, self.our_address)
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser("chief-keeper") self.add_arguments(parser) parser.set_defaults(cageFacilitated=False) self.arguments = parser.parse_args(args) 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 register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) if self.arguments.dss_deployment_file: self.dss = DssDeployment.from_json( web3=self.web3, conf=open(self.arguments.dss_deployment_file, "r").read()) else: self.dss = DssDeployment.from_node(web3=self.web3) self.deployment_block = self.arguments.chief_deployment_block self.max_errors = self.arguments.max_errors self.errors = 0 self.confirmations = 0 if self.arguments.fixed_gas_price is not None and self.arguments.fixed_gas_price > 0: self.gas_price_strategy = FixedGasPrice( gas_price=int(round(self.arguments.fixed_gas_price * self.GWEI))) else: self.gas_price_strategy = DefaultGasPrice() setup_logging(self.arguments)
def connect(self): try: primary_url = self.rpc_url backup_url = self.backup_rpc_url self.web3 = Web3( HTTPProvider(endpoint_uri=primary_url, request_kwargs={"timeout": self.rpc_timeout})) self.mcd = DssDeployment.from_node(web3=self.web3) self.flapper = self.mcd.flapper self.rpc_url = backup_url self.backup_rpc_url = primary_url except requests.exceptions.ConnectionError: self.logger.error(f"Failed to connect to node!")
def d(web3, our_address, other_address, gal_address): d = DssDeployment.deploy(web3=web3, debt_ceiling=Wad.from_number(1000000)) # mint gem for cdp frob() by gal_address for c in d.collaterals: assert c.gem.mint(Wad.from_number(1000000)).transact() assert c.gem.transfer(gal_address, Wad.from_number(1000000)).transact() # Require to join the adapter assert c.gem.approve( c.adapter.address).transact(from_address=gal_address) # Liquidation Quantity at 100 assert d.cat.file_lump(c.ilk, Wad.from_number(100)).transact() # mint dai for auction participant assert d.dai.mint(Wad.from_number(20000000)).transact() assert d.dai.transfer(other_address, Wad.from_number(10000000)).transact() return d
def d(web3, our_address, keeper_address, gal_address): # dss = """ # {"MCD_VAT": "0x08a3a91978B277c5797747A3671bDb6eE86e900E", "MCD_VOW": "0x7ef07c8DfddEECC23D50672b09310e45C8692ad2", "MCD_DRIP": "0xdc127E4DfF5F68740B8e21dEA687A4C3C690c176", "MCD_PIT": "0xaccbA2bB146405241507D7F3e39B85C5d1179d2B", "MCD_CAT": "0x41541A8692e00b9F5db706305dA1ee395Ba4680E", "MCD_FLOP": "0x5958E69d5795823F0F84b2ccbCE8D210cb67f418", "MCD_DAI": "0x787Cb94B57C23b9617265F1a6B80569d10dAaa42", "MCD_JOIN_DAI": "0xCc13a1a2E5AE9F6eC05CA7aF762967c5BD1Dd53f", "MCD_MOVE_DAI": "0x18792385d6c9AE2236cAc48473eb30D7d669BfFC", "MCD_GOV": "0xbE8Ae37bE4a1b1e22bAFD0cDdD921bc8FD5aD134", "COLLATERALS": ["WETH"], "WETH": "0x4Cdd635f050f9ca5bD7533D8c86044c4B86339A5", "MCD_JOIN_WETH": "0x050B6E24a805A027E3a31e7D4dE7E79A88A84e6D", "MCD_MOVE_WETH": "0x2ae154E870F53AC72f0D2E39FA2bb3a812b6A55d", "MCD_FLIP_WETH": "0x0953b1f31BBFA2633d7Ec92eb0C16511269aD4d0", "MCD_SPOT_WETH": "0x3731b266f67A9307CfaC6407D893070944F0684F", "PIP_WETH": "0x4C4EC6939152E7cD455D6586BC3eb5fF22ED94BE"} # """ # return DssDeployment.from_json(web3=web3, conf=dss) d = DssDeployment.deploy(web3=web3, debt_ceiling=Wad.from_number(100000000)) c = d.collaterals[0] assert d.pit.file_line(c.ilk, Wad.from_number(100000000)).transact() # Set collateral debt ceiling assert d.cat.file_lump(c.ilk, Wad.from_number(100)).transact() # Set liquidation Quantity of c at 100 # mint gem for cdp frob() by gal_address and our_address to draw dai assert c.gem.mint(Wad.from_number(2000000)).transact() assert c.gem.transfer(gal_address, Wad.from_number(1000000)).transact() # Require to join the adapter assert c.gem.approve(c.adapter.address).transact() assert c.gem.approve(c.adapter.address).transact(from_address=gal_address) # draw dai for our_address assert c.adapter.join(Urn(our_address), Wad.from_number(1000000)).transact() assert d.pit.frob(c.ilk, Wad.from_number(1000000), Wad.from_number(1000000)).transact() assert d.dai_move.move(our_address, keeper_address, Wad.from_number(10000)).transact() return d
from pymaker.numeric import Wad, Ray from tests.conftest import create_risky_cdp, is_cdp_safe web3 = Web3(HTTPProvider(endpoint_uri=os.environ['ETH_RPC_URL'], request_kwargs={"timeout": 30})) web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO) mcd = DssDeployment.from_node(web3) our_address = Address(web3.eth.defaultAccount) collateral = mcd.collaterals[str(sys.argv[3])] if len(sys.argv) > 2 else mcd.collaterals['ETH-A'] ilk = mcd.vat.ilk(collateral.ilk.name) urn = mcd.vat.urn(collateral.ilk, our_address) def create_risky_vault(): # Create a vault close to the liquidation ratio if not is_cdp_safe(mcd.vat.ilk(collateral.ilk.name), urn): print("Vault is already unsafe; no action taken") else: osm_price = collateral.pip.peek() print(f"dust={ilk.dust} osm_price={osm_price} mat={mcd.spotter.mat(ilk)}") # To make a (barely) safe urn, I expected to add 10^-8, but for some reason I need to add 10^-7 normalized_collateral_amount = (Wad(ilk.dust) / osm_price * Wad(mcd.spotter.mat(ilk))) + Wad.from_number(0.00000050)
def mcd(web3) -> DssDeployment: deployment = DssDeployment.from_network(web3=web3, network="testnet") validate_contracts_loaded(deployment) return deployment
def test_from_node(self, web3: Web3): mcd_testnet = DssDeployment.from_node(web3) validate_contracts_loaded(mcd_testnet)
def test_to_json(self, web3: Web3, mcd: DssDeployment): config_out = mcd.to_json() dict = json.loads(config_out) assert "MCD_GOV" in dict assert "MCD_DAI" in dict assert len(dict) > 20
def test_from_network(self, web3: Web3): mcd_testnet = DssDeployment.from_network(web3, "testnet") validate_contracts_loaded(mcd_testnet) with pytest.raises(Exception): DssDeployment.from_network(web3, "bogus")
def mcd(web3): return DssDeployment.from_network(web3=web3, network="testnet")
def __init__(self, args: list, **kwargs): """Pass in arguements assign necessary variables/objects and instantiate other Classes""" parser = argparse.ArgumentParser("chief-keeper") parser.add_argument( "--rpc-host", type=str, default="https://localhost:8545", help="JSON-RPC host:port (default: 'localhost:8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--network", type=str, required=True, help= "Network that you're running the Keeper on (options, 'mainnet', 'kovan', 'testnet')" ) parser.add_argument( "--eth-from", type=str, required=True, help= "Ethereum address from which to send transactions; checksummed (e.g. '0x12AebC')" ) 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( "--dss-deployment-file", type=str, required=False, help= "Json description of all the system addresses (e.g. /Full/Path/To/configFile.json)" ) parser.add_argument( "--chief-deployment-block", type=int, required=False, default=0, help= " Block that the Chief from dss-deployment-file was deployed at (e.g. 8836668" ) parser.add_argument( "--max-errors", type=int, default=100, help= "Maximum number of allowed errors before the keeper terminates (default: 100)" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") parser.add_argument("--ethgasstation-api-key", type=str, default=None, help="ethgasstation API key") parser.add_argument("--gas-initial-multiplier", type=str, default=1.0, help="ethgasstation API key") parser.add_argument("--gas-reactive-multiplier", type=str, default=2.25, help="gas strategy tuning") parser.add_argument("--gas-maximum", type=str, default=5000, help="gas strategy tuning") parser.set_defaults(cageFacilitated=False) self.arguments = parser.parse_args(args) self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else web3_via_http( endpoint_uri=self.arguments.rpc_host, timeout=self.arguments.rpc_timeout, http_pool_size=100) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) if self.arguments.dss_deployment_file: self.dss = DssDeployment.from_json( web3=self.web3, conf=open(self.arguments.dss_deployment_file, "r").read()) else: self.dss = DssDeployment.from_network( web3=self.web3, network=self.arguments.network) self.deployment_block = self.arguments.chief_deployment_block self.max_errors = self.arguments.max_errors self.errors = 0 self.confirmations = 0 # Create dynamic gas strategy if self.arguments.ethgasstation_api_key: self.gas_price = DynamicGasPrice(self.arguments, self.web3) else: self.gas_price = DefaultGasPrice() logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO))
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser(prog='auction-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", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help="Ethereum account from which to send transactions") parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')" ) parser.add_argument('--type', type=str, choices=['clip', 'flip', 'flap', 'flop'], help="Auction type in which to participate") parser.add_argument( '--ilk', type=str, help= "Name of the collateral type for a clip or flip keeper (e.g. 'ETH-B', 'ZRX-A'); " "available collateral types can be found at the left side of the Oasis Borrow" ) parser.add_argument( '--bid-only', dest='create_auctions', action='store_false', help="Do not take opportunities to create new auctions") parser.add_argument('--kick-only', dest='bid_on_auctions', action='store_false', help="Do not bid on auctions") parser.add_argument( '--deal-for', type=str, nargs="+", help="List of addresses for which auctions will be dealt") parser.add_argument('--min-auction', type=int, default=1, help="Lowest auction id to consider") parser.add_argument( '--max-auctions', type=int, default=1000, help="Maximum number of auctions to simultaneously interact with, " "used to manage OS and hardware limitations") parser.add_argument( '--min-collateral-lot', type=float, default=0, help= "Minimum lot size to create or bid upon/take from a collateral auction" ) parser.add_argument( '--bid-check-interval', type=float, default=4.0, help= "Period of timer [in seconds] used to check bidding models for changes" ) parser.add_argument( '--bid-delay', type=float, default=0.0, help= "Seconds to wait between bids, used to manage OS and hardware limitations" ) parser.add_argument( '--shard-id', type=int, default=0, help= "When sharding auctions across multiple keepers, this identifies the shard" ) parser.add_argument( '--shards', type=int, default=1, help= "Number of shards; should be one greater than your highest --shard-id" ) parser.add_argument( '--from-block', type=int, help= "Starting block from which to find vaults to bite or debt to queue " "(set to block where MCD was deployed)") parser.add_argument( '--chunk-size', type=int, default=20000, help= "When batching chain history requests, this is the number of blocks for each request" ) parser.add_argument( "--tokenflow-url", type=str, help= "When specified, urn history will be initialized using the TokenFlow API" ) parser.add_argument("--tokenflow-key", type=str, help="API key for the TokenFlow endpoint") parser.add_argument( "--vulcanize-endpoint", type=str, help= "When specified, urn history will be initialized from a VulcanizeDB node" ) parser.add_argument("--vulcanize-key", type=str, help="API key for the Vulcanize endpoint") parser.add_argument( '--vat-dai-target', type=str, help= "Amount of Dai to keep in the Vat contract or ALL to join entire token balance" ) parser.add_argument( '--keep-dai-in-vat-on-exit', dest='exit_dai_on_shutdown', action='store_false', help= "Retain Dai in the Vat on exit, saving gas when restarting the keeper" ) parser.add_argument('--keep-gem-in-vat-on-exit', dest='exit_gem_on_shutdown', action='store_false', help="Retain collateral in the Vat on exit") parser.add_argument( '--return-gem-interval', type=int, default=300, help= "Period of timer [in seconds] used to check and exit won collateral" ) parser.add_argument( "--model", type=str, nargs='+', help="Commandline to use in order to start the bidding model") parser.add_argument( "--oracle-gas-price", action='store_true', help="Use a fast gas price aggregated across multiple oracles") parser.add_argument("--ethgasstation-api-key", type=str, default=None, help="EthGasStation API key") parser.add_argument("--etherscan-api-key", type=str, default=None, help="Etherscan API key") parser.add_argument("--blocknative-api-key", type=str, default=None, help="Blocknative API key") parser.add_argument( '--fixed-gas-price', type=float, default=None, help= "Uses a fixed value (in Gwei) instead of an external API to determine initial gas" ) parser.add_argument("--poanetwork-url", type=str, default=None, help="Alternative POANetwork URL") parser.add_argument( "--gas-initial-multiplier", type=float, default=1.0, help= "Adjusts the initial API-provided 'fast' gas price, default 1.0") parser.add_argument( "--gas-reactive-multiplier", type=float, default=1.125, help= "Increases gas price when transactions haven't been mined after some time" ) parser.add_argument( "--gas-maximum", type=float, default=2000, help= "Places an upper bound (in Gwei) on the amount of gas to use for a single TX" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) # Configure connection to the chain self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else web3_via_http( endpoint_uri=self.arguments.rpc_host, timeout=self.arguments.rpc_timeout, http_pool_size=100) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) # Check configuration for retrieving urns/bites if self.arguments.type in ['clip', 'flip'] and self.arguments.create_auctions \ and self.arguments.from_block is None \ and self.arguments.tokenflow_url is None \ and self.arguments.vulcanize_endpoint is None: raise RuntimeError( "One of --from-block, --tokenflow_url, or --vulcanize-endpoint must be specified " "to bite and kick off new collateral auctions") if self.arguments.type in ['clip', 'flip'] and not self.arguments.ilk: raise RuntimeError( "--ilk must be supplied when configuring a collateral auction keeper" ) if self.arguments.type == 'flop' and self.arguments.create_auctions \ and self.arguments.from_block is None: raise RuntimeError( "--from-block must be specified to kick off flop auctions") # Configure core and token contracts self.mcd = DssDeployment.from_node(web3=self.web3) self.vat = self.mcd.vat self.vow = self.mcd.vow self.mkr = self.mcd.mkr self.dai_join = self.mcd.dai_adapter if self.arguments.type in ['clip', 'flip']: self.collateral = self.mcd.collaterals[self.arguments.ilk] self.ilk = self.collateral.ilk self.gem_join = self.collateral.adapter else: self.collateral = None self.ilk = None self.gem_join = None # Configure auction contracts self.auction_contract = self.get_contract() self.auction_type = None is_collateral_auction = False self.is_dealable = True self.urn_history = None if isinstance(self.auction_contract, Clipper): self.auction_type = 'clip' is_collateral_auction = True self.min_collateral_lot = Wad.from_number( self.arguments.min_collateral_lot) self.is_dealable = False self.strategy = ClipperStrategy(self.auction_contract, self.min_collateral_lot) elif isinstance(self.auction_contract, Flipper): self.auction_type = 'flip' is_collateral_auction = True self.min_collateral_lot = Wad.from_number( self.arguments.min_collateral_lot) self.strategy = FlipperStrategy(self.auction_contract, self.min_collateral_lot) elif isinstance(self.auction_contract, Flapper): self.auction_type = 'flap' self.strategy = FlapperStrategy(self.auction_contract, self.mkr.address) elif isinstance(self.auction_contract, Flopper): self.auction_type = 'flop' self.strategy = FlopperStrategy(self.auction_contract) else: raise RuntimeError( f"{self.auction_contract} auction contract is not supported") if is_collateral_auction and self.arguments.create_auctions: if self.arguments.vulcanize_endpoint: self.urn_history = VulcanizeUrnHistoryProvider( self.mcd, self.ilk, self.arguments.vulcanize_endpoint, self.arguments.vulcanize_key) elif self.arguments.tokenflow_url: self.urn_history = TokenFlowUrnHistoryProvider( self.web3, self.mcd, self.ilk, self.arguments.tokenflow_url, self.arguments.tokenflow_key, self.arguments.chunk_size) else: self.urn_history = ChainUrnHistoryProvider( self.web3, self.mcd, self.ilk, self.arguments.from_block, self.arguments.chunk_size) # Create the collection used to manage auctions relevant to this keeper if self.arguments.model: model_command = ' '.join(self.arguments.model) else: if self.arguments.bid_on_auctions: raise RuntimeError( "--model must be specified to bid on auctions") else: model_command = ":" self.auctions = Auctions(auction_contract=self.auction_contract, model_factory=ModelFactory(model_command)) self.auctions_lock = threading.Lock() # Since we don't want periodically-pollled bidding threads to back up, use a flag instead of a lock. self.is_joining_dai = False self.dead_since = {} self.lifecycle = None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO)) # Create gas strategy used for non-bids and bids which do not supply gas price self.gas_price = DynamicGasPrice(self.arguments, self.web3) # Configure account(s) for which we'll deal auctions self.deal_all = False self.deal_for = set() if self.is_dealable: if self.arguments.deal_for is None: self.deal_for.add(self.our_address) elif len( self.arguments.deal_for ) == 1 and self.arguments.deal_for[0].upper() in ["ALL", "NONE"]: if self.arguments.deal_for[0].upper() == "ALL": self.deal_all = True # else no auctions will be dealt elif len(self.arguments.deal_for) > 0: for account in self.arguments.deal_for: self.deal_for.add(Address(account)) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO)
import sys from web3 import Web3, HTTPProvider from pymaker import Address from pymaker.deployment import DssDeployment from pymaker.keys import register_keys from pymaker.numeric import Wad web3 = Web3(HTTPProvider(endpoint_uri="http://0.0.0.0:8545", request_kwargs={"timeout": 10})) web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass mcd = DssDeployment.from_json(web3=web3, conf=open("config/kovan-addresses.json", "r").read()) our_address = Address(web3.eth.defaultAccount) # Choose the desired collateral; in this case we'll wrap some Eth collateral = mcd.collaterals['ETH-A'] ilk = collateral.ilk collateral.gem.deposit(Wad.from_number(3)).transact() # Add collateral and allocate the desired amount of Dai collateral.approve(our_address) collateral.adapter.join(our_address, Wad.from_number(3)).transact() mcd.vat.frob(ilk, our_address, dink=Wad.from_number(3), dart=Wad.from_number(153)).transact() print(f"CDP Dai balance before withdrawal: {mcd.vat.dai(our_address)}") # Mint and withdraw our Dai mcd.approve_dai(our_address)
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser() self.add_arguments(parser=parser) 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 register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) # Check configuration for retrieving urns/bites if self.arguments.type == 'flip' and self.arguments.create_auctions \ and self.arguments.from_block is None and self.arguments.vulcanize_endpoint is None: raise RuntimeError( "Either --from-block or --vulcanize-endpoint must be specified to kick off " "flip auctions") if self.arguments.type == 'flip' and not self.arguments.ilk: raise RuntimeError( "--ilk must be supplied when configuring a flip keeper") if self.arguments.type == 'flop' and self.arguments.create_auctions \ and self.arguments.from_block is None: raise RuntimeError( "--from-block must be specified to kick off flop auctions") self.addresses_path = kwargs[ "addresses_path"] if "addresses-path" in kwargs else self.arguments.addresses_path # Configure core and token contracts if self.addresses_path is not None: self.mcd = DssDeployment.from_json(web3=self.web3, conf=open( self.addresses_path, "r").read()) else: self.mcd = DssDeployment.from_node(web3=self.web3) self.vat = self.mcd.vat self.cat = self.mcd.cat self.vow = self.mcd.vow self.mkr = self.mcd.mkr self.dai_join = self.mcd.dai_adapter if self.arguments.type == 'flip': self.collateral = self.mcd.collaterals[self.arguments.ilk] self.ilk = self.collateral.ilk self.gem_join = self.collateral.adapter else: self.collateral = None self.ilk = None self.gem_join = None # Configure auction contracts self.flipper = self.collateral.flipper if self.arguments.type == 'flip' else None self.flapper = self.mcd.flapper if self.arguments.type == 'flap' else None self.flopper = self.mcd.flopper if self.arguments.type == 'flop' else None self.urn_history = None if self.flipper: self.min_flip_lot = Wad.from_number(self.arguments.min_flip_lot) self.strategy = FlipperStrategy(self.flipper, self.min_flip_lot) self.urn_history = UrnHistory(self.web3, self.mcd, self.ilk, self.arguments.from_block, self.arguments.vulcanize_endpoint) elif self.flapper: self.strategy = FlapperStrategy(self.flapper, self.mkr.address) elif self.flopper: self.strategy = FlopperStrategy(self.flopper) else: raise RuntimeError("Please specify auction type") # Create the collection used to manage auctions relevant to this keeper self.auctions = Auctions( flipper=self.flipper.address if self.flipper else None, flapper=self.flapper.address if self.flapper else None, flopper=self.flopper.address if self.flopper else None, model_factory=ModelFactory(' '.join(self.arguments.model))) self.auctions_lock = threading.Lock() self.dead_auctions = set() self.lifecycle = None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO)) # Create gas strategy used for non-bids and bids which do not supply gas price self.gas_price = DynamicGasPrice(self.arguments) self.vat_dai_target = Wad.from_number(self.arguments.vat_dai_target) if \ self.arguments.vat_dai_target is not None else None # Configure account(s) for which we'll deal auctions self.deal_all = False self.deal_for = set() if self.arguments.deal_for is None: self.deal_for.add(self.our_address) elif len( self.arguments.deal_for ) == 1 and self.arguments.deal_for[0].upper() in ["ALL", "NONE"]: if self.arguments.deal_for[0].upper() == "ALL": self.deal_all = True # else no auctions will be dealt elif len(self.arguments.deal_for) > 0: for account in self.arguments.deal_for: self.deal_for.add(Address(account)) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO)
def mcd(web3) -> DssDeployment: # for local dockerized parity testchain deployment = DssDeployment.from_node(web3=web3) validate_contracts_loaded(deployment) return deployment
from pymaker.keys import register_keys from pymaker.numeric import Wad logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO) pool_size = int(sys.argv[3]) if len(sys.argv) > 3 else 10 web3 = web3_via_http(endpoint_uri=os.environ['ETH_RPC_URL'], http_pool_size=pool_size) web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass mcd = DssDeployment.from_node(web3) our_address = Address(web3.eth.defaultAccount) weth = DssDeployment.from_node(web3).collaterals['ETH-A'].gem GWEI = 1000000000 slow_gas = GeometricGasPrice(initial_price=int(0.8 * GWEI), every_secs=30, max_price=2000 * GWEI) fast_gas = GeometricGasPrice(initial_price=int(1.1 * GWEI), every_secs=30, max_price=2000 * GWEI) class TestApp: def main(self): self.test_replacement() self.test_simultaneous() self.shutdown() def test_replacement(self):
def __init__(self, args: list, **kwargs): """Pass in arguements assign necessary variables/objects and instantiate other Classes""" parser = argparse.ArgumentParser("cage-keeper") parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", type=int, default=8545, help="JSON-RPC port (default: `8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--network", type=str, required=True, help= "Network that you're running the Keeper on (options, 'mainnet', 'kovan', 'testnet')" ) parser.add_argument( '--previous-cage', dest='cageFacilitated', action='store_true', help= 'Include this argument if this keeper previously helped to facilitate the processing phase of ES' ) parser.add_argument( "--eth-from", type=str, required=True, help= "Ethereum address from which to send transactions; checksummed (e.g. '0x12AebC')" ) 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( "--dss-deployment-file", type=str, required=False, help= "Json description of all the system addresses (e.g. /Full/Path/To/configFile.json)" ) parser.add_argument( "--vat-deployment-block", type=int, required=False, default=0, help= " Block that the Vat from dss-deployment-file was deployed at (e.g. 8836668" ) parser.add_argument( "--max-errors", type=int, default=100, help= "Maximum number of allowed errors before the keeper terminates (default: 100)" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") parser.set_defaults(cageFacilitated=False) 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 register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) if self.arguments.dss_deployment_file: self.dss = DssDeployment.from_json( web3=self.web3, conf=open(self.arguments.dss_deployment_file, "r").read()) else: self.dss = DssDeployment.from_network( web3=self.web3, network=self.arguments.network) self.deployment_block = self.arguments.vat_deployment_block self.max_errors = self.arguments.max_errors self.errors = 0 self.cageFacilitated = self.arguments.cageFacilitated self.confirmations = 0 logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO))
def mcd(web3): return DssDeployment.from_node(web3=web3)
def mcd(web3) -> DssDeployment: # for local dockerized parity testchain deployment = DssDeployment.from_json(web3=web3, conf=open("config/testnet-addresses.json", "r").read()) validate_contracts_loaded(deployment) return deployment
from pymaker import Address from pymaker.auctions import Flipper, Flapper, Flopper from pymaker.deployment import DssDeployment from pymaker.keys import register_keys from pymaker.numeric import Wad web3 = Web3( HTTPProvider(endpoint_uri="http://0.0.0.0:8545", request_kwargs={"timeout": 10})) web3.eth.defaultAccount = sys.argv[ 1] # ex: 0x0000000000000000000000000000000aBcdef123 register_keys( web3, [sys.argv[2]] ) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass mcd = DssDeployment.from_network(web3, "kovan") our_address = Address(web3.eth.defaultAccount) # Choose the desired collateral; in this case we'll wrap some Eth collateral = mcd.collaterals['ETH-A'] ilk = collateral.ilk collateral_amount = Wad.from_number(0.03) collateral.gem.deposit(collateral_amount).transact() # Add collateral and allocate the desired amount of Dai collateral.approve(our_address) assert collateral.adapter.join(our_address, collateral_amount).transact() dai_amount = Wad.from_number(0.153) mcd.vat.frob(ilk, our_address, dink=collateral_amount, dart=Wad(0)).transact() mcd.vat.frob(ilk, our_address, dink=Wad(0), dart=dai_amount).transact() print(f"CDP Dai balance before withdrawal: {mcd.vat.dai(our_address)}")
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser(prog='auction-keeper') parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", type=int, default=8545, help="JSON-RPC port (default: `8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help="Ethereum account from which to send transactions") parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')" ) parser.add_argument('--type', type=str, choices=['flip', 'flap', 'flop'], help="Auction type in which to participate") parser.add_argument( '--ilk', type=str, help= "Name of the collateral type for a flip keeper (e.g. 'ETH-B', 'ZRX-A'); " "available collateral types can be found at the left side of the CDP Portal" ) parser.add_argument( '--bid-only', dest='create_auctions', action='store_false', help="Do not take opportunities to create new auctions") parser.add_argument( '--max-auctions', type=int, default=100, help="Maximum number of auctions to simultaneously interact with, " "used to manage OS and hardware limitations") parser.add_argument( '--min-flip-lot', type=float, default=0, help="Minimum lot size to create or bid upon a flip auction") parser.add_argument( "--vulcanize-endpoint", type=str, help= "When specified, frob history will be queried from a VulcanizeDB lite node, " "reducing load on the Ethereum node for flip auctions") parser.add_argument( '--from-block', type=int, help= "Starting block from which to look at history (set to block where MCD was deployed)" ) parser.add_argument( '--vat-dai-target', type=float, help="Amount of Dai to keep in the Vat contract (e.g. 2000)") parser.add_argument( '--keep-dai-in-vat-on-exit', dest='exit_dai_on_shutdown', action='store_false', help= "Retain Dai in the Vat on exit, saving gas when restarting the keeper" ) parser.add_argument('--keep-gem-in-vat-on-exit', dest='exit_gem_on_shutdown', action='store_false', help="Retain collateral in the Vat on exit") parser.add_argument( "--model", type=str, required=True, nargs='+', help="Commandline to use in order to start the bidding model") parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) # Configure connection to the chain if self.arguments.rpc_host.startswith("http"): endpoint_uri = f"{self.arguments.rpc_host}:{self.arguments.rpc_port}" else: # Should probably default this to use TLS, but I don't want to break existing configs endpoint_uri = f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}" self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else Web3( HTTPProvider( endpoint_uri=endpoint_uri, request_kwargs={"timeout": self.arguments.rpc_timeout})) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) # Check configuration for retrieving urns/bites if self.arguments.type == 'flip' and self.arguments.create_auctions \ and self.arguments.from_block is None and self.arguments.vulcanize_endpoint is None: raise RuntimeError( "Either --from-block or --vulcanize-endpoint must be specified to kick off " "flip auctions") if self.arguments.type == 'flip' and not self.arguments.ilk: raise RuntimeError( "--ilk must be supplied when configuring a flip keeper") if self.arguments.type == 'flop' and self.arguments.create_auctions \ and self.arguments.from_block is None: raise RuntimeError( "--from-block must be specified to kick off flop auctions") # Configure core and token contracts mcd = DssDeployment.from_node(web3=self.web3) self.vat = mcd.vat self.cat = mcd.cat self.vow = mcd.vow self.mkr = mcd.mkr self.dai_join = mcd.dai_adapter if self.arguments.type == 'flip': self.collateral = mcd.collaterals[self.arguments.ilk] self.ilk = self.collateral.ilk self.gem_join = self.collateral.adapter else: self.collateral = None self.ilk = None self.gem_join = None # Configure auction contracts self.flipper = self.collateral.flipper if self.arguments.type == 'flip' else None self.flapper = mcd.flapper if self.arguments.type == 'flap' else None self.flopper = mcd.flopper if self.arguments.type == 'flop' else None self.urn_history = None if self.flipper: self.min_flip_lot = Wad.from_number(self.arguments.min_flip_lot) self.strategy = FlipperStrategy(self.flipper, self.min_flip_lot) self.urn_history = UrnHistory(self.web3, mcd, self.ilk, self.arguments.from_block, self.arguments.vulcanize_endpoint) elif self.flapper: self.strategy = FlapperStrategy(self.flapper, self.mkr.address) elif self.flopper: self.strategy = FlopperStrategy(self.flopper) else: raise RuntimeError("Please specify auction type") # Create the collection used to manage auctions relevant to this keeper self.auctions = Auctions( flipper=self.flipper.address if self.flipper else None, flapper=self.flapper.address if self.flapper else None, flopper=self.flopper.address if self.flopper else None, model_factory=ModelFactory(' '.join(self.arguments.model))) self.auctions_lock = threading.Lock() self.dead_auctions = set() self.vat_dai_target = Wad.from_number(self.arguments.vat_dai_target) if \ self.arguments.vat_dai_target is not None else None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO)) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO)
def __init__(self, args: list, **kwargs): parser = argparse.ArgumentParser(prog='auction-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", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help="Ethereum account from which to send transactions") parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')" ) parser.add_argument('--type', type=str, choices=['flip', 'flap', 'flop'], help="Auction type in which to participate") parser.add_argument( '--ilk', type=str, help= "Name of the collateral type for a flip keeper (e.g. 'ETH-B', 'ZRX-A'); " "available collateral types can be found at the left side of the CDP Portal" ) parser.add_argument( '--bid-only', dest='create_auctions', action='store_false', help="Do not take opportunities to create new auctions") parser.add_argument('--kick-only', dest='bid_on_auctions', action='store_false', help="Do not bid on auctions") parser.add_argument( '--deal-for', type=str, nargs="+", help="List of addresses for which auctions will be dealt") parser.add_argument('--min-auction', type=int, default=1, help="Lowest auction id to consider") parser.add_argument( '--max-auctions', type=int, default=1000, help="Maximum number of auctions to simultaneously interact with, " "used to manage OS and hardware limitations") parser.add_argument( '--min-flip-lot', type=float, default=0, help="Minimum lot size to create or bid upon a flip auction") parser.add_argument( '--bid-check-interval', type=float, default=2.0, help= "Period of timer [in seconds] used to check bidding models for changes" ) parser.add_argument( '--bid-delay', type=float, default=0.0, help= "Seconds to wait between bids, used to manage OS and hardware limitations" ) parser.add_argument( '--shard-id', type=int, default=0, help= "When sharding auctions across multiple keepers, this identifies the shard" ) parser.add_argument( '--shards', type=int, default=1, help= "Number of shards; should be one greater than your highest --shard-id" ) parser.add_argument( "--vulcanize-endpoint", type=str, help= "When specified, frob history will be queried from a VulcanizeDB lite node, " "reducing load on the Ethereum node for flip auctions") parser.add_argument( '--from-block', type=int, help= "Starting block from which to find vaults to bite or debt to queue " "(set to block where MCD was deployed)") parser.add_argument( '--vat-dai-target', type=float, help="Amount of Dai to keep in the Vat contract (e.g. 2000)") parser.add_argument( '--keep-dai-in-vat-on-exit', dest='exit_dai_on_shutdown', action='store_false', help= "Retain Dai in the Vat on exit, saving gas when restarting the keeper" ) parser.add_argument('--keep-gem-in-vat-on-exit', dest='exit_gem_on_shutdown', action='store_false', help="Retain collateral in the Vat on exit") parser.add_argument( "--model", type=str, required=True, nargs='+', help="Commandline to use in order to start the bidding model") parser.add_argument( "--max-gem-balance", type=float, required=True, help= "Max gem (e.g. ETH, BAT) balance to store in keeper account before selling for DAI" ) parser.add_argument( "--max-gem-sale", type=float, required=True, help= "Max gem (e.g. ETH, BAT) to sell in a single transaction in order to reduce risk of slippage" ) parser.add_argument("--gem-eth-ratio", type=float, default=1, help="gem/eth ratio for estimating gas price") parser.add_argument( "--profit-margin", type=float, default=0.01, help="Minimum percent discount from feed price for bidding") parser.add_argument( "--tab-discount", type=float, nargs=4, help= "Enables adaptive profit margins based on the amount of total Dai currently being auctioned. Useful for detecting when large CDPs or rapid declines in ETH price have occurred. Profit margins will be added to base profit margin. Usage = tab level 1 in dai, discount 1, tab level 2 in dai, discount 2", default=[100000, .15, 25000, .01]) parser.add_argument( "--bid-start-time", type=float, default=30, help="Number of minutes before auction end time to start bidding") parser.add_argument( "--force-premium", dest='force_premium', action='store_true', help= "Use to enable bidding with negative margins (i.e. above the market feed rate)" ) gas_group = parser.add_mutually_exclusive_group() gas_group.add_argument("--ethgasstation-api-key", type=str, default=None, help="ethgasstation API key") gas_group.add_argument('--etherchain-gas-price', dest='etherchain_gas', action='store_true', help="Use etherchain.org gas price") gas_group.add_argument('--poanetwork-gas-price', dest='poanetwork_gas', action='store_true', help="Use POANetwork gas price") gas_group.add_argument( '--fixed-gas-price', type=float, default=None, help= "Uses a fixed value (in Gwei) instead of an external API to determine initial gas" ) parser.add_argument("--poanetwork-url", type=str, default=None, help="Alternative POANetwork URL") parser.add_argument( "--gas-initial-multiplier", type=float, default=1.0, help= "Adjusts the initial API-provided 'fast' gas price, default 1.0") parser.add_argument( "--gas-reactive-multiplier", type=float, default=2.25, help= "Increases gas price when transactions haven't been mined after some time" ) parser.add_argument( "--gas-maximum", type=float, default=5000, help= "Places an upper bound (in Gwei) on the amount of gas to use for a single TX" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") 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 register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) # Check configuration for retrieving urns/bites if self.arguments.type == 'flip' and self.arguments.create_auctions \ and self.arguments.from_block is None and self.arguments.vulcanize_endpoint is None: raise RuntimeError( "Either --from-block or --vulcanize-endpoint must be specified to kick off " "flip auctions") if self.arguments.type == 'flip' and not self.arguments.ilk: raise RuntimeError( "--ilk must be supplied when configuring a flip keeper") if self.arguments.type == 'flop' and self.arguments.create_auctions \ and self.arguments.from_block is None: raise RuntimeError( "--from-block must be specified to kick off flop auctions") if self.arguments.type != 'flip': raise RuntimeError("Thrifty Keeper only works with Flip auctions") if self.arguments.profit_margin < 0 and not self.arguments.force_premium: raise RuntimeError( "Negative profit margins will place bids above market price. Run with '--force-premium' to override" ) # Configure core and token contracts self.mcd = DssDeployment.from_node(web3=self.web3) self.vat = self.mcd.vat self.cat = self.mcd.cat self.vow = self.mcd.vow self.mkr = self.mcd.mkr self.dai_join = self.mcd.dai_adapter if self.arguments.type == 'flip': self.collateral = self.mcd.collaterals[self.arguments.ilk] self.ilk = self.collateral.ilk self.gem_join = self.collateral.adapter else: self.collateral = None self.ilk = None self.gem_join = None # Configure auction contracts self.flipper = self.collateral.flipper if self.arguments.type == 'flip' else None self.flapper = self.mcd.flapper if self.arguments.type == 'flap' else None self.flopper = self.mcd.flopper if self.arguments.type == 'flop' else None self.urn_history = None if self.flipper: self.min_flip_lot = Wad.from_number(self.arguments.min_flip_lot) self.strategy = FlipperStrategy(self.flipper, self.min_flip_lot) self.urn_history = UrnHistory(self.web3, self.mcd, self.ilk, self.arguments.from_block, self.arguments.vulcanize_endpoint) elif self.flapper: self.strategy = FlapperStrategy(self.flapper, self.mkr.address) elif self.flopper: self.strategy = FlopperStrategy(self.flopper) else: raise RuntimeError("Please specify auction type") # Create the collection used to manage auctions relevant to this keeper self.auctions = Auctions( flipper=self.flipper.address if self.flipper else None, flapper=self.flapper.address if self.flapper else None, flopper=self.flopper.address if self.flopper else None, model_factory=ModelFactory(' '.join(self.arguments.model))) self.auctions_lock = threading.Lock() self.dead_since = {} self.lifecycle = None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO)) # Create gas strategy used for non-bids and bids which do not supply gas price self.gas_price = DynamicGasPrice(self.arguments) self.vat_dai_target = Wad.from_number(self.arguments.vat_dai_target) if \ self.arguments.vat_dai_target is not None else None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO)) ###Thrifty Keeper self.balance_manager = Balance_Manager( self.our_address, self.web3, self.mcd, self.ilk, self.gem_join, self.vat_dai_target, self.arguments.max_gem_balance, self.arguments.max_gem_sale, self.arguments.gem_eth_ratio, self.arguments.profit_margin, self.arguments.tab_discount, self.arguments.bid_start_time) # Configure account(s) for which we'll deal auctions self.deal_all = False self.deal_for = set() if self.arguments.deal_for is None: self.deal_for.add(self.our_address) elif len( self.arguments.deal_for ) == 1 and self.arguments.deal_for[0].upper() in ["ALL", "NONE"]: if self.arguments.deal_for[0].upper() == "ALL": self.deal_all = True # else no auctions will be dealt elif len(self.arguments.deal_for) > 0: for account in self.arguments.deal_for: self.deal_for.add(Address(account)) # reduce logspew logging.getLogger('urllib3').setLevel(logging.INFO) logging.getLogger("web3").setLevel(logging.INFO) logging.getLogger("asyncio").setLevel(logging.INFO) logging.getLogger("requests").setLevel(logging.INFO)