From 663014c5a3582989b3f77367aecbeee74e9e289c Mon Sep 17 00:00:00 2001 From: Hakim <59644786+haammar-ledger@users.noreply.github.com> Date: Mon, 16 May 2022 18:24:46 +0200 Subject: [PATCH] LIVE-1292 Cover Polkadot existential deposit edge case (#1953) (#1957) * LIVE-1292 Cover Polkadot existential deposit edge case (#1953) * Fix transaction mode mapping Send max must be balances.transfer palletMethod instead of balances.transferKeepAlive * Fix 1 DOT Polkadot limitation On the Polkadot network, an address is only active when it holds a minimum amount, currently set at 1 DOT. There's a limitation when you hold ~ 1 DOT. Use recommended 1.1 DOT value. * Improve code readability Don't use ternary operator * Split Existential Deposit and margin Restore EXISTENTIAL_DEPOSIT, use new constant EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN to handle recommended margin * Polkabot * trigger bot * trigger bot * Change Polkadot bot to 'Mooncake' seed * Change Polkadot bot to 'Mere Denis' seed * Change Polkadot bot to 'Silicium' seed * Update Polkadot bot spec to withdraw staking funds sometimes * Fix bot condition to do bond transactions * trigger bot * trigger bot * trigger bot * trigger bot * trigger bot * lint - remove unused import Co-authored-by: Alexandre Alouit --- .github/workflows/bot-polkadot-silicium.yml | 79 +++++++++++++++++++ src/families/polkadot/cli-transaction.ts | 2 +- .../polkadot/js-getTransactionStatus.ts | 7 ++ src/families/polkadot/js-signOperation.ts | 20 ++--- src/families/polkadot/logic.ts | 1 + src/families/polkadot/specs.ts | 65 +++++++++------ 6 files changed, 139 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/bot-polkadot-silicium.yml diff --git a/.github/workflows/bot-polkadot-silicium.yml b/.github/workflows/bot-polkadot-silicium.yml new file mode 100644 index 0000000000..4d8896812e --- /dev/null +++ b/.github/workflows/bot-polkadot-silicium.yml @@ -0,0 +1,79 @@ +name: Bot 'Polkadot on Silicium' +on: + push: + branches: + - family/polkadot + +jobs: + start-runner: + name: "start ec2 instance (Linux)" + if: ${{ always() }} + uses: ledgerhq/actions/.github/workflows/start-linux-runner.yml@main + secrets: + CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }} + + stop-runner: + name: "stop ec2 instance (Linux)" + needs: [start-runner, run-bot] + uses: ledgerhq/actions/.github/workflows/stop-linux-runner.yml@main + if: ${{ always() }} + with: + label: ${{ needs.start-runner.outputs.label }} + ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }} + secrets: + CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }} + + run-bot: + needs: [start-runner] + runs-on: ${{ needs.start-runner.outputs.label }} + steps: + - name: prepare runner + run: | + sudo growpart /dev/nvme0n1 1 + sudo resize2fs /dev/nvme0n1p1 + - uses: actions/checkout@v2 + - name: Retrieving coin apps + uses: actions/checkout@v2 + with: + repository: LedgerHQ/coin-apps + token: ${{ secrets.PAT }} + path: coin-apps + - uses: actions/setup-node@master + with: + node-version: 14.x + - name: install yarn + run: npm i -g yarn + - name: pull docker image + run: docker pull ghcr.io/ledgerhq/speculos + - name: kill apt-get + run: sudo killall -w apt-get apt || echo OK + - name: Install linux deps + run: sudo apt-get install -y libusb-1.0-0-dev jq + - name: Install dependencies + run: | + yarn global add yalc + yarn --frozen-lockfile + yarn ci-setup-cli + - name: BOT + env: + SEED: ${{ secrets.SEED3 }} + BOT_REPORT_FOLDER: botreport + VERBOSE_FILE: botreport/logs.txt + GITHUB_SHA: ${GITHUB_SHA} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_WORKFLOW: ${{ github.workflow }} + SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }} + SLACK_CHANNEL: ci-dot-ll + BOT_FILTER_FAMILY: polkadot + run: mkdir botreport; COINAPPS=$PWD/coin-apps yarn ci-test-bot + timeout-minutes: 120 + - name: Run coverage + if: failure() || success() + run: CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} npx codecov + - name: upload logs + if: failure() || success() + uses: actions/upload-artifact@v1 + with: + name: botreport + path: botreport/ diff --git a/src/families/polkadot/cli-transaction.ts b/src/families/polkadot/cli-transaction.ts index 8a4e761357..2bcb200c1d 100644 --- a/src/families/polkadot/cli-transaction.ts +++ b/src/families/polkadot/cli-transaction.ts @@ -13,7 +13,7 @@ const options = [ { name: "mode", type: String, - desc: "mode of transaction: send, nominate, bond, claimReward", + desc: "mode of transaction: send, nominate, bond, claimReward, withdrawUnbonded", }, { name: "fees", diff --git a/src/families/polkadot/js-getTransactionStatus.ts b/src/families/polkadot/js-getTransactionStatus.ts index a2feed207d..2ed1a9db4f 100644 --- a/src/families/polkadot/js-getTransactionStatus.ts +++ b/src/families/polkadot/js-getTransactionStatus.ts @@ -36,6 +36,7 @@ import { calculateAmount, getMinimumAmountToBond, getMinimumBalance, + EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN, } from "./logic"; import { isValidAddress } from "./address"; import { getCurrentPolkadotPreloadData } from "./preload"; @@ -78,6 +79,12 @@ const getSendTransactionStatus = async ( const leftover = a.spendableBalance.minus(totalSpent); if ( + a.spendableBalance.lte( + EXISTENTIAL_DEPOSIT.plus(EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN) + ) + ) { + errors.amount = new NotEnoughBalance(); + } else if ( minimumBalanceExistential.gt(0) && leftover.lt(minimumBalanceExistential) && leftover.gt(0) diff --git a/src/families/polkadot/js-signOperation.ts b/src/families/polkadot/js-signOperation.ts index 565d321614..c0f204aa08 100644 --- a/src/families/polkadot/js-signOperation.ts +++ b/src/families/polkadot/js-signOperation.ts @@ -29,6 +29,7 @@ const MODE_TO_TYPE = { }; const MODE_TO_PALLET_METHOD = { send: "balances.transferKeepAlive", + sendMax: "balances.transfer", bond: "staking.bond", bondExtra: "staking.bondExtra", unbond: "staking.unbond", @@ -41,16 +42,15 @@ const MODE_TO_PALLET_METHOD = { }; const getExtra = (type: string, account: Account, transaction: Transaction) => { - const extra = MODE_TO_PALLET_METHOD[transaction.mode] - ? { - palletMethod: - MODE_TO_PALLET_METHOD[ - transaction.mode === "bond" && !isFirstBond(account) - ? "bondExtra" - : transaction.mode - ], - } - : {}; + const extra = { + palletMethod: MODE_TO_PALLET_METHOD[transaction.mode], + }; + + if (transaction.mode == "send" && transaction.useAllAmount) { + extra.palletMethod = MODE_TO_PALLET_METHOD["sendMax"]; + } else if (transaction.mode === "bond" && !isFirstBond(account)) { + extra.palletMethod = MODE_TO_PALLET_METHOD["bondExtra"]; + } switch (type) { case "OUT": diff --git a/src/families/polkadot/logic.ts b/src/families/polkadot/logic.ts index a701e685d6..daaa26383a 100644 --- a/src/families/polkadot/logic.ts +++ b/src/families/polkadot/logic.ts @@ -4,6 +4,7 @@ import type { Transaction } from "./types"; import { getCurrentPolkadotPreloadData } from "./preload"; export const EXISTENTIAL_DEPOSIT = new BigNumber(10000000000); +export const EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN = new BigNumber(1000000000); // Polkadot recommended Existential Deposit error margin export const MAX_NOMINATIONS = 16; export const MAX_UNLOCKINGS = 32; export const PRELOAD_MAX_AGE = 60 * 1000; diff --git a/src/families/polkadot/specs.ts b/src/families/polkadot/specs.ts index 9db3888439..983b5e31dd 100644 --- a/src/families/polkadot/specs.ts +++ b/src/families/polkadot/specs.ts @@ -16,12 +16,14 @@ import { canUnbond, canNominate, isFirstBond, - getMinimumAmountToBond, + hasMinimumBondBalance, } from "../../families/polkadot/logic"; import { DeviceModelId } from "@ledgerhq/devices"; const currency = getCryptoCurrencyById("polkadot"); -const POLKADOT_MIN_SAFE = parseCurrencyUnit(currency.units[0], "0.05"); +// FIXME Should be replaced with EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN in logic.ts +const POLKADOT_MIN_SAFE = parseCurrencyUnit(currency.units[0], "0.1"); +// FIXME Should be replaced with EXISTENTIAL_DEPOSIT in logic.ts const EXISTENTIAL_DEPOSIT = parseCurrencyUnit(currency.units[0], "1.0"); const MIN_LOCKED_BALANCE_REQ = parseCurrencyUnit(currency.units[0], "1.0"); const polkadot: AppSpec = { @@ -49,8 +51,9 @@ const polkadot: AppSpec = { mutations: [ { name: "send 50%~", - maxRun: 1, + maxRun: 2, transaction: ({ account, siblings, bridge }) => { + invariant(account.polkadotResources, "polkadot"); const sibling = pickSiblings(siblings, 2); let amount = account.spendableBalance .div(1.9 + 0.2 * Math.random()) @@ -81,18 +84,11 @@ const polkadot: AppSpec = { }, { name: "bond - bondExtra", - maxRun: 2, + maxRun: 1, transaction: ({ account, bridge }) => { + invariant(account.polkadotResources, "polkadot"); invariant(canBond(account), "can't bond"); - const { minimumBondBalance } = getCurrentPolkadotPreloadData(); - invariant( - new BigNumber(100000).gt( - getMinimumAmountToBond(account, new BigNumber(minimumBondBalance)) - ), - "can't bond because too much unbond" - ); - const { polkadotResources } = account; - invariant(polkadotResources, "polkadot"); + invariant(hasMinimumBondBalance(account), "not enough balance to bond"); const options: { recipient?: string; rewardDestination?: string; @@ -134,14 +130,14 @@ const polkadot: AppSpec = { }, { name: "unbond", - maxRun: 1, + maxRun: 2, transaction: ({ account, bridge }) => { - invariant(canUnbond(account), "can't unbond"); const { polkadotResources } = account; invariant(polkadotResources, "polkadot"); + invariant(canUnbond(account), "can't unbond"); invariant( account.spendableBalance.gt(POLKADOT_MIN_SAFE), - "cant cover fee" + "can't cover fee" ); const amount = (polkadotResources as PolkadotResources).lockedBalance .minus((polkadotResources as PolkadotResources).unlockingBalance) @@ -163,17 +159,15 @@ const polkadot: AppSpec = { name: "rebond", maxRun: 1, transaction: ({ account, bridge }) => { + const { polkadotResources } = account; + invariant(polkadotResources, "polkadot"); invariant( - account.polkadotResources?.unlockingBalance.gt( - MIN_LOCKED_BALANCE_REQ - ), + polkadotResources?.unlockingBalance.gt(MIN_LOCKED_BALANCE_REQ), "can't rebond" ); - const { polkadotResources } = account; - invariant(polkadotResources, "polkadot"); invariant( account.spendableBalance.gt(POLKADOT_MIN_SAFE), - "cant cover fee" + "can't cover fee" ); const amount = BigNumber.maximum( (polkadotResources as PolkadotResources).unlockingBalance.times(0.2), @@ -196,9 +190,8 @@ const polkadot: AppSpec = { name: "nominate", maxRun: 1, transaction: ({ account, bridge }) => { + invariant(account.polkadotResources, "polkadot"); invariant(canNominate(account), "can't nominate"); - const { polkadotResources } = account; - invariant(polkadotResources, "polkadot"); invariant( account.spendableBalance.gt(POLKADOT_MIN_SAFE), "cant cover fee" @@ -221,6 +214,30 @@ const polkadot: AppSpec = { }; }, }, + { + name: "withdraw", + maxRun: 2, + transaction: ({ account, bridge }) => { + const { polkadotResources } = account; + invariant(polkadotResources, "polkadot"); + invariant( + polkadotResources?.unlockedBalance.gt(0), + "nothing to withdraw" + ); + invariant( + account.spendableBalance.gt(POLKADOT_MIN_SAFE), + "can't cover fee" + ); + return { + transaction: bridge.createTransaction(account), + updates: [ + { + mode: "withdrawUnbonded", + }, + ], + }; + }, + }, ], }; export default {