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

WIP Token splits changes from master #1259

Closed
wants to merge 6 commits into from
Closed
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
4 changes: 3 additions & 1 deletion src/masternodes/govvariables/attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,9 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const
if (!token->IsDAT()) {
return Res::Err("Only DATs can be split");
}
if (!view.GetLoanTokenByID(DCT_ID{tokenId}).has_value()) {
return Res::Err("No loan token with id (%d)", tokenId);
}
}
} else {
return Res::Err("Unsupported key");
Expand Down Expand Up @@ -1147,7 +1150,6 @@ Res ATTRIBUTES::Apply(CCustomCSView& mnview, CFutureSwapView& futureSwapView, ui
continue;
}

// Loan token check imposed on lock
if (!mnview.GetLoanTokenByID(DCT_ID{split}).has_value()) {
return Res::Err("Auto lock. No loan token with id (%d)", split);
}
Expand Down
6 changes: 4 additions & 2 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -953,9 +953,11 @@ bool IsVaultPriceValid(CCustomCSView& mnview, const CVaultId& vaultId, uint32_t
if (auto loans = mnview.GetLoanTokens(vaultId))
for (const auto& loan : loans->balances)
if (auto loanToken = mnview.GetLoanTokenByID(loan.first))
if (auto fixedIntervalPrice = mnview.GetFixedIntervalPrice(loanToken->fixedIntervalPriceId))
if (auto fixedIntervalPrice = mnview.GetFixedIntervalPrice(loanToken->fixedIntervalPriceId)) {
if (!fixedIntervalPrice.val->isLive(mnview.GetPriceDeviation()))
return false;

} else {
Copy link
Member

@prasannavl prasannavl May 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears correct, and probably should also branch more like this to short-circuit above as well. However, we might want to test this with a sync.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Already mentioned this to Jeremy. He is performing a full sync of #1243 with the same change.

return false;
}
return true;
}
47 changes: 39 additions & 8 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4188,11 +4188,6 @@ static Res VaultSplits(CCustomCSView& view, CFutureSwapView& futureSwapView, ATT
});

