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

Expand dex fee capability #1118

Merged
merged 1 commit into from
Feb 25, 2022
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
324 changes: 219 additions & 105 deletions src/masternodes/govvariables/attributes.cpp

Large diffs are not rendered by default.

91 changes: 13 additions & 78 deletions src/masternodes/govvariables/attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ enum DFIP2201Keys : uint8_t {
enum TokenKeys : uint8_t {
PaybackDFI = 'a',
PaybackDFIFeePCT = 'b',
DexInFeePct = 'c',
DexOutFeePct = 'd',
};

enum PoolKeys : uint8_t {
Expand Down Expand Up @@ -119,88 +121,21 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator<GovVariable, ATTRI

private:
// Defined allowed arguments
inline static const std::map<std::string, uint8_t> allowedVersions{
{"v0", VersionTypes::v0},
};

inline static const std::map<std::string, uint8_t> allowedTypes{
{"params", AttributeTypes::Param},
{"poolpairs", AttributeTypes::Poolpairs},
{"token", AttributeTypes::Token},
};

inline static const std::map<std::string, uint8_t> allowedParamIDs{
{"dfip2201", ParamIDs::DFIP2201}
};

inline static const std::map<uint8_t, std::map<std::string, uint8_t>> allowedKeys{
{
AttributeTypes::Token, {
{"payback_dfi", TokenKeys::PaybackDFI},
{"payback_dfi_fee_pct", TokenKeys::PaybackDFIFeePCT},
}
},
{
AttributeTypes::Poolpairs, {
{"token_a_fee_pct", PoolKeys::TokenAFeePCT},
{"token_b_fee_pct", PoolKeys::TokenBFeePCT},
}
},
{
AttributeTypes::Param, {
{"active", DFIP2201Keys::Active},
{"minswap", DFIP2201Keys::MinSwap},
{"premium", DFIP2201Keys::Premium},
}
},
};
static const std::map<std::string, uint8_t>& allowedVersions();
static const std::map<std::string, uint8_t>& allowedTypes();
static const std::map<std::string, uint8_t>& allowedParamIDs();
static const std::map<uint8_t, std::map<std::string, uint8_t>>& allowedKeys();
static const std::map<uint8_t, std::map<uint8_t,
std::function<ResVal<CAttributeValue>(const std::string&)>>>& parseValue();

// For formatting in export
inline static const std::map<uint8_t, std::string> displayVersions{
{VersionTypes::v0, "v0"},
};

inline static const std::map<uint8_t, std::string> displayTypes{
{AttributeTypes::Live, "live"},
{AttributeTypes::Param, "params"},
{AttributeTypes::Poolpairs, "poolpairs"},
{AttributeTypes::Token, "token"},
};

inline static const std::map<uint8_t, std::string> displayParamsIDs{
{ParamIDs::DFIP2201, "dfip2201"},
{ParamIDs::Economy, "economy"},
};

inline static const std::map<uint8_t, std::map<uint8_t, std::string>> displayKeys{
{
AttributeTypes::Token, {
{TokenKeys::PaybackDFI, "payback_dfi"},
{TokenKeys::PaybackDFIFeePCT, "payback_dfi_fee_pct"},
}
},
{
AttributeTypes::Poolpairs, {
{PoolKeys::TokenAFeePCT, "token_a_fee_pct"},
{PoolKeys::TokenBFeePCT, "token_b_fee_pct"},
}
},
{
AttributeTypes::Param, {
{DFIP2201Keys::Active, "active"},
{DFIP2201Keys::Premium, "premium"},
{DFIP2201Keys::MinSwap, "minswap"},
}
},
{
AttributeTypes::Live, {
{EconomyKeys::PaybackDFITokens, "dfi_payback_tokens"},
}
},
};
static const std::map<uint8_t, std::string>& displayVersions();
static const std::map<uint8_t, std::string>& displayTypes();
static const std::map<uint8_t, std::string>& displayParamsIDs();
static const std::map<uint8_t, std::map<uint8_t, std::string>>& displayKeys();

Res ProcessVariable(const std::string& key, const std::string& value,
std::function<Res(const CAttributeType&, const CAttributeValue&)> applyVariable = {}) const;
std::function<Res(const CAttributeType&, const CAttributeValue&)> applyVariable) const;
};

