Skip to content

Commit

Permalink
Floor token swap result (#1023)
Browse files Browse the repository at this point in the history
* Floor token swap result

Signed-off-by: Anthony Fieroni <[email protected]>

* Add poolswap round up test

* Do not use floor in unit tests

Signed-off-by: Anthony Fieroni <[email protected]>

* Use floor right after pool calculation

Signed-off-by: Anthony Fieroni <[email protected]>

Co-authored-by: Peter Bushnell <[email protected]>
Co-authored-by: Prasanna Loganathar <[email protected]>
  • Loading branch information
3 people authored Jan 11, 2022
1 parent 18199de commit 079ce94
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 44 deletions.
10 changes: 7 additions & 3 deletions src/masternodes/poolpairs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,22 +428,22 @@ Res CPoolPair::Swap(CTokenAmount in, PoolPrice const & maxPrice, std::function<R
return Res::Err("Swapping will lead to pool's reserve overflow");
}

CAmount result = slopeSwap(in.nValue, reserveF, reserveT, height >= Params().GetConsensus().BayfrontGardensHeight);
CAmount result = slopeSwap(in.nValue, reserveF, reserveT, height);

swapEvent = true; // (!!!)

return onTransfer({ forward ? idTokenB : idTokenA, result });
}

CAmount CPoolPair::slopeSwap(CAmount unswapped, CAmount &poolFrom, CAmount &poolTo, bool postBayfrontGardens) {
CAmount CPoolPair::slopeSwap(CAmount unswapped, CAmount &poolFrom, CAmount &poolTo, int height) {
assert (unswapped >= 0);
assert (SafeAdd(unswapped, poolFrom).ok);

arith_uint256 poolF = arith_uint256(poolFrom);
arith_uint256 poolT = arith_uint256(poolTo);

arith_uint256 swapped = 0;
if (!postBayfrontGardens) {
if (height < Params().GetConsensus().BayfrontGardensHeight) {
CAmount chunk = poolFrom/SLOPE_SWAP_RATE < unswapped ? poolFrom/SLOPE_SWAP_RATE : unswapped;
while (unswapped > 0) {
//arith_uint256 stepFrom = std::min(poolFrom/1000, unswapped); // 0.1%
Expand All @@ -459,6 +459,10 @@ CAmount CPoolPair::slopeSwap(CAmount unswapped, CAmount &poolFrom, CAmount &pool
arith_uint256 unswappedA = arith_uint256(unswapped);

swapped = poolT - (poolT * poolF / (poolF + unswappedA));
if (height >= Params().GetConsensus().FortCanningHillHeight && swapped != 0) {
// floor the result
--swapped;
}
poolF += unswappedA;
poolT -= swapped;
}
Expand Down
2 changes: 1 addition & 1 deletion src/masternodes/poolpairs.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class CPoolPair : public CPoolPairMessage
Res Swap(CTokenAmount in, PoolPrice const & maxPrice, std::function<Res(CTokenAmount const &)> onTransfer, int height = INT_MAX);

private:
CAmount slopeSwap(CAmount unswapped, CAmount & poolFrom, CAmount & poolTo, bool postBayfrontGardens = false);
CAmount slopeSwap(CAmount unswapped, CAmount & poolFrom, CAmount & poolTo, int height);

inline void ioProofer() const { // Maybe it's more reasonable to use unsigned everywhere, but for basic CAmount compatibility
if (reserveA < 0 || reserveB < 0 ||
Expand Down
13 changes: 8 additions & 5 deletions src/test/liquidity_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade)
auto optPool = mnview.GetPoolPair(idPool);
BOOST_REQUIRE(optPool);

// do not include FCH floor in swap
auto height = Params().GetConsensus().FortCanningHillHeight - 1;

Res res{};
{ // basic fails
CPoolPair pool = *optPool;
Expand Down Expand Up @@ -165,7 +168,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade)
res = pool.Swap(CTokenAmount{pool.idTokenA, 1000000}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
BOOST_CHECK_EQUAL(ta.nValue, 1000);
return Res::Ok();
});
}, height);
BOOST_CHECK(res.ok);
BOOST_CHECK_EQUAL(pool.blockCommissionA, 10000);
BOOST_CHECK_EQUAL(pool.reserveA, 991001);
Expand All @@ -181,7 +184,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade)
res = pool.Swap(CTokenAmount{pool.idTokenA, 2*COIN}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
BOOST_CHECK_EQUAL(ta.nValue, 66442954); // pre-optimization: 66464593
return Res::Ok();
});
}, height);
BOOST_CHECK(res.ok);
BOOST_CHECK_EQUAL(pool.blockCommissionA, 2000000);
BOOST_CHECK_EQUAL(pool.reserveA, 298000000);
Expand All @@ -198,7 +201,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade)
res = pool.Swap(CTokenAmount{pool.idTokenA, 2*COIN}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
BOOST_CHECK_EQUAL(ta.nValue, 66442953021); // pre-optimization: 66465256146
return Res::Ok();
});
}, height);
BOOST_CHECK(res.ok);
BOOST_CHECK_EQUAL(pool.blockCommissionA, 2000000);
BOOST_CHECK_EQUAL(pool.reserveA, 298000000);
Expand All @@ -213,7 +216,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade)
res = pool.Swap(CTokenAmount{pool.idTokenA, COIN}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
BOOST_CHECK_EQUAL(ta.nValue, 49748743719); // pre-optimization: 49773755285
return Res::Ok();
});
}, height);
BOOST_CHECK(res.ok);
BOOST_CHECK_EQUAL(pool.blockCommissionA, 1000000);
BOOST_CHECK_EQUAL(pool.reserveA, 199000000);
Expand All @@ -228,7 +231,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade)
res = pool.Swap(CTokenAmount{pool.idTokenA, COIN/1000}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
BOOST_CHECK_EQUAL(ta.nValue, 98902087); // pre-optimization: 99000000
return Res::Ok();
});
}, height);
BOOST_CHECK(res.ok);
BOOST_CHECK_EQUAL(pool.blockCommissionA, 1000);
BOOST_CHECK_EQUAL(pool.reserveA, 100099000);
Expand Down
137 changes: 102 additions & 35 deletions test/functional/feature_poolswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,17 @@ 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', '-acindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-acindex=1'],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163',],
['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163',]]
['-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']]


