Skip to content

Commit

Permalink
Asymmetric pool swap fee
Browse files Browse the repository at this point in the history
  • Loading branch information
Bushstar committed Jun 21, 2022
1 parent 2dda0f7 commit 153a5f2
Show file tree
Hide file tree
Showing 11 changed files with 480 additions and 48 deletions.
63 changes: 45 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,16 @@ static ResVal<CAttributeValue> VerifyCurrencyPair(const std::string& str) {
return {CTokenCurrencyPair{token, currency}, Res::Ok()};
}

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

static ResVal<CAttributeValue> VerifyFeeDirection(const std::string& str) {
auto lowerStr = ToLower(str);
if (!dirSet.count(lowerStr)) {
return Res::Err("Fee direction value must be both, in or out");
}
return {lowerStr, 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 +330,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 +728,8 @@ 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 str = std::get_if<std::string>(&attribute.second)) {
ret.pushKV(key, *str);
}
} catch (const std::out_of_range&) {
// Should not get here, that's mean maps are mismatched
Expand Down Expand Up @@ -839,16 +857,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 +920,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
4 changes: 3 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 @@ -133,7 +135,7 @@ 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, std::string>;

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, std::string{"both"});
const auto dirB = attributes->GetValue(dirBKey, std::string{"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
4 changes: 2 additions & 2 deletions src/masternodes/poolpairs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,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<std::string, std::string>& 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 +422,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
18 changes: 17 additions & 1 deletion src/masternodes/poolpairs.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,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<std::string, std::string>& 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 +282,20 @@ struct CRemoveLiquidityMessage {
}
};

inline bool poolInFee(const bool forward, const std::pair<std::string, std::string>& asymmetricFee) {
const auto& [dirA, dirB] = asymmetricFee;
if ((forward && (dirA == "both" || dirA == "in")) || (!forward && (dirB == "both" || dirB == "in"))) {
return true;
}
return false;
}

inline bool poolOutFee(const bool forward, const std::pair<std::string, std::string>& asymmetricFee) {
const auto& [dirA, dirB] = asymmetricFee;
if ((forward && (dirB == "both" || dirB == "out")) || (!forward && (dirA == "both" || dirA == "out"))) {
return true;
}
return false;
}

#endif // DEFI_MASTERNODES_POOLPAIRS_H
49 changes: 37 additions & 12 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,34 @@ 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, std::string{"both"});
const auto dirB = attributes->GetValue(dirBKey, std::string{"both"});

if (const auto dexFee = pcustomcsview->GetDexFeeInPct(id, pool.idTokenA)) {
poolObj.pushKV("dexFeePctTokenA", ValueFromAmount(dexFee));
poolObj.pushKV("dexFeeInPctTokenA", ValueFromAmount(dexFee));
if (dirA == "in" || dirA == "both") {
poolObj.pushKV("dexFeeInPctTokenA", ValueFromAmount(dexFee));
}
if (dirA == "out" || dirA == "both") {
poolObj.pushKV("dexFeeOutPctTokenA", ValueFromAmount(dexFee));
}
}

if (const auto dexFee = pcustomcsview->GetDexFeeOutPct(id, pool.idTokenB)) {
poolObj.pushKV("dexFeePctTokenB", ValueFromAmount(dexFee));
poolObj.pushKV("dexFeeOutPctTokenB", ValueFromAmount(dexFee));
}
if (const auto dexFee = pcustomcsview->GetDexFeeInPct(id, pool.idTokenB)) {
poolObj.pushKV("dexFeeInPctTokenB", ValueFromAmount(dexFee));
}
if (const auto dexFee = pcustomcsview->GetDexFeeOutPct(id, pool.idTokenA)) {
poolObj.pushKV("dexFeeOutPctTokenA", ValueFromAmount(dexFee));
if (dirB == "in" || dirB == "both") {
poolObj.pushKV("dexFeeInPctTokenB", ValueFromAmount(dexFee));
}
if (dirB == "out" || dirB == "both") {
poolObj.pushKV("dexFeeOutPctTokenB", 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 +234,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 +276,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 +1099,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, std::string{"both"});
const auto dirB = attributes->GetValue(dirBKey, std::string{"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

0 comments on commit 153a5f2

Please sign in to comment.