Skip to content

Commit

Permalink
Loan token interest calculation (#665)
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony Fieroni <bvbfan@abv.bg>
  • Loading branch information
bvbfan committed Aug 11, 2021
1 parent 8702565 commit ef176ca
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ DEFI_TESTS =\
test/key_tests.cpp \
test/limitedmap_tests.cpp \
test/liquidity_tests.cpp \
test/loan_tests.cpp \
test/dbwrapper_tests.cpp \
test/validation_tests.cpp \
test/mempool_tests.cpp \
Expand Down
68 changes: 68 additions & 0 deletions src/masternodes/loan.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

#include <chainparams.h>
#include <masternodes/loan.h>

const unsigned char CLoanView::LoanSetCollateralTokenCreationTx ::prefix = 0x10;
Expand All @@ -8,6 +10,7 @@ const unsigned char CLoanView::DelayedLoanSchemeKey ::pref
const unsigned char CLoanView::DestroyLoanSchemeKey ::prefix = 0x15;
const unsigned char CLoanView::LoanSetLoanTokenCreationTx ::prefix = 0x17;
const unsigned char CLoanView::LoanSetLoanTokenKey ::prefix = 0x18;
const unsigned char CLoanView::LoanInterestedRate ::prefix = 0x19;
// Vault
const unsigned char CVaultView::VaultKey ::prefix = 0x16;

Expand Down Expand Up @@ -192,6 +195,71 @@ void CLoanView::EraseDelayedDestroyScheme(const std::string& loanSchemeID)
EraseBy<DestroyLoanSchemeKey>(loanSchemeID);
}

boost::optional<CInterestRate> CLoanView::GetInterestRate(const std::string& loanSchemeID, DCT_ID id)
{
return ReadBy<LoanInterestedRate, CInterestRate>(std::make_pair(loanSchemeID, id));
}

Res CLoanView::StoreInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id)
{
auto scheme = GetLoanScheme(loanSchemeID);
if (!scheme) {
return Res::Err("No such scheme id %s", loanSchemeID);
}
auto token = GetLoanSetLoanTokenByID(id);
if (!token) {
return Res::Err("No such loan token id %s", id.ToString());
}
CInterestRate rate{};
if (auto storedRate = GetInterestRate(loanSchemeID, id)) {
rate = *storedRate;
}
if (rate.height > height) {
return Res::Err("Cannot store height in the past");
}
if (rate.height) {
rate.interestToHeight += (height - rate.height) * rate.interestPerBlock;
}
rate.count++;
rate.height = height;
int64_t netInterest = scheme->rate + token->interest;
rate.interestPerBlock = netInterest * rate.count / (365 * Params().GetConsensus().blocksPerDay());
WriteBy<LoanInterestedRate>(std::make_pair(loanSchemeID, id), rate);
return Res::Ok();
}

Res CLoanView::EraseInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id)
{
auto scheme = GetLoanScheme(loanSchemeID);
if (!scheme) {
return Res::Err("No such scheme id %s", loanSchemeID);
}
auto token = GetLoanSetLoanTokenByID(id);
if (!token) {
return Res::Err("No such loan token id %s", id.ToString());
}
CInterestRate rate{};
if (auto storedRate = GetInterestRate(loanSchemeID, id)) {
rate = *storedRate;
}
if (rate.count <= 1) {
EraseBy<LoanInterestedRate>(std::make_pair(loanSchemeID, id));
return Res::Ok();
}
if (rate.height > height) {
return Res::Err("Cannot store height in the past");
}
if (rate.height == 0) {
return Res::Err("Data mismatch height == 0");
}
rate.interestToHeight += (height - rate.height) * rate.interestPerBlock;
rate.count--;
rate.height = height;
int64_t netInterest = scheme->rate + token->interest;
rate.interestPerBlock = netInterest * rate.count / (365 * Params().GetConsensus().blocksPerDay());
WriteBy<LoanInterestedRate>(std::make_pair(loanSchemeID, id), rate);
return Res::Ok();
}

// VAULT

Expand Down
23 changes: 23 additions & 0 deletions src/masternodes/loan.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,24 @@ struct CDestroyLoanSchemeMessage : public CDefaultLoanSchemeMessage
}
};

struct CInterestRate {
uint32_t count = 0;
uint32_t height = 0;
CAmount interestToHeight = 0;
CAmount interestPerBlock = 0;

ADD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
READWRITE(count);
READWRITE(height);
READWRITE(interestToHeight);
READWRITE(interestPerBlock);
}
};

