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/package.json b/package.json index c458aa65b9..380024eac4 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "secp256k1": "^4.0.3", "semver": "^7.3.5", "sha.js": "^2.4.11", - "stellar-sdk": "^10.0.1", + "stellar-sdk": "^10.1.0", "superstruct": "^0.14.2", "triple-beam": "^1.3.0", "varuint-bitcoin": "1.1.2", 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 { diff --git a/yarn.lock b/yarn.lock index 013bb7fbbd..4ce37d9a27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5944,11 +5944,6 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.5: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" @@ -7420,7 +7415,7 @@ multihashes@^0.4.15, multihashes@~0.4.15: multibase "^0.7.0" varint "^5.0.0" -nan@^2.13.2, nan@^2.14.0, nan@^2.2.1: +nan@^2.13.2, nan@^2.2.1: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== @@ -7475,7 +7470,7 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -node-gyp-build@^4.1.0, node-gyp-build@^4.2.0, node-gyp-build@^4.2.2, node-gyp-build@^4.3.0: +node-gyp-build@^4.2.0, node-gyp-build@^4.2.2, node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== @@ -8719,14 +8714,12 @@ snakecase-keys@3.2.1: map-obj "^4.1.0" to-snake-case "^1.0.0" -sodium-native@^2.3.0: - version "2.4.9" - resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-2.4.9.tgz#7a7beb997efdbd2c773a385fb959f0cead5f5162" - integrity sha512-mbkiyA2clyfwAyOFIzMvsV6ny2KrKEIhFVASJxWfsmgfUEymgLIS2MLHHcGIQMkrcKhPErRaMR5Dzv0EEn+BWg== +sodium-native@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-3.3.0.tgz#50ee52ac843315866cce3d0c08ab03eb78f22361" + integrity sha512-rg6lCDM/qa3p07YGqaVD+ciAbUqm6SoO4xmlcfkbU5r1zIGrguXztLiEtaLYTV5U6k8KSIUFmnU3yQUSKmf6DA== dependencies: - ini "^1.3.5" - nan "^2.14.0" - node-gyp-build "^4.1.0" + node-gyp-build "^4.3.0" source-map-support@^0.5.17, source-map-support@^0.5.6: version "0.5.21" @@ -8793,10 +8786,10 @@ stack-utils@^2.0.3: resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -stellar-base@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/stellar-base/-/stellar-base-7.0.0.tgz" - integrity sha512-Sfk/u/6HT+8xSQ4HvTI3XgthTS3fhv/ie6Jx4OzLvg81pt09bDDN5YvRbG6v3gZdiRA0pwg2RRz5YS3ph5ePEg== +stellar-base@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/stellar-base/-/stellar-base-8.0.0.tgz#619e5fb7951fa8abb26322e51e611169a5d1cf62" + integrity sha512-MH0V94Hn/Ekq/yZxd+EQijkvfU+21zuqJAyEP8X+fhRR0IQYwGNByY003oLqIkgwqXotyofEQ76W5MoNFZmlpw== dependencies: base32.js "^0.1.0" bignumber.js "^4.0.0" @@ -8804,14 +8797,14 @@ stellar-base@^7.0.0: js-xdr "^1.1.3" lodash "^4.17.21" sha.js "^2.3.6" - tweetnacl "^1.0.0" + tweetnacl "^1.0.3" optionalDependencies: - sodium-native "^2.3.0" + sodium-native "^3.3.0" -stellar-sdk@^10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/stellar-sdk/-/stellar-sdk-10.0.1.tgz" - integrity sha512-NWvDAldw4GVFqWo1wGoR9lCyJ6xIcO2ab4YX8BQYp1Z45/Rogv3Kj3RERsso/9m+MR+d2OfTmAhIy0vruRGv/Q== +stellar-sdk@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-10.1.0.tgz#91cf9ed3c84c9a9d4c8aaa6cca29bd997b7c778a" + integrity sha512-8PTKuyrQ5bIbsyrLav+JE/c+rrLynBygiB4dpvG30ou7K8P6a5ainmGyVMbCzlz1OkKWoaAW5rJAkl3RhlTZkA== dependencies: "@types/eventsource" "^1.1.2" "@types/node" ">= 8" @@ -8824,7 +8817,7 @@ stellar-sdk@^10.0.1: eventsource "^1.0.7" lodash "^4.17.21" randombytes "^2.1.0" - stellar-base "^7.0.0" + stellar-base "^8.0.0" toml "^2.3.0" tslib "^1.10.0" urijs "^1.19.1"