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

Asymmetric pool swap fee #1354

Merged
merged 4 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
70 changes: 52 additions & 18 deletions src/masternodes/govvariables/attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ const std::map<uint8_t, std::map<std::string, uint8_t>>& ATTRIBUTES::allowedKeys
},
{
AttributeTypes::Poolpairs, {
{"token_a_fee_pct", PoolKeys::TokenAFeePCT},
{"token_b_fee_pct", PoolKeys::TokenBFeePCT},
{"token_a_fee_pct", PoolKeys::TokenAFeePCT},
{"token_a_fee_direction",PoolKeys::TokenAFeeDir},
{"token_b_fee_pct", PoolKeys::TokenBFeePCT},
{"token_b_fee_direction",PoolKeys::TokenBFeeDir},
}
},
{
Expand Down Expand Up @@ -173,7 +175,9 @@ const std::map<uint8_t, std::map<uint8_t, std::string>>& ATTRIBUTES::displayKeys
{
AttributeTypes::Poolpairs, {
{PoolKeys::TokenAFeePCT, "token_a_fee_pct"},
{PoolKeys::TokenAFeeDir, "token_a_fee_direction"},
{PoolKeys::TokenBFeePCT, "token_b_fee_pct"},
{PoolKeys::TokenBFeeDir, "token_b_fee_direction"},
}
},
{
Expand Down Expand Up @@ -282,6 +286,17 @@ static ResVal<CAttributeValue> VerifyCurrencyPair(const std::string& str) {
return {CTokenCurrencyPair{token, currency}, Res::Ok()};
}

static std::set<std::string> dirSet{"both", "in", "out"};

static ResVal<CAttributeValue> VerifyFeeDirection(const std::string& str) {
auto lowerStr = ToLower(str);
const auto it = dirSet.find(lowerStr);
if (it == dirSet.end()) {
return Res::Err("Fee direction value must be both, in or out");
}
return {CFeeDir{static_cast<uint8_t>(std::distance(dirSet.begin(), it))}, Res::Ok()};
}

static bool VerifyToken(const CCustomCSView& view, const uint32_t id) {
return view.GetToken(DCT_ID{id}).has_value();
}
Expand Down Expand Up @@ -316,7 +331,9 @@ const std::map<uint8_t, std::map<uint8_t,
{
AttributeTypes::Poolpairs, {
{PoolKeys::TokenAFeePCT, VerifyPct},
{PoolKeys::TokenAFeeDir, VerifyFeeDirection},
{PoolKeys::TokenBFeePCT, VerifyPct},
{PoolKeys::TokenBFeeDir, VerifyFeeDirection},
}
},
{
Expand Down Expand Up @@ -712,6 +729,14 @@ UniValue ATTRIBUTES::ExportFiltered(GovVarsFilter filter, const std::string &pre
ret.pushKV(key, KeyBuilder(ascendantPair->first, ascendantPair->second));
} else if (const auto currencyPair = std::get_if<CTokenCurrencyPair>(&attribute.second)) {
ret.pushKV(key, currencyPair->first + '/' + currencyPair->second);
} else if (const auto result = std::get_if<CFeeDir>(&attribute.second)) {
if (result->feeDir == FeeDirValues::Both) {
ret.pushKV(key, "both");
} else if (result->feeDir == FeeDirValues::In) {
ret.pushKV(key, "in");
} else if (result->feeDir == FeeDirValues::Out) {
ret.pushKV(key, "out");
}
}
} catch (const std::out_of_range&) {
// Should not get here, that's mean maps are mismatched
Expand Down Expand Up @@ -839,16 +864,22 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const
break;

case AttributeTypes::Poolpairs:
if (!std::get_if<CAmount>(&attribute.second)) {
return Res::Err("Unsupported value");
}
switch (attrV0->key) {
case PoolKeys::TokenAFeePCT:
case PoolKeys::TokenBFeePCT:
if (!view.GetPoolPair({attrV0->typeId})) {
return Res::Err("No such pool (%d)", attrV0->typeId);
}
break;
case PoolKeys::TokenAFeeDir:
case PoolKeys::TokenBFeeDir:
if (view.GetLastHeight() < Params().GetConsensus().FortCanningGardensHeight) {
return Res::Err("Cannot be set before FortCanningGardensHeight");
}
if (!view.GetPoolPair({attrV0->typeId})) {
return Res::Err("No such pool (%d)", attrV0->typeId);
}
break;
default:
return Res::Err("Unsupported key");
}
Expand Down Expand Up @@ -896,20 +927,23 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height)
continue;
}
if (attrV0->type == AttributeTypes::Poolpairs) {
auto poolId = DCT_ID{attrV0->typeId};
auto pool = mnview.GetPoolPair(poolId);
if (!pool) {
return Res::Err("No such pool (%d)", poolId.v);
}
auto tokenId = attrV0->key == PoolKeys::TokenAFeePCT ?
pool->idTokenA : pool->idTokenB;
if (attrV0->key == PoolKeys::TokenAFeePCT ||
attrV0->key == PoolKeys::TokenBFeePCT) {
auto poolId = DCT_ID{attrV0->typeId};
auto pool = mnview.GetPoolPair(poolId);
if (!pool) {
return Res::Err("No such pool (%d)", poolId.v);
}
auto tokenId = attrV0->key == PoolKeys::TokenAFeePCT ?
pool->idTokenA : pool->idTokenB;

const auto valuePct = std::get_if<CAmount>(&attribute.second);
if (!valuePct) {
return Res::Err("Unexpected type");
}
if (auto res = mnview.SetDexFeePct(poolId, tokenId, *valuePct); !res) {
return res;
const auto valuePct = std::get_if<CAmount>(&attribute.second);
if (!valuePct) {
return Res::Err("Unexpected type");
}
if (auto res = mnview.SetDexFeePct(poolId, tokenId, *valuePct); !res) {
return res;
}
}
} else if (attrV0->type == AttributeTypes::Token) {
if (attrV0->key == TokenKeys::DexInFeePct
Expand Down
21 changes: 20 additions & 1 deletion src/masternodes/govvariables/attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ enum TokenKeys : uint8_t {
enum PoolKeys : uint8_t {
TokenAFeePCT = 'a',
TokenBFeePCT = 'b',
TokenAFeeDir = 'c',
TokenBFeeDir = 'd',
};

struct CDataStructureV0 {
Expand Down Expand Up @@ -127,13 +129,30 @@ struct CTokenPayback {
}
};

struct CFeeDir {
uint8_t feeDir;

ADD_SERIALIZE_METHODS;

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

ResVal<CScript> GetFutureSwapContractAddress();

enum FeeDirValues : uint8_t {
Both,
In,
Out
};

using OracleSplits = std::map<uint32_t, int32_t>;
using DescendantValue = std::pair<uint32_t, int32_t>;
using AscendantValue = std::pair<uint32_t, std::string>;
using CAttributeType = std::variant<CDataStructureV0, CDataStructureV1>;
using CAttributeValue = std::variant<bool, CAmount, CBalances, CTokenPayback, CTokenCurrencyPair, OracleSplits, DescendantValue, AscendantValue>;
using CAttributeValue = std::variant<bool, CAmount, CBalances, CTokenPayback, CTokenCurrencyPair, OracleSplits, DescendantValue, AscendantValue, CFeeDir>;

enum GovVarsFilter {
All,
Expand Down
13 changes: 11 additions & 2 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4298,15 +4298,24 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector<DCT_ID> poolIDs, boo

auto dexfeeInPct = view.GetDexFeeInPct(currentID, swapAmount.nTokenId);

const auto attributes = view.GetAttributes();
assert(attributes);

CDataStructureV0 dirAKey{AttributeTypes::Poolpairs, currentID.v, PoolKeys::TokenAFeeDir};
CDataStructureV0 dirBKey{AttributeTypes::Poolpairs, currentID.v, PoolKeys::TokenBFeeDir};
const auto dirA = attributes->GetValue(dirAKey, CFeeDir{FeeDirValues::Both});
const auto dirB = attributes->GetValue(dirBKey, CFeeDir{FeeDirValues::Both});
const auto asymmetricFee = std::make_pair(dirA, dirB);

// Perform swap
poolResult = pool->Swap(swapAmount, dexfeeInPct, poolPrice, [&] (const CTokenAmount& dexfeeInAmount, const CTokenAmount& tokenAmount) {
poolResult = pool->Swap(swapAmount, dexfeeInPct, poolPrice, asymmetricFee, [&] (const CTokenAmount& dexfeeInAmount, const CTokenAmount& tokenAmount) {
// Save swap amount for next loop
swapAmountResult = tokenAmount;

CTokenAmount dexfeeOutAmount{tokenAmount.nTokenId, 0};

auto dexfeeOutPct = view.GetDexFeeOutPct(currentID, tokenAmount.nTokenId);
if (dexfeeOutPct > 0) {
if (dexfeeOutPct > 0 && poolOutFee(swapAmount.nTokenId == pool->idTokenA, asymmetricFee)) {
dexfeeOutAmount.nValue = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct);
swapAmountResult.nValue -= dexfeeOutAmount.nValue;
}
Expand Down
21 changes: 19 additions & 2 deletions src/masternodes/poolpairs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <masternodes/poolpairs.h>
#include <core_io.h>
#include <primitives/transaction.h>
#include <masternodes/govvariables/attributes.h>

struct PoolSwapValue {
bool swapEvent;
Expand Down Expand Up @@ -387,7 +388,7 @@ Res CPoolPair::RemoveLiquidity(CAmount liqAmount, std::function<Res(CAmount, CAm
return onReclaim(resAmountA, resAmountB);
}

Res CPoolPair::Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxPrice, std::function<Res (const CTokenAmount &, const CTokenAmount &)> onTransfer, int height) {
Res CPoolPair::Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxPrice, const std::pair<CFeeDir, CFeeDir>& asymmetricFee, std::function<Res (const CTokenAmount &, const CTokenAmount &)> onTransfer, int height) {
if (in.nTokenId != idTokenA && in.nTokenId != idTokenB)
return Res::Err("Error, input token ID (" + in.nTokenId.ToString() + ") doesn't match pool tokens (" + idTokenA.ToString() + "," + idTokenB.ToString() + ")");

Expand Down Expand Up @@ -422,7 +423,7 @@ Res CPoolPair::Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxP
}

CTokenAmount dexfeeInAmount{in.nTokenId, 0};
if (dexfeeInPct > 0) {
if (dexfeeInPct > 0 && poolInFee(forward, asymmetricFee)) {
if (dexfeeInPct > COIN) {
return Res::Err("Dex fee input percentage over 100%%");
}
Expand Down Expand Up @@ -730,3 +731,19 @@ CAmount CPoolPairView::GetDexFeeOutPct(DCT_ID poolId, DCT_ID tokenId) const {
|| ReadBy<ByTokenDexFeePct>(std::make_pair(DCT_ID{~0u}, tokenId), feePct)
? feePct : 0;
}

bool poolInFee(const bool forward, const std::pair<CFeeDir, CFeeDir>& asymmetricFee) {
const auto& [dirA, dirB] = asymmetricFee;
if ((forward && (dirA.feeDir == FeeDirValues::Both || dirA.feeDir == FeeDirValues::In)) || (!forward && (dirB.feeDir == FeeDirValues::Both || dirB.feeDir == FeeDirValues::In))) {
return true;
}
return false;
}

bool poolOutFee(const bool forward, const std::pair<CFeeDir, CFeeDir>& asymmetricFee) {
const auto& [dirA, dirB] = asymmetricFee;
if ((forward && (dirB.feeDir == FeeDirValues::Both || dirB.feeDir == FeeDirValues::Out)) || (!forward && (dirA.feeDir == FeeDirValues::Both || dirA.feeDir == FeeDirValues::Out))) {
return true;
}
return false;
}
7 changes: 6 additions & 1 deletion src/masternodes/poolpairs.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include <uint256.h>
#include <masternodes/balances.h>

struct CFeeDir;

struct ByPairKey {
DCT_ID idTokenA;
DCT_ID idTokenB;
Expand Down Expand Up @@ -127,7 +129,7 @@ class CPoolPair : public CPoolPairMessage
Res AddLiquidity(CAmount amountA, CAmount amountB, std::function<Res(CAmount)> onMint, bool slippageProtection = false);
Res RemoveLiquidity(CAmount liqAmount, std::function<Res(CAmount, CAmount)> onReclaim);

Res Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxPrice, std::function<Res(CTokenAmount const &, CTokenAmount const &)> onTransfer, int height = INT_MAX);
Res Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxPrice, const std::pair<CFeeDir, CFeeDir>& asymmetricFee, std::function<Res(CTokenAmount const &, CTokenAmount const &)> onTransfer, int height = INT_MAX);

private:
CAmount slopeSwap(CAmount unswapped, CAmount & poolFrom, CAmount & poolTo, int height);
Expand Down Expand Up @@ -282,4 +284,7 @@ struct CRemoveLiquidityMessage {
}
};

bool poolInFee(const bool forward, const std::pair<CFeeDir, CFeeDir>& asymmetricFee);
bool poolOutFee(const bool forward, const std::pair<CFeeDir, CFeeDir>& asymmetricFee);

#endif // DEFI_MASTERNODES_POOLPAIRS_H
44 changes: 36 additions & 8 deletions src/masternodes/rpc_poolpair.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <masternodes/mn_rpc.h>

UniValue poolToJSON(DCT_ID const& id, CPoolPair const& pool, CToken const& token, bool verbose) {
#include <masternodes/govvariables/attributes.h>

UniValue poolToJSON(const CCustomCSView view, DCT_ID const& id, CPoolPair const& pool, CToken const& token, bool verbose) {
UniValue poolObj(UniValue::VOBJ);
poolObj.pushKV("symbol", token.symbol);
poolObj.pushKV("name", token.name);
Expand All @@ -9,20 +11,37 @@ UniValue poolToJSON(DCT_ID const& id, CPoolPair const& pool, CToken const& token
poolObj.pushKV("idTokenB", pool.idTokenB.ToString());

if (verbose) {
const auto attributes = view.GetAttributes();
assert(attributes);

CDataStructureV0 dirAKey{AttributeTypes::Poolpairs, id.v, PoolKeys::TokenAFeeDir};
CDataStructureV0 dirBKey{AttributeTypes::Poolpairs, id.v, PoolKeys::TokenBFeeDir};
const auto dirA = attributes->GetValue(dirAKey, CFeeDir{FeeDirValues::Both});
const auto dirB = attributes->GetValue(dirBKey, CFeeDir{FeeDirValues::Both});

if (const auto dexFee = pcustomcsview->GetDexFeeInPct(id, pool.idTokenA)) {
poolObj.pushKV("dexFeePctTokenA", ValueFromAmount(dexFee));
poolObj.pushKV("dexFeeInPctTokenA", ValueFromAmount(dexFee));
if (dirA.feeDir == FeeDirValues::In || dirA.feeDir == FeeDirValues::Both) {
poolObj.pushKV("dexFeeInPctTokenA", ValueFromAmount(dexFee));
}
}
if (const auto dexFee = pcustomcsview->GetDexFeeOutPct(id, pool.idTokenB)) {
poolObj.pushKV("dexFeePctTokenB", ValueFromAmount(dexFee));
poolObj.pushKV("dexFeeOutPctTokenB", ValueFromAmount(dexFee));
if (dirB.feeDir == FeeDirValues::Out || dirB.feeDir == FeeDirValues::Both) {
poolObj.pushKV("dexFeeOutPctTokenB", ValueFromAmount(dexFee));
}
}
if (const auto dexFee = pcustomcsview->GetDexFeeInPct(id, pool.idTokenB)) {
poolObj.pushKV("dexFeeInPctTokenB", ValueFromAmount(dexFee));
if (dirB.feeDir == FeeDirValues::In || dirB.feeDir == FeeDirValues::Both) {
poolObj.pushKV("dexFeeInPctTokenB", ValueFromAmount(dexFee));
}
}
if (const auto dexFee = pcustomcsview->GetDexFeeOutPct(id, pool.idTokenA)) {
poolObj.pushKV("dexFeeOutPctTokenA", ValueFromAmount(dexFee));
if (dirA.feeDir == FeeDirValues::Out || dirA.feeDir == FeeDirValues::Both) {
poolObj.pushKV("dexFeeOutPctTokenA", ValueFromAmount(dexFee));
}
}

poolObj.pushKV("reserveA", ValueFromAmount(pool.reserveA));
poolObj.pushKV("reserveB", ValueFromAmount(pool.reserveB));
poolObj.pushKV("commission", ValueFromAmount(pool.commission));
Expand Down Expand Up @@ -218,7 +237,7 @@ UniValue listpoolpairs(const JSONRPCRequest& request) {
pcustomcsview->ForEachPoolPair([&](DCT_ID const & id, CPoolPair pool) {
const auto token = pcustomcsview->GetToken(id);
if (token) {
ret.pushKVs(poolToJSON(id, pool, *token, verbose));
ret.pushKVs(poolToJSON(*pcustomcsview, id, pool, *token, verbose));
limit--;
}

Expand Down Expand Up @@ -260,7 +279,7 @@ UniValue getpoolpair(const JSONRPCRequest& request) {
if (token) {
auto pool = pcustomcsview->GetPoolPair(id);
if (pool) {
auto res = poolToJSON(id, *pool, *token, verbose);
auto res = poolToJSON(*pcustomcsview, id, *pool, *token, verbose);
return GetRPCResultCache().Set(request, res);
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pool not found");
Expand Down Expand Up @@ -1083,7 +1102,16 @@ UniValue testpoolswap(const JSONRPCRequest& request) {

auto dexfeeInPct = mnview_dummy.GetDexFeeInPct(poolPair->first, poolSwapMsg.idTokenFrom);

res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, dexfeeInPct, poolSwapMsg.maxPrice, [&] (const CTokenAmount &, const CTokenAmount &tokenAmount) {
const auto attributes = mnview_dummy.GetAttributes();
assert(attributes);

CDataStructureV0 dirAKey{AttributeTypes::Poolpairs, poolPair->first.v, PoolKeys::TokenAFeeDir};
CDataStructureV0 dirBKey{AttributeTypes::Poolpairs, poolPair->first.v, PoolKeys::TokenBFeeDir};
const auto dirA = attributes->GetValue(dirAKey, CFeeDir{FeeDirValues::Both});
const auto dirB = attributes->GetValue(dirBKey, CFeeDir{FeeDirValues::Both});
const auto asymmetricFee = std::make_pair(dirA, dirB);

res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, dexfeeInPct, poolSwapMsg.maxPrice, asymmetricFee, [&] (const CTokenAmount &, const CTokenAmount &tokenAmount) {
auto resPP = mnview_dummy.SetPoolPair(poolPair->first, targetHeight, pp);
if (!resPP) {
return resPP;
Expand Down
Loading