def run_test(self):
assert_equal(len(self.nodes[0].listtokens()), 1) # only one token == DFI

#self.nodes[0].generate(100)
#self.sync_all()
print("Generating initial chain...")
self.setup_tokens()
# Stop node #3 for future revert
self.stop_node(3)
Expand All @@ -53,10 +52,6 @@ def run_test(self):
idSilver = list(self.nodes[0].gettoken(symbolSILVER).keys())[0]
accountGN0 = self.nodes[0].get_genesis_keys().ownerAuthAddress
accountSN1 = self.nodes[1].get_genesis_keys().ownerAuthAddress
initialGold = self.nodes[0].getaccount(accountGN0, {}, True)[idGold]
initialSilver = self.nodes[1].getaccount(accountSN1, {}, True)[idSilver]
print("Initial GOLD AccN0:", initialGold, ", id", idGold)
print("Initial SILVER AccN1:", initialSilver, ", id", idSilver)

owner = self.nodes[0].getnewaddress("", "legacy")

Expand All @@ -68,9 +63,7 @@ def run_test(self):
self.nodes[0].generate(1)

silverCheckN0 = self.nodes[0].getaccount(accountGN0, {}, True)[idSilver]
print("Checking Silver on AccN0:", silverCheckN0, ", id", idSilver)
silverCheckN1 = self.nodes[1].getaccount(accountSN1, {}, True)[idSilver]
print("Checking Silver on AccN1:", silverCheckN1, ", id", idSilver)

# 3 Creating poolpair
self.nodes[0].createpoolpair({
Expand Down Expand Up @@ -130,9 +123,7 @@ def run_test(self):
#print (list_poolshares)

goldCheckN0 = self.nodes[0].getaccount(accountGN0, {}, True)[idGold]
print("Checking Gold on AccN0:", goldCheckN0, ", id", idGold)
silverCheckN0 = self.nodes[0].getaccount(accountGN0, {}, True)[idSilver]
print("Checking Silver on AccN0:", silverCheckN0, ", id", idSilver)

# 5 Checking that liquidity is correct
assert_equal(goldCheckN0, 700)
Expand Down Expand Up @@ -162,13 +153,8 @@ def run_test(self):
self.nodes[0].updatepoolpair({"pool": "GS", "status": True})
self.nodes[0].generate(1)

print("Before swap")
print("Checking Gold on AccN0:", goldCheckN0, ", id", idGold)
print("Checking Silver on AccN0:", silverCheckN0, ", id", idSilver)
goldCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idGold]
print("Checking Gold on AccN1:", goldCheckN1, ", id", idGold)
silverCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idSilver]
print("Checking Silver on AccN1:", silverCheckN1, ", id", idSilver)

