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

Negative interest rates #1386

Merged
merged 54 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e0ec4c3
Fixes type check on verbose parameter
dcorral Jun 13, 2022
66f2707
Merge branch 'master' of github.com:DeFiCh/ain into fix-getvault-verbose
dcorral Jul 6, 2022
4700496
Add high precission string for getvault verbose
dcorral Jul 7, 2022
35b00eb
Fixes error, return -1 when there are locked tokens
dcorral Jul 7, 2022
5b91dc6
Merge branch 'master' into fix-getvault-verbose
dcorral Jul 13, 2022
f224c4d
Negative Interest
Bushstar Jul 18, 2022
7b6148a
Merge branch 'fix-getvault-verbose' of github.com:DeFiCh/ain into neg…
dcorral Jul 18, 2022
304b7c0
Allows negative interests on loan tokens
dcorral Jul 18, 2022
d3851d8
tests: change error text
Bushstar Jul 18, 2022
bef9c14
Add testing to negative interest
dcorral Jul 28, 2022
e39414b
Remove breakpoint from failing test
dcorral Jul 28, 2022
2a329b1
Allow fallthrough from LoanMintingInterest
Bushstar Aug 2, 2022
14a0857
Update test to fail as expected
Bushstar Aug 2, 2022
ae92630
Apply token interest to all vaults immediately
Bushstar Aug 2, 2022
dc5066c
Partial negative vault interest
Bushstar Aug 4, 2022
6eb99b4
lint: Fix error
Bushstar Aug 4, 2022
3e815b5
Adds tests for negative interests
dcorral Aug 17, 2022
67a92e2
Add failing tests for discussion
dcorral Aug 17, 2022
4ce146a
Fix tests for correct behaviour
dcorral Aug 17, 2022
2425c53
Further negative interest rate updates
Bushstar Aug 18, 2022
79e8847
Check correct height value for incorrect amount. Restores behaviour.
Bushstar Aug 18, 2022
52ef2fc
TotalInterestCalculation use higher precision
Bushstar Aug 18, 2022
3f95ce1
Fix getinterest RPC when token param is passed
Jouzo Aug 18, 2022
cf46254
Add tests for loan paid by negative interest
dcorral Aug 18, 2022
b755727
Do not sub minted token
Bushstar Aug 19, 2022
801789e
Store negative interest the same as positive interest was stored
Bushstar Aug 19, 2022
400582d
Set negative bool in all branches
Bushstar Aug 19, 2022
4c16685
Remove unused variable
Bushstar Aug 19, 2022
da65598
Call EraseInterest on take loan
Bushstar Aug 19, 2022
715366d
Fixes tests to match new implementation
dcorral Aug 19, 2022
acc0e61
Total loan should not wrap around
Bushstar Aug 20, 2022
b20d3f3
Add getstoredinterest
Bushstar Aug 20, 2022
84ac0b1
Simplify interest addition lambda
Bushstar Aug 20, 2022
cd127ea
Add TotalInterestCalculation unit test
Jouzo Aug 20, 2022
439a451
Fix interest cal on tests after new updates
dcorral Aug 22, 2022
2164a44
Add new suite of functional test for getstoredinterest
dcorral Aug 22, 2022
1724978
Introduce helper functions and function template
dcorral Aug 22, 2022
2060940
Add helper functions
dcorral Aug 22, 2022
140a78b
Add stored interest test
Bushstar Aug 22, 2022
3cd816c
Add negative ITH positive IPB cases
Bushstar Aug 22, 2022
2f97316
Combine duplicate interest additions
Bushstar Aug 22, 2022
1a4e140
Update vault failing test
dcorral Aug 22, 2022
1ebf931
Pass new scheme to StoreInterest
Bushstar Aug 22, 2022
b910ffb
Add updatevault complete tests
dcorral Aug 22, 2022
d3da038
Remove subInterest assignation
dcorral Aug 22, 2022
be83d04
Adds test for takeloan
dcorral Aug 22, 2022
9c8a994
Wipe interest on take loan with negative interest
Bushstar Aug 23, 2022
bb3d90d
Test full payback
Bushstar Aug 23, 2022
a4edbd0
On take loan negate existing loan amount only
Bushstar Aug 24, 2022
1c8eeeb
Update negative interest test
Bushstar Aug 24, 2022
2c2c4d6
Do not sub balance on payback if loan negated
Bushstar Aug 24, 2022
0179613
Negative interest pays loan back first
Bushstar Aug 24, 2022
d2c134c
Add payback loan functional tests
Jouzo Aug 24, 2022
896f4e0
Adds remainning tests for takeloan
dcorral Aug 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 131 additions & 51 deletions src/masternodes/loan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,37 +186,50 @@ void CLoanView::EraseDelayedDestroyScheme(const std::string& loanSchemeID)
EraseBy<DestroyLoanSchemeKey>(loanSchemeID);
}

std::optional<CInterestRateV2> CLoanView::GetInterestRate(const CVaultId& vaultId, DCT_ID id, uint32_t height)
std::optional<CInterestRateV3> CLoanView::GetInterestRate(const CVaultId& vaultId, DCT_ID id, uint32_t height)
{
if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight))
return ReadBy<LoanInterestV2ByVault, CInterestRateV2>(std::make_pair(vaultId, id));
if (height >= static_cast<uint32_t>(Params().GetConsensus().GreatWorldHeight)) {
return ReadBy<LoanInterestV3ByVault, CInterestRateV3>(std::make_pair(vaultId, id));
}

if (auto rate = ReadBy<LoanInterestByVault, CInterestRate>(std::make_pair(vaultId, id)))
return ConvertInterestRateToV2(*rate);
if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight)) {
if (const auto rate = ReadBy<LoanInterestV2ByVault, CInterestRateV2>(std::make_pair(vaultId, id))) {
return ConvertInterestRateToV3(*rate);
}
}

if (auto rate = ReadBy<LoanInterestByVault, CInterestRate>(std::make_pair(vaultId, id))) {
return ConvertInterestRateToV3(*rate);
}

return {};
}

// precision COIN
// Precision 64bit
template<typename T>
inline T InterestPerBlockCalculationV1(CAmount amount, CAmount tokenInterest, CAmount schemeInterest)
{
auto netInterest = (tokenInterest + schemeInterest) / 100; // in %
const auto netInterest = (tokenInterest + schemeInterest) / 100; // in %
static const auto blocksPerYear = T(365) * Params().GetConsensus().blocksPerDay();
return MultiplyAmounts(netInterest, amount) / blocksPerYear;
}

// precisoin COIN ^3
// Precision 128bit
inline base_uint<128> InterestPerBlockCalculationV2(CAmount amount, CAmount tokenInterest, CAmount schemeInterest)
{
auto netInterest = (tokenInterest + schemeInterest) / 100; // in %
if (netInterest < 0) {
return 0;
}
const auto netInterest = (tokenInterest + schemeInterest) / 100; // in %
static const auto blocksPerYear = 365 * Params().GetConsensus().blocksPerDay();
return arith_uint256(amount) * netInterest * COIN / blocksPerYear;
}

// Precision 128bit with negative interest
CNegativeInterest InterestPerBlockCalculationV3(CAmount amount, CAmount tokenInterest, CAmount schemeInterest)
{
const auto netInterest = (tokenInterest + schemeInterest) / 100; // in %
static const auto blocksPerYear = 365 * Params().GetConsensus().blocksPerDay();
return {netInterest < 0, arith_uint256(amount) * std::abs(netInterest) * COIN / blocksPerYear};
}

