Esempio n. 1
0
    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='oasis-market-maker-cancel')
        parser.add_argument("--endpoint-uri", type=str,
                            help="JSON-RPC uri (example: `http://localhost:8545`)")
        parser.add_argument("--rpc-host", default="localhost", type=str, help="[DEPRECATED] JSON-RPC host (default: `localhost')")
        parser.add_argument("--rpc-port", default=8545, type=int, help="[DEPRECATED] JSON-RPC port (default: `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")
        parser.add_argument("--oasis-address", help="Ethereum address of the OasisDEX contract", required=True, type=str)
        parser.add_argument("--gas-price", help="Gas price in Wei (default: node default)", default=0, type=int)
        self.arguments = parser.parse_args(args)

        if 'web3' in kwargs:
            self.web3 = kwargs['web3']
        elif self.arguments.endpoint_uri:
            self.web3: Web3 = web3_via_http(self.arguments.endpoint_uri, self.arguments.rpc_timeout)
        else:
            self.web3 = 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)
        register_keys(self.web3, self.arguments.eth_key)
        self.otc = MatchingMarket(web3=self.web3, address=Address(self.arguments.oasis_address))

        logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.INFO)
Esempio n. 2
0
    def test_connect_to_testchain(self, our_address):
        uri = "http://0.0.0.0:8545"
        web3 = web3_via_http(uri, 63, 39)
        assert isinstance(web3.provider, HTTPProvider)
        assert web3.provider._request_kwargs['timeout'] == 63

        for adapter in _get_session(uri).adapters.values():
            assert adapter._pool_connections == 39
            assert adapter._pool_maxsize == 39

        assert isinstance(web3, Web3)
        assert eth_balance(web3, our_address) > Wad(0)
Esempio n. 3
0
def web3():
    # These details are specific to the MCD testchain used for pymaker unit tests.
    web3 = web3_via_http("http://0.0.0.0:8545", 3, 100)
    web3.eth.defaultAccount = "0x50FF810797f75f6bfbf2227442e0c961a8562F4C"
    register_keys(web3, [
        "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key1.json,pass_file=/dev/null",
        "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key2.json,pass_file=/dev/null",
        "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key3.json,pass_file=/dev/null",
        "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key4.json,pass_file=/dev/null",
        "key_file=lib/pymaker/tests/config/keys/UnlimitedChain/key.json,pass_file=/dev/null"
    ])

    # reduce logspew
    logging.getLogger("web3").setLevel(logging.INFO)
    logging.getLogger("urllib3").setLevel(logging.INFO)
    logging.getLogger("asyncio").setLevel(logging.INFO)

    return web3
Esempio n. 4
0
def web3() -> Web3:
    # for local dockerized parity testchain
    web3 = web3_via_http("http://0.0.0.0:8545")
    web3.eth.defaultAccount = "0x50FF810797f75f6bfbf2227442e0c961a8562F4C"
    register_keys(web3,
                  ["key_file=tests/config/keys/UnlimitedChain/key1.json,pass_file=/dev/null",
                   "key_file=tests/config/keys/UnlimitedChain/key2.json,pass_file=/dev/null",
                   "key_file=tests/config/keys/UnlimitedChain/key3.json,pass_file=/dev/null",
                   "key_file=tests/config/keys/UnlimitedChain/key4.json,pass_file=/dev/null",
                   "key_file=tests/config/keys/UnlimitedChain/key.json,pass_file=/dev/null"])

    # reduce logspew
    logging.getLogger("web3").setLevel(logging.INFO)
    logging.getLogger("urllib3").setLevel(logging.INFO)
    logging.getLogger("asyncio").setLevel(logging.INFO)

    assert len(web3.eth.accounts) > 3
    return web3
Esempio n. 5
0
 def test_unsupported_url(self):
     with pytest.raises(ValueError):
         web3_via_http("wss://0.0.0.0:8545")
Esempio n. 6
0
    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))
Esempio n. 7
0
    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)
Esempio n. 8
0
    Transact, Wad, web3_via_http
from pymaker.gas import FixedGasPrice, GasStrategy, GeometricGasPrice
from pymaker.keys import register_keys
from pymaker.util import synchronize, bytes_to_hexstring