testPoolSwapRes = self.nodes[0].testpoolswap({
"from": accountGN0,
Expand All @@ -180,8 +166,6 @@ def run_test(self):

# this acc will be
goldCheckPS = self.nodes[2].getaccount(accountSN1, {}, True)[idGold]
print("goldCheckPS:", goldCheckPS)
print("testPoolSwapRes:", testPoolSwapRes)

testPoolSwapSplit = str(testPoolSwapRes).split("@", 2)

Expand Down Expand Up @@ -214,16 +198,11 @@ def run_test(self):
self.sync_blocks([self.nodes[0], self.nodes[2]])

# 8 Checking that poolswap is correct
print("After swap")
goldCheckN0 = self.nodes[2].getaccount(accountGN0, {}, True)[idGold]
print("Checking Gold on AccN0:", goldCheckN0, ", id", idGold)
silverCheckN0 = self.nodes[2].getaccount(accountGN0, {}, True)[idSilver]
print("Checking Silver on AccN0:", silverCheckN0, ", id", idSilver)

goldCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idGold]
print("Checking Gold on AccN1:", goldCheckN1, ", id", idGold)
silverCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idSilver]
print("Checking Silver on AccN1:", silverCheckN1, ", id", idSilver)

list_pool = self.nodes[2].listpoolpairs()
#print (list_pool)
Expand Down Expand Up @@ -253,15 +232,6 @@ def run_test(self):
errorString = e.error['message']
assert("Price is higher than indicated." in errorString)

# Visual test for listaccounthistory
print("mine@0:", self.nodes[0].listaccounthistory())
print("mine@1:", self.nodes[1].listaccounthistory())
print("all@0", self.nodes[0].listaccounthistory("all"))
print("accountGN0@0", self.nodes[0].listaccounthistory(accountGN0))
print("mine@0, depth=3:", self.nodes[0].listaccounthistory("mine", {"depth":3}))
print("mine@0, height=158 depth=2:", self.nodes[0].listaccounthistory("mine", {"maxBlockHeight":158, "depth":2}))
print("all@0, height=158 depth=2:", self.nodes[0].listaccounthistory("all", {"maxBlockHeight":158, "depth":2}))

# activate max price protection
maxPrice = self.nodes[0].listpoolpairs()['1']['reserveB/reserveA']
self.nodes[0].poolswap({
Expand Down Expand Up @@ -307,7 +277,6 @@ def run_test(self):
# Test fort canning max price change
disconnect_nodes(self.nodes[0], 1)
disconnect_nodes(self.nodes[0], 2)
print(self.nodes[0].getconnectioncount())
destination = self.nodes[0].getnewaddress("", "legacy")
swap_from = 200
coin = 100000000
Expand Down Expand Up @@ -355,6 +324,104 @@ def run_test(self):
errorString = e.error['message']
assert("Price is higher than indicated" in errorString)

# Test round up setup
self.nodes[0].createtoken({
"symbol": "BTC",
"name": "Bitcoin",
"collateralAddress": accountGN0
})
self.nodes[0].createtoken({
"symbol": "LTC",
"name": "Litecoin",
"collateralAddress": accountGN0
})
self.nodes[0].generate(1)

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

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

self.nodes[0].createpoolpair({
"tokenA": symbolBTC,
"tokenB": symbolLTC,
"commission": 0.01,
"status": True,
"ownerAddress": owner,
"pairSymbol": "BTC-LTC",
}, [])
self.nodes[0].generate(1)

self.nodes[0].addpoolliquidity({
accountGN0: ["1@" + symbolBTC, "100@" + symbolLTC]
}, accountGN0, [])
self.nodes[0].generate(1)

# Test round up
new_dest = self.nodes[0].getnewaddress("", "legacy")
self.nodes[0].poolswap({
"from": accountGN0,
"tokenFrom": symbolLTC,
"amountFrom": 0.00000001,
"to": new_dest,
"tokenTo": symbolBTC
})
self.nodes[0].generate(1)

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

# Reset swap
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))
self.nodes[0].clearmempool()

# Test for 2 Sat round up
self.nodes[0].poolswap({
"from": accountGN0,
"tokenFrom": symbolLTC,
"amountFrom": 0.00000190,
"to": new_dest,
"tokenTo": symbolBTC
})
self.nodes[0].generate(1)

assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBitcoin], 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()))
self.nodes[0].clearmempool()
self.nodes[0].generate(170 - self.nodes[0].getblockcount())

# Test swap now results in zero amount
self.nodes[0].poolswap({
"from": accountGN0,
"tokenFrom": symbolLTC,
"amountFrom": 0.00000001,
"to": new_dest,
"tokenTo": symbolBTC
})
self.nodes[0].generate(1)

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

# Reset swap
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))
self.nodes[0].clearmempool()

# Test previous 2 Sat swap now results in 1 Sat
self.nodes[0].poolswap({
"from": accountGN0,
"tokenFrom": symbolLTC,
"amountFrom": 0.00000190,
"to": new_dest,
"tokenTo": symbolBTC
})
self.nodes[0].generate(1)

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

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

0 comments on commit 079ce94

Please sign in to comment.