Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates for LIQ-2.0 and ESM changes #21

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ To be consistent with the Protocol's technical terminology for the rest of this

The central goal of the `cage-keeper` is to process all under-collateralized `urns`. This accounting step is performed within `End.skim()`, and since it is surrounded by other required/important steps in the Emergency Shutdown, a first iteration of this keeper will help to call most of the other public function calls within the `End` contract.

As can be seen in the above flowchart, the keeper checks if the system has been caged before attempting to `skim` all underwater urns and `skip` all flip auctions. After the processing period has been facilitated and the `End.wait` waittime has been reached, it will transition the system into the Dai redemption phase of Emergency Shutdown by calling `End.thaw()` and `End.flow()`. This first iteration of this keeper is naive, as it assumes it's the only keeper and attempts to account for all urns, ilks, and auctions. Because of this, it's important that the keeper's address has enough ETH to cover the gas costs involved with sending numerous transactions. Any transaction that attempts to call a function that's already been invoked by another Keeper/user would simply fail.
As can be seen in the above flowchart, the keeper checks if the system has been caged before attempting to `skim` all underwater urns, `snip` all clip auctions, and `skip` all flip auctions. After the processing period has been facilitated and the `End.wait` waittime has been reached, it will transition the system into the Dai redemption phase of Emergency Shutdown by calling `End.thaw()` and `End.flow()`. This first iteration of this keeper is naive, as it assumes it's the only keeper and attempts to account for all urns, ilks, and auctions. Because of this, it's important that the keeper's address has enough ETH to cover the gas costs involved with sending numerous transactions. Any transaction that attempts to call a function that's already been invoked by another Keeper/user would simply fail.


## Operation
Expand All @@ -39,6 +39,7 @@ The keeper's ethereum address should have enough ETH to cover gas costs and is a
min_ETH = average_gasPrice * [ ( Flopper.yank()_gas * #_of_Flop_Auctions ) +
( Flapper.yank()_gas * #_of_Flap_Auctions ) +
( End.cage(Ilk)_gas * #_of_Collateral_Types ) +
( End.snip()_gas * #_of_Clip_Auctions ) +
( End.skip()_gas * #_of_Flip_Auctions ) +
( End.skim()_gas * #_of_Underwater_Vaults ) +
( Vow.heal()_gas ) +
Expand Down Expand Up @@ -95,6 +96,9 @@ Make a run-cage-keeper.sh to easily spin up the cage-keeper.
[--vulcanize-endpoint 'http://vdb.sampleendpoint.com:8545/graphql']
```

To `flow` the PSM along with other collaterals, pass the `--psm` address. Current addresses may be obtained from
https://github.com/BellwoodStudios/dss-psm .


## Testing

Expand Down
2 changes: 1 addition & 1 deletion lib/auction-keeper
Submodule auction-keeper updated 53 files
+2 −2 .gitmodules
+ README-from-block.png
+96 −63 README.md
+20 −13 auction_keeper/gas.py
+6 −16 auction_keeper/logic.py
+269 −95 auction_keeper/main.py
+41 −32 auction_keeper/model.py
+1 −1 auction_keeper/process.py
+113 −4 auction_keeper/strategy.py
+35 −165 auction_keeper/urn_history.py
+98 −0 auction_keeper/urn_history_tokenflow.py
+203 −0 auction_keeper/urn_history_vulcanize.py
+1 −1 lib/pygasprice-client
+1 −1 lib/pymaker
+1 −1 test.sh
+40 −0 tests/TESTING.md
+189 −29 tests/conftest.py
+34 −0 tests/create-vault.sh
+14 −1 tests/helper.py
+84 −0 tests/manual_test_check_vaults.py
+19 −7 tests/manual_test_create_unsafe_vault.py
+58 −0 tests/manual_test_get_unsafe_vault_params.py
+92 −63 tests/manual_test_urn_history.py
+1 −2 tests/random_bid.py
+4 −15 tests/test_accounting.py
+0 −111 tests/test_bite.py
+8 −12 tests/test_config.py
+21 −58 tests/test_gas.py
+41 −80 tests/test_imbalance_flap.py
+44 −27 tests/test_imbalance_flop.py
+454 −0 tests/test_liquidation_clip.py
+138 −34 tests/test_liquidation_flip.py
+0 −74 tests/testchain/README.md
+0 −15 tests/testchain/create-cdp.sh
+0 −16 tests/testchain/create-cdps.sh
+0 −43 tests/testchain/create_surplus.py
+0 −34 tests/testchain/create_unsafe_cdp.py
+0 −43 tests/testchain/docker-compose.yml
+0 −32 tests/testchain/mint_mkr.py
+0 −4 tests/testchain/model_1.sh
+0 −4 tests/testchain/model_153.sh
+0 −4 tests/testchain/model_192.sh
+0 −4 tests/testchain/model_512.sh
+0 −67 tests/testchain/print.py
+0 −38 tests/testchain/purchase_dai.py
+0 −27 tests/testchain/set_eth_price.py
+0 −22 tests/testchain/start-flap-keeper.sh
+0 −24 tests/testchain/start-flip-keeper.sh
+0 −22 tests/testchain/start-flop-keeper.sh
+0 −17 tests/testchain/test-build-surplus.sh
+0 −21 tests/testchain/test-drop-price.sh
+0 −17 tests/testchain/test-monitor.sh
+0 −18 tests/testchain/testnet.sh
2 changes: 1 addition & 1 deletion lib/pymaker
Submodule pymaker updated 83 files
+22 −5 README.md
+85 −7 config/kovan-addresses.json
+121 −7 config/mainnet-addresses.json
+65 −62 config/testnet-addresses.json
+1 −1 docker-compose.yml
+49 −15 pymaker/__init__.py
+1 −1 pymaker/abi/Cat.bin
+1 −0 pymaker/abi/Clipper.abi
+1 −0 pymaker/abi/Clipper.bin
+1 −0 pymaker/abi/ClipperCallee.abi
+1 −0 pymaker/abi/ClipperCallee.bin
+1 −1 pymaker/abi/DSChief.abi
+1 −1 pymaker/abi/DSChief.bin
+1 −1 pymaker/abi/DaiJoin.bin
+1 −0 pymaker/abi/Dog.abi
+1 −0 pymaker/abi/Dog.bin
+1 −1 pymaker/abi/DsrManager.bin
+1 −1 pymaker/abi/DssCdpManager.bin
+1 −1 pymaker/abi/ESM.abi
+1 −1 pymaker/abi/ESM.bin
+1 −1 pymaker/abi/End.abi
+1 −1 pymaker/abi/End.bin
+0 −1 pymaker/abi/ExpiringMarket.abi
+0 −1 pymaker/abi/ExpiringMarket.bin
+1 −1 pymaker/abi/Flapper.bin
+1 −1 pymaker/abi/Flipper.bin
+1 −1 pymaker/abi/Flopper.bin
+1 −1 pymaker/abi/GemJoin.bin
+1 −1 pymaker/abi/GemJoin5.abi
+1 −1 pymaker/abi/GemJoin5.bin
+1 −1 pymaker/abi/Jug.bin
+1 −1 pymaker/abi/MatchingMarket.abi
+1 −1 pymaker/abi/MatchingMarket.bin
+1 −1 pymaker/abi/OSM.bin
+1 −1 pymaker/abi/Pot.bin
+1 −1 pymaker/abi/ProxyRegistry.bin
+1 −1 pymaker/abi/SimpleMarket.abi
+1 −1 pymaker/abi/SimpleMarket.bin
+1 −1 pymaker/abi/Spotter.bin
+1 −0 pymaker/abi/TokenFaucet.abi
+1 −0 pymaker/abi/TokenFaucet.bin
+1 −1 pymaker/abi/Vat.bin
+1 −1 pymaker/abi/Vow.bin
+0 −0 pymaker/abi/diff-abi.sh
+374 −89 pymaker/auctions.py
+68 −0 pymaker/collateral.py
+66 −29 pymaker/deployment.py
+2 −1 pymaker/dsrmanager.py
+222 −228 pymaker/dss.py
+9 −0 pymaker/governance.py
+90 −0 pymaker/ilk.py
+120 −0 pymaker/join.py
+20 −0 pymaker/keys.py
+5 −3 pymaker/lifecycle.py
+23 −1 pymaker/model.py
+28 −0 pymaker/numeric.py
+37 −99 pymaker/oasis.py
+20 −0 pymaker/oracles.py
+118 −0 pymaker/reloadable_config.py
+14 −4 pymaker/shutdown.py
+15 −0 pymaker/token.py
+1 −0 requirements.txt
+6 −7 setup.py
+24 −0 test-dss.sh
+1 −0 tests/abi/OasisMockPriceOracle.abi
+1 −0 tests/abi/OasisMockPriceOracle.bin
+12 −0 tests/abi/OasisMockPriceOracle.sol
+10 −1 tests/conftest.py
+3 −0 tests/helpers.py
+8 −6 tests/manual_test_async_tx.py
+15 −4 tests/manual_test_mcd.py
+8 −4 tests/manual_test_node.py
+6 −5 tests/manual_test_tx_recovery.py
+334 −126 tests/test_auctions.py
+5 −3 tests/test_dsrmanager.py
+105 −53 tests/test_dss.py
+38 −17 tests/test_governance.py
+44 −0 tests/test_numeric.py
+79 −60 tests/test_oasis.py
+128 −0 tests/test_reloadable_config.py
+26 −18 tests/test_savings.py
+5 −8 tests/test_shutdown.py
+17 −0 tests/test_token.py
Binary file modified operation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
248 changes: 133 additions & 115 deletions src/cage_keeper.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

# Pull the docker image
docker pull makerdao/testchain-pymaker:unit-testing
docker pull makerdao/testchain-pymaker:unit-testing-2.0.0

# Start the docker image and wait for parity to initialize
pushd ./lib/pymaker
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def mcd(web3) -> DssDeployment:

@pytest.fixture(scope="session")
def keeper(mcd: DssDeployment, keeper_address: Address) -> CageKeeper:
keeper = CageKeeper(args=args(f"--eth-from {keeper_address} --network testnet --vat-deployment-block {1}"), web3=mcd.web3)
keeper = CageKeeper(args=args(f"--eth-from {keeper_address} --vat-deployment-block {1}"), web3=mcd.web3)
assert isinstance(keeper, CageKeeper)

return keeper
Expand Down
117 changes: 74 additions & 43 deletions tests/test_cageKeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
from pymaker import Address
from pymaker.approval import directly, hope_directly
from pymaker.auctions import Flapper, Flopper, Flipper
from pymaker.deployment import DssDeployment
from pymaker.dss import Collateral, Ilk, Urn
from pymaker.deployment import Collateral, DssDeployment
from pymaker.dss import Ilk, Urn
from pymaker.numeric import Wad, Ray, Rad
from pymaker.shutdown import ShutdownModule, End

Expand Down Expand Up @@ -86,7 +86,7 @@ def create_surplus(mcd: DssDeployment, flapper: Flapper, deployment_address: Add
if joy < mcd.vow.hump() + mcd.vow.bump():
# Create a CDP with surplus
print('Creating a CDP with surplus')
collateral = mcd.collaterals['ETH-B']
collateral = mcd.collaterals['ETH-C']
assert flapper.kicks() == 0
wrap_eth(mcd, deployment_address, Wad.from_number(10))
collateral.approve(deployment_address)
Expand Down Expand Up @@ -119,7 +119,6 @@ def create_flap_auction(mcd: DssDeployment, deployment_address: Address, our_add
assert kick == 1
assert len(flapper.active_auctions()) == 1


mint_mkr(mcd.mkr, our_address, Wad.from_number(10))
flapper.approve(mcd.mkr.address, directly(from_address=our_address))
bid = Wad.from_number(0.001)
Expand Down Expand Up @@ -150,7 +149,6 @@ def create_flop_auction(mcd: DssDeployment, deployment_address: Address, our_add
check_active_auctions(flopper)
current_bid = flopper.bids(kicks)


bid = Wad.from_number(0.000005)
flopper.approve(mcd.vat.address, approval_function=hope_directly(from_address=our_address))
assert mcd.vat.can(our_address, flopper.address)
Expand Down Expand Up @@ -179,14 +177,16 @@ def dent(flopper: Flopper, id: int, address: Address, lot: Wad, bid: Rad):
assert flopper.dent(id, lot, bid).transact(from_address=address)


def create_flip_auction(mcd: DssDeployment, deployment_address: Address, our_address: Address):
def create_clip_auction(mcd: DssDeployment, deployment_address: Address, our_address: Address):
assert isinstance(mcd, DssDeployment)
assert isinstance(our_address, Address)
assert isinstance(deployment_address, Address)

# Create a CDP
collateral = mcd.collaterals['ETH-A']
collateral = mcd.collaterals['ETH-B']
ilk = collateral.ilk
original_price = Wad(mcd.web3.toInt(collateral.pip.read()))

# Create a vault
wrap_eth(mcd, deployment_address, Wad.from_number(1))
collateral.approve(deployment_address)
assert collateral.adapter.join(deployment_address, Wad.from_number(1)).transact(
Expand All @@ -195,27 +195,66 @@ def create_flip_auction(mcd: DssDeployment, deployment_address: Address, our_add
dart = max_dart(mcd, collateral, deployment_address) - Wad(1)
frob(mcd, collateral, deployment_address, dink=Wad(0), dart=dart)

# Undercollateralize and bark the vault
set_collateral_price(mcd, collateral, original_price / Wad.from_number(2))
urn = mcd.vat.urn(collateral.ilk, deployment_address)
ilk = mcd.vat.ilk(ilk.name)
safe = Ray(urn.art) * mcd.vat.ilk(ilk.name).rate <= Ray(urn.ink) * ilk.spot
assert not safe
assert mcd.dog.bark(ilk, urn).transact()
clip_kick = collateral.clipper.kicks()

# Generate some Dai, bid on part of the clip auction
wrap_eth(mcd, our_address, Wad.from_number(10))
collateral.approve(our_address)
assert collateral.adapter.join(our_address, Wad.from_number(10)).transact(from_address=our_address)
mcd.web3.eth.defaultAccount = our_address.address
frob(mcd, collateral, our_address, dink=Wad.from_number(10), dart=Wad.from_number(200))
collateral.clipper.approve(mcd.vat.address, approval_function=hope_directly())
(needs_redo, price, lot, tab) = collateral.clipper.status(clip_kick)
assert collateral.clipper.take(clip_kick, lot/Wad.from_number(2), price, our_address).transact(
from_address=our_address)

# Reset price
set_collateral_price(mcd, collateral, original_price)


def create_flip_auction(mcd: DssDeployment, other_address: Address, our_address: Address):
assert isinstance(mcd, DssDeployment)
assert isinstance(our_address, Address)
assert isinstance(other_address, Address)

# Create a CDP
collateral = mcd.collaterals['ETH-A']
ilk = collateral.ilk
wrap_eth(mcd, other_address, Wad.from_number(1))
collateral.approve(other_address)
assert collateral.adapter.join(other_address, Wad.from_number(1)).transact(
from_address=other_address)
frob(mcd, collateral, other_address, dink=Wad.from_number(1), dart=Wad(0))
# dart = max_dart(mcd, collateral, other_address) - Wad(1)
# frob(mcd, collateral, other_address, dink=Wad(0), dart=dart)

# Undercollateralize and bite the CDP
to_price = Wad(mcd.web3.toInt(collateral.pip.read())) / Wad.from_number(2)
set_collateral_price(mcd, collateral, to_price)
urn = mcd.vat.urn(collateral.ilk, deployment_address)
urn = mcd.vat.urn(collateral.ilk, other_address)
ilk = mcd.vat.ilk(ilk.name)
safe = Ray(urn.art) * mcd.vat.ilk(ilk.name).rate <= Ray(urn.ink) * ilk.spot
assert not safe
assert mcd.cat.can_bite(collateral.ilk, Urn(deployment_address))
assert mcd.cat.bite(collateral.ilk, Urn(deployment_address)).transact()
assert mcd.cat.can_bite(collateral.ilk, Urn(other_address))
assert mcd.cat.bite(collateral.ilk, Urn(other_address)).transact()
flip_kick = collateral.flipper.kicks()

# Generate some Dai, bid on the flip auction without covering all the debt
wrap_eth(mcd, our_address, Wad.from_number(10))
collateral.approve(our_address)
assert collateral.adapter.join(our_address, Wad.from_number(10)).transact(from_address=our_address)
mcd.web3.eth.defaultAccount = our_address.address
frob(mcd, collateral, our_address, dink=Wad.from_number(10), dart=Wad.from_number(200))
frob(mcd, collateral, our_address, dink=Wad.from_number(10), dart=Wad.from_number(0))
collateral.flipper.approve(mcd.vat.address, approval_function=hope_directly())
current_bid = collateral.flipper.bids(flip_kick)
urn = mcd.vat.urn(collateral.ilk, our_address)
assert Rad(urn.art) > current_bid.tab
bid = Rad.from_number(6)
tend(collateral.flipper, flip_kick, our_address, current_bid.lot, bid)

Expand Down Expand Up @@ -245,7 +284,7 @@ def prepare_esm(mcd: DssDeployment, our_address: Address):
assert isinstance(mcd.esm.address, Address)
assert mcd.esm.sum() == Wad(0)
assert mcd.esm.min() > Wad(0)
assert not mcd.esm.fired()
assert mcd.end.live()

assert mcd.mkr.approve(mcd.esm.address).transact()

Expand All @@ -264,7 +303,6 @@ def prepare_esm(mcd: DssDeployment, our_address: Address):
def fire_esm(mcd: DssDeployment):
assert mcd.end.live()
assert mcd.esm.fire().transact()
assert mcd.esm.fired()
assert not mcd.end.live()


Expand All @@ -283,38 +321,17 @@ def test_check_deployment(self, mcd: DssDeployment, keeper: CageKeeper):
print_out("test_check_deployment")
keeper.check_deployment()

def test_get_underwater_urns(self, mcd: DssDeployment, keeper: CageKeeper, guy_address: Address, our_address: Address):
print_out("test_get_underwater_urns")
def test_get_collaterals(self, mcd: DssDeployment, keeper: CageKeeper):
print_out("test_get_collaterals")

previous_eth_price = open_underwater_urn(mcd, mcd.collaterals['ETH-A'], guy_address)
open_vault(mcd, mcd.collaterals['ETH-C'], our_address)

ilks = keeper.get_ilks()

urns = keeper.get_underwater_urns(ilks)
assert type(urns) is list
assert all(isinstance(x, Urn) for x in urns)
assert len(urns) == 1
assert urns[0].address.address == guy_address.address

## We've multiplied by a small Ray amount to counteract
## the residual dust (or lack thereof) in this step that causes
## create_flop_auction fail
set_collateral_price(mcd, mcd.collaterals['ETH-A'], Wad(previous_eth_price * Ray.from_number(1.0001)))

pytest.global_urns = urns

def test_get_ilks(self, mcd: DssDeployment, keeper: CageKeeper):
print_out("test_get_ilks")

ilks = keeper.get_ilks()
assert type(ilks) is list
assert all(isinstance(x, Ilk) for x in ilks)
collaterals = keeper.get_collaterals()
assert type(collaterals) is list
assert all(isinstance(x, Collateral) for x in collaterals)
deploymentIlks = [mcd.vat.ilk(key) for key in mcd.collaterals.keys()]

empty_deploymentIlks = list(filter(lambda l: mcd.vat.ilk(l.name).art == Wad(0), deploymentIlks))

assert all(elem not in empty_deploymentIlks for elem in ilks)
assert all(elem.ilk not in empty_deploymentIlks for elem in collaterals)

def test_active_auctions(self, mcd: DssDeployment, keeper: CageKeeper, our_address: Address, other_address: Address, deployment_address: Address):
print_out("test_active_auctions")
Expand All @@ -323,18 +340,27 @@ def test_active_auctions(self, mcd: DssDeployment, keeper: CageKeeper, our_addre

create_flap_auction(mcd, deployment_address, our_address)
create_flop_auction(mcd, deployment_address, other_address)
create_clip_auction(mcd, deployment_address, our_address)
# this flip auction sets the collateral back to a price that makes the guy's vault underwater again.
# 49 to make it underwater, and create_flip_auction sets it to 33
create_flip_auction(mcd, deployment_address, our_address)

auctions = keeper.all_active_auctions()
assert "clips" in auctions
assert "flips" in auctions
assert "flops" in auctions
assert "flaps" in auctions

nobody = Address("0x0000000000000000000000000000000000000000")

# All auctions active before cage have been yanked
for ilk in auctions["clips"].keys():
for auction in auctions["clips"][ilk]:
assert len(auctions["clips"][ilk]) == 1
assert auction.id > 0
assert auction.lot > Wad(0)
assert auction.usr != nobody

for ilk in auctions["flips"].keys():
for auction in auctions["flips"][ilk]:
assert len(auctions["flips"][ilk]) == 1
Expand Down Expand Up @@ -387,11 +413,12 @@ def test_check_cage(self, mcd: DssDeployment, keeper: CageKeeper, our_address: A

def test_cage_keeper(self, mcd: DssDeployment, keeper: CageKeeper, our_address: Address, other_address: Address):
print_out("test_cage_keeper")
ilks = keeper.get_ilks()
ilks = list(map(lambda l: l.ilk, keeper.get_collaterals()))
urns = pytest.global_urns
auctions = pytest.global_auctions

for ilk in ilks:
print(f"Checking end.tag for {ilk.name}")
# Check if cage(ilk) called on all ilks
assert mcd.end.tag(ilk) > Ray(0)

Expand All @@ -404,6 +431,10 @@ def test_cage_keeper(self, mcd: DssDeployment, keeper: CageKeeper, our_address:
assert urn.art == Wad(0)

# All auctions active before cage have been yanked
for ilk in auctions["clips"].keys():
for auction in auctions["clips"][ilk]:
assert mcd.collaterals[ilk].clipper.sales(auction.id).lot == Wad(0)

for ilk in auctions["flips"].keys():
for auction in auctions["flips"][ilk]:
assert mcd.collaterals[ilk].flipper.bids(auction.id).lot == Wad(0)
Expand Down