Skip to content

Commit

Permalink
Refund future swaps on split (#1290)
Browse files Browse the repository at this point in the history
* Refund future swaps on split

* Prevent future swap for locked token

Co-authored-by: Prasanna Loganathar <[email protected]>
  • Loading branch information
Bushstar and prasannavl authored May 25, 2022
1 parent 9cdd498 commit f3ef5eb
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 7 deletions.
3 changes: 2 additions & 1 deletion src/masternodes/accounts.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ class CAccountsView : public virtual CStorageView
ResVal<CFuturesUserValue> GetFuturesUserValues(const CFuturesUserKey& key);
Res EraseFuturesUserValues(const CFuturesUserKey& key);
boost::optional<uint32_t> GetMostRecentFuturesHeight();
void ForEachFuturesUserValues(std::function<bool(const CFuturesUserKey&, const CFuturesUserValue&)> callback, const CFuturesUserKey& start = {});
void ForEachFuturesUserValues(std::function<bool(const CFuturesUserKey&, const CFuturesUserValue&)> callback, const CFuturesUserKey& start =
{std::numeric_limits<uint32_t>::max(), {}, std::numeric_limits<uint32_t>::max()});

// tags
struct ByBalanceKey { static constexpr uint8_t prefix() { return 'a'; } };
Expand Down
7 changes: 3 additions & 4 deletions src/masternodes/govvariables/attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,12 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator<GovVariable, ATTRI
static const std::map<uint8_t, std::string>& displayOracleIDs();
static const std::map<uint8_t, std::map<uint8_t, std::string>>& displayKeys();

Res RefundFuturesContracts(CCustomCSView &mnview, const uint32_t height, const uint32_t tokenID = std::numeric_limits<uint32_t>::max());

private:
friend class CGovView;
bool futureBlockUpdated{};
std::set<uint32_t> tokenSplits{};
std::set<CAttributeType> changed;
std::map<CAttributeType, CAttributeValue> attributes;

Expand All @@ -247,10 +250,6 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator<GovVariable, ATTRI

Res ProcessVariable(const std::string& key, const std::string& value,
std::function<Res(const CAttributeType&, const CAttributeValue&)> applyVariable);
Res RefundFuturesContracts(CCustomCSView &mnview, const uint32_t height, const uint32_t tokenID = std::numeric_limits<uint32_t>::max());

private:
std::set<uint32_t> tokenSplits{};
};

#endif // DEFI_MASTERNODES_GOVVARIABLES_ATTRIBUTES_H
8 changes: 8 additions & 0 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1514,11 +1514,19 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor
if (!loanToken) {
return Res::Err("Could not get destination loan token %d. Set valid destination.", obj.destination);
}

if (mnview.AreTokensLocked({obj.destination})) {
return Res::Err("Cannot create future swap for locked token");
}
} else {
if (obj.destination != 0) {
return Res::Err("Destination should not be set when source amount is a dToken");
}

if (mnview.AreTokensLocked({obj.source.nTokenId.v})) {
return Res::Err("Cannot create future swap for locked token");
}

CDataStructureV0 tokenKey{AttributeTypes::Token, obj.source.nTokenId.v, TokenKeys::DFIP2203Enabled};
const auto enabled = attributes->GetValue(tokenKey, true);
if (!enabled) {
Expand Down
2 changes: 1 addition & 1 deletion src/masternodes/rpc_accounts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2262,7 +2262,7 @@ UniValue listpendingfutureswaps(const JSONRPCRequest& request) {
listFutures.push_back(value);

return true;
}, {std::numeric_limits<uint32_t>::max(), {}, std::numeric_limits<uint32_t>::max()});
});

return listFutures;
}
Expand Down
9 changes: 8 additions & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4230,6 +4230,13 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin

auto view{cache};

// Refund affected future swaps
auto res = attributes->RefundFuturesContracts(view, std::numeric_limits<uint32_t>::max(), id);
if (!res) {
LogPrintf("Token split failed on refunding futures: %s\n", res.msg);
continue;
}

const DCT_ID oldTokenId{id};

auto token = view.GetToken(oldTokenId);
Expand All @@ -4239,7 +4246,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin
}

std::string newTokenSuffix = "/v";
auto res = GetTokenSuffix(cache, *attributes, oldTokenId.v, newTokenSuffix);
res = GetTokenSuffix(cache, *attributes, oldTokenId.v, newTokenSuffix);
if (!res) {
LogPrintf("Token split failed on GetTokenSuffix %s\n", res.msg);
continue;
Expand Down
73 changes: 73 additions & 0 deletions test/functional/feature_token_split.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def run_test(self):
self.setup_test_vaults()
self.vault_split()
self.check_govvar_deletion()
self.check_future_swap_refund()

def setup_test_tokens(self):
self.nodes[0].generate(101)
Expand All @@ -40,6 +41,7 @@ def setup_test_tokens(self):
self.symbolTSLA = 'TSLA'
self.symbolGOOGL = 'GOOGL'
self.symbolNVDA = 'NVDA'
self.symbolMSFT = 'MSFT'
self.symbolGD = 'GOOGL-DUSD'

# Store address
Expand All @@ -52,6 +54,7 @@ def setup_test_tokens(self):
{"currency": "USD", "token": self.symbolGOOGL},
{"currency": "USD", "token": self.symbolTSLA},
{"currency": "USD", "token": self.symbolNVDA},
{"currency": "USD", "token": self.symbolMSFT},
]