#endif // DEFI_MASTERNODES_GOVVARIABLES_ATTRIBUTES_H
11 changes: 5 additions & 6 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector<DCT_ID> poolIDs, boo
}
}

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

// Perform swap
poolResult = pool->Swap(swapAmount, dexfeeInPct, poolPrice, [&] (const CTokenAmount& dexfeeInAmount, const CTokenAmount& tokenAmount) {
Expand All @@ -787,11 +787,10 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector<DCT_ID> poolIDs, boo

CTokenAmount dexfeeOutAmount{tokenAmount.nTokenId, 0};

if (height >= Params().GetConsensus().FortCanningHillHeight) {
if (auto dexfeeOutPct = view.GetDexFeePct(currentID, tokenAmount.nTokenId)) {
dexfeeOutAmount.nValue = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct);
swapAmountResult.nValue -= dexfeeOutAmount.nValue;
}
auto dexfeeOutPct = view.GetDexFeeOutPct(currentID, tokenAmount.nTokenId);
if (dexfeeOutPct > 0) {
dexfeeOutAmount.nValue = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct);
swapAmountResult.nValue -= dexfeeOutAmount.nValue;
}

// If we're just testing, don't do any balance transfers.
Expand Down
27 changes: 12 additions & 15 deletions src/masternodes/poolpairs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,12 +407,6 @@ Res CPoolPair::Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxP
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() + ")");

// TODO: The whole block of the fork condition can be removed safely after FCH.
if (height < Params().GetConsensus().FortCanningHillHeight) {
if (in.nValue <= 0)
return Res::Err("Input amount should be positive!");
}

if (!status)
return Res::Err("Pool trading is turned off!");

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