CAmount CeilInterest(const base_uint<128>& value, uint32_t height)
{
if (int(height) >= Params().GetConsensus().FortCanningHillHeight) {
Expand All @@ -227,11 +240,29 @@ CAmount CeilInterest(const base_uint<128>& value, uint32_t height)
return value.GetLow64();
}

base_uint<128> TotalInterestCalculation(const CInterestRateV2& rate, uint32_t height)
{
auto interest = rate.interestToHeight + ((height - rate.height) * rate.interestPerBlock);
CNegativeInterest TotalInterestCalculation(const CInterestRateV3& rate, uint32_t height)
{
CNegativeInterest interest;
if (rate.interestPerBlock.negative) {
// TODO this formula is inaccurate, it assumes that the base amount will remain the same,
// when with a negative interest rate the loan amount should reduce on each block. Required
// is a formula to reduce the loan amount on each height by the negative interest per block,
// and then update the interest per block for the next height, repeat until we are at the
// current height. This requires that the loan amount be passed to this function which was
// until now not required as the loan amount was static before and so therefore was the interest.
Bushstar marked this conversation as resolved.
Show resolved Hide resolved
auto reduction = (height - rate.height) * rate.interestPerBlock.amount;
if (reduction >= rate.interestToHeight) {
interest.amount = reduction - rate.interestToHeight;
interest.negative = true;
} else {
interest.amount = rate.interestToHeight - reduction;
}
} else {
interest.amount = rate.interestToHeight + ((height - rate.height) * rate.interestPerBlock.amount);
}

LogPrint(BCLog::LOAN, "%s(): CInterestRate{.height=%d, .perBlock=%d, .toHeight=%d}, height %d - totalInterest %d\n", __func__, rate.height, InterestPerBlock(rate, height), CeilInterest(rate.interestToHeight, height), height, CeilInterest(interest, height));
LogPrint(BCLog::LOAN, "%s(): CInterestRate{.height=%d, .perBlock=%d, .toHeight=%d}, height %d - totalInterest %d\n",
__func__, rate.height, rate.interestPerBlock.negative ? -InterestPerBlock(rate, height) : InterestPerBlock(rate, height), CeilInterest(rate.interestToHeight, height), height, CeilInterest(interest.amount, height));
return interest;
}

Expand All @@ -244,22 +275,27 @@ static base_uint<128> ToHigherPrecision(CAmount amount, uint32_t height)
return amountHP;
}

CAmount TotalInterest(const CInterestRateV2& rate, uint32_t height)
CAmount TotalInterest(const CInterestRateV3& rate, uint32_t height)
{
return CeilInterest(TotalInterestCalculation(rate, height), height);
const auto totalInterest = TotalInterestCalculation(rate, height);
const auto amount = CeilInterest(totalInterest.amount, height);
return totalInterest.negative ? -amount : amount;
}

CAmount InterestPerBlock(const CInterestRateV2& rate, uint32_t height)
CAmount InterestPerBlock(const CInterestRateV3& rate, uint32_t height)
{
return CeilInterest(rate.interestPerBlock, height);
return CeilInterest(rate.interestPerBlock.amount, height);
}

void CLoanView::WriteInterestRate(const std::pair<CVaultId, DCT_ID>& pair, const CInterestRateV2& rate, uint32_t height)
void CLoanView::WriteInterestRate(const std::pair<CVaultId, DCT_ID>& pair, const CInterestRateV3& rate, uint32_t height)
{
if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight))
WriteBy<LoanInterestV2ByVault>(pair, rate);
else
if (height >= static_cast<uint32_t>(Params().GetConsensus().GreatWorldHeight)) {
WriteBy<LoanInterestV3ByVault>(pair, rate);
} else if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight)) {
WriteBy<LoanInterestV2ByVault>(pair, ConvertInterestRateToV2(rate));
} else {
WriteBy<LoanInterestByVault>(pair, ConvertInterestRateToV1(rate));
}
}

