-
Notifications
You must be signed in to change notification settings - Fork 20
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
Add multi currency vaults #415
Add multi currency vaults #415
Conversation
…rk with changed signature
…r the vault' Edge case of 0 wrapped tokens, require 0 collateral. Therefore, compare that collateral is greater than or equal to wrapped (backed) tokens as opposed to only greater than.
…collateral vaults
…erbtcPrimitivesVaultId instead of only AccountId
…s test covering vault theft and liquidation
Minor version change due to breaking changes in `VaultsAPI.reportVaultTheft` signature.
…tion (escrow tests)
await waitForBlockFinalization(bitcoinCoreClient, userInterBtcAPI.btcRelay); | ||
|
||
// report theft | ||
await reporterInterBtcAPI.vaults.reportVaultTheft(vaultToLiquidateId, btcTxId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Anyone can report theft, so you could have reused userInterBtcAPI
instead of instantiating reporterInterBtcAPI
"0", | ||
`Expected non-zero amount for refund request with vault 3 (${currencyTicker})` | ||
); | ||
const refundRequests = await interBtcAPI.refund.list(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The length of this list should be checked after the for-loop ends, with an expected length of at least vault_3_ids.length
. We can merge without this change though, since the assert on the line above already checks this essentially.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot, thanks. I have a follow-up task to address this.
interBtcAPI.setAccount(vault_3); | ||
await interBtcAPI.replace.request( | ||
replaceAmount, | ||
currencyIdToMonetaryCurrency(vault_3_id.currencies.collateral) as CollateralCurrency | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be cleaner to refactor this to use the callWith
utility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, nice! Thank you for that. Added these to the follow-up task, too.
interBtcAPI.setAccount(vault_2); | ||
|
||
// fetch tokens held by vault | ||
const tokensInVault = await interBtcAPI.vaults.getIssuedAmount( | ||
newAccountId(api, vault_2.address), | ||
currencyIdToLiteral(vault_2_id.currencies.collateral) | ||
); | ||
assert.fail("Expected error to be thrown due to lack of issued tokens for vault, but call completed."); | ||
} catch (e) { | ||
assert.isTrue(e instanceof Error, "Expected replace request to fail with Error"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be cleaner to refactor this to use the callWith
utility.
await interBtcAPI.replace.request( | ||
replaceAmount, | ||
currencyIdToMonetaryCurrency(vault_2_id.currencies.collateral) as CollateralCurrency | ||
); | ||
assert.fail(`Expected error to be thrown due to lack of issued tokens | ||
for vault (collateral: ${currencyTicker}), but call completed.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be cleaner to check that the Promise is rejected, also not blocking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also added to the follow-up task.
Thank you for the suggestion.
For later: We should think of a way to make it clear that the tests are running on new parachain and clients versions - maybe tag parachain-launch versions. The yarn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, all my comments can be fixed later
Yes! I was thinking along those lines, too. I also want to at least check if we can possibly go back to the upstream version of parachain-launch rather than maintaining a fork, and keep the configuration details in the lib to then be generated as part of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good, though it is hard to comment on whether there is enough to catch flaws in our multi-collateral architecture. There are a fair number of skipped and commented tests but I assume these are for future work? We should definitely sync on what can be moved into the parachain / client tests or even removed to make your life easier. I like the overly verbose nature of the assertions and the reduced reliance on test ordering - idempotent tests make for easier debugging. Anyway, none of my comments are particularly critical to block merging this as-is so feel free address in future PRs.
// report theft | ||
await reporterInterBtcAPI.vaults.reportVaultTheft(vaultToLiquidateId, btcTxId); | ||
|
||
const finalizedPromise = new Promise<void>((resolve, _) => userInterBtcAPI.system.subscribeToFinalizedBlockHeads( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: could await
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point, there are two places where I do that.
assert.isTrue(flaggedForTheft); | ||
assert.isTrue( | ||
flaggedForTheft, | ||
`Expected vault (collateral: ${collateralCurrency.ticker}) to be flagged for theft, but it was not.` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for useful expectation messages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
It would drive me bonkers if I saw a failure and wouldn't know which collateral variation failed without manually running the test with breakpoints. :)
await issueAndRedeem( | ||
userInterBtcAPI, | ||
userBitcoinCoreClient, | ||
userAccount, | ||
vaultToBanId, | ||
issueAmount, | ||
redeemAmount, | ||
false, | ||
ExecuteRedeem.False | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future ref: #416
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes. I hope to have some time to add more builder patterns soon™ :)
await new Promise<void>((resolve, _) => { | ||
// redeemAPI.subscribeToRedeemExpiry(newAccountId(api, userAccount.address), (requestId) => { | ||
// if (stripHexPrefix(redeemRequest.id.toString()) === stripHexPrefix(requestId.toString())) { | ||
// resolve(); | ||
// } | ||
// }); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to implement this now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had a chat last week with @nud3l to pare down the scope of tests to add/fix to get multi-collateral vaults unblocked sooner rather than later.
This test fell victim to that.
@@ -103,8 +98,7 @@ describe("Initialize parachain state", () => { | |||
userInterBtcAPI = new DefaultInterBtcApi(api, "regtest", userAccount, ESPLORA_BASE_PATH); | |||
sudoInterBtcAPI = new DefaultInterBtcApi(api, "regtest", sudoAccount, ESPLORA_BASE_PATH); | |||
|
|||
wrappedCurrency = userInterBtcAPI.getWrappedCurrency(); | |||
collateralCurrency = getCorrespondingCollateralCurrency(userInterBtcAPI.getGovernanceCurrency()); | |||
collateralCurrency = getCorrespondingCollateralCurrencies(userInterBtcAPI.getGovernanceCurrency())[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this implicitly assume KSM
? What happens to the tests if I change the return value of getCorrespondingCollateralCurrency
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only needs one collateral currency, whether that is KSM, or KINT doesn't matter here.
The currency is used in conjunction with vault account ids to wait for them to be available - as means to delay test execution until the parachain / docker images are good to go with vaults registered.
await new Promise<void>((resolve, _) => { | ||
// userInterBtcAPI.issue.subscribeToIssueExpiry(newAccountId(api, userAccount.address), (requestId) => { | ||
// if (stripHexPrefix(requestResult.id.toString()) === stripHexPrefix(requestId.toString())) { | ||
// resolve(); | ||
// } | ||
// }); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here as above, for a future PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, I just added the looping for the time being.
} catch (e) { | ||
// Set issue period back to its initial value to minimize side effects. | ||
await sudo(userInterBtcAPI, () => userInterBtcAPI.issue.setIssuePeriod(initialIssuePeriod)); | ||
throw e; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea to reset the state, though I wonder if we should have some common routine that runs after most tests to "re-genesis" from a blank state. However I guess that may dramatically slowdown the pipeline if Substrate doesn't have an easy way to do that though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once we have instant seal, we might be able to afford re-genesis delays, assuming it can be done.
I'm certainly open to the idea.
for(const collateralCurrency of collateralCurrencies) { | ||
const bitcoinAmount = BitcoinAmount.from.BTC(100); | ||
const exchangeRate = await interBtcAPI.oracle.getExchangeRate(collateralCurrency); | ||
const expectedCollateral = exchangeRate.toBig(undefined).mul(bitcoinAmount.toBig(BitcoinUnit.BTC)).round(0, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why undefined
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! That was supposed to be exchangeRate.toBig()
- looks like me stripping the previously defined, single collateralCurrency, auto-refactored that into undefined
.
Awkward, but at least not breaking.
for (const [vaultId, amount] of premiumRedeemVaults) { | ||
if (encodeVaultId(vaultId) === encodeVaultId(vault_3_id)) { | ||
premiumRedeemAmount = amount; | ||
break; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is probably a more functional way to write this logic but that's probably more a nit from my side
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. A version of array.find().map()
would do the trick.
Keeping this for my clean up task.
const expectedThresholdByTicker: Map<string, string> = new Map([ | ||
[Polkadot.ticker, "1.1"], | ||
[Kusama.ticker, "1.1"], | ||
[Kintsugi.ticker, "2"] | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these set during initialization? If not we should definitely review our use of magic numbers in tests so us changing the genesis on the parachain doesn't randomly break things here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, those are magic numbers.
Happy to set during initialization - I couldn't find a way when working the tests.
I think that piece of work would fit in well with the proposed changes by Dom here: #415 (comment)
As for the skipped/commented tests: Yes, they are future work and haven't been deemed critical for the goal of getting multi-collateral vaults out. As for reducing integration test numbers: Yes, I absolutely would love to get rid of the ones that the parachain and/or clients test themselves. |
Add KINT as collateral
Breaking changes to exposed API:
VaultsAPI.reportVaultTheft
now takes vault ID (InterbtcPrimitivesVaultId) instead of only account IDVaultsAPI.isVaultFlaggedForTheft
now takes vault ID (InterbtcPrimitivesVaultId) instead of account ID and collateral currency literal IDConstantsAPI.getTransactionByteFee()
; constant no longer exists