Skip to content

Commit

Permalink
fixup! fixup! fixup! contracts: migration simulation: Test migration …
Browse files Browse the repository at this point in the history
…simulation
  • Loading branch information
bingen committed Mar 26, 2021
1 parent 646b2a8 commit b748e99
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.6.11;

import "../TroveManager.sol";


contract TroveManagerNoBootstrap is TroveManager {
function _requireAfterBootstrapPeriod() internal view override {}
}
2 changes: 1 addition & 1 deletion packages/contracts/contracts/TroveManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1488,7 +1488,7 @@ contract TroveManager is LiquityBase, Ownable, CheckContract, ITroveManager {
require(_getTCR(_price) >= MCR, "TroveManager: Cannot redeem when TCR < MCR");
}

function _requireAfterBootstrapPeriod() internal view {
function _requireAfterBootstrapPeriod() internal view virtual {
uint systemDeploymentTime = lqtyToken.getDeploymentStartTime();
require(block.timestamp >= systemDeploymentTime.add(BOOTSTRAP_PERIOD), "TroveManager: Redemptions are not allowed during bootstrap phase");
}
Expand Down
9 changes: 7 additions & 2 deletions packages/contracts/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ def deploy_contracts():

contracts.priceFeedTestnet = PriceFeedTestnet.deploy({ 'from': accounts[0] })
contracts.sortedTroves = SortedTroves.deploy({ 'from': accounts[0] })
contracts.troveManager = TroveManager.deploy({ 'from': accounts[0] })
#contracts.troveManager = TroveManager.deploy({ 'from': accounts[0] })
contracts.troveManager = TroveManagerNoBootstrap.deploy({ 'from': accounts[0] })
contracts.activePool = ActivePool.deploy({ 'from': accounts[0] })
contracts.stabilityPool = StabilityPool.deploy({ 'from': accounts[0] })
contracts.gasPool = GasPool.deploy({ 'from': accounts[0] })
Expand Down Expand Up @@ -169,7 +170,11 @@ def deploy_contracts():
return contracts

@pytest.fixture
def add_accounts():
@pytest.mark.require_network("openethereum")
def add_accounts(scope="module"):
import_accounts(accounts)
"""
if network.show_active() != 'development':
print("Importing accounts...")
import_accounts(accounts)
"""
155 changes: 127 additions & 28 deletions packages/contracts/tests/migration_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
ETHER_PRICE = Wei(1000 * 1e18)
LUSD_GAS_COMPENSATION = Wei(50 * 1e18)

NONE = 0
CLOSED = 1
REDEEMED = 2
ALREADY_CLOSED = 3
CLOSE_FAILED = -1
OPEN_FAILED = -2

# Subtracts the borrowing fee
def get_lusd_amount_from_net_debt(contracts, net_debt):
borrowing_rate = contracts.troveManager.getBorrowingRateWithDecay()
Expand Down Expand Up @@ -38,92 +45,184 @@ def flesh_out_system(accounts, contracts):
for i in range(1, 1000):
lusd_amount = MIN_LUSD_AMOUNT + floatToWei(1000000 * random.random()) # lusd ranging from min to 1M
borrowing_fee = contracts.troveManager.getBorrowingFeeWithDecay(lusd_amount)
ICR = ICR - 0.76 * random.random() # last trove ICR should be at ~ 500 - 999*0.76*0.5 = 120.38
if ICR < 110:
print(f"MCR reached at account {i}!")
break
if ICR < 120:
ICR = ICR - 0.01
else:
ICR = ICR - 0.76 * random.random() # last trove ICR should be at ~ 500 - 999*0.76*0.5 = 120.38
coll = Wei((lusd_amount + borrowing_fee + LUSD_GAS_COMPENSATION) * floatToWei(ICR / 100) / ETHER_PRICE)
try:
contracts.borrowerOperations.openTrove(MAX_FEE, lusd_amount, accounts[i-1], ZERO_ADDRESS,
{ 'from': accounts[i], 'value': coll })
except:
print("\n Opening trove failed! \n")
print(f"Borrower Operations: {contracts.borrowerOperations.address}")
print(f"i: {i}")
print(f"account: {accounts[i]}")
print(f"ICR: {ICR}")
print(f"coll: {coll}")
print(f"debt: {lusd_amount}")
exit(1)


# claim LUSD gain from LQTY Staking
contracts.lqtyStaking.unstake(0, { 'from': accounts[1] })

def get_lusd_to_repay(accounts, contracts, account, debt):
if account == accounts[1]:
return
lusdBalance = contracts.lusdToken.balanceOf(account)
lusd_balance = contracts.lusdToken.balanceOf(account)
pending = Wei(0)

if debt > lusdBalance:
pending = debt - lusdBalance
if debt > lusd_balance:
pending = debt - lusd_balance
sender_balance = contracts.lusdToken.balanceOf(accounts[1])
if sender_balance < pending:
print(f"\n ***Error: not enough LUSD to repay! {debt / 1e18} LUSD for {account}, pending {pending / 1e18}, sender balance {sender_balance / 1e18}")
return

contracts.lusdToken.transfer(account, pending, { 'from': accounts[1] })

def migrate_trove(accounts, contracts1, contracts2, i):
return lusd_balance + pending

def migrate_trove(accounts, contracts1, contracts2, i, upper_hint=None, lower_hint=None):
result = NONE
# [debt, coll] = contracts1.troveManager.getEntireDebtAndColl(accounts[i])
trove = contracts1.troveManager.getEntireDebtAndColl(accounts[i])
debt = trove[0]
coll = trove[1]
get_lusd_to_repay(accounts, contracts1, accounts[i], debt - LUSD_GAS_COMPENSATION)

# close trove in old system
try:
contracts1.borrowerOperations.closeTrove({ 'from': accounts[i] })
except:
print("\n Closing trove failed! \n")
print(f"i: {i}")
print(f"account: {accounts[i]}")
print(f"coll: {coll}")
print(f"debt: {debt}")
total_coll = contracts1.troveManager.getEntireSystemColl() - contracts1.troveManager.getTroveColl(accounts[i])
total_debt = contracts1.troveManager.getEntireSystemDebt() - contracts1.troveManager.getTroveDebt(accounts[i])
print(f"Resulting ICR: {contracts1.hintHelpers.computeCR(total_coll, total_debt, ETHER_PRICE)}")
exit(1)
total_coll = contracts1.troveManager.getEntireSystemColl() - coll
total_debt = contracts1.troveManager.getEntireSystemDebt() - debt
# Trove may have been closed by a redemption
trove_status = contracts1.troveManager.getTroveStatus(accounts[i])
if trove_status != Wei(1):
# TODO: claim remaining collateral and then open
return ALREADY_CLOSED
TCR = contracts1.hintHelpers.computeCR(total_coll, total_debt, ETHER_PRICE)
if TCR >= Wei(150 * 1e16):
# close trove in old system
try:
contracts1.borrowerOperations.closeTrove({ 'from': accounts[i] })
result = CLOSED
except:
print("\n Closing trove failed! \n")
print(f"Borrower Operations: {contracts1.borrowerOperations.address}")
print(f"i: {i}")
print(f"account: {accounts[i]}")
print(f"coll: {coll}")
print(f"debt: {debt}")
return CLOSE_FAILED
else:
print(f"After account {i}, resulting TCR: {TCR / 1e18}, redeeming")
if redeem_trove(accounts, contracts1, i):
# TODO: try to claim remaining collateral (if any) and then open
return REDEEMED

# open trove in new system
try:
if not upper_hint or not lower_hint:
NICR = contracts2.hintHelpers.computeNominalCR(coll, debt)
[upper_hint, lower_hint] = contracts2.sortedTroves.findInsertPosition(NICR, ZERO_ADDRESS, ZERO_ADDRESS)

lusd_amount = get_lusd_amount_from_total_debt(contracts2, debt)

#coll = Wei((lusd_amount + borrowing_fee + LUSD_GAS_COMPENSATION) * floatToWei(ICR / 100) / ETHER_PRICE)
contracts2.borrowerOperations.openTrove(MAX_FEE, lusd_amount, accounts[i-1], ZERO_ADDRESS,
contracts2.borrowerOperations.openTrove(MAX_FEE, lusd_amount, upper_hint, lower_hint,
{ 'from': accounts[i], 'value': coll })
except:
print("\n Opening trove in the new system failed! \n")
print(f"Borrower Operations: {contracts2.borrowerOperations.address}")
print(f"i: {i}")
print(f"account: {accounts[i]}")
print(f"coll: {coll}")
print(f"lusd: {lusd_amount}")
print(f"ICR: {coll * ETHER_PRICE / debt / 1e18}")
print(f"ETH bal : {accounts[i].balance()}")
print(f"LUSD bal: {contracts2.lusdToken.balanceOf(accounts[i])}")
print(f"upper hint: {upper_hint}")
print(f"NICR: {contracts2.troveManager.getNominalICR(upper_hint)}")
print(f"lower hint: {lower_hint}")
print(f"NICR: {contracts2.troveManager.getNominalICR(lower_hint)}")
last = contracts2.sortedTroves.getLast()
print(f"last trove: {last}")
print(f"NICR: {contracts2.troveManager.getNominalICR(last)}")
return OPEN_FAILED

return result

def redeem_trove(accounts, contracts, i):
lusd_balance = contracts.lusdToken.balanceOf(accounts[i])
[firstRedemptionHint, partialRedemptionHintNICR, truncatedLUSDamount] = contracts.hintHelpers.getRedemptionHints(lusd_balance, ETHER_PRICE, 70)
if truncatedLUSDamount == Wei(0):
return False
approxHint = contracts.hintHelpers.getApproxHint(partialRedemptionHintNICR, 2000, 0)
hints = contracts.sortedTroves.findInsertPosition(partialRedemptionHintNICR, approxHint[0], approxHint[0])
try:
contracts.troveManager.redeemCollateral(
truncatedLUSDamount,
firstRedemptionHint,
hints[0],
hints[1],
partialRedemptionHintNICR,
70,
MAX_FEE,
{ 'from': accounts[i], 'gas_limit': 8000000, 'allow_revert': True }
)
except:
print(f"\n Redemption failed! ")
print(f"Trove Manager: {contracts.troveManager.address}")
print(f"LUSD Token: {contracts.lusdToken.address}")
print(f"i: {i}")
print(f"account: {accounts[i]}")
print(f"LUSD bal: {lusd_balance / 1e18}")
print(f"truncated: {truncatedLUSDamount / 1e18}")
print(f"approx: {approxHint[0]}")
print(f"diff: {approxHint[1]}")
print(f"diff: {approxHint[1] / 1e18}")
print(f"seed: {approxHint[2]}")
print(f"amount: {truncatedLUSDamount}")
print(f"first: {firstRedemptionHint}")
print(f"hint: {hints[0]}")
print(f"hint: {hints[1]}")
print(f"nicr: {partialRedemptionHintNICR}")
print(f"nicr: {partialRedemptionHintNICR / 1e18}")
print(f"70")
print(f"{MAX_FEE}")
exit(1)

return True

def run_migration_desc(accounts, contracts1, contracts2):
# redemption break the previous order
redemption_happened = False
for i in range(1, 1000):
migrate_trove(accounts, contracts1, contracts2, i)
if redemption_happened:
upper_hint = None
lower_hint = None
else:
upper_hint = contracts2.sortedTroves.getLast()
lower_hint = ZERO_ADDRESS

result = migrate_trove(accounts, contracts1, contracts2, i, upper_hint, lower_hint)
if result == REDEEMED:
redemption_happened = True
if result < 0:
break
if redemption_happened:
# TODO: make another pass to claim remaining collateral, close troves, and re-open
pass

# TODO: if in recovery mode, try redeeming instead of closing trove
def run_migration_asc(accounts, contracts1, contracts2):
for i in range(999, 0, -1):
migrate_trove(accounts, contracts1, contracts2, i)
migrate_trove(accounts, contracts1, contracts2, i, ZERO_ADDRESS, contracts2.sortedTroves.getFirst())

# TODO: if in recovery mode, try redeeming instead of closing trove
def run_migration_rand(accounts, contracts1, contracts2):
remaining = list(range(1, 1000))
while(len(remaining) > 0):
i = random.randrange(0, len(remaining))
migrate_trove(accounts, contracts1, contracts2, i)
migrate_trove(accounts, contracts1, contracts2, remaining[i])
remaining.pop(i)

def run_migration_redeem(accounts, contracts1, contracts2):
for i in range(1, 1000):
redeem_trove(accounts, contracts1, i)
27 changes: 21 additions & 6 deletions packages/contracts/tests/migration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from helpers import *
from migration_helpers import *

def setup():
def _setup():
contracts1 = deploy_contracts()
contracts1.priceFeedTestnet.setPrice(ETHER_PRICE, { 'from': accounts[0] })

Expand All @@ -16,35 +16,50 @@ def setup():

logGlobalState(contracts2, message="contracts2 initial")

# fast forward the bootstrap phase to allow redemptions
# doesn’t work with OpenEthereum
# chain.sleep(14 * 24 * 60 * 60)
# chain.mine()

return [contracts1, contracts2]

def test_run_migration_desc(add_accounts):
[contracts1, contracts2] = setup()
[contracts1, contracts2] = _setup()

# migrate troves in ICR descending order
run_migration_desc(accounts, contracts1, contracts2)

logGlobalState(contracts1, message="contracts1 final")
logGlobalState(contracts2, message="contracts2 final")

'''
"""
# It fails at the first attempt, because first trove in the new system is < CCR
def test_run_migration_asc(add_accounts):
[contracts1, contracts2] = setup()
[contracts1, contracts2] = _setup()
# migrate troves in ICR ascending order
run_migration_asc(accounts, contracts1, contracts2)
logGlobalState(contracts1)
logGlobalState(contracts2)
"""

def test_run_migration_rand(add_accounts):
[contracts1, contracts2] = setup()
[contracts1, contracts2] = _setup()

# migrate troves in ICR random order
run_migration_rand(accounts, contracts1, contracts2)

logGlobalState(contracts1)
logGlobalState(contracts2)
'''

def test_run_migration_redeem(add_accounts):
[contracts1, contracts2] = _setup()

# migrate troves in ICR random order
run_migration_redeem(accounts, contracts1, contracts2)

logGlobalState(contracts1)
logGlobalState(contracts2)

# TODO: try with only redemptions
7 changes: 0 additions & 7 deletions packages/contracts/tests/simulation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ def print_expectations():
def contracts():
return deploy_contracts()

def _test_test(contracts):
print(len(accounts))
contracts.borrowerOperations.openTrove(Wei(1e18), Wei(2000e18), ZERO_ADDRESS, ZERO_ADDRESS,
{ 'from': accounts[1], 'value': Wei("100 ether") })

#assert False

"""# Simulation Program
**Sequence of events**
Expand Down

0 comments on commit b748e99

Please sign in to comment.