Skip to content

Commit

Permalink
Merge pull request #727 from bvotteler/feat-enable-vault-withdraw-all…
Browse files Browse the repository at this point in the history
…-collateral

Feat: enable vault withdraw all collateral
  • Loading branch information
bvotteler authored Mar 5, 2024
2 parents e01fc14 + 252682f commit 7747549
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 12 deletions.
10 changes: 5 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: "3.8"
services:
interbtc:
image: "interlayhq/interbtc:1.25.0"
image: "interlayhq/interbtc:1.25.3"
command:
- --rpc-external
- --ws-external
Expand Down Expand Up @@ -54,7 +54,7 @@ services:
- "3002:3002"
restart: always
oracle:
image: "interlayhq/interbtc-clients:oracle-parachain-metadata-kintsugi-1.23.0-rc3"
image: "interlayhq/interbtc-clients:oracle-parachain-metadata-kintsugi-1.23.0"
command:
- oracle-parachain-metadata-kintsugi
- --keyring=bob
Expand All @@ -64,7 +64,7 @@ services:
volumes:
- ./docker/oracle-config.json:/oracle-config.json
vault_1:
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0-rc3"
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0"
command:
- vault-parachain-metadata-kintsugi
- --keyfile=/keyfile.json
Expand All @@ -81,7 +81,7 @@ services:
volumes:
- ./docker/vault_1-keyfile.json:/keyfile.json
vault_2:
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0-rc3"
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0"
command:
- vault-parachain-metadata-kintsugi
- --keyfile=/keyfile.json
Expand All @@ -94,7 +94,7 @@ services:
volumes:
- ./docker/vault_2-keyfile.json:/keyfile.json
vault_3:
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0-rc3"
image: "interlayhq/interbtc-clients:vault-parachain-metadata-kintsugi-1.23.0"
command:
- vault-parachain-metadata-kintsugi
- --keyfile=/keyfile.json
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@interlay/interbtc-api",
"version": "2.6.0",
"version": "2.7.0",
"description": "JavaScript library to interact with interBTC",
"main": "build/cjs/src/index.js",
"module": "build/esm/src/index.js",
Expand Down Expand Up @@ -51,7 +51,8 @@
"watch:build": "tsc -p tsconfig.json -w",
"watch:test": "jest --watch test/**/*.test.ts",
"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",
"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"
"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",
"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"
},
"engines": {
"node": ">=11"
Expand Down
2 changes: 1 addition & 1 deletion src/json/parachain.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions src/parachain/nomination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ export class DefaultNominationAPI implements NominationAPI {
const parsedNonce = api.createType("Index", definedNonce);
return api.tx.nomination.withdrawCollateral(vaultId, amountAsPlanck, parsedNonce);
}

static async buildWithdrawAllCollateralExtrinsic(
api: ApiPromise,
rewardsAPI: RewardsAPI,
vaultAccountId: AccountId,
collateralCurrency: Currency,
wrappedCurrency: Currency,
nonce?: number
): Promise<SubmittableExtrinsic<"promise", ISubmittableResult>> {
const vaultId = newVaultId(api, vaultAccountId.toString(), collateralCurrency, wrappedCurrency);
const definedNonce = nonce ? nonce : await rewardsAPI.getStakingPoolNonce(collateralCurrency, vaultAccountId);
const parsedNonce = api.createType("Index", definedNonce);
return api.tx.nomination.withdrawCollateral(vaultId, null, parsedNonce);
}

async withdrawCollateral(
vaultAccountId: AccountId,
Expand Down
41 changes: 40 additions & 1 deletion src/parachain/vaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,22 @@ export interface VaultsAPI {
*/
withdrawCollateral(amount: MonetaryAmount<CollateralCurrencyExt>): Promise<ExtrinsicData>;

/**
* Build withdraw collateral extrinsic (transaction) without sending it.
*
* @param collateralCurrency The collateral currency for which to withdraw all
* @returns A withdraw collateral submittable extrinsic as promise.
*/
buildWithdrawAllCollateralExtrinsic(
collateralCurrency: CollateralCurrencyExt
): Promise<SubmittableExtrinsic<"promise", ISubmittableResult>>;

/**
* @param collateralCurrency The collateral currency for which to withdraw all
* @returns {Promise<ExtrinsicData>} A submittable extrinsic and an event that is emitted when extrinsic is submitted.
*/
withdrawAllCollateral(collateralCurrency: CollateralCurrencyExt): Promise<ExtrinsicData>;

/**
* Build deposit collateral extrinsic (transaction) without sending it.
*
Expand Down Expand Up @@ -462,7 +478,7 @@ export class DefaultVaultsAPI implements VaultsAPI {
this.rewardsAPI,
vaultAccountId,
amount,
this.wrappedCurrency
this.wrappedCurrency,
);
}

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

async buildWithdrawAllCollateralExtrinsic(
collateralCurrency: CollateralCurrencyExt
): Promise<SubmittableExtrinsic<"promise", ISubmittableResult>> {
const account = this.transactionAPI.getAccount();
if (account == undefined) {
throw new Error("Account must be connected to create a collateral withdrawal request.");
}
const vaultAccountId = addressOrPairAsAccountId(this.api, account);

return await DefaultNominationAPI.buildWithdrawAllCollateralExtrinsic(
this.api,
this.rewardsAPI,
vaultAccountId,
collateralCurrency,
this.wrappedCurrency
);
}

async withdrawAllCollateral(collateralCurrency: CollateralCurrencyExt): Promise<ExtrinsicData> {
const tx = await this.buildWithdrawAllCollateralExtrinsic(collateralCurrency);
return { extrinsic: tx, event: this.api.events.vaultRegistry.WithdrawCollateral };
}

buildDepositCollateralExtrinsic(
amount: MonetaryAmount<CollateralCurrencyExt>
): SubmittableExtrinsic<"promise", ISubmittableResult> {
Expand Down
64 changes: 61 additions & 3 deletions test/integration/parachain/staging/sequential/vaults.partial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import {
GovernanceCurrency,
AssetRegistryAPI,
DefaultAssetRegistryAPI,
DefaultTransactionAPI,
} from "../../../../../src/index";

import { createSubstrateAPI } from "../../../../../src/factory";
import { VAULT_1_URI, VAULT_2_URI, PARACHAIN_ENDPOINT, VAULT_3_URI, ESPLORA_BASE_PATH } from "../../../../config";
import { VAULT_1_URI, VAULT_2_URI, PARACHAIN_ENDPOINT, VAULT_3_URI, ESPLORA_BASE_PATH, SUDO_URI } from "../../../../config";
import { newAccountId, WrappedCurrency, newVaultId } from "../../../../../src";
import { getSS58Prefix, newMonetaryAmount } from "../../../../../src/utils";
import { getSS58Prefix, newCurrencyId, newMonetaryAmount } from "../../../../../src/utils";
import {
getAUSDForeignAsset,
getCorrespondingCollateralCurrenciesForTests,
Expand All @@ -27,6 +28,7 @@ import {

export const vaultsTests = () => {
describe("vaultsAPI", () => {
let sudoAccount: KeyringPair;
let vault_1: KeyringPair;
let vault_1_ids: Array<InterbtcPrimitivesVaultId>;
let vault_2: KeyringPair;
Expand Down Expand Up @@ -56,7 +58,8 @@ export const vaultsTests = () => {
// also add aUSD collateral vaults if they exist (ie. the foreign asset exists)
collateralCurrencies.push(aUSD);
}


sudoAccount = keyring.addFromUri(SUDO_URI);
vault_1 = keyring.addFromUri(VAULT_1_URI);
vault_1_ids = collateralCurrencies.map((collateralCurrency) =>
newVaultId(api, vault_1.address, collateralCurrency, wrappedCurrency)
Expand Down Expand Up @@ -142,6 +145,61 @@ export const vaultsTests = () => {
expect(collateralizationBeforeDeposit.toString()).toEqual(collateralizationAfterWithdrawal.toString());
}
});

it("should be able to withdraw all collateral", async () => {
const vaults = await interBtcAPI.vaults.list();
// find vault with issued tokens, but zero to-be-issued tokens
const vaultExt = vaults.find((vault) => vault.toBeIssuedTokens.isZero() && vault.issuedTokens.toBig().gt(0));

if (vaultExt === undefined) {
throw Error("Precondition failure: Unable to find test vault to attempt withdraw all collateral");
}

const vaultAccountId = newAccountId(api, vaultExt.id.accountId.toHuman());
const collateralCurrency = await currencyIdToMonetaryCurrency(api, vaultExt.id.currencies.collateral);

// give enough wrapped tokens to vault to be able to self redeem all
const amountIssued = vaultExt.getBackedTokens();
// .toBig(0) returns amount in atomic units
const amountIssuedAtomic = amountIssued.toBig(0).toNumber();
const issuedCurrencyId = newCurrencyId(api, amountIssued.currency);

const vaultIssuedBalance = await interBtcAPI.tokens.balance(amountIssued.currency, vaultAccountId);
if (!vaultIssuedBalance.free.gt(amountIssued)) {
// set balance and wait for event
const result = await DefaultTransactionAPI.sendLogged(
api,
sudoAccount,
api.tx.sudo.sudo(api.tx.tokens.setBalance(vaultAccountId, issuedCurrencyId , amountIssuedAtomic * 2, 0)),
api.events.tokens.BalanceSet
);
expect(result.isCompleted).toBe(true);
}

// find matching keyring
const vaultKR = (vault_1.address === vaultAccountId.toHuman())
? vault_1
: (vault_2.address === vaultAccountId.toHuman())
? vault_2
: vault_3;

// self redeem so vault has no more issued tokens
const result2 = await DefaultTransactionAPI.sendLogged(
api,
vaultKR,
api.tx.redeem.selfRedeem(vaultExt.id.currencies, amountIssuedAtomic),
api.events.redeem.ExecuteRedeem
);
expect(result2.isCompleted).toBe(true);

const vaultInterBtcApi = new DefaultInterBtcApi(api, "regtest", vaultKR, ESPLORA_BASE_PATH);

// finally, withdraw all collateral
await submitExtrinsic(vaultInterBtcApi, await vaultInterBtcApi.vaults.withdrawAllCollateral(collateralCurrency));

const collateralAfter = await vaultInterBtcApi.vaults.getCollateral(vaultAccountId, collateralCurrency);
expect(collateralAfter.toBig().toNumber()).toEqual(0);
});

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

0 comments on commit 7747549

Please sign in to comment.