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

Future Swap limitation #2843

Merged
merged 18 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading