Skip to content

Commit 7747549

Browse files
authored
Merge pull request #727 from bvotteler/feat-enable-vault-withdraw-all-collateral
Feat: enable vault withdraw all collateral
2 parents e01fc14 + 252682f commit 7747549

6 files changed

Lines changed: 124 additions & 12 deletions

File tree

docker-compose.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: "3.8"
22
services:
33
interbtc:
4-
image: "interlayhq/interbtc:1.25.0"
4+
image: "interlayhq/interbtc:1.25.3"
55
command:
66
- --rpc-external
77
- --ws-external
@@ -54,7 +54,7 @@ services:
5454
- "3002:3002"
5555
restart: always
5656
oracle:
57-
image: "interlayhq/interbtc-clients:oracle-parachain-metadata-kintsugi-1.23.0-rc3"
57+
image: "interlayhq/interbtc-clients:oracle-parachain-metadata-kintsugi-1.23.0"
5858
command:
5959
- oracle-parachain-metadata-kintsugi
6060
- --keyring=bob
@@ -64,7 +64,7 @@ services:
6464
volumes:
6565
- ./docker/oracle-config.json:/oracle-config.json
6666
vault_1:
67-
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0-rc3"
67+
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0"
6868
command:
6969
- vault-parachain-metadata-kintsugi
7070
- --keyfile=/keyfile.json
@@ -81,7 +81,7 @@ services:
8181
volumes:
8282
- ./docker/vault_1-keyfile.json:/keyfile.json
8383
vault_2:
84-
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0-rc3"
84+
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0"
8585
command:
8686
- vault-parachain-metadata-kintsugi
8787
- --keyfile=/keyfile.json
@@ -94,7 +94,7 @@ services:
9494
volumes:
9595
- ./docker/vault_2-keyfile.json:/keyfile.json
9696
vault_3:
97-
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0-rc3"
97+
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0"
9898
command:
9999
- vault-parachain-metadata-kintsugi
100100
- --keyfile=/keyfile.json

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@interlay/interbtc-api",
3-
"version": "2.6.0",
3+
"version": "2.7.0",
44
"description": "JavaScript library to interact with interBTC",
55
"main": "build/cjs/src/index.js",
66
"module": "build/esm/src/index.js",
@@ -51,7 +51,8 @@
5151
"watch:build": "tsc -p tsconfig.json -w",
5252
"watch:test": "jest --watch test/**/*.test.ts",
5353
"update-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/json/parachain.json",
54-
"update-metadata-kintnet": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' https://api-dev-kintsugi.interlay.io/parachain > src/json/parachain.json"
54+
"update-metadata-kintnet": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' https://api-dev-kintsugi.interlay.io/parachain > src/json/parachain.json",
55+
"update-metadata-interlay": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' https://api.interlay.io/parachain > src/json/parachain.json"
5556
},
5657
"engines": {
5758
"node": ">=11"

src/json/parachain.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/parachain/nomination.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,20 @@ export class DefaultNominationAPI implements NominationAPI {
184184
const parsedNonce = api.createType("Index", definedNonce);
185185
return api.tx.nomination.withdrawCollateral(vaultId, amountAsPlanck, parsedNonce);
186186
}
187+
188+
static async buildWithdrawAllCollateralExtrinsic(
189+
api: ApiPromise,
190+
rewardsAPI: RewardsAPI,
191+
vaultAccountId: AccountId,
192+
collateralCurrency: Currency,
193+
wrappedCurrency: Currency,
194+
nonce?: number
195+
): Promise<SubmittableExtrinsic<"promise", ISubmittableResult>> {
196+
const vaultId = newVaultId(api, vaultAccountId.toString(), collateralCurrency, wrappedCurrency);
197+
const definedNonce = nonce ? nonce : await rewardsAPI.getStakingPoolNonce(collateralCurrency, vaultAccountId);
198+
const parsedNonce = api.createType("Index", definedNonce);
199+
return api.tx.nomination.withdrawCollateral(vaultId, null, parsedNonce);
200+
}
187201

188202
async withdrawCollateral(
189203
vaultAccountId: AccountId,

src/parachain/vaults.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,22 @@ export interface VaultsAPI {
227227
*/
228228
withdrawCollateral(amount: MonetaryAmount<CollateralCurrencyExt>): Promise<ExtrinsicData>;
229229

230+
/**
231+
* Build withdraw collateral extrinsic (transaction) without sending it.
232+
*
233+
* @param collateralCurrency The collateral currency for which to withdraw all
234+
* @returns A withdraw collateral submittable extrinsic as promise.
235+
*/
236+
buildWithdrawAllCollateralExtrinsic(
237+
collateralCurrency: CollateralCurrencyExt
238+
): Promise<SubmittableExtrinsic<"promise", ISubmittableResult>>;
239+
240+
/**
241+
* @param collateralCurrency The collateral currency for which to withdraw all
242+
* @returns {Promise<ExtrinsicData>} A submittable extrinsic and an event that is emitted when extrinsic is submitted.
243+
*/
244+
withdrawAllCollateral(collateralCurrency: CollateralCurrencyExt): Promise<ExtrinsicData>;
245+
230246
/**
231247
* Build deposit collateral extrinsic (transaction) without sending it.
232248
*
@@ -462,7 +478,7 @@ export class DefaultVaultsAPI implements VaultsAPI {
462478
this.rewardsAPI,
463479
vaultAccountId,
464480
amount,
465-
this.wrappedCurrency
481+
this.wrappedCurrency,
466482
);
467483
}
468484

@@ -471,6 +487,29 @@ export class DefaultVaultsAPI implements VaultsAPI {
471487
return { extrinsic: tx, event: this.api.events.vaultRegistry.WithdrawCollateral };
472488
}
473489

490+
async buildWithdrawAllCollateralExtrinsic(
491+
collateralCurrency: CollateralCurrencyExt
492+
): Promise<SubmittableExtrinsic<"promise", ISubmittableResult>> {
493+
const account = this.transactionAPI.getAccount();
494+
if (account == undefined) {
495+
throw new Error("Account must be connected to create a collateral withdrawal request.");
496+
}
497+
const vaultAccountId = addressOrPairAsAccountId(this.api, account);
498+
499+
return await DefaultNominationAPI.buildWithdrawAllCollateralExtrinsic(
500+
this.api,
501+
this.rewardsAPI,
502+
vaultAccountId,
503+
collateralCurrency,
504+
this.wrappedCurrency
505+
);
506+
}
507+
508+
async withdrawAllCollateral(collateralCurrency: CollateralCurrencyExt): Promise<ExtrinsicData> {
509+
const tx = await this.buildWithdrawAllCollateralExtrinsic(collateralCurrency);
510+
return { extrinsic: tx, event: this.api.events.vaultRegistry.WithdrawCollateral };
511+
}
512+
474513
buildDepositCollateralExtrinsic(
475514
amount: MonetaryAmount<CollateralCurrencyExt>
476515
): SubmittableExtrinsic<"promise", ISubmittableResult> {

test/integration/parachain/staging/sequential/vaults.partial.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import {
1111
GovernanceCurrency,
1212
AssetRegistryAPI,
1313
DefaultAssetRegistryAPI,
14+
DefaultTransactionAPI,
1415
} from "../../../../../src/index";
1516

1617
import { createSubstrateAPI } from "../../../../../src/factory";
17-
import { VAULT_1_URI, VAULT_2_URI, PARACHAIN_ENDPOINT, VAULT_3_URI, ESPLORA_BASE_PATH } from "../../../../config";
18+
import { VAULT_1_URI, VAULT_2_URI, PARACHAIN_ENDPOINT, VAULT_3_URI, ESPLORA_BASE_PATH, SUDO_URI } from "../../../../config";
1819
import { newAccountId, WrappedCurrency, newVaultId } from "../../../../../src";
19-
import { getSS58Prefix, newMonetaryAmount } from "../../../../../src/utils";
20+
import { getSS58Prefix, newCurrencyId, newMonetaryAmount } from "../../../../../src/utils";
2021
import {
2122
getAUSDForeignAsset,
2223
getCorrespondingCollateralCurrenciesForTests,
@@ -27,6 +28,7 @@ import {
2728

2829
export const vaultsTests = () => {
2930
describe("vaultsAPI", () => {
31+
let sudoAccount: KeyringPair;
3032
let vault_1: KeyringPair;
3133
let vault_1_ids: Array<InterbtcPrimitivesVaultId>;
3234
let vault_2: KeyringPair;
@@ -56,7 +58,8 @@ export const vaultsTests = () => {
5658
// also add aUSD collateral vaults if they exist (ie. the foreign asset exists)
5759
collateralCurrencies.push(aUSD);
5860
}
59-
61+
62+
sudoAccount = keyring.addFromUri(SUDO_URI);
6063
vault_1 = keyring.addFromUri(VAULT_1_URI);
6164
vault_1_ids = collateralCurrencies.map((collateralCurrency) =>
6265
newVaultId(api, vault_1.address, collateralCurrency, wrappedCurrency)
@@ -142,6 +145,61 @@ export const vaultsTests = () => {
142145
expect(collateralizationBeforeDeposit.toString()).toEqual(collateralizationAfterWithdrawal.toString());
143146
}
144147
});
148+
149+
it("should be able to withdraw all collateral", async () => {
150+
const vaults = await interBtcAPI.vaults.list();
151+
// find vault with issued tokens, but zero to-be-issued tokens
152+
const vaultExt = vaults.find((vault) => vault.toBeIssuedTokens.isZero() && vault.issuedTokens.toBig().gt(0));
153+
154+
if (vaultExt === undefined) {
155+
throw Error("Precondition failure: Unable to find test vault to attempt withdraw all collateral");
156+
}
157+
158+
const vaultAccountId = newAccountId(api, vaultExt.id.accountId.toHuman());
159+
const collateralCurrency = await currencyIdToMonetaryCurrency(api, vaultExt.id.currencies.collateral);
160+
161+
// give enough wrapped tokens to vault to be able to self redeem all
162+
const amountIssued = vaultExt.getBackedTokens();
163+
// .toBig(0) returns amount in atomic units
164+
const amountIssuedAtomic = amountIssued.toBig(0).toNumber();
165+
const issuedCurrencyId = newCurrencyId(api, amountIssued.currency);
166+
167+
const vaultIssuedBalance = await interBtcAPI.tokens.balance(amountIssued.currency, vaultAccountId);
168+
if (!vaultIssuedBalance.free.gt(amountIssued)) {
169+
// set balance and wait for event
170+
const result = await DefaultTransactionAPI.sendLogged(
171+
api,
172+
sudoAccount,
173+
api.tx.sudo.sudo(api.tx.tokens.setBalance(vaultAccountId, issuedCurrencyId , amountIssuedAtomic * 2, 0)),
174+
api.events.tokens.BalanceSet
175+
);
176+
expect(result.isCompleted).toBe(true);
177+
}
178+
179+
// find matching keyring
180+
const vaultKR = (vault_1.address === vaultAccountId.toHuman())
181+
? vault_1
182+
: (vault_2.address === vaultAccountId.toHuman())
183+
? vault_2
184+
: vault_3;
185+
186+
// self redeem so vault has no more issued tokens
187+
const result2 = await DefaultTransactionAPI.sendLogged(
188+
api,
189+
vaultKR,
190+
api.tx.redeem.selfRedeem(vaultExt.id.currencies, amountIssuedAtomic),
191+
api.events.redeem.ExecuteRedeem
192+
);
193+
expect(result2.isCompleted).toBe(true);
194+
195+
const vaultInterBtcApi = new DefaultInterBtcApi(api, "regtest", vaultKR, ESPLORA_BASE_PATH);
196+
197+
// finally, withdraw all collateral
198+
await submitExtrinsic(vaultInterBtcApi, await vaultInterBtcApi.vaults.withdrawAllCollateral(collateralCurrency));
199+
200+
const collateralAfter = await vaultInterBtcApi.vaults.getCollateral(vaultAccountId, collateralCurrency);
201+
expect(collateralAfter.toBig().toNumber()).toEqual(0);
202+
});
145203

146204
it("should getLiquidationCollateralThreshold", async () => {
147205
for (const collateralCurrency of collateralCurrencies) {

0 commit comments

Comments
 (0)