logging.basicConfig(
    format='%(asctime)-15s [%(thread)d] %(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)

transact = False
web3 = web3_via_http(endpoint_uri=os.environ['ETH_RPC_URL'])
if len(sys.argv) > 1:
    web3.eth.defaultAccount = sys.argv[
        1]  # ex: 0x0000000000000000000000000000000aBcdef123
    if len(sys.argv) > 2:
        register_keys(
            web3, [sys.argv[2]]
        )  # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass
        transact = True
    our_address = Address(web3.eth.defaultAccount)
    stuck_txes_to_submit = int(sys.argv[3]) if len(sys.argv) > 3 else 0
else:
    our_address = None
    stuck_txes_to_submit = 0

GWEI = 1000000000
Esempio n. 9
0
from pymaker import Address, eth_transfer, web3_via_http
from pymaker.gas import DefaultGasPrice, GeometricGasPrice
from pymaker.lifecycle import Lifecycle
from pymaker.keys import register_keys
from pymaker.numeric import Wad
from pymaker.token import EthToken

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)

endpoint_uri = sys.argv[1]
web3 = web3_via_http(endpoint_uri, timeout=10)
print(web3.clientVersion)

"""
Purpose: Tests pymaker on chains or layer-2s where multi-collateral Dai is not deployed.

Argument:           Reqd?   Example:
Ethereum node URI   yes     https://localhost:8545
Ethereum address    no      0x0000000000000000000000000000000aBcdef123
Private key         no      key_file=~keys/default-account.json,pass_file=~keys/default-account.pass
Gas tip (GWEI)      no      9
"""


if len(sys.argv) > 3:
    web3.eth.defaultAccount = sys.argv[2]
