Skip to content

Commit

Permalink
Future Swap limitation (#2843)
Browse files Browse the repository at this point in the history
* FutureSwap limitation

* Make limit bi-directional

* Fix typo

* Format Python

* Remove redundant conditional

* Rename test

* Fix remnant changes

* Cleanup header

* fmt

* Add gov variable for sampling period

* Resolve parse value

* Sample a non-consecutive period

* Update required sample for block sample size

---------

Co-authored-by: Prasanna Loganathar <pvl@prasannavl.com>
Co-authored-by: Niven <sieniven@gmail.com>
  • Loading branch information
3 people authored Mar 22, 2024
1 parent 3134753 commit 055fae7
Show file tree
Hide file tree
Showing 13 changed files with 757 additions and 39 deletions.
46 changes: 46 additions & 0 deletions src/dfi/consensus/smartcontracts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <dfi/govvariables/attributes.h>
#include <dfi/masternodes.h>
#include <dfi/mn_checks.h>
#include <dfi/validation.h>

Res CSmartContractsConsensus::HandleDFIP2201Contract(const CSmartContractMessage &obj) const {
const auto &consensus = txCtx.GetConsensus();
Expand Down Expand Up @@ -288,6 +289,51 @@ Res CSmartContractsConsensus::operator()(const CFutureSwapMessage &obj) const {
return res;
}
} else {
if (height >= static_cast<uint32_t>(consensus.DF23Height) && !dfiToDUSD) {
CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::Active};
const auto dfip11fEnabled = attributes->GetValue(activeKey, false);
const auto dusdToken = mnview.GetToken("DUSD");
if (!dusdToken) {
return Res::Err("No DUSD token defined");
}
const auto dest = !obj.destination ? dusdToken->first.v : obj.destination;
const auto averageLiquidity = mnview.GetLoanTokenAverageLiquidity({obj.source.nTokenId.v, dest});

if (dfip11fEnabled && averageLiquidity) {
CDataStructureV0 averageKey{
AttributeTypes::Param, ParamIDs::DFIP2211F, DFIPKeys::AverageLiquidityPercentage};
const auto averageLiquidityPercentage =
attributes->GetValue(averageKey, DEFAULT_AVERAGE_LIQUIDITY_PERCENTAGE);

const auto maxSwapAmount = MultiplyAmounts(*averageLiquidity, averageLiquidityPercentage);

arith_uint256 totalSwapAmount{};

mnview.ForEachFuturesUserValues(
[&](const CFuturesUserKey &key, const CFuturesUserValue &futuresValues) {
if (futuresValues.source.nTokenId == obj.source.nTokenId &&
futuresValues.destination == obj.destination) {
totalSwapAmount += futuresValues.source.nValue;
}
return true;
},
{height, {}, std::numeric_limits<uint32_t>::max()});

if (obj.source.nValue + totalSwapAmount > maxSwapAmount) {
auto percentageString = GetDecimalString(averageLiquidityPercentage * 100);
rtrim(percentageString, '0');
if (percentageString.back() == '.') {
percentageString.pop_back();
}
return Res::Err(
"Swap amount exceeds %d%% of average pool liquidity limit. Available amount to swap: %s@%s",
percentageString,
GetDecimalString((maxSwapAmount - totalSwapAmount).GetLow64()),
source->symbol);
}
}
}

if (auto res = TransferTokenBalance(obj.source.nTokenId, obj.source.nValue, obj.owner, *contractAddressValue);
!res) {
return res;
Expand Down
2 changes: 2 additions & 0 deletions src/dfi/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ class DeFiErrors {

static Res GovVarValidateFortCanningSpring() { return Res::Err("Cannot be set before FortCanningSpringHeight"); }

static Res GovVarValidateDF23Height() { return Res::Err("Cannot be set before DF23Height"); }

static Res GovVarValidateToken(const uint32_t token) { return Res::Err("No such token (%d)", token); }

static Res GovVarValidateTokenExist(const uint32_t token) { return Res::Err("Token (%d) does not exist", token); }
Expand Down
61 changes: 55 additions & 6 deletions src/dfi/govvariables/attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const std::map<std::string, uint8_t> &ATTRIBUTES::allowedParamIDs() {
// Note: DFIP2206F is currently in beta testing
// for testnet. May not be enabled on mainnet until testing is complete.
{"dfip2206f", ParamIDs::DFIP2206F },
{"dfip2211f", ParamIDs::DFIP2211F },
{"feature", ParamIDs::Feature },
{"foundation", ParamIDs::Foundation},
};
Expand All @@ -112,6 +113,7 @@ const std::map<uint8_t, std::string> &ATTRIBUTES::allowedExportParamsIDs() {
{ParamIDs::DFIP2203, "dfip2203" },
{ParamIDs::DFIP2206A, "dfip2206a" },
{ParamIDs::DFIP2206F, "dfip2206f" },
{ParamIDs::DFIP2211F, "dfip2211f" },
{ParamIDs::Feature, "feature" },
{ParamIDs::Foundation, "foundation"},
};
Expand Down Expand Up @@ -276,6 +278,8 @@ const std::map<uint8_t, std::map<std::string, uint8_t>> &ATTRIBUTES::allowedKeys
{"emission-unused-fund", DFIPKeys::EmissionUnusedFund},
{"mint-tokens-to-address", DFIPKeys::MintTokens},
{"transferdomain", DFIPKeys::TransferDomain},
{"liquidity_calc_sampling_period", DFIPKeys::LiquidityCalcSamplingPeriod},
{"average_liquidity_percentage", DFIPKeys::AverageLiquidityPercentage},
}},
{AttributeTypes::EVMType,
{
Expand Down Expand Up @@ -379,6 +383,8 @@ const std::map<uint8_t, std::map<uint8_t, std::string>> &ATTRIBUTES::displayKeys
{DFIPKeys::EmissionUnusedFund, "emission-unused-fund"},
{DFIPKeys::MintTokens, "mint-tokens-to-address"},
{DFIPKeys::TransferDomain, "transferdomain"},
{DFIPKeys::LiquidityCalcSamplingPeriod, "liquidity_calc_sampling_period"},
{DFIPKeys::AverageLiquidityPercentage, "average_liquidity_percentage"},
}},
{AttributeTypes::EVMType,
{
Expand Down Expand Up @@ -740,10 +746,6 @@ static ResVal<CAttributeValue> VerifyEVMAuthTypes(const UniValue &array) {
return {addressSet, Res::Ok()};
}

static inline void rtrim(std::string &s, unsigned char remove) {
s.erase(std::find_if(s.rbegin(), s.rend(), [&remove](unsigned char ch) { return ch != remove; }).base(), s.end());
}

static inline std::string GetDecimalStringNormalized(const CAmount amount) {
auto decimalStr = GetDecimalString(amount);
rtrim(decimalStr, '0');
Expand Down Expand Up @@ -802,6 +804,8 @@ const std::map<uint8_t, std::map<uint8_t, std::function<ResVal<CAttributeValue>(
{DFIPKeys::EmissionUnusedFund, VerifyBool},
{DFIPKeys::MintTokens, VerifyBool},
{DFIPKeys::TransferDomain, VerifyBool},
{DFIPKeys::LiquidityCalcSamplingPeriod, VerifyInt64},
{DFIPKeys::AverageLiquidityPercentage, VerifyPctInt64},
}},
{AttributeTypes::Locks,
{
Expand Down Expand Up @@ -961,6 +965,11 @@ static Res CheckValidAttrV0Key(const uint8_t type, const uint32_t typeId, const
if (typeKey != DFIPKeys::DUSDInterestBurn && typeKey != DFIPKeys::DUSDLoanBurn) {
return DeFiErrors::GovVarVariableUnsupportedDFIPType(typeKey);
}
} else if (typeId == ParamIDs::DFIP2211F) {
if (typeKey != DFIPKeys::Active && typeKey != DFIPKeys::BlockPeriod &&
typeKey != DFIPKeys::LiquidityCalcSamplingPeriod && typeKey != DFIPKeys::AverageLiquidityPercentage) {
return DeFiErrors::GovVarVariableUnsupportedDFIPType(typeKey);
}
} else if (typeId == ParamIDs::Feature) {
if (typeKey != DFIPKeys::GovUnset && typeKey != DFIPKeys::GovFoundation &&
typeKey != DFIPKeys::MNSetRewardAddress && typeKey != DFIPKeys::MNSetOperatorAddress &&
Expand Down Expand Up @@ -1651,8 +1660,9 @@ UniValue ATTRIBUTES::ExportFiltered(GovVarsFilter filter, const std::string &pre
ret.pushKV(key, KeyBuilder(*number));
} else if (const auto amount = std::get_if<CAmount>(&attribute.second)) {
if (attrV0->type == AttributeTypes::Param &&
(attrV0->typeId == DFIP2203 || attrV0->typeId == DFIP2206F) &&
(attrV0->key == DFIPKeys::BlockPeriod || attrV0->key == DFIPKeys::StartBlock)) {
(attrV0->typeId == DFIP2203 || attrV0->typeId == DFIP2206F || attrV0->typeId == DFIP2211F) &&
(attrV0->key == DFIPKeys::BlockPeriod || attrV0->key == DFIPKeys::StartBlock ||
attrV0->key == DFIPKeys::LiquidityCalcSamplingPeriod)) {
ret.pushKV(key, KeyBuilder(*amount));
} else {
const auto decimalStr = GetDecimalStringNormalized(*amount);
Expand Down Expand Up @@ -2040,6 +2050,10 @@ Res ATTRIBUTES::Validate(const CCustomCSView &view) const {
return Res::Err("Cannot be set before FortCanningSpringHeight");
}
}
} else if (attrV0->typeId == ParamIDs::DFIP2211F) {
if (view.GetLastHeight() < Params().GetConsensus().DF23Height) {
return DeFiErrors::GovVarValidateDF23Height();
}
} else if (attrV0->typeId != ParamIDs::DFIP2201) {
return Res::Err("Unrecognised param id");
}
Expand Down Expand Up @@ -2314,6 +2328,41 @@ Res ATTRIBUTES::Apply(CCustomCSView &mnview, const uint32_t height) {
return DeFiErrors::GovVarApplyDFIPActive("DFIP2206F");
}
}
} else if (attrV0->typeId == ParamIDs::DFIP2211F) {
if (attrV0->key == DFIPKeys::Active) {
const auto value = std::get_if<bool>(&attribute.second);
if (!value) {
return DeFiErrors::GovVarApplyUnexpectedType();
}

if (*value) {
continue;
}

// Disabled so delete all data to prevent stale data
// when re-enabled. Gov var TX could enable it again
// and a subsequent TX in the block could be impacted.
std::vector<LoanTokenLiquidityPerBlockKey> perBlockKeys;
mnview.ForEachTokenLiquidityPerBlock(
[&](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) {
perBlockKeys.push_back(key);
return true;
});

for (const auto &key : perBlockKeys) {
mnview.EraseTokenLiquidityPerBlock(key);
}

std::vector<LoanTokenAverageLiquidityKey> averageKeys;
mnview.ForEachTokenAverageLiquidity([&](const LoanTokenAverageLiquidityKey &key, const uint64_t) {
averageKeys.push_back(key);
return true;
});

for (const auto &key : averageKeys) {
mnview.EraseTokenAverageLiquidity(key);
}
}
}

} else if (attrV0->type == AttributeTypes::Oracles && attrV0->typeId == OracleIDs::Splits &&
Expand Down
3 changes: 3 additions & 0 deletions src/dfi/govvariables/attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ enum ParamIDs : uint8_t {
Feature = 'h',
Auction = 'i',
Foundation = 'j',
DFIP2211F = 'k',
};

enum OracleIDs : uint8_t {
Expand Down Expand Up @@ -121,6 +122,8 @@ enum DFIPKeys : uint8_t {
EVMEnabled = 'u',
ICXEnabled = 'v',
TransferDomain = 'w',
LiquidityCalcSamplingPeriod = 'x',
AverageLiquidityPercentage = 'y',
};

enum GovernanceKeys : uint8_t {
Expand Down
41 changes: 41 additions & 0 deletions src/dfi/poolpairs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,47 @@ Res CPoolPairView::SetShare(DCT_ID const &poolId, const CScript &provider, uint3
return Res::Ok();
}

bool CPoolPairView::SetLoanTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key,
const CAmount liquidityPerBlock) {
return WriteBy<ByLoanTokenLiquidityPerBlock>(key, liquidityPerBlock);
}

bool CPoolPairView::EraseTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key) {
return EraseBy<ByLoanTokenLiquidityPerBlock>(key);
}

void CPoolPairView::ForEachTokenLiquidityPerBlock(
std::function<bool(const LoanTokenLiquidityPerBlockKey &key, const CAmount liquidityPerBlock)> callback,
const LoanTokenLiquidityPerBlockKey &start) {
ForEach<ByLoanTokenLiquidityPerBlock, LoanTokenLiquidityPerBlockKey, CAmount>(
[&callback](const LoanTokenLiquidityPerBlockKey &key, const CAmount &liquidityPerBlock) {
return callback(key, liquidityPerBlock);
},
start);
}

bool CPoolPairView::SetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity) {
return WriteBy<ByLoanTokenLiquidityAverage>(key, liquidity);
}

std::optional<uint64_t> CPoolPairView::GetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key) {
return ReadBy<ByLoanTokenLiquidityAverage, CAmount>(key);
}

bool CPoolPairView::EraseTokenAverageLiquidity(const LoanTokenAverageLiquidityKey key) {
return EraseBy<ByLoanTokenLiquidityAverage>(key);
}

void CPoolPairView::ForEachTokenAverageLiquidity(
std::function<bool(const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity)> callback,
const LoanTokenAverageLiquidityKey start) {
ForEach<ByLoanTokenLiquidityAverage, LoanTokenAverageLiquidityKey, uint64_t>(
[&callback](const LoanTokenAverageLiquidityKey &key, const uint64_t &liquidity) {
return callback(key, liquidity);
},
start);
}

Res CPoolPairView::DelShare(DCT_ID const &poolId, const CScript &provider) {
EraseBy<ByShare>(PoolShareKey{poolId, provider});
return Res::Ok();
Expand Down
54 changes: 54 additions & 0 deletions src/dfi/poolpairs.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,41 @@ struct PoolShareKey {
}
};

struct LoanTokenAverageLiquidityKey {
uint32_t sourceID;
uint32_t destID;

ADD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
READWRITE(sourceID);
READWRITE(destID);
}

bool operator<(const LoanTokenAverageLiquidityKey &other) const {
if (sourceID == other.sourceID) {
return destID < other.destID;
}
return sourceID < other.sourceID;
}
};

struct LoanTokenLiquidityPerBlockKey {
uint32_t height;
uint32_t sourceID;
uint32_t destID;

ADD_SERIALIZE_METHODS;

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

struct PoolHeightKey {
DCT_ID poolID;
uint32_t height;
Expand Down Expand Up @@ -301,6 +336,19 @@ class CPoolPairView : public virtual CStorageView {
std::function<Res(const CScript &, const CScript &, CTokenAmount)> onTransfer,
int nHeight = 0);

bool SetLoanTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key, const CAmount liquidityPerBlock);
bool EraseTokenLiquidityPerBlock(const LoanTokenLiquidityPerBlockKey &key);
void ForEachTokenLiquidityPerBlock(
std::function<bool(const LoanTokenLiquidityPerBlockKey &key, const CAmount liquidityPerBlock)> callback,
const LoanTokenLiquidityPerBlockKey &start = LoanTokenLiquidityPerBlockKey{});

bool SetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity);
std::optional<uint64_t> GetLoanTokenAverageLiquidity(const LoanTokenAverageLiquidityKey &key);
bool EraseTokenAverageLiquidity(const LoanTokenAverageLiquidityKey key);
void ForEachTokenAverageLiquidity(
std::function<bool(const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity)> callback,
const LoanTokenAverageLiquidityKey start = LoanTokenAverageLiquidityKey{});

// tags
struct ByID {
static constexpr uint8_t prefix() { return 'i'; }
Expand Down Expand Up @@ -347,6 +395,12 @@ class CPoolPairView : public virtual CStorageView {
struct ByTokenDexFeePct {
static constexpr uint8_t prefix() { return 'l'; }
};
struct ByLoanTokenLiquidityPerBlock {
static constexpr uint8_t prefix() { return 'p'; }
};
struct ByLoanTokenLiquidityAverage {
static constexpr uint8_t prefix() { return '+'; }
};
};

struct CLiquidityMessage {
Expand Down
50 changes: 40 additions & 10 deletions src/dfi/rpc_poolpair.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1414,19 +1414,49 @@ UniValue listpoolshares(const JSONRPCRequest &request) {
return GetRPCResultCache().Set(request, ret);
}

UniValue listloantokenliquidity(const JSONRPCRequest &request) {
RPCHelpMan{
"listloantokenliquidity",
"\nReturns information about the average liquidity for loan tokens if a sufficient sample is available.\n",
{{}},
RPCResult{"{token symbol : value } (array) JSON object with token ID and average liquidity\n"},
RPCExamples{HelpExampleCli("listloantokenliquidity", "") + HelpExampleRpc("listloantokenliquidity", "")},
}
.Check(request);

if (auto res = GetRPCResultCache().TryGet(request)) {
return *res;
}

auto view = ::GetViewSnapshot();

UniValue ret(UniValue::VOBJ);
view->ForEachTokenAverageLiquidity([&](const LoanTokenAverageLiquidityKey &key, const uint64_t liquidity) {
const auto sourceToken = view->GetToken(DCT_ID{key.sourceID});
const auto destToken = view->GetToken(DCT_ID{key.destID});
if (sourceToken && destToken) {
ret.pushKV(sourceToken->symbol + '-' + destToken->symbol, GetDecimalString(liquidity));
}
return true;
});

return GetRPCResultCache().Set(request, ret);
}

static const CRPCCommand commands[] = {
// category name actor (function) params
// ------------- ----------------------- --------------------- ----------
{"poolpair", "listpoolpairs", &listpoolpairs, {"pagination", "verbose"} },
{"poolpair", "getpoolpair", &getpoolpair, {"key", "verbose"} },
{"poolpair", "addpoolliquidity", &addpoolliquidity, {"from", "shareAddress", "inputs"} },
{"poolpair", "removepoolliquidity", &removepoolliquidity, {"from", "amount", "inputs"} },
{"poolpair", "createpoolpair", &createpoolpair, {"metadata", "inputs"} },
{"poolpair", "updatepoolpair", &updatepoolpair, {"metadata", "inputs"} },
{"poolpair", "poolswap", &poolswap, {"metadata", "inputs"} },
{"poolpair", "compositeswap", &compositeswap, {"metadata", "inputs"} },
{"poolpair", "listpoolshares", &listpoolshares, {"pagination", "verbose", "is_mine_only"}},
{"poolpair", "testpoolswap", &testpoolswap, {"metadata", "path", "verbose"} },
{"poolpair", "listpoolpairs", &listpoolpairs, {"pagination", "verbose"} },
{"poolpair", "getpoolpair", &getpoolpair, {"key", "verbose"} },
{"poolpair", "addpoolliquidity", &addpoolliquidity, {"from", "shareAddress", "inputs"} },
{"poolpair", "removepoolliquidity", &removepoolliquidity, {"from", "amount", "inputs"} },
{"poolpair", "createpoolpair", &createpoolpair, {"metadata", "inputs"} },
{"poolpair", "updatepoolpair", &updatepoolpair, {"metadata", "inputs"} },
{"poolpair", "poolswap", &poolswap, {"metadata", "inputs"} },
{"poolpair", "compositeswap", &compositeswap, {"metadata", "inputs"} },
{"poolpair", "listpoolshares", &listpoolshares, {"pagination", "verbose", "is_mine_only"}},
{"poolpair", "testpoolswap", &testpoolswap, {"metadata", "path", "verbose"} },
{"poolpair", "listloantokenliquidity", &listloantokenliquidity, {} },
};

void RegisterPoolpairRPCCommands(CRPCTable &tableRPC) {
Expand Down
Loading

0 comments on commit 055fae7

Please sign in to comment.