Res CLoanView::StoreInterest(uint32_t height, const CVaultId& vaultId, const std::string& loanSchemeID, DCT_ID id, CAmount tokenInterest, CAmount loanIncreased)
Expand All @@ -268,7 +304,7 @@ Res CLoanView::StoreInterest(uint32_t height, const CVaultId& vaultId, const std
if (!scheme)
return Res::Err("No such scheme id %s", loanSchemeID);

CInterestRateV2 rate{};
CInterestRateV3 rate{};
if (auto readRate = GetInterestRate(vaultId, id, height))
rate = *readRate;

Expand All @@ -277,19 +313,24 @@ Res CLoanView::StoreInterest(uint32_t height, const CVaultId& vaultId, const std

if (rate.height) {
LogPrint(BCLog::LOAN,"%s():\n", __func__);
rate.interestToHeight = TotalInterestCalculation(rate, height);
const auto totalInterest = TotalInterestCalculation(rate, height);
rate.interestToHeight = totalInterest.negative ? 0 : totalInterest.amount;
}

if (int(height) >= Params().GetConsensus().FortCanningHillHeight) {
if (height >= static_cast<uint32_t>(Params().GetConsensus().GreatWorldHeight)) {
CBalances amounts;
ReadBy<LoanTokenAmount>(vaultId, amounts);
rate.interestPerBlock = InterestPerBlockCalculationV3(amounts.balances[id], tokenInterest, scheme->rate);
} else if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight)) {
CBalances amounts;
ReadBy<LoanTokenAmount>(vaultId, amounts);
rate.interestPerBlock = InterestPerBlockCalculationV2(amounts.balances[id], tokenInterest, scheme->rate);
} else if (int(height) >= Params().GetConsensus().FortCanningMuseumHeight) {
CAmount interestPerBlock = rate.interestPerBlock.GetLow64();
rate.interestPerBlock = {false, InterestPerBlockCalculationV2(amounts.balances[id], tokenInterest, scheme->rate)};
} else if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningMuseumHeight)) {
CAmount interestPerBlock = rate.interestPerBlock.amount.GetLow64();
interestPerBlock += std::ceil(InterestPerBlockCalculationV1<float>(loanIncreased, tokenInterest, scheme->rate));
rate.interestPerBlock = interestPerBlock;
rate.interestPerBlock = {false, interestPerBlock};
} else {
rate.interestPerBlock += InterestPerBlockCalculationV1<CAmount>(loanIncreased, tokenInterest, scheme->rate);
rate.interestPerBlock.amount += InterestPerBlockCalculationV1<CAmount>(loanIncreased, tokenInterest, scheme->rate);
}
rate.height = height;

Expand All @@ -307,7 +348,7 @@ Res CLoanView::EraseInterest(uint32_t height, const CVaultId& vaultId, const std
if (!token)
return Res::Err("No such loan token id %s", id.ToString());

CInterestRateV2 rate{};
CInterestRateV3 rate{};
if (auto readRate = GetInterestRate(vaultId, id, height))
rate = *readRate;

Expand All @@ -317,29 +358,31 @@ Res CLoanView::EraseInterest(uint32_t height, const CVaultId& vaultId, const std
if (rate.height == 0)
return Res::Err("Data mismatch height == 0");

auto interestDecreasedHP = ToHigherPrecision(interestDecreased, height);

LogPrint(BCLog::LOAN,"%s():\n", __func__);
auto interestToHeight = TotalInterestCalculation(rate, height);
rate.interestToHeight = interestToHeight < interestDecreasedHP ? 0
: interestToHeight - interestDecreasedHP;

const auto totalInterest = TotalInterestCalculation(rate, height);
const auto interestToHeight = totalInterest.negative ? 0 : totalInterest.amount;
const auto interestDecreasedHP = ToHigherPrecision(std::abs(interestDecreased), height);

rate.interestToHeight = interestToHeight < interestDecreasedHP ? 0 : interestToHeight - interestDecreasedHP;
rate.height = height;

if (int(height) >= Params().GetConsensus().FortCanningHillHeight) {
if (height >= static_cast<uint32_t>(Params().GetConsensus().GreatWorldHeight)) {
CBalances amounts;
ReadBy<LoanTokenAmount>(vaultId, amounts);
rate.interestPerBlock = InterestPerBlockCalculationV2(amounts.balances[id], token->interest, scheme->rate);

} else if (int(height) >= Params().GetConsensus().FortCanningMuseumHeight) {
CAmount interestPerBlock = rate.interestPerBlock.GetLow64();
rate.interestPerBlock = InterestPerBlockCalculationV3(amounts.balances[id], token->interest, scheme->rate);
} else if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight)) {
CBalances amounts;
ReadBy<LoanTokenAmount>(vaultId, amounts);
rate.interestPerBlock = {false, InterestPerBlockCalculationV2(amounts.balances[id], token->interest, scheme->rate)};
} else if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningMuseumHeight)) {
CAmount interestPerBlock = rate.interestPerBlock.amount.GetLow64();
CAmount newInterestPerBlock = std::ceil(InterestPerBlockCalculationV1<float>(loanDecreased, token->interest, scheme->rate));
rate.interestPerBlock = std::max(CAmount{0}, interestPerBlock - newInterestPerBlock);

rate.interestPerBlock = {false ,std::max(CAmount{0}, interestPerBlock - newInterestPerBlock)};
} else {
auto interestPerBlock = InterestPerBlockCalculationV1<CAmount>(loanDecreased, token->interest, scheme->rate);
rate.interestPerBlock = rate.interestPerBlock < interestPerBlock ? 0
: rate.interestPerBlock - interestPerBlock;
rate.interestPerBlock = rate.interestPerBlock.amount < interestPerBlock ? CNegativeInterest{false, 0}
: CNegativeInterest{false, rate.interestPerBlock.amount - interestPerBlock};
}