Esempio n. 10
0
from pymaker import Address, web3_via_http
from pymaker.deployment import DssDeployment
from pymaker.gas import FixedGasPrice, GeometricGasPrice
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()
    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='uniswap-market-maker-keeper')

        parser.add_argument(
            "--endpoint-uri",
            type=str,
            default="http://localhost:8545",
            help="JSON-RPC uri (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(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--token-config",
                            type=str,
                            required=True,
                            help="Token configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-accepted-delay",
            type=int,
            default=60,
            help=
            "Number of seconds the keeper will tolerate the price feed being null before removing liquidity"
        )

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the price feed (in seconds, default: 86400)")

        parser.add_argument(
            "--max-add-liquidity-slippage",
            type=int,
            default=2,
            help=
            "Maximum percentage off the desired amount of liquidity to add in add_liquidity()"
        )

        parser.add_argument(
            "--accepted-price-slippage-up",
            type=float,
            required=True,
            help=
            "Percentage difference between Uniswap exchange rate and aggregated price above which liquidity would be added"
        )

        parser.add_argument(
            "--accepted-price-slippage-down",
            type=float,
            required=True,
            help=
            "Percentage difference between Uniswap exchange rate and aggregated price below which liquidity would be added"
        )

        parser.add_argument("--target-a-min-balance",
                            type=float,
                            required=True,
                            help="Minimum balance of token A to maintain.")

        parser.add_argument("--target-a-max-balance",
                            type=float,
                            required=True,
                            help="Minimum balance of token A to maintain.")

        parser.add_argument("--target-b-min-balance",
                            type=float,
                            required=True,
                            help="Minimum balance of token B to maintain.")

        parser.add_argument("--target-b-max-balance",
                            type=float,
                            required=True,
                            help="Minimum balance of token B to maintain.")

        parser.add_argument(
            "--factory-address",
            type=str,
            default="0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
            help=
            "Address of the UniswapV2 Factory smart contract used to create new pools"
        )

        parser.add_argument(
            "--router-address",
            type=str,
            default="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
            help=
            "Address of the UniswapV2 RouterV2 smart contract used to handle liquidity management"
        )

        parser.add_argument(
            "--initial-delay",
            type=int,
            default=10,
            help="Initial number of seconds to wait before placing liquidity")

        parser.add_argument(
            '--staking-rewards-name',
            type=StakingRewardsName,
            choices=StakingRewardsName,
            help="Name of contract to stake liquidity tokens with")

        parser.add_argument(
            "--staking-rewards-contract-address",
            type=str,
            help="Address of contract to stake liquidity tokens with")

        parser.add_argument(
            "--staking-rewards-target-reward-amount",
            type=float,
            help="Address of contract to stake liquidity tokens with")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        add_gas_arguments(parser)
        self.arguments = parser.parse_args(args)

        setup_logging(self.arguments)

        self.web3: Web3 = web3_via_http(self.arguments.endpoint_uri,
                                        self.arguments.rpc_timeout)
        self.web3.eth.defaultAccount = self.arguments.eth_from
        self.our_address = Address(self.web3.eth.defaultAccount)
        if 'web3' not in kwargs:
            register_keys(self.web3, self.arguments.eth_key)

        self.gas_price = GasPriceFactory().create_gas_price(
            self.web3, self.arguments)

        # TODO: Add a more sophisticated regex for different variants of eth on the exchange
        # Record if eth is in pair, so can check which liquidity method needs to be used
        self.is_eth = 'ETH' in self.pair()

        # Identify which token is ETH, so we can provide the arguments to Uniswap Router in expected order
        self.eth_position = 1
        if self.is_eth:
            self.eth_position = 0 if self.pair().split('-')[0] == 'ETH' else 1

        self.reloadable_config = ReloadableConfig(self.arguments.token_config)
        self._last_config_dict = None
        self._last_config = None
        self.token_config = self.get_token_config().token_config

        self.token_a, self.token_b = self.instantiate_tokens(self.pair())

        self.uniswap = UniswapV2(self.web3, self.token_a, self.token_b,
                                 self.our_address,
                                 Address(self.arguments.router_address),
                                 Address(self.arguments.factory_address))

        # instantiate specific StakingRewards depending on arguments
        self.staking_rewards = StakingRewardsFactory().create_staking_rewards(
            self.arguments, self.web3)
        self.staking_rewards_target_reward_amount = self.arguments.staking_rewards_target_reward_amount

        # configure price feed
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.price_feed_accepted_delay = self.arguments.price_feed_accepted_delay
        self.control_feed = create_control_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.feed_price_null_counter = 0

        # testing_feed_price is used by the integration tests in tests/test_uniswapv2.py, to test different pricing scenarios
        # as the keeper consistently checks the price, some long running state variable is needed to
        self.testing_feed_price = False
        self.test_price = Wad.from_number(0)

        # initalize uniswap price
        self.uniswap_current_exchange_price = self.uniswap.get_exchange_rate()

        # set target min and max amounts for each side of the pair
        # balance doesnt exceed some level, as an effective stop loss against impermanent loss
        self.target_a_min_balance = Wad.from_number(
            self.arguments.target_a_min_balance)
        self.target_a_max_balance = Wad.from_number(
            self.arguments.target_a_max_balance)
        self.target_b_min_balance = Wad.from_number(
            self.arguments.target_b_min_balance)
        self.target_b_max_balance = Wad.from_number(
            self.arguments.target_b_max_balance)

        self.accepted_price_slippage_up = Wad.from_number(
            self.arguments.accepted_price_slippage_up / 100)
        self.accepted_price_slippage_down = Wad.from_number(
            self.arguments.accepted_price_slippage_down / 100)
        self.max_add_liquidity_slippage = Wad.from_number(
            self.arguments.max_add_liquidity_slippage / 100)
