Skip to content

Commit

Permalink
Allow higher collateral factor (#1452)
Browse files Browse the repository at this point in the history
* Add FortCanningEpilogue fork

* Allow higher collateral factor

* Test fix: apply scheme check after fork

* Test error before scheme set

* Test: Change error code

Co-authored-by: Prasanna Loganathar <[email protected]>
  • Loading branch information
Bushstar and prasannavl authored Sep 10, 2022
1 parent 2b3ce01 commit 1a68c9e
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 6 deletions.
3 changes: 2 additions & 1 deletion src/amount.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ struct DCT_ID {
}
};

static const CAmount COIN = 100000000;
static constexpr CAmount COIN = 100000000;
static constexpr CAmount CENT = 1000000;

//Converts the given value to decimal format string with COIN precision.
inline std::string GetDecimaleString(CAmount nValue)
Expand Down
29 changes: 27 additions & 2 deletions src/masternodes/govvariables/attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ const std::map<uint8_t, std::map<uint8_t,
{TokenKeys::DexOutFeePct, VerifyPct},
{TokenKeys::FixedIntervalPriceId, VerifyCurrencyPair},
{TokenKeys::LoanCollateralEnabled, VerifyBool},
{TokenKeys::LoanCollateralFactor, VerifyPct},
{TokenKeys::LoanCollateralFactor, VerifyPositiveFloat},
{TokenKeys::LoanMintingEnabled, VerifyBool},
{TokenKeys::LoanMintingInterest, VerifyFloat},
{TokenKeys::DFIP2203Enabled, VerifyBool},
Expand Down Expand Up @@ -917,6 +917,14 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const
return Res::Err("No such token (%d)", attrV0->typeId);
}
break;
case TokenKeys::LoanCollateralFactor:
if (view.GetLastHeight() < Params().GetConsensus().FortCanningEpilogueHeight) {
const auto amount = std::get_if<CAmount>(&value);
if (amount && *amount > COIN) {
return Res::Err("Percentage exceeds 100%%");
}
}
[[fallthrough]];
case TokenKeys::LoanMintingInterest:
if (view.GetLastHeight() < Params().GetConsensus().FortCanningGreatWorldHeight) {
const auto amount = std::get_if<CAmount>(&value);
Expand All @@ -926,7 +934,6 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const
}
[[fallthrough]];
case TokenKeys::LoanCollateralEnabled:
case TokenKeys::LoanCollateralFactor:
case TokenKeys::LoanMintingEnabled: {
if (view.GetLastHeight() < Params().GetConsensus().FortCanningCrunchHeight) {
return Res::Err("Cannot be set before FortCanningCrunch");
Expand Down Expand Up @@ -1177,6 +1184,24 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height)
mnview.IncreaseInterest(height, vaultId, vault->schemeId, {attrV0->typeId}, *tokenInterest, 0);
}
}
} else if (attrV0->key == TokenKeys::LoanCollateralFactor) {
if (height >= Params().GetConsensus().FortCanningEpilogueHeight) {
std::set<CAmount> ratio;
mnview.ForEachLoanScheme([&ratio](const std::string &identifier, const CLoanSchemeData &data) {
ratio.insert(data.ratio);
return true;
});
if (ratio.empty()) {
return Res::Err("Set loan scheme before setting collateral factor.");
}
const auto factor = std::get_if<CAmount>(&attribute.second);
if (!factor) {
return Res::Err("Unexpected type");
}
if (*factor >= *ratio.begin() * CENT) {
return Res::Err("Factor cannot be more than or equal to the lowest scheme rate of %d\n", GetDecimaleString(*ratio.begin() * CENT));
}
}
}
} else if (attrV0->type == AttributeTypes::Param) {
if (attrV0->typeId == ParamIDs::DFIP2203) {
Expand Down
2 changes: 0 additions & 2 deletions src/test/setup_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ static inline uint64_t InsecureRandBits(int bits) { return g_insecure_rand_ctx.r
static inline uint64_t InsecureRandRange(uint64_t range) { return g_insecure_rand_ctx.randrange(range); }
static inline bool InsecureRandBool() { return g_insecure_rand_ctx.randbool(); }

static constexpr CAmount CENT{1000000};

struct TestMasternodeKeys {
CKey ownerKey;
CKey operatorKey;
Expand Down
198 changes: 198 additions & 0 deletions test/functional/feature_higher_collateral_factor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2019 The Bitcoin Core developers
# Copyright (c) DeFi Blockchain Developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
"""Test DUSD collateral factor."""

from test_framework.test_framework import DefiTestFramework

from test_framework.util import assert_equal, assert_raises_rpc_error
from decimal import Decimal
import time

class DUSDCollateralFactorTest(DefiTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.extra_args = [['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1', '-fortcanningroadheight=1', '-fortcanninghillheight=1', '-fortcanningcrunchheight=1', '-fortcanninggreatworldheight=1', '-fortcanningepilogueheight=200', '-jellyfish_regtest=1']]

def run_test(self):
# Setup
self.setup()

# Test setting of DUSD collateral factor
self.set_collateral_factor()

# Test new factor when taking DUSD loan
self.take_dusd_loan()

# Test with multiple loan tokens
self.take_multiple_loans()

def setup(self):
# Generate chain
self.nodes[0].generate(120)

# Get MN address
self.address = self.nodes[0].get_genesis_keys().ownerAuthAddress

# Token symbols
self.symbolDFI = "DFI"
self.symbolDUSD = "DUSD"
self.symbolTSLA = "TSLA"

# Create Oracle address
oracle_address = self.nodes[0].getnewaddress("", "legacy")

# Define price feeds
price_feed = [
{"currency": "USD", "token": f"{self.symbolDFI}"},
{"currency": "USD", "token": f"{self.symbolTSLA}"},
]

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

# Set Oracle prices
oracle_prices = [
{"currency": "USD", "tokenAmount": f"1@{self.symbolDFI}"},
{"currency": "USD", "tokenAmount": f"1@{self.symbolTSLA}"},
]
self.nodes[0].setoracledata(oracle, int(time.time()), oracle_prices)
self.nodes[0].generate(10)

# Create loan tokens
self.nodes[0].setloantoken({
'symbol': self.symbolDUSD,
'name': self.symbolDUSD,
'fixedIntervalPriceId': f"{self.symbolDUSD}/USD",
'mintable': True,
'interest': -1
})
self.nodes[0].generate(1)

self.nodes[0].setloantoken({
'symbol': self.symbolTSLA,
'name': self.symbolTSLA,
'fixedIntervalPriceId': f"{self.symbolTSLA}/USD",
'mintable': True,
'interest': -1
})
self.nodes[0].generate(1)

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

self.nodes[0].setcollateraltoken({
'token': self.symbolDUSD,
'factor': 1,
'fixedIntervalPriceId': f'{self.symbolDFI}/USD'
})
self.nodes[0].generate(1)

# Store DUSD ID
self.idDUSD = list(self.nodes[0].gettoken(self.symbolDUSD).keys())[0]

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

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

def set_collateral_factor(self):

# Test setting new token factor before fork
assert_raises_rpc_error(-32600, "Percentage exceeds 100%", self.nodes[0].setgov, {"ATTRIBUTES":{f'v0/token/{self.idDUSD}/loan_collateral_factor': '1.49'}})

# Move to fork
self.nodes[0].generate(200 - self.nodes[0].getblockcount())

# Test setting before scheme set
assert_raises_rpc_error(-32600, "Set loan scheme before setting collateral factor", self.nodes[0].setgov, {"ATTRIBUTES":{f'v0/token/{self.idDUSD}/loan_collateral_factor': '1.50'}})

# Create loan scheme
self.nodes[0].createloanscheme(150, 1, 'LOAN001')
self.nodes[0].generate(1)

# Test setting higher than the lowest scheme rate
assert_raises_rpc_error(-32600, "Factor cannot be more than or equal to the lowest scheme rate of 1.50000000", self.nodes[0].setgov, {"ATTRIBUTES":{f'v0/token/{self.idDUSD}/loan_collateral_factor': '1.50'}})

# Now set new token factor
self.nodes[0].setgov({"ATTRIBUTES":{f'v0/token/{self.idDUSD}/loan_collateral_factor': '1.49'}})
self.nodes[0].generate(1)

# Check results
attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES']
assert_equal(attributes['v0/token/1/loan_collateral_factor'], '1.49')

def take_dusd_loan(self):

# Create vault
vault_address = self.nodes[0].getnewaddress('', 'legacy')
vault_id = self.nodes[0].createvault(vault_address, 'LOAN001')
self.nodes[0].generate(1)

# Deposit DUSD and DFI to vault
self.nodes[0].deposittovault(vault_id, self.address, f"1.5@{self.symbolDFI}")
self.nodes[0].generate(1)
self.nodes[0].deposittovault(vault_id, self.address, f"1.5@{self.symbolDUSD}")
self.nodes[0].generate(1)

# Take DUSD loan greater than collateral amount
self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": f"2.49@{self.symbolDUSD}"})
self.nodes[0].generate(1)

# Check that we are on 150% collateral ratio
vault = self.nodes[0].getvault(vault_id)
assert_equal(vault['collateralRatio'], 150)
assert_equal(vault['informativeRatio'], Decimal('150.00000000'))
assert_equal(vault['collateralValue'], Decimal('3.73500000'))
assert_equal(vault['loanValue'], Decimal('2.49000000'))

def take_multiple_loans(self):

# Create vault
vault_address = self.nodes[0].getnewaddress('', 'legacy')
vault_id = self.nodes[0].createvault(vault_address, 'LOAN001')
self.nodes[0].generate(1)

# Deposit DUSD and DFI to vault
self.nodes[0].deposittovault(vault_id, self.address, f"1.5@{self.symbolDFI}")
self.nodes[0].generate(1)
self.nodes[0].deposittovault(vault_id, self.address, f"1.5@{self.symbolDUSD}")
self.nodes[0].generate(1)

# Take TSLA loan for half the available ratio
self.nodes[0].takeloan({ "vaultId": vault_id, "amounts": f"1@{self.symbolTSLA}"})
self.nodes[0].generate(1)

# Check that we are on 300% collateral ratio
vault = self.nodes[0].getvault(vault_id)
assert_equal(vault['collateralRatio'], 374)
assert_equal(vault['informativeRatio'], Decimal('373.50000000'))
assert_equal(vault['collateralValue'], Decimal('3.73500000'))
assert_equal(vault['loanValue'], Decimal('1.00000000'))

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

# Check that vault still has capacity to take more loans
vault = self.nodes[0].getvault(vault_id)
assert_equal(vault['loanValue'], Decimal('2.00000000'))
assert_equal(vault['collateralValue'], Decimal('3.73500000'))
assert_equal(vault['informativeRatio'], Decimal('186.75000000'))
assert_equal(vault['collateralRatio'], 187)

if __name__ == '__main__':
DUSDCollateralFactorTest().main()

2 changes: 1 addition & 1 deletion test/functional/feature_setgov.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ def run_test(self):
assert_raises_rpc_error(-32600, "No such token (127)", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/127/loan_collateral_enabled':'true'}})
assert_raises_rpc_error(-32600, "Fixed interval price currency pair must be set first", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_collateral_enabled':'true'}})
assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_collateral_factor':'-1'}})
assert_raises_rpc_error(-5, "Percentage exceeds 100%", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_collateral_factor':'1.00000001'}})
assert_raises_rpc_error(-32600, "Percentage exceeds 100%", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_collateral_factor':'1.00000001'}})
assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_collateral_factor':'not_a_number'}})
assert_raises_rpc_error(-32600, "No such token (127)", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/127/loan_collateral_factor':'1'}})
assert_raises_rpc_error(-32600, "Fixed interval price currency pair must be set first", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_collateral_factor':'1'}})
Expand Down
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@
'rpc_named_arguments.py',
'wallet_listsinceblock.py',
'p2p_leak.py',
'feature_higher_collateral_factor.py',
'wallet_encryption.py',
'feature_dersig.py',
'feature_cltv.py',
Expand Down

0 comments on commit 1a68c9e

Please sign in to comment.