// use vault's creation tx for ID
using CVaultId = uint256;
struct CVaultMessage {
Expand Down Expand Up @@ -226,6 +244,10 @@ class CLoanView : public virtual CStorageView {
void ForEachDelayedLoanScheme(std::function<bool (const std::pair<std::string, uint64_t>&, const CLoanSchemeMessage&)> callback);
void ForEachDelayedDestroyScheme(std::function<bool (const std::string&, const uint64_t&)> callback);

boost::optional<CInterestRate> GetInterestRate(const std::string& loanSchemeID, DCT_ID id);
Res StoreInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id);
Res EraseInterest(uint32_t height, const std::string& loanSchemeID, DCT_ID id);

struct LoanSetCollateralTokenCreationTx { static const unsigned char prefix; };
struct LoanSetCollateralTokenKey { static const unsigned char prefix; };
struct LoanSetLoanTokenCreationTx { static const unsigned char prefix; };
Expand All @@ -234,6 +256,7 @@ class CLoanView : public virtual CStorageView {
struct DefaultLoanSchemeKey { static const unsigned char prefix; };
struct DelayedLoanSchemeKey { static const unsigned char prefix; };
struct DestroyLoanSchemeKey { static const unsigned char prefix; };
struct LoanInterestedRate { static const unsigned char prefix; };
};

class CVaultView : public virtual CStorageView
Expand Down
99 changes: 99 additions & 0 deletions src/test/loan_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include <chainparams.h>
#include <masternodes/loan.h>
#include <masternodes/masternodes.h>
#include <validation.h>

#include <test/setup_common.h>

#include <boost/test/unit_test.hpp>


inline uint256 NextTx()
{
static int txs_counter = 1;

std::stringstream stream;
stream << std::hex << txs_counter++;
return uint256S(stream.str() );
}

DCT_ID CreateLoanToken(CCustomCSView &mnview, CAmount interest)
{
CTokenImplementation token;
CLoanSetLoanTokenImplementation loanToken;
loanToken.interest = interest;
loanToken.creationTx = token.creationTx = NextTx();
token.flags = (uint8_t)CToken::TokenFlags::Default;
token.flags |= (uint8_t)CToken::TokenFlags::LoanToken | (uint8_t)CToken::TokenFlags::DAT;

token.symbol = "TST";
token.name = "Test";

auto res = mnview.CreateToken(token, false);
if (!res.ok) printf("%s\n", res.msg.c_str());
BOOST_REQUIRE(res.ok);
auto id = *res.val;
mnview.LoanSetLoanToken(loanToken, id);
return id;
}

BOOST_FIXTURE_TEST_SUITE(loan_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(loan_iterest_rate)
{
CCustomCSView mnview(*pcustomcsview);

const std::string id("sch1");
CLoanSchemeMessage msg;
msg.ratio = 150;
msg.rate = 2 * COIN;
msg.identifier = id;
mnview.StoreLoanScheme(msg);

const CAmount tokenInterest = 5 * COIN;
auto token_id = CreateLoanToken(mnview, tokenInterest);
auto scheme = mnview.GetLoanScheme(id);
BOOST_REQUIRE(scheme);
BOOST_CHECK_EQUAL(scheme->ratio, 150);
BOOST_CHECK_EQUAL(scheme->rate, 2 * COIN);

mnview.StoreInterest(1, id, token_id);
mnview.StoreInterest(1, id, token_id);
mnview.StoreInterest(1, id, token_id);

auto rate = mnview.GetInterestRate(id, token_id);
BOOST_REQUIRE(rate);
BOOST_CHECK_EQUAL(rate->count, 3);
BOOST_CHECK_EQUAL(rate->height, 1);
auto netInterest = scheme->rate + tokenInterest;
BOOST_CHECK_EQUAL(rate->interestPerBlock, netInterest * rate->count / (365 * Params().GetConsensus().blocksPerDay()));

auto interestPerBlock = rate->interestPerBlock + rate->interestToHeight;
mnview.StoreInterest(5, id, token_id);
mnview.StoreInterest(5, id, token_id);

rate = mnview.GetInterestRate(id, token_id);
BOOST_REQUIRE(rate);
BOOST_CHECK_EQUAL(rate->count, 5);
BOOST_CHECK_EQUAL(rate->height, 5);
BOOST_CHECK_EQUAL(rate->interestToHeight, 4 * interestPerBlock);
BOOST_CHECK_EQUAL(rate->interestPerBlock, netInterest * rate->count / (365 * Params().GetConsensus().blocksPerDay()));

interestPerBlock = rate->interestPerBlock + rate->interestToHeight;
mnview.EraseInterest(6, id, token_id);
rate = mnview.GetInterestRate(id, token_id);

BOOST_REQUIRE(rate);
BOOST_CHECK_EQUAL(rate->count, 4);
BOOST_CHECK_EQUAL(rate->interestToHeight, interestPerBlock);
BOOST_CHECK_EQUAL(rate->interestPerBlock, netInterest * rate->count / (365 * Params().GetConsensus().blocksPerDay()));

for (int i = 0; i < 4; i++) {
mnview.EraseInterest(6, id, token_id);
}
rate = mnview.GetInterestRate(id, token_id);

BOOST_REQUIRE(!rate);
}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit ef176ca

Please sign in to comment.