Esempio n. 12
0
    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='oasis-market-maker-keeper')

        parser.add_argument(
            "--endpoint-uri",
            type=str,
            help="JSON-RPC uri (example: `http://localhost:8545`)")

        parser.add_argument(
            "--rpc-host",
            default="localhost",
            type=str,
            help="[DEPRECATED] JSON-RPC host (default: `localhost')")

        parser.add_argument(
            "--rpc-port",
            default=8545,
            type=int,
            help="[DEPRECATED] 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("--tub-address",
                            type=str,
                            required=False,
                            help="Ethereum address of the Tub contract")

        parser.add_argument("--oasis-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the OasisDEX contract")

        parser.add_argument(
            "--oasis-support-address",
            type=str,
            required=False,
            help="Ethereum address of the OasisDEX support contract")

        parser.add_argument("--buy-token-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the buy token")

        parser.add_argument("--sell-token-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the sell token")

        parser.add_argument("--buy-token-name",
                            type=str,
                            required=True,
                            help="Ethereum address of the buy token")

        parser.add_argument("--sell-token-name",
                            type=str,
                            required=True,
                            help="Ethereum address of the sell token")

        parser.add_argument("--buy-token-decimals",
                            type=int,
                            required=True,
                            help="Ethereum address of the buy token")

        parser.add_argument("--sell-token-decimals",
                            type=int,
                            required=True,
                            help="Ethereum address of the sell token")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--round-places",
            type=int,
            default=2,
            help="Number of decimal places to round order prices to (default=2)"
        )

        parser.add_argument(
            "--min-eth-balance",
            type=float,
            default=0,
            help="Minimum ETH balance below which keeper will cease operation")

        parser.add_argument("--gas-price",
                            type=int,
                            default=0,
                            help="Gas price (in Wei)")

        parser.add_argument(
            "--smart-gas-price",
            dest='smart_gas_price',
            action='store_true',
            help=
            "Use smart gas pricing strategy, based on the ethgasstation.info feed"
        )

        parser.add_argument("--ethgasstation-api-key",
                            type=str,
                            default=None,
                            help="ethgasstation API key")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=10,
            help="Order book refresh frequency (in seconds, default: 10)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        if 'web3' in kwargs:
            self.web3 = kwargs['web3']
        elif self.arguments.endpoint_uri:
            self.web3: Web3 = web3_via_http(self.arguments.endpoint_uri,
                                            self.arguments.rpc_timeout)
        else:
            self.logger.warning(
                "Configuring node endpoint by host and port is deprecated; please use --endpoint-uri"
            )
            self.web3 = 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
        register_keys(self.web3, self.arguments.eth_key)
        self.our_address = Address(self.arguments.eth_from)
        self.otc = MatchingMarket(
            web3=self.web3,
            address=Address(self.arguments.oasis_address),
            support_address=Address(self.arguments.oasis_support_address)
            if self.arguments.oasis_support_address else None)

        tub = Tub(web3=self.web3, address=Address(self.arguments.tub_address)) \
            if self.arguments.tub_address is not None else None

        self.token_buy = ERC20Token(web3=self.web3,
                                    address=Address(
                                        self.arguments.buy_token_address))
        self.token_sell = ERC20Token(web3=self.web3,
                                     address=Address(
                                         self.arguments.sell_token_address))
        self.buy_token = Token(name=self.arguments.buy_token_name,
                               address=Address(
                                   self.arguments.buy_token_address),
                               decimals=self.arguments.buy_token_decimals)
        self.sell_token = Token(name=self.arguments.sell_token_name,
                                address=Address(
                                    self.arguments.sell_token_address),
                                decimals=self.arguments.sell_token_decimals)
        self.min_eth_balance = Wad.from_number(self.arguments.min_eth_balance)
        self.bands_config = ReloadableConfig(self.arguments.config)
        self.gas_price = GasPriceFactory().create_gas_price(
            self.web3, self.arguments)
        self.price_feed = PriceFeedFactory().create_price_feed(
            self.arguments, tub)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(lambda: self.our_orders())
        self.order_book_manager.place_orders_with(self.place_order_function)
        self.order_book_manager.cancel_orders_with(self.cancel_order_function)
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()
    def __init__(self, args, **kwargs):
        """Pass in arguements assign necessary variables/objects and instantiate other Classes"""

        parser = argparse.ArgumentParser("simple-arbitrage-keeper")

        parser.add_argument("--rpc-host",
                            type=str,
                            default="localhost",
                            help="JSON-RPC host (default: `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 address from which to send transactions; checksummed (e.g. '0x12AebC')"
        )

        parser.add_argument(
            "--eth-key",
            type=str,
            nargs='*',
            required=True,
            help=
            "Ethereum private key(s) to use (e.g. 'key_file=/path/to/keystore.json,pass_file=/path/to/passphrase.txt')"
        )

        parser.add_argument(
            "--uniswap-entry-exchange",
            type=str,
            required=True,
            help=
            "Ethereum address of the Uniswap Exchange contract for the entry token market; checksummed (e.g. '0x12AebC')"
        )

        parser.add_argument(
            "--uniswap-arb-exchange",
            type=str,
            required=True,
            help=
            "Ethereum address of the Uniswap Exchange contract for the arb token market; checksummed (e.g. '0x12AebC')"
        )

        parser.add_argument(
            "--oasis-address",
            type=str,
            required=True,
            help=
            "Ethereum address of the OasisDEX contract; checksummed (e.g. '0x12AebC')"
        )

        parser.add_argument(
            "--oasis-api-endpoint",
            type=str,
            required=True,
            help=
            "Endpoint of of the Oasis V2 REST API (e.g. 'https://kovan-api.oasisdex.com' )"
        )

        parser.add_argument(
            "--relayer-per-page",
            type=int,
            default=100,
            help=
            "Number of orders to fetch per one page from the 0x Relayer API (default: 100)"
        )

        parser.add_argument(
            "--tx-manager",
            type=str,
            required=True,
            help=
            "Ethereum address of the TxManager contract to use for multi-step arbitrage; checksummed (e.g. '0x12AebC')"
        )

        parser.add_argument(
            "--gas-price",
            type=int,
            default=0,
            help=
            "Gas price in Wei (default: node default), (e.g. 1000000000 for 1 GWei)"
        )

        parser.add_argument(
            "--entry-token",
            type=str,
            required=True,
            help=
            "The token address that the bot starts and ends with in every transaction; checksummed (e.g. '0x12AebC')"
        )

        parser.add_argument(
            "--arb-token",
            type=str,
            required=True,
            help=
            "The token address that arbitraged between both exchanges; checksummed (e.g. '0x12AebC')"
        )

        parser.add_argument(
            "--arb-token-name",
            type=str,
            required=True,
            help=
            "The token name that arbitraged between both exchanges (e.g. 'SAI', 'WETH', 'REP')"
        )

        parser.add_argument(
            "--min-profit",
            type=int,
            required=True,
            help=
            "Ether amount of minimum profit (in base token) from one arbitrage operation (e.g. 1 for 1 Sai min profit)"
        )

        parser.add_argument(
            "--max-engagement",
            type=int,
            required=True,
            help=
            "Ether amount of maximum engagement (in base token) in one arbitrage operation (e.g. 100 for 100 Sai max engagement)"
        )

        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")

        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)

        self.web3.eth.defaultAccount = self.arguments.eth_from
        register_keys(self.web3, self.arguments.eth_key)
        self.our_address = Address(self.arguments.eth_from)

        # self.sai = ERC20Token(web3=self.web3, address=Address('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359'))  # Mainnet Sai
        # self.dai = ERC20Token(web3=self.web3, address=Address('0x6b175474e89094c44da98b954eedeac495271d0f'))  # Mainnet Dai

        self.ksai = ERC20Token(
            web3=self.web3,
            address=Address(
                '0xC4375B7De8af5a38a93548eb8453a498222C4fF2'))  #Kovan Sai
        self.kdai = ERC20Token(
            web3=self.web3,
            address=Address(
                '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa'))  #Kovan Dai

        self.entry_token = ERC20Token(web3=self.web3,
                                      address=Address(
                                          self.arguments.entry_token))
        self.arb_token = ERC20Token(web3=self.web3,
                                    address=Address(self.arguments.arb_token))
        self.arb_token.name = self.arguments.arb_token_name \
            if self.arguments.arb_token_name != 'WETH' else 'ETH'


        self.uniswap_entry_exchange = UniswapWrapper(self.web3, self.entry_token.address, Address(self.arguments.uniswap_entry_exchange)) \
            if self.arguments.uniswap_entry_exchange is not None else None

        self.uniswap_arb_exchange = UniswapWrapper(self.web3, self.arb_token.address, Address(self.arguments.uniswap_arb_exchange)) \
            if self.arguments.uniswap_arb_exchange is not None else None

        self.oasis_api_endpoint = OasisAPI(api_server=self.arguments.oasis_api_endpoint,
                                           entry_token_name=self.token_name(self.entry_token.address),
                                           arb_token_name=self.arb_token.name) \
            if self.arguments.oasis_api_endpoint is not None else None

        self.oasis = MatchingMarket(web3=self.web3,
                                    address=Address(
                                        self.arguments.oasis_address))

        self.min_profit = Wad(int(self.arguments.min_profit * 10**18))
        self.max_engagement = Wad(int(self.arguments.max_engagement * 10**18))
        self.max_errors = self.arguments.max_errors
        self.errors = 0

        if self.arguments.tx_manager:
            self.tx_manager = TxManager(web3=self.web3,
                                        address=Address(
                                            self.arguments.tx_manager))
            if self.tx_manager.owner() != self.our_address:
                raise Exception(
                    f"The TxManager has to be owned by the address the keeper is operating from."
                )
        else:
            self.tx_manager = None

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