for (auto& [vaultId, rate, schemeId] : loanInterestRates) {
auto amounts = view.GetLoanTokens(vaultId);
if (!amounts) {
return Res::Err("Failed to get loan token amounts.");
}

CAmount loanSchemeRate{0};
try {
loanSchemeRate = loanSchemes.at(schemeId);
Expand All @@ -4202,8 +4197,13 @@ static Res VaultSplits(CCustomCSView& view, CFutureSwapView& futureSwapView, ATT

view.EraseInterestDirect(vaultId, oldTokenId);
rate.interestToHeight = CalculateNewAmount(multiplier, rate.interestToHeight);
rate.interestPerBlock = InterestPerBlockCalculationV2(amounts->balances[newTokenId], loanToken->interest, loanSchemeRate);
view.WriteInterestRate(std::make_pair(vaultId, newTokenId), rate, height);

auto amounts = view.GetLoanTokens(vaultId);
if (amounts) {
rate.interestPerBlock = InterestPerBlockCalculationV2(amounts->balances[newTokenId], loanToken->interest, loanSchemeRate);
}

view.WriteInterestRate(std::make_pair(vaultId, newTokenId), rate, rate.height);
}

return Res::Ok();
Expand All @@ -4221,6 +4221,11 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin
CDataStructureV0 splitKey{AttributeTypes::Oracles, OracleIDs::Splits, static_cast<uint32_t>(pindex->nHeight)};
const auto splits = attributes->GetValue(splitKey, OracleSplits{});

if (!splits.empty()) {
attributes->EraseKey(splitKey);
cache.SetVariable(*attributes);
}

for (const auto& [id, multiplier] : splits) {

if (!cache.AreTokensLocked({id})) {
Expand Down Expand Up @@ -4366,9 +4371,35 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin
LogPrintf("%s: Token split failed. %s\n", __func__, res.msg);
continue;
}
view.SetVariable(*attributes);
}

std::vector<std::pair<CDataStructureV0, OracleSplits>> updateAttributesKeys;
for (const auto& [key, value] : attributes->attributes) {
if (const auto v0Key = std::get_if<CDataStructureV0>(&key);
v0Key->type == AttributeTypes::Oracles && v0Key->typeId == OracleIDs::Splits) {
if (const auto splitMap = std::get_if<OracleSplits>(&value)) {
for (auto [splitMapKey, splitMapValue] : *splitMap) {
if (splitMapKey == oldTokenId.v) {
auto copyMap{*splitMap};
copyMap.erase(splitMapKey);
updateAttributesKeys.emplace_back(*v0Key, copyMap);
break;
}
}
}
}
}

for (const auto& [key, value] : updateAttributesKeys) {
if (value.empty()) {
attributes->EraseKey(key);
} else {
attributes->SetValue(key, value);
}
}

view.SetVariable(*attributes);

view.Flush();
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/functional/feature_setgov.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ def run_test(self):
assert_raises_rpc_error(-32600, "ATTRIBUTES: Pool tokens cannot be split", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/oracles/splits/1201': '1/50'}})
assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot be set at or below current height", self.nodes[0].setgov, {"ATTRIBUTES":{f'v0/oracles/splits/{self.nodes[0].getblockcount()}': '5/50'}})
assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot set block period while DFIP2203 is active", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/start_block':'0'}})
assert_raises_rpc_error(-32600, "Auto lock. No loan token with id (4)", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/oracles/splits/4000':'4/2'}})
assert_raises_rpc_error(-32600, "No loan token with id (4)", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/oracles/splits/4000':'4/2'}})

self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/5/dex_in_fee_pct':'0.6','v0/token/5/dex_out_fee_pct':'0.12'}})
self.nodes[0].generate(1)
Expand Down
4 changes: 2 additions & 2 deletions test/functional/feature_token_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@ def vault_lock(self):
self.nodes[0].generate(1)

# Try and take loan while token in vault locked
assert_raises_rpc_error(-32600, "Fixed interval price currently disabled due to locked token", self.nodes[0].takeloan, {'vaultId': self.vault, 'amounts': f'1@{self.symbolTSLA}'})
assert_raises_rpc_error(-32600, "Fixed interval price currently disabled due to locked token", self.nodes[0].takeloan, {'vaultId': self.vault, 'amounts': f'1@{self.symbolGOOGL}'})
assert_raises_rpc_error(-32600, "Cannot take loan while any of the asset's price in the vault is not live", self.nodes[0].takeloan, {'vaultId': self.vault, 'amounts': f'1@{self.symbolTSLA}'})
assert_raises_rpc_error(-32600, "Cannot take loan while any of the asset's price in the vault is not live", self.nodes[0].takeloan, {'vaultId': self.vault, 'amounts': f'1@{self.symbolGOOGL}'})

# Vault amounts should be zero while token locked
result = self.nodes[0].getvault(self.vault)
Expand Down
101 changes: 97 additions & 4 deletions test/functional/feature_token_split.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def run_test(self):
self.pool_split()
self.setup_test_vaults()
self.vault_split()
self.check_govvar_deletion()

def setup_test_tokens(self):
self.nodes[0].generate(101)
Expand Down Expand Up @@ -154,6 +155,38 @@ def setup_test_pools(self):
})
self.nodes[0].generate(1)

self.nodes[0].createpoolpair({
"tokenA": self.symbolDUSD,
"tokenB": self.symbolDFI,
"commission": Decimal('0.001'),
"status": True,
"ownerAddress": self.address
}, [])
self.nodes[0].generate(1)

self.nodes[0].createpoolpair({
"tokenA": self.symbolDUSD,
"tokenB": self.symbolNVDA,
"commission": Decimal('0.001'),
"status": True,
"ownerAddress": self.address
}, [])
self.nodes[0].generate(1)

# Fund address for pool
self.nodes[0].utxostoaccount({self.address: f'100@{self.symbolDFI}'})
self.nodes[0].minttokens([f'200@{self.idDUSD}', f'100@{self.idNVDA}'])
self.nodes[0].generate(1)

# Fund pools
self.nodes[0].addpoolliquidity({
self.address: [f'100@{self.symbolDUSD}', f'100@{self.symbolDFI}']
}, self.address)
self.nodes[0].addpoolliquidity({
self.address: [f'100@{self.symbolDUSD}', f'100@{self.symbolNVDA}']
}, self.address)
self.nodes[0].generate(1)

# Store pool ID
self.idGD = list(self.nodes[0].gettoken(self.symbolGD).keys())[0]

Expand Down Expand Up @@ -197,7 +230,7 @@ def setup_test_vaults(self):
# Deposit random collateral
collateral = round(random.uniform(1, 100), 8)
loan = truncate(str(collateral / 3), 8)
self.nodes[0].deposittovault(vault_id, self.address, f'{str(collateral)}@DFI')
self.nodes[0].deposittovault(vault_id, self.address, f'{str(collateral)}@{self.symbolDFI}')
self.nodes[0].generate(1)

# Take loan
Expand Down Expand Up @@ -242,7 +275,7 @@ def check_token_split(self, token_id, token_symbol, token_suffix, multiplier, mi
if loan:
assert_equal(result[f'v0/token/{token_id}/loan_minting_enabled'], 'true')
assert_equal(result[f'v0/token/{token_id}/loan_minting_interest'], '0')
assert_equal(result[f'v0/oracles/splits/{self.nodes[0].getblockcount()}'], f'{token_idv1}/{multiplier},')
assert(f'v0/oracles/splits/{self.nodes[0].getblockcount()}' not in result)
assert_equal(result[f'v0/token/{token_idv1}/descendant'], f'{token_id}/{self.nodes[0].getblockcount()}')
assert_equal(result[f'v0/token/{token_id}/ascendant'], f'{token_idv1}/split')
assert_equal(result[f'v0/locks/token/{token_id}'], 'true')
Expand Down Expand Up @@ -490,8 +523,9 @@ def execute_vault_split(self, token_id, token_symbol, multiplier, suffix):
# Gather pre-split info to compare later
pre_get_interest = self.nodes[0].getinterest('LOAN0001', 'NVDA')[0]
for vault_info in self.nodes[0].listvaults():
vault = self.nodes[0].getvault(vault_info['vaultId'])
self.vault_balances.append([vault_info['vaultId'], vault])
if self.skip_vault != vault_info['vaultId']:
vault = self.nodes[0].getvault(vault_info['vaultId'])
self.vault_balances.append([vault_info['vaultId'], vault])

# Move to split block
self.nodes[0].generate(1)
Expand Down Expand Up @@ -532,6 +566,31 @@ def execute_vault_split(self, token_id, token_symbol, multiplier, suffix):
self.nodes[0].generate(1)

def vault_split(self):

# Create vault to test split after payback
self.skip_vault = self.nodes[0].createvault(self.address, '')
self.nodes[0].generate(1)

# Deposit to vault
self.nodes[0].deposittovault(self.skip_vault, self.address, f'100@{self.symbolDFI}')
self.nodes[0].generate(1)

# Take loan
self.nodes[0].takeloan({
'vaultId': self.skip_vault,
'amounts': f'100@{self.symbolNVDA}'
})
self.nodes[0].generate(1)

# Payback loan
result = self.nodes[0].getvault(self.skip_vault)
self.nodes[0].paybackloan({
'vaultId': self.skip_vault,
'from': self.address,
'amounts': result['loanAmounts'][0]
})
self.nodes[0].generate(1)

# Multiplier 2
self.execute_vault_split(self.idNVDA, self.symbolNVDA, 2, '/v1')

Expand All @@ -541,5 +600,39 @@ def vault_split(self):
# Multiplier -3
self.execute_vault_split(self.idNVDA, self.symbolNVDA, -3, '/v2')

# Swap old for new values
self.idNVDA = list(self.nodes[0].gettoken(self.symbolNVDA).keys())[0]

def check_govvar_deletion(self):
# Lock token
self.nodes[0].setgov({"ATTRIBUTES":{f'v0/locks/token/{self.idTSLA}':'true'}})
self.nodes[0].generate(1)

# Token split
split_height = self.nodes[0].getblockcount() + 2
self.nodes[0].setgov({"ATTRIBUTES":{f'v0/oracles/splits/{split_height}':f'{self.idTSLA}/2'}})
self.nodes[0].setgov({"ATTRIBUTES":{f'v0/oracles/splits/500000':f'{self.idTSLA}/2'}})
self.nodes[0].setgov({"ATTRIBUTES":{'v0/oracles/splits/1000000':f'{self.idTSLA}/2'}})
self.nodes[0].setgov({"ATTRIBUTES":{'v0/oracles/splits/1000000':f'{self.idNVDA}/2'}})
self.nodes[0].generate(1)

# Check splits
result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES']
assert_equal(result[f'v0/oracles/splits/{split_height}'], f'{self.idTSLA}/2,')
assert_equal(result[f'v0/oracles/splits/500000'], f'{self.idTSLA}/2,')
assert_equal(result[f'v0/oracles/splits/1000000'], f'{self.idTSLA}/2,{self.idNVDA}/2,')

# Split
self.nodes[0].generate(1)

# Check TSLA entries removed
result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES']
assert(f'v0/oracles/splits/{split_height}' not in result)
assert(f'v0/oracles/splits/500000' not in result)
assert_equal(result[f'v0/oracles/splits/1000000'], f'{self.idNVDA}/2,')

# Swap old for new values
self.idTSLA = list(self.nodes[0].gettoken(self.symbolTSLA).keys())[0]

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