# Appoint oracle
Expand All @@ -66,6 +69,7 @@ def setup_test_tokens(self):
{"currency": "USD", "tokenAmount": f"1@{self.symbolGOOGL}"},
{"currency": "USD", "tokenAmount": f"1@{self.symbolTSLA}"},
{"currency": "USD", "tokenAmount": f"1@{self.symbolNVDA}"},
{"currency": "USD", "tokenAmount": f"1@{self.symbolMSFT}"},
]
self.nodes[0].setoracledata(oracle, int(time.time()), oracle_prices)
self.nodes[0].generate(10)
Expand Down Expand Up @@ -107,6 +111,15 @@ def setup_test_tokens(self):
})
self.nodes[0].generate(1)

self.nodes[0].setloantoken({
'symbol': self.symbolMSFT,
'name': self.symbolMSFT,
'fixedIntervalPriceId': f"{self.symbolMSFT}/USD",
'mintable': True,
'interest': 0
})
self.nodes[0].generate(1)

# Set collateral tokens
self.nodes[0].setcollateraltoken({
'token': self.symbolDFI,
Expand Down Expand Up @@ -141,6 +154,7 @@ def setup_test_tokens(self):
self.idGOOGL = list(self.nodes[0].gettoken(self.symbolGOOGL).keys())[0]
self.idTSLA = list(self.nodes[0].gettoken(self.symbolTSLA).keys())[0]
self.idNVDA = list(self.nodes[0].gettoken(self.symbolNVDA).keys())[0]
self.idMSFT = list(self.nodes[0].gettoken(self.symbolMSFT).keys())[0]

def setup_test_pools(self):

Expand Down Expand Up @@ -678,6 +692,65 @@ def check_govvar_deletion(self):
# Swap old for new values
self.idTSLA = list(self.nodes[0].gettoken(self.symbolTSLA).keys())[0]

def check_future_swap_refund(self):

# Set all futures attributes but set active to false
self.nodes[0].setgov({"ATTRIBUTES":{
'v0/params/dfip2203/reward_pct':'0.05',
'v0/params/dfip2203/block_period':'2880'
}})
self.nodes[0].generate(1)

self.nodes[0].setgov({"ATTRIBUTES":{
'v0/params/dfip2203/active':'true'
}})
self.nodes[0].generate(1)

# Create addresses for futures
address_msft = self.nodes[0].getnewaddress("", "legacy")
address_locked = self.nodes[0].getnewaddress("", "legacy")

# Fund addresses
self.nodes[0].minttokens([f'2@{self.idMSFT}', f'2@{self.idDUSD}'])
self.nodes[0].generate(1)
self.nodes[0].accounttoaccount(self.address, {address_msft: [f'1@{self.symbolMSFT}', f'1@{self.symbolDUSD}']})
self.nodes[0].accounttoaccount(self.address, {address_locked: [f'1@{self.symbolMSFT}', f'1@{self.symbolDUSD}']})
self.nodes[0].generate(1)

# Create user futures contracts
self.nodes[0].futureswap(address_msft, f'1@{self.symbolDUSD}', int(self.idMSFT))
self.nodes[0].generate(1)
self.nodes[0].futureswap(address_msft, f'1@{self.symbolMSFT}')
self.nodes[0].generate(1)

# Check pending swaps
result = self.nodes[0].listpendingfutureswaps()
assert_equal(result[0]['owner'], address_msft)
assert_equal(result[0]['source'], f'1.00000000@{self.symbolMSFT}')
assert_equal(result[0]['destination'], self.symbolDUSD)
assert_equal(result[1]['owner'], address_msft)
assert_equal(result[1]['source'], f'1.00000000@{self.symbolDUSD}')
assert_equal(result[1]['destination'], self.symbolMSFT)

# Check balance empty
result = self.nodes[0].getaccount(address_msft)
assert_equal(result, [])

# Token split
self.nodes[0].setgov({"ATTRIBUTES":{f'v0/oracles/splits/{str(self.nodes[0].getblockcount() + 2)}':f'{self.idMSFT}/2'}})
self.nodes[0].generate(1)

# Test creating future swaps with locked tokens
assert_raises_rpc_error(-32600, 'Cannot create future swap for locked token', self.nodes[0].futureswap, address_locked, f'1@{self.symbolDUSD}', int(self.idMSFT))
assert_raises_rpc_error(-32600, 'Cannot create future swap for locked token', self.nodes[0].futureswap, address_locked, f'1@{self.symbolMSFT}')

# Move to split block
self.nodes[0].generate(1)

# Check balance returned with multiplier applied
result = self.nodes[0].getaccount(address_msft)
assert_equal(result, [f'1.00000000@{self.symbolDUSD}', f'2.00000000@{self.symbolMSFT}'])

if __name__ == '__main__':
TokenSplitTest().main()

0 comments on commit f3ef5eb

Please sign in to comment.