WriteInterestRate(std::make_pair(vaultId, id), rate, height);
Expand All @@ -360,6 +403,13 @@ void CLoanView::ForEachVaultInterestV2(std::function<bool(const CVaultId&, DCT_I
}, std::make_pair(vaultId, id));
}

void CLoanView::ForEachVaultInterestV3(std::function<bool(const CVaultId&, DCT_ID, CInterestRateV3)> callback, const CVaultId& vaultId, DCT_ID id)
{
ForEach<LoanInterestV3ByVault, std::pair<CVaultId, DCT_ID>, CInterestRateV3>([&](const std::pair<CVaultId, DCT_ID>& pair, CInterestRateV3 rate) {
return callback(pair.first, pair.second, rate);
}, std::make_pair(vaultId, id));
}

template<typename BoundType>
void DeleteInterest(CLoanView& view, const CVaultId& vaultId)
{
Expand All @@ -375,17 +425,24 @@ void DeleteInterest(CLoanView& view, const CVaultId& vaultId)

Res CLoanView::DeleteInterest(const CVaultId& vaultId, uint32_t height)
{
if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight))
if (height >= static_cast<uint32_t>(Params().GetConsensus().GreatWorldHeight)) {
::DeleteInterest<LoanInterestV3ByVault>(*this, vaultId);
} else if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight)) {
::DeleteInterest<LoanInterestV2ByVault>(*this, vaultId);
else
} else {
::DeleteInterest<LoanInterestByVault>(*this, vaultId);
}

return Res::Ok();
}

void CLoanView::EraseInterestDirect(const CVaultId& vaultId, DCT_ID id)
void CLoanView::EraseInterestDirect(const CVaultId& vaultId, DCT_ID id, uint32_t height)
{
EraseBy<LoanInterestV2ByVault>(std::make_pair(vaultId, id));
if (height >= static_cast<uint32_t>(Params().GetConsensus().GreatWorldHeight)) {
EraseBy<LoanInterestV3ByVault>(std::make_pair(vaultId, id));
} else if (height >= static_cast<uint32_t>(Params().GetConsensus().FortCanningHillHeight)) {
EraseBy<LoanInterestV2ByVault>(std::make_pair(vaultId, id));
}
}

void CLoanView::RevertInterestRateToV1()
Expand All @@ -402,6 +459,20 @@ void CLoanView::RevertInterestRateToV1()
}
}

void CLoanView::RevertInterestRateToV2()
{
std::vector<std::pair<CVaultId, DCT_ID>> pairs;

ForEach<LoanInterestV3ByVault, std::pair<CVaultId, DCT_ID>, CInterestRateV3>([&](const std::pair<CVaultId, DCT_ID>& pair, const CInterestRateV3&) {
pairs.emplace_back(pair);
return true;
});

for (auto it = pairs.begin(); it != pairs.end(); it = pairs.erase(it)) {
EraseBy<LoanInterestV3ByVault>(*it);
}
}

void CLoanView::MigrateInterestRateToV2(CVaultView &view, uint32_t height)
{
ForEachVaultInterest([&](const CVaultId& vaultId, DCT_ID tokenId, CInterestRate rate) {
Expand All @@ -421,6 +492,15 @@ void CLoanView::MigrateInterestRateToV2(CVaultView &view, uint32_t height)
});
}

void CLoanView::MigrateInterestRateToV3(CVaultView &view, uint32_t height)
{
ForEachVaultInterestV2([&](const CVaultId& vaultId, DCT_ID tokenId, const CInterestRateV2 &rate) {
auto newRate = ConvertInterestRateToV3(rate);
WriteBy<LoanInterestV3ByVault>(std::make_pair(vaultId, tokenId), newRate);
return true;
});
}

Res CLoanView::AddLoanToken(const CVaultId& vaultId, CTokenAmount amount)
{
if (!GetLoanTokenByID(amount.nTokenId))
Expand Down
Loading