Skip to content

Commit

Permalink
Negative Interest
Browse files Browse the repository at this point in the history
  • Loading branch information
Bushstar committed Jul 18, 2022
1 parent 6d5d5a9 commit 7ce7530
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 6 deletions.
23 changes: 19 additions & 4 deletions src/masternodes/govvariables/attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,14 @@ static ResVal<CAttributeValue> VerifyInt64(const std::string& str) {
}

static ResVal<CAttributeValue> VerifyFloat(const std::string& str) {
CAmount amount = 0;
if (!ParseFixedPoint(str, 8, &amount)) {
return Res::Err("Amount must be a valid number");
}
return {amount, Res::Ok()};
}

ResVal<CAttributeValue> VerifyPositiveFloat(const std::string& str) {
CAmount amount = 0;
if (!ParseFixedPoint(str, 8, &amount) || amount < 0) {
return Res::Err("Amount must be a positive value");
Expand All @@ -253,7 +261,7 @@ static ResVal<CAttributeValue> VerifyFloat(const std::string& str) {
}

static ResVal<CAttributeValue> VerifyPct(const std::string& str) {
auto resVal = VerifyFloat(str);
auto resVal = VerifyPositiveFloat(str);
if (!resVal) {
return resVal;
}
Expand Down Expand Up @@ -359,7 +367,7 @@ const std::map<uint8_t, std::map<uint8_t,
AttributeTypes::Param, {
{DFIPKeys::Active, VerifyBool},
{DFIPKeys::Premium, VerifyPct},
{DFIPKeys::MinSwap, VerifyFloat},
{DFIPKeys::MinSwap, VerifyPositiveFloat},
{DFIPKeys::RewardPct, VerifyPct},
{DFIPKeys::BlockPeriod, VerifyInt64},
{DFIPKeys::DUSDInterestBurn, VerifyBool},
Expand Down Expand Up @@ -906,10 +914,17 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const
return Res::Err("No such token (%d)", attrV0->typeId);
}
break;
case TokenKeys::LoanMintingInterest: {
if (view.GetLastHeight() < Params().GetConsensus().GreatWorldHeight) {
const auto amount = std::get_if<CAmount>(&value);
if (amount && *amount < 0) {
return Res::Err("Amount must be a positive value");
}
}
}
case TokenKeys::LoanCollateralEnabled:
case TokenKeys::LoanCollateralFactor:
case TokenKeys::LoanMintingEnabled:
case TokenKeys::LoanMintingInterest: {
case TokenKeys::LoanMintingEnabled: {
if (view.GetLastHeight() < Params().GetConsensus().FortCanningCrunchHeight) {
return Res::Err("Cannot be set before FortCanningCrunch");
}
Expand Down
3 changes: 3 additions & 0 deletions src/masternodes/loan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ inline T InterestPerBlockCalculationV1(CAmount amount, CAmount tokenInterest, CA
inline base_uint<128> InterestPerBlockCalculationV2(CAmount amount, CAmount tokenInterest, CAmount schemeInterest)
{
auto netInterest = (tokenInterest + schemeInterest) / 100; // in %
if (netInterest < 0) {
return 0;
}
static const auto blocksPerYear = 365 * Params().GetConsensus().blocksPerDay();
return arith_uint256(amount) * netInterest * COIN / blocksPerYear;
}
Expand Down
159 changes: 159 additions & 0 deletions test/functional/feature_negative_loan_interest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/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 negative interest."""

from test_framework.test_framework import DefiTestFramework

from test_framework.util import assert_equal
import time

class NegativeInterestTest (DefiTestFramework):
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=1', '-fortcanningcrunchheight=1', '-greatworldheight=1', '-jellyfish_regtest=1']]

def run_test(self):
# Create tokens for tests
self.setup_test_tokens()

# Setup pools
self.setup_test_pools()

# Setup Oracles
self.setup_test_oracles()

# Test negative interest
self.test_negative_interest()

def setup_test_tokens(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"

# Create loan token
self.nodes[0].setloantoken({
'symbol': self.symbolDUSD,
'name': self.symbolDUSD,
'fixedIntervalPriceId': f"{self.symbolDUSD}/USD",
'mintable': True,
'interest': 0
})
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 setup_test_pools(self):

# Create pool pair
self.nodes[0].createpoolpair({
"tokenA": self.symbolDFI,
"tokenB": self.symbolDUSD,
"commission": 0,
"status": True,
"ownerAddress": self.address
})
self.nodes[0].generate(1)

# Add pool liquidity
self.nodes[0].addpoolliquidity({
self.address: [
'10000@' + self.symbolDFI,
'10000@' + self.symbolDUSD]
}, self.address)
self.nodes[0].generate(1)

def setup_test_oracles(self):

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

# Define price feeds
price_feed = [
{"currency": "USD", "token": "DFI"},
{"currency": "USD", "token": "BTC"}
]

# 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}"},
]
self.nodes[0].setoracledata(oracle, int(time.time()), oracle_prices)
self.nodes[0].generate(10)

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

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

def test_negative_interest(self):

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

# Fund vault address
self.nodes[0].accounttoaccount(self.address, {vault_address: f"1000@{self.symbolDFI}"})
self.nodes[0].generate(1)

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

# Set negative interest rate to cancel out scheme interest
self.nodes[0].setgov({"ATTRIBUTES":{f'v0/token/{self.idDUSD}/loan_minting_interest':'-5'}})
self.nodes[0].generate(1)

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

# Check loan interest
vault = self.nodes[0].getvault(vault_id)
assert_equal(vault['interestAmounts'], [f'0.00000000@{self.symbolDUSD}'])

# Set negative interest rate to go below 0 when combined with scheme interest
self.nodes[0].setgov({"ATTRIBUTES":{f'v0/token/{self.idDUSD}/loan_minting_interest':'-10'}})
self.nodes[0].generate(1)

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

# Check loan interest
vault = self.nodes[0].getvault(vault_id)
assert_equal(vault['interestAmounts'], [f'0.00000000@{self.symbolDUSD}'])

if __name__ == '__main__':
NegativeInterestTest().main()
4 changes: 2 additions & 2 deletions test/functional/feature_setgov.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,8 +677,8 @@ def run_test(self):
assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_enabled':'not_a_bool'}})
assert_raises_rpc_error(-32600, "No such token (127)", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/127/loan_minting_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_minting_enabled':'true'}})
assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_interest':'-1'}})
assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_interest':'not_a_number'}})
assert_raises_rpc_error(-32600, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_interest':'-1'}})
assert_raises_rpc_error(-5, "Amount must be a valid number", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_interest':'not_a_number'}})
assert_raises_rpc_error(-32600, "No such token (127)", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/127/loan_minting_interest':'1'}})
assert_raises_rpc_error(-32600, "Fixed interval price currency pair must be set first", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/loan_minting_interest':'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 @@ -160,6 +160,7 @@
'wallet_multiwallet.py --usecli',
'wallet_createwallet.py',
'wallet_createwallet.py --usecli',
'feature_negative_loan_interest.py',
'wallet_watchonly.py',
'wallet_watchonly.py --usecli',
'feature_poolpair.py',
Expand Down

0 comments on commit 7ce7530

Please sign in to comment.