CTokenAmount dexfeeInAmount{in.nTokenId, 0};
if (dexfeeInPct > 0 && height >= Params().GetConsensus().FortCanningHillHeight) {
if (dexfeeInPct > 0) {
if (dexfeeInPct > COIN) {
return Res::Err("Dex fee input percentage over 100%%");
}
Expand Down Expand Up @@ -726,20 +720,23 @@ void CPoolPairView::ForEachPoolShare(std::function<bool (DCT_ID const &, CScript
}

Res CPoolPairView::SetDexFeePct(DCT_ID poolId, DCT_ID tokenId, CAmount feePct) {
if (!HasPoolPair(poolId)) {
return Res::Err("No such pool pair");
}
if (feePct < 0 || feePct > COIN) {
return Res::Err("Token dex fee should be in percentage");
}
WriteBy<ByTokenDexFeePct>(std::make_pair(poolId, tokenId), uint32_t(feePct));
return Res::Ok();
}

CAmount CPoolPairView::GetDexFeePct(DCT_ID poolId, DCT_ID tokenId) const {
CAmount CPoolPairView::GetDexFeeInPct(DCT_ID poolId, DCT_ID tokenId) const {
uint32_t feePct;
if (ReadBy<ByTokenDexFeePct>(std::make_pair(poolId, tokenId), feePct)) {
return feePct;
}
return 0;
return ReadBy<ByTokenDexFeePct>(std::make_pair(poolId, tokenId), feePct)
|| ReadBy<ByTokenDexFeePct>(std::make_pair(tokenId, DCT_ID{~0u}), feePct)
? feePct : 0;
}

CAmount CPoolPairView::GetDexFeeOutPct(DCT_ID poolId, DCT_ID tokenId) const {
uint32_t feePct;
return ReadBy<ByTokenDexFeePct>(std::make_pair(poolId, tokenId), feePct)
|| ReadBy<ByTokenDexFeePct>(std::make_pair(DCT_ID{~0u}, tokenId), feePct)
? feePct : 0;
}
3 changes: 2 additions & 1 deletion src/masternodes/poolpairs.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ class CPoolPairView : public virtual CStorageView
bool HasPoolPair(DCT_ID const & poolId) const;

Res SetDexFeePct(DCT_ID poolId, DCT_ID tokenId, CAmount feePct);
CAmount GetDexFeePct(DCT_ID poolId, DCT_ID tokenId) const;
CAmount GetDexFeeInPct(DCT_ID poolId, DCT_ID tokenId) const;
CAmount GetDexFeeOutPct(DCT_ID poolId, DCT_ID tokenId) const;

std::pair<CAmount, CAmount> UpdatePoolRewards(std::function<CTokenAmount(CScript const &, DCT_ID)> onGetBalance, std::function<Res(CScript const &, CScript const &, CTokenAmount)> onTransfer, int nHeight = 0);

Expand Down
23 changes: 15 additions & 8 deletions src/masternodes/rpc_poolpair.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@ UniValue poolToJSON(CCustomCSView& view, DCT_ID const& id, CPoolPair const& pool
poolObj.pushKV("idTokenB", pool.idTokenB.ToString());

if (verbose) {
if (const auto dexFee = view.GetDexFeePct(id, pool.idTokenA)) {
if (const auto dexFee = view.GetDexFeeInPct(id, pool.idTokenA)) {
poolObj.pushKV("dexFeePctTokenA", ValueFromAmount(dexFee));
poolObj.pushKV("dexFeeInPctTokenA", ValueFromAmount(dexFee));
}
if (const auto dexFee = view.GetDexFeePct(id, pool.idTokenB)) {
if (const auto dexFee = view.GetDexFeeOutPct(id, pool.idTokenB)) {
poolObj.pushKV("dexFeePctTokenB", ValueFromAmount(dexFee));
poolObj.pushKV("dexFeeOutPctTokenB", ValueFromAmount(dexFee));
}
if (const auto dexFee = view.GetDexFeeInPct(id, pool.idTokenB)) {
poolObj.pushKV("dexFeeInPctTokenB", ValueFromAmount(dexFee));
}
if (const auto dexFee = view.GetDexFeeOutPct(id, pool.idTokenA)) {
poolObj.pushKV("dexFeeOutPctTokenA", ValueFromAmount(dexFee));
}
poolObj.pushKV("reserveA", ValueFromAmount(pool.reserveA));
poolObj.pushKV("reserveB", ValueFromAmount(pool.reserveB));
Expand Down Expand Up @@ -1065,7 +1073,7 @@ UniValue testpoolswap(const JSONRPCRequest& request) {
throw JSONRPCError(RPC_INVALID_REQUEST, "Input amount should be positive");

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

res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, dexfeeInPct, poolSwapMsg.maxPrice, [&] (const CTokenAmount &, const CTokenAmount &tokenAmount) {
auto resPP = mnview_dummy.SetPoolPair(poolPair->first, targetHeight, pp);
Expand All @@ -1074,11 +1082,10 @@ UniValue testpoolswap(const JSONRPCRequest& request) {
}

auto resultAmount = tokenAmount;
if (targetHeight >= Params().GetConsensus().FortCanningHillHeight) {
if (auto dexfeeOutPct = mnview_dummy.GetDexFeePct(poolPair->first, tokenAmount.nTokenId)) {
auto dexfeeOutAmount = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct);
resultAmount.nValue -= dexfeeOutAmount;
}
auto dexfeeOutPct = mnview_dummy.GetDexFeeOutPct(poolPair->first, tokenAmount.nTokenId);
if (dexfeeOutPct > 0) {
auto dexfeeOutAmount = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct);
resultAmount.nValue -= dexfeeOutAmount;
}

return Res::Ok(resultAmount.ToString());
Expand Down
62 changes: 49 additions & 13 deletions test/functional/feature_poolswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)

from decimal import Decimal
from math import trunc

class PoolPairTest (DefiTestFramework):
def set_test_params(self):
Expand All @@ -28,10 +29,10 @@ def set_test_params(self):
# node2: Non Foundation
self.setup_clean_chain = True
self.extra_args = [
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-acindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-acindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170']]
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177', '-acindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177', '-acindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177']]


def run_test(self):
Expand Down Expand Up @@ -338,10 +339,11 @@ def run_test(self):

symbolBTC = "BTC#" + self.get_id_token("BTC")
symbolLTC = "LTC#" + self.get_id_token("LTC")
idBitcoin = list(self.nodes[0].gettoken(symbolBTC).keys())[0]
idBTC = list(self.nodes[0].gettoken(symbolBTC).keys())[0]
idLTC = list(self.nodes[0].gettoken(symbolLTC).keys())[0]

self.nodes[0].minttokens("1@" + symbolBTC)
self.nodes[0].minttokens("101@" + symbolLTC)
self.nodes[0].minttokens("111@" + symbolLTC)
self.nodes[0].generate(1)

self.nodes[0].createpoolpair({
Expand Down Expand Up @@ -372,7 +374,7 @@ def run_test(self):
})
self.nodes[0].generate(1)

assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBitcoin], Decimal('0.00000001'))
assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000001'))

# Reset swap
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))
Expand All @@ -388,7 +390,7 @@ def run_test(self):
})
self.nodes[0].generate(1)

assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBitcoin], Decimal('0.00000002'))
assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000002'))

# Reset swap and move to Fort Canning Park Height and try swap again
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))
Expand All @@ -405,7 +407,7 @@ def run_test(self):
})
self.nodes[0].generate(1)

assert(idBitcoin not in self.nodes[0].getaccount(new_dest, {}, True))
assert(idBTC not in self.nodes[0].getaccount(new_dest, {}, True))

# Reset swap
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))
Expand All @@ -421,7 +423,7 @@ def run_test(self):
})
self.nodes[0].generate(1)

assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBitcoin], Decimal('0.00000001'))
assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000001'))

self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(idGS): '0.05', 'v0/poolpairs/%s/token_b_fee_pct'%(idGS): '0.08'}})
self.nodes[0].generate(1)
Expand Down Expand Up @@ -487,11 +489,45 @@ def run_test(self):
self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(idBL): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idBL): '0.01'}})
self.nodes[0].generate(1)

print(self.nodes[0].getblockcount())
print(self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'])

assert_equal(self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'], {'v0/poolpairs/%s/token_a_fee_pct'%(idGS): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idGS): '0.01', 'v0/poolpairs/%s/token_a_fee_pct'%(idBL): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idBL): '0.01'})

self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))
self.nodes[0].clearmempool()
self.nodes[0].generate(1)

self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/%s/dex_in_fee_pct'%(idLTC): '0.02', 'v0/token/%s/dex_out_fee_pct'%(idBTC): '0.05'}})
self.nodes[0].generate(1)

result = self.nodes[0].getpoolpair(idBL)
assert_equal(result[idBL]['dexFeeInPctTokenB'], Decimal('0.02'))
assert_equal(result[idBL]['dexFeeOutPctTokenA'], Decimal('0.05'))

destBTC = self.nodes[0].getnewaddress("", "legacy")
swapltc = 10
self.nodes[0].poolswap({
"from": accountGN0,
"tokenFrom": symbolLTC,
"amountFrom": swapltc,
"to": destBTC,
"tokenTo": symbolBTC
})
commission = round((swapltc * 0.01), 8)
amountB = Decimal(swapltc - commission)
dexinfee = amountB * Decimal(0.02)
amountB = amountB - dexinfee
pool = self.nodes[0].getpoolpair("BTC-LTC")[idBL]
reserveA = pool['reserveA']
reserveB = pool['reserveB']

self.nodes[0].generate(1)

pool = self.nodes[0].getpoolpair("BTC-LTC")[idBL]
assert_equal(pool['reserveB'] - reserveB, round(amountB, 8))
swapped = self.nodes[0].getaccount(destBTC, {}, True)[idBTC]
amountA = reserveA - pool['reserveA']
dexoutfee = round(trunc(amountA * Decimal(0.05) * coin) / coin, 8)
assert_equal(round(amountA - Decimal(dexoutfee), 8), round(swapped, 8))

# REVERTING:
#========================
print ("Reverting...")
Expand Down
Loading