Skip to content

Commit

Permalink
DUSD contributes to the 50% minimum required collateral (#1128)
Browse files Browse the repository at this point in the history
* Add fortcanningroad height

* DUSD contributes to the 50% minimum required collateral

* Set regtest FortCanningRoadHeight

* Adds test for DUSD collateral factor

* Fix lint

* Simpler test for dUSD collateral factor

Co-authored-by: Prasanna Loganathar <pvl@prasannavl.com>
  • Loading branch information
Jouzo and prasannavl authored Mar 18, 2022
1 parent 2471a96 commit 6345276
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 34 deletions.
5 changes: 5 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class CMainParams : public CChainParams {
consensus.FortCanningMuseumHeight = 1430640;
consensus.FortCanningParkHeight = 1503143;
consensus.FortCanningHillHeight = 1604999; // Feb 7, 2022.
consensus.FortCanningRoadHeight = std::numeric_limits<int>::max();

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
// consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
Expand Down Expand Up @@ -355,6 +356,7 @@ class CTestNetParams : public CChainParams {
consensus.FortCanningMuseumHeight = 724000;
consensus.FortCanningParkHeight = 828800;
consensus.FortCanningHillHeight = 828900;
consensus.FortCanningRoadHeight = std::numeric_limits<int>::max();

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
// consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
Expand Down Expand Up @@ -542,6 +544,7 @@ class CDevNetParams : public CChainParams {
consensus.FortCanningMuseumHeight = std::numeric_limits<int>::max();
consensus.FortCanningParkHeight = std::numeric_limits<int>::max();
consensus.FortCanningHillHeight = std::numeric_limits<int>::max();
consensus.FortCanningRoadHeight = std::numeric_limits<int>::max();

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks
Expand Down Expand Up @@ -721,6 +724,7 @@ class CRegTestParams : public CChainParams {
consensus.FortCanningMuseumHeight = 10000000;
consensus.FortCanningParkHeight = 10000000;
consensus.FortCanningHillHeight = 10000000;
consensus.FortCanningRoadHeight = 10000000;

consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
Expand Down Expand Up @@ -939,6 +943,7 @@ void CRegTestParams::UpdateActivationParametersFromArgs()
UpdateHeightValidation("Fort Canning Museum", "-fortcanningmuseumheight", consensus.FortCanningMuseumHeight);
UpdateHeightValidation("Fort Canning Park", "-fortcanningparkheight", consensus.FortCanningParkHeight);
UpdateHeightValidation("Fort Canning Hill", "-fortcanninghillheight", consensus.FortCanningHillHeight);
UpdateHeightValidation("Fort Canning Road", "-fortcanningroadheight", consensus.FortCanningRoadHeight);

if (gArgs.GetBoolArg("-simulatemainnet", false)) {
consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks
Expand Down
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ struct Params {
int FortCanningMuseumHeight;
int FortCanningParkHeight;
int FortCanningHillHeight;
int FortCanningRoadHeight;

/** Foundation share after AMK, normalized to COIN = 100% */
CAmount foundationShareDFIP1;
Expand Down
1 change: 1 addition & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ void SetupServerArgs()
gArgs.AddArg("-fortcanningmuseumheight", "Fort Canning Museum fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-fortcanningparkheight", "Fort Canning Park fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-fortcanninghillheight", "Fort Canning Hill fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-fortcanningroadheight", "Fort Canning Road fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-jellyfish_regtest", "Configure the regtest network for jellyfish testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
gArgs.AddArg("-simulatemainnet", "Configure the regtest network to mainnet target timespan and spacing ", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
#ifdef USE_UPNP
Expand Down
36 changes: 24 additions & 12 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2634,6 +2634,10 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor
{
if (auto collaterals = mnview.GetVaultCollaterals(obj.vaultId))
{
boost::optional<std::pair<DCT_ID, std::unique_ptr<CToken>>> tokenDUSD;
if (static_cast<int>(height) >= consensus.FortCanningRoadHeight) {
tokenDUSD = mnview.GetToken("DUSD");
}
const auto scheme = mnview.GetLoanScheme(vault->schemeId);
for (int i = 0; i < 2; i++) {
// check collaterals for active and next price
Expand All @@ -2643,17 +2647,19 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor
if (!collateralsLoans)
return std::move(collateralsLoans);

uint64_t totalDFI = 0;
uint64_t totalCollaterals = 0;
for (auto& col : collateralsLoans.val->collaterals)
if (col.nTokenId == DCT_ID{0})
totalDFI += col.nValue;
if (col.nTokenId == DCT_ID{0}
|| (tokenDUSD && col.nTokenId == tokenDUSD->first))
totalCollaterals += col.nValue;

if (static_cast<int>(height) < consensus.FortCanningHillHeight) {
if (totalDFI < collateralsLoans.val->totalCollaterals / 2)
if (totalCollaterals < collateralsLoans.val->totalCollaterals / 2)
return Res::Err("At least 50%% of the collateral must be in DFI");
} else {
if (arith_uint256(totalDFI) * 100 < arith_uint256(collateralsLoans.val->totalLoans) * scheme->ratio / 2)
return Res::Err("At least 50%% of the minimum required collateral must be in DFI");
if (arith_uint256(totalCollaterals) * 100 < arith_uint256(collateralsLoans.val->totalLoans) * scheme->ratio / 2)
return static_cast<int>(height) < consensus.FortCanningRoadHeight ? Res::Err("At least 50%% of the minimum required collateral must be in DFI")
: Res::Err("At least 50%% of the minimum required collateral must be in DFI or DUSD");
}

if (collateralsLoans.val->ratio() < scheme->ratio)
Expand Down Expand Up @@ -2745,6 +2751,10 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor
return res;
}

boost::optional<std::pair<DCT_ID, std::unique_ptr<CToken>>> tokenDUSD;
if (static_cast<int>(height) >= consensus.FortCanningRoadHeight) {
tokenDUSD = mnview.GetToken("DUSD");
}
auto scheme = mnview.GetLoanScheme(vault->schemeId);
for (int i = 0; i < 2; i++) {
// check ratio against current and active price
Expand All @@ -2754,17 +2764,19 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor
if (!collateralsLoans)
return std::move(collateralsLoans);

uint64_t totalDFI = 0;
uint64_t totalCollaterals = 0;
for (auto& col : collateralsLoans.val->collaterals)
if (col.nTokenId == DCT_ID{0})
totalDFI += col.nValue;
if (col.nTokenId == DCT_ID{0}
|| (tokenDUSD && col.nTokenId == tokenDUSD->first))
totalCollaterals += col.nValue;

if (static_cast<int>(height) < consensus.FortCanningHillHeight) {
if (totalDFI < collateralsLoans.val->totalCollaterals / 2)
if (totalCollaterals < collateralsLoans.val->totalCollaterals / 2)
return Res::Err("At least 50%% of the collateral must be in DFI when taking a loan.");
} else {
if (arith_uint256(totalDFI) * 100 < arith_uint256(collateralsLoans.val->totalLoans) * scheme->ratio / 2)
return Res::Err("At least 50%% of the minimum required collateral must be in DFI when taking a loan.");
if (arith_uint256(totalCollaterals) * 100 < arith_uint256(collateralsLoans.val->totalLoans) * scheme->ratio / 2)
return static_cast<int>(height) < consensus.FortCanningRoadHeight ? Res::Err("At least 50%% of the minimum required collateral must be in DFI when taking a loan.")
: Res::Err("At least 50%% of the minimum required collateral must be in DFI or DUSD when taking a loan.");
}

if (collateralsLoans.val->ratio() < scheme->ratio)
Expand Down
1 change: 1 addition & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
BuriedForkDescPushBack(softforks, "fortcanningmuseum", consensusParams.FortCanningMuseumHeight);
BuriedForkDescPushBack(softforks, "fortcanningpark", consensusParams.FortCanningParkHeight);
BuriedForkDescPushBack(softforks, "fortcanninghill", consensusParams.FortCanningHillHeight);
BuriedForkDescPushBack(softforks, "fortcanningroad", consensusParams.FortCanningRoadHeight);
BIP9SoftForkDescPushBack(softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
obj.pushKV("softforks", softforks);

Expand Down
113 changes: 91 additions & 22 deletions test/functional/feature_loan_dusd_as_collateral.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Test Loan - DUSD as collateral."""

from test_framework.test_framework import DefiTestFramework
from test_framework.authproxy import JSONRPCException

from test_framework.util import assert_equal, assert_raises_rpc_error
from decimal import Decimal
Expand All @@ -16,15 +17,16 @@ def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.extra_args = [
['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=200', '-jellyfish_regtest=1']]
['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=200', '-fortcanningroadheight=215', '-jellyfish_regtest=1']]

def run_test(self):
self.nodes[0].generate(120)

mn_address = self.nodes[0].get_genesis_keys().ownerAuthAddress

symbol_dfi = "DFI"
symbol_dusd = "DUSD"
symbolDFI = "DFI"
symbolBTC = "BTC"
symbolDUSD = "DUSD"

self.nodes[0].setloantoken({
'symbol': "DUSD",
Expand All @@ -35,20 +37,36 @@ def run_test(self):
})
self.nodes[0].generate(1)

id_usd = list(self.nodes[0].gettoken(symbol_dusd).keys())[0]
self.nodes[0].createtoken({
"symbol": symbolBTC,
"name": "BTC token",
"isDAT": True,
"collateralAddress": mn_address
})
self.nodes[0].generate(1)

idDUSD = list(self.nodes[0].gettoken(symbolDUSD).keys())[0]

# Mint DUSD
self.nodes[0].minttokens("100000@DUSD")
self.nodes[0].minttokens("100000@BTC")
self.nodes[0].generate(1)

# Create DFI tokens
self.nodes[0].utxostoaccount({mn_address: "100000@" + symbol_dfi})
self.nodes[0].utxostoaccount({mn_address: "100000@" + symbolDFI})
self.nodes[0].generate(1)

# Create pool pair
self.nodes[0].createpoolpair({
"tokenA": symbol_dfi,
"tokenB": symbol_dusd,
"tokenA": symbolDFI,
"tokenB": symbolDUSD,
"commission": 0,
"status": True,
"ownerAddress": mn_address
})
self.nodes[0].createpoolpair({
"tokenA": symbolDFI,
"tokenB": symbolBTC,
"commission": 0,
"status": True,
"ownerAddress": mn_address
Expand All @@ -58,37 +76,50 @@ def run_test(self):
# Add pool liquidity
self.nodes[0].addpoolliquidity({
mn_address: [
'10000@' + symbol_dfi,
'8000@' + symbol_dusd]
'10000@' + symbolDFI,
'8000@' + symbolDUSD]
}, mn_address)
self.nodes[0].addpoolliquidity({
mn_address: [
'10000@' + symbolDFI,
'8000@' + symbolBTC]
}, mn_address)
self.nodes[0].generate(1)

# Set up Oracles
oracle_address = self.nodes[0].getnewaddress("", "legacy")
price_feed = [
{"currency": "USD", "token": "DFI"}
{"currency": "USD", "token": "DFI"},
{"currency": "USD", "token": "BTC"}
]

oracle = self.nodes[0].appointoracle(oracle_address, price_feed, 10)
self.nodes[0].generate(1)

oracle_prices = [
{"currency": "USD", "tokenAmount": "1@DFI"},
{"currency": "USD", "tokenAmount": "1@BTC"},
]
self.nodes[0].setoracledata(oracle, int(time.time()), oracle_prices)
self.nodes[0].generate(1)

# Set collateral tokens
self.nodes[0].setcollateraltoken({
'token': symbol_dfi,
'token': symbolDFI,
'factor': 1,
'fixedIntervalPriceId': "DFI/USD"
})

self.nodes[0].setcollateraltoken({
'token': symbolBTC,
'factor': 1,
'fixedIntervalPriceId': "BTC/USD"
})

token_factor_dusd = 0.99
activate = self.nodes[0].getblockcount() + 50
self.nodes[0].setcollateraltoken({
'token': symbol_dusd,
'token': symbolDUSD,
'factor': token_factor_dusd,
'fixedIntervalPriceId': "DUSD/USD",
'activateAfterBlock': activate
Expand All @@ -107,19 +138,20 @@ def run_test(self):
# Fund vault address with DUSD and DFI
collateral = 2000
loan_dusd = 1000
self.nodes[0].accounttoaccount(mn_address, {vault_address: str(collateral) + "@" + symbol_dusd})
self.nodes[0].accounttoaccount(mn_address, {vault_address: str(collateral) + "@" + symbol_dfi})
self.nodes[0].accounttoaccount(mn_address, {vault_address: str(collateral) + "@" + symbolDUSD})
self.nodes[0].accounttoaccount(mn_address, {vault_address: str(collateral) + "@" + symbolDFI})
self.nodes[0].accounttoaccount(mn_address, {vault_address: str(collateral) + "@" + symbolBTC})
self.nodes[0].generate(1)

# DUSD is not active as a collateral token yet
assert_raises_rpc_error(-32600, "Collateral token with id (1) does not exist!", self.nodes[0].deposittovault, vault_id, vault_address, str(collateral) + "@" + symbol_dusd)
assert_raises_rpc_error(-32600, "Collateral token with id (1) does not exist!", self.nodes[0].deposittovault, vault_id, vault_address, str(collateral) + "@" + symbolDUSD)

# Activates DUSD as collateral token
self.nodes[0].generate(activate - self.nodes[0].getblockcount())

# Deposit DUSD and DFI to vault
self.nodes[0].deposittovault(vault_id, vault_address, str(collateral) + "@" + symbol_dusd)
self.nodes[0].deposittovault(vault_id, vault_address, str(collateral) + "@" + symbol_dfi)
self.nodes[0].deposittovault(vault_id, vault_address, str(collateral) + "@" + symbolDUSD)
self.nodes[0].deposittovault(vault_id, vault_address, str(collateral) + "@" + symbolDFI)
self.nodes[0].generate(1)

vault = self.nodes[0].getvault(vault_id)
Expand All @@ -130,11 +162,11 @@ def run_test(self):
self.nodes[0].generate(200 - self.nodes[0].getblockcount())

# Enable loan payback
self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/' + id_usd + '/payback_dfi':'true'}})
self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/' + idDUSD + '/payback_dfi':'true'}})
self.nodes[0].generate(1)

# Take DUSD loan
self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": str(loan_dusd) + "@" + symbol_dusd })
self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": str(loan_dusd) + "@" + symbolDUSD })
self.nodes[0].generate(1)

# Loan value loan amount + interest
Expand All @@ -144,10 +176,10 @@ def run_test(self):
# Swap DUSD from loan to DFI
self.nodes[0].poolswap({
"from": vault_address,
"tokenFrom": symbol_dusd,
"tokenFrom": symbolDUSD,
"amountFrom": loan_dusd,
"to": vault_address,
"tokenTo": symbol_dfi
"tokenTo": symbolDFI
})
self.nodes[0].generate(1)

Expand All @@ -156,12 +188,49 @@ def run_test(self):
self.nodes[0].paybackloan({
'vaultId': vault_id,
'from': vault_address,
'amounts': [dfi_balance + '@' + symbol_dfi]})
'amounts': [dfi_balance + '@' + symbolDFI]})
self.nodes[0].generate(1)

# Loan should be paid back in full
vault = self.nodes[0].getvault(vault_id)
assert_equal(vault['loanValue'], Decimal('0'))

assert_equal(vault['collateralAmounts'], ['2000.00000000@DFI', '2000.00000000@DUSD'])

# Withdraw DFI and use DUSD as sole collateral
self.nodes[0].withdrawfromvault(vault_id, vault_address, '2000.00000000@DFI')
self.nodes[0].generate(1)

# Try to take DUSD loan with DUSD as sole collateral
try:
self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": str(loan_dusd) + "@" + symbolDUSD })
except JSONRPCException as e:
errorString = e.error['message']
assert("At least 50% of the minimum required collateral must be in DFI when taking a loan." in errorString)

self.nodes[0].generate(215 - self.nodes[0].getblockcount()) # move to fortcanningroad height

# Take DUSD loan with DUSD as sole collateral
self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": str(loan_dusd) + "@" + symbolDUSD })
self.nodes[0].generate(1)

vault = self.nodes[0].getvault(vault_id)
assert_equal(vault['collateralAmounts'], ['2000.00000000@DUSD'])

# Try to take DUSD loan with DUSD less than 50% of total collateralized loan value
# This tests for collateral factor
assert_raises_rpc_error(-32600, "Vault does not have enough collateralization ratio defined by loan scheme - 149 < 150", self.nodes[0].takeloan, { "vaultId": vault_id, "amounts": "333@" + symbolDUSD })

# Set DUSD collateral factor back to 1
self.nodes[0].setcollateraltoken({
'token': symbolDUSD,
'factor': 1,
'fixedIntervalPriceId': "DUSD/USD"
})
self.nodes[0].generate(10)

self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": "333@" + symbolDUSD })
self.nodes[0].generate(1)

if __name__ == '__main__':
LoanDUSDCollateralTest().main()
1 change: 1 addition & 0 deletions test/functional/rpc_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def _test_getblockchaininfo(self):
'fortcanningmuseum': {'type': 'buried', 'active': False, 'height': 10000000},
'fortcanningpark': {'type': 'buried', 'active': False, 'height': 10000000},
'fortcanninghill': {'type': 'buried', 'active': False, 'height': 10000000},
'fortcanningroad': {'type': 'buried', 'active': False, 'height': 10000000},
'testdummy': {
'type': 'bip9',
'bip9': {
Expand Down

0 comments on commit 6345276

Please sign in to comment.