Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
reverendus committed May 17, 2018
0 parents commit ea7d339
Show file tree
Hide file tree
Showing 16 changed files with 949 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.idea
*.iml

__pycache__
.cache
.coverage

.DS_Store
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/pymaker"]
path = lib/pymaker
url = https://github.com/makerdao/pymaker.git
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.6.2
26 changes: 26 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
language: python

os:
- linux

python:
- 3.6

cache: pip

before_cache:
- rm -f $HOME/.cache/pip/log/debug.log

install:
- pip install virtualenv --upgrade
- pip install -r requirements.txt
- pip install -r requirements-dev.txt

script:
- ./test.sh

after_success:
- codecov

notifications:
email: false
661 changes: 661 additions & 0 deletions COPYING

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# auction-keeper

[![Build Status](https://travis-ci.org/makerdao/auction-keeper.svg?branch=master)](https://travis-ci.org/makerdao/auction-keeper)
[![codecov](https://codecov.io/gh/makerdao/auction-keeper/branch/master/graph/badge.svg)](https://codecov.io/gh/makerdao/auction-keeper)

The _DAI Stablecoin System_ incentivizes external agents, called _keepers_,
to automate certain operations around the Ethereum blockchain.

`auction-keeper` is TODO.

<https://chat.makerdao.com/channel/keeper>


## Installation

This project uses *Python 3.6.2*.

In order to clone the project and install required third-party packages please execute:
```
git clone https://github.com/makerdao/auction-keeper.git
cd auction-keeper
git submodule update --init --recursive
pip3 install -r requirements.txt
```

For some known macOS issues see the [pymaker](https://github.com/makerdao/pymaker) README.


## Usage

TODO


## License

See [COPYING](https://github.com/makerdao/auction-keeper/blob/master/COPYING) file.
Empty file added auction_keeper/__init__.py
Empty file.
104 changes: 104 additions & 0 deletions auction_keeper/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# This file is part of Maker Keeper Framework.
#
# Copyright (C) 2018 reverendus
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import argparse
import logging
import sys

from web3 import Web3, HTTPProvider

from pymaker import Address, Wad
from pymaker.auctions import Flopper
from pymaker.gas import FixedGasPrice, DefaultGasPrice
from pymaker.lifecycle import Lifecycle


class AuctionKeeper:
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("--flopper", type=str, required=True,
help="Ethereum address of the Flopper contract")

parser.add_argument("--price", type=float, required=True,
help="Our price")

parser.add_argument("--spread", type=float, required=True,
help="Our spread")

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

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

self.arguments = parser.parse_args(args)

self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3(HTTPProvider(endpoint_uri=f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}",
request_kwargs={"timeout": self.arguments.rpc_timeout}))
self.web3.eth.defaultAccount = self.arguments.eth_from
self.our_address = Address(self.arguments.eth_from)

self.flopper = Flopper(web3=self.web3, address=Address(self.arguments.flopper))
self.price = Wad.from_number(self.arguments.price)
self.spread = Wad.from_number(self.arguments.spread)

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

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

def check_all_auctions(self):
for auction_id in range(1, self.flopper.kicks()+1):
self.check_auction(auction_id)

def check_auction(self, auction_id: int):
assert(isinstance(auction_id, int))

auction = self.flopper.bids(auction_id)
auction_price = auction.bid / auction.lot
auction_price_min_increment = auction_price * self.flopper.beg()

our_price = self.price * (Wad.from_number(1) - self.spread)
if our_price >= auction_price_min_increment:
our_lot = auction.bid / our_price

self.flopper.dent(auction_id, our_lot, auction.bid).transact(gas_price=self.gas_price())

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


if __name__ == '__main__':
AuctionKeeper(sys.argv[1:]).main()
4 changes: 4 additions & 0 deletions bin/auction-keeper
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
dir="$(dirname "$0")"/..
export PYTHONPATH=$PYTHONPATH:$dir:$dir/lib/pymaker
exec python3 -m auction_keeper.main $@
1 change: 1 addition & 0 deletions lib/pymaker
Submodule pymaker added at d339ad
7 changes: 7 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codecov
pytest
pytest-asyncio
pytest-cov
pytest-mock
pytest-timeout
asynctest
7 changes: 7 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
web3 == 3.16.4
eth-abi == 0.5.0
eth-utils == 0.7.1
eth-testrpc == 1.3.0
rlp == 0.6.0
requests == 2.18.4
pytz == 2017.3
3 changes: 3 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

PYTHONPATH=$PYTHONPATH:./lib/pymaker py.test --cov=auction_keeper --cov-report=term --cov-append tests/
Empty file added tests/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions tests/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This file is part of Maker Keeper Framework.
#
# Copyright (C) 2018 reverendus
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import sys
from contextlib import contextmanager
from io import StringIO


def args(arguments: str) -> list:
return arguments.split()


@contextmanager
def captured_output():
new_out, new_err = StringIO(), StringIO()
old_out, old_err = sys.stdout, sys.stderr
try:
sys.stdout, sys.stderr = new_out, new_err
yield sys.stdout, sys.stderr
finally:
sys.stdout, sys.stderr = old_out, old_err
53 changes: 53 additions & 0 deletions tests/test_flop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# This file is part of Maker Keeper Framework.
#
# Copyright (C) 2018 reverendus
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from web3 import Web3, EthereumTesterProvider

from auction_keeper.main import AuctionKeeper
from pymaker import Address
from pymaker.approval import directly
from pymaker.auctions import Flopper
from pymaker.numeric import Wad
from pymaker.token import DSToken
from tests.helper import args


class TestAuctionKeeperFlopper:
def setup_method(self):
self.web3 = Web3(EthereumTesterProvider())
self.web3.eth.defaultAccount = self.web3.eth.accounts[0]
self.our_address = Address(self.web3.eth.defaultAccount)
self.dai = DSToken.deploy(self.web3, 'DAI')
self.dai.mint(Wad.from_number(10000000)).transact()
self.mkr = DSToken.deploy(self.web3, 'MKR')
self.flopper = Flopper.deploy(self.web3, self.dai.address, self.mkr.address)

def test_should_make_initial_bid(self):
# given
keeper = AuctionKeeper(args=args(f"--eth-from {self.web3.eth.defaultAccount} "
f"--flopper {self.flopper.address} "
f"--price 850.0 "
f"--spread 0.03"), web3=self.web3)

# and
self.flopper.approve(directly())
self.flopper.kick(Address(self.web3.eth.accounts[1]), Wad.from_number(2), Wad.from_number(10)).transact()

# when
keeper.check_all_auctions()
# then
assert self.flopper.bids(self.flopper.kicks()).lot < Wad.from_number(2)

0 comments on commit ea7d339

Please sign in to comment.