From 36f08c4595d50768aa0d8c141b561adcee8ce51d Mon Sep 17 00:00:00 2001 From: Noel Hawat Date: Wed, 10 Apr 2024 09:54:16 -0400 Subject: [PATCH] fix(sdk-coin-avaxp): stake output cannot be 0 throw error if change output is 0 for permissionless validator tx CR-1073 TICKET: CR-1073 --- .../lib/permissionlessValidatorTxBuilder.ts | 8 ++++++- .../sdk-coin-avaxp/test/resources/avaxp.ts | 1 + .../sdk-coin-avaxp/test/resources/errors.ts | 2 ++ .../lib/permissionlessValidatorTxBuilder.ts | 24 +++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/modules/sdk-coin-avaxp/src/lib/permissionlessValidatorTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/permissionlessValidatorTxBuilder.ts index 7cbd32ef01..2cb432448f 100644 --- a/modules/sdk-coin-avaxp/src/lib/permissionlessValidatorTxBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/permissionlessValidatorTxBuilder.ts @@ -476,10 +476,16 @@ export class PermissionlessValidatorTxBuilder extends TransactionBuilder { stakeOutputs.push(stakeOutput); if (currentTotal >= totalTarget) { + const changeOutputAmount = currentTotal - totalTarget; + if (changeOutputAmount <= 0) { + throw new BuildTransactionError( + 'Change output amount must be greater than 0. Stake less or more to avoid 0 amount in utxo output.' + ); + } const changeOutput = new avaxSerial.TransferableOutput( assetId, new TransferOutput( - new BigIntPr(currentTotal - totalTarget), + new BigIntPr(changeOutputAmount), new OutputOwners( new BigIntPr(this.transaction._locktime), new Int(this.transaction._threshold), diff --git a/modules/sdk-coin-avaxp/test/resources/avaxp.ts b/modules/sdk-coin-avaxp/test/resources/avaxp.ts index f6cdadb09b..874b35e573 100644 --- a/modules/sdk-coin-avaxp/test/resources/avaxp.ts +++ b/modules/sdk-coin-avaxp/test/resources/avaxp.ts @@ -614,6 +614,7 @@ export const BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE = { startTime: '1656423398', endTime: '1659053398', stakeAmount: '2370000000', + stakeAmountNoOutput: '4097000000', delegationFeeRate: 10, nodeId: 'NodeID-2hMqBQdjZMWdHvYu7ZPLA2CmrAdbTvpGf', blsPublicKey: '0xad9e9476b701edec88e53b1c314456053b3cf846a1192117872e41455f440c074d6ee89530d45e88f79ac0eda06f2887', diff --git a/modules/sdk-coin-avaxp/test/resources/errors.ts b/modules/sdk-coin-avaxp/test/resources/errors.ts index 50100acaa6..c82a0949f8 100644 --- a/modules/sdk-coin-avaxp/test/resources/errors.ts +++ b/modules/sdk-coin-avaxp/test/resources/errors.ts @@ -27,6 +27,8 @@ export const ERROR_CHAIN_ID_LENGTH = 'Chain id are 32 byte size'; export const ERROR_KEY_CANNOT_SIGN = 'Private key cannot sign the transaction'; export const ERROR_AMOUNT = 'Amount must be greater than 0'; +export const ERROR_CHANGE_AMOUNT = + 'Change output amount must be greater than 0. Stake less or more to avoid 0 amount in utxo output.'; export const ERROR_NONCE = 'Nonce must be greater or equal than 0'; diff --git a/modules/sdk-coin-avaxp/test/unit/lib/permissionlessValidatorTxBuilder.ts b/modules/sdk-coin-avaxp/test/unit/lib/permissionlessValidatorTxBuilder.ts index b150024ded..6b429d47fd 100644 --- a/modules/sdk-coin-avaxp/test/unit/lib/permissionlessValidatorTxBuilder.ts +++ b/modules/sdk-coin-avaxp/test/unit/lib/permissionlessValidatorTxBuilder.ts @@ -8,6 +8,7 @@ import * as AvaxpLib from '../../../src/lib'; import { TransactionBuilderFactory } from '../../../src/lib'; import { PermissionlessValidatorTxBuilder } from '../../../src/lib/permissionlessValidatorTxBuilder'; import * as testData from '../../resources/avaxp'; +import { ERROR_CHANGE_AMOUNT } from '../../resources/errors'; // import { pvm } from '@bitgo/avalanchejs'; describe('AvaxP permissionlessValidatorTxBuilder', () => { @@ -298,4 +299,27 @@ describe('AvaxP permissionlessValidatorTxBuilder', () => { console.log(fullSignedTx.toJson()); }); }); + it('Should fail to build if utxos change output 0', async () => { + const unixNow = BigInt(Math.round(new Date().getTime() / 1000)); + const startTime = unixNow + BigInt(60); + const endTime = startTime + BigInt(60 * 60 * 24 + 600); + + const txBuilder = new AvaxpLib.TransactionBuilderFactory(coins.get('tavaxp')) + .getPermissionlessValidatorTxBuilder() + .threshold(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.threshold) + .locktime(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.locktime) + .recoverMode(false) + .fromPubKey(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.bitgoAddresses) + .startTime(startTime.toString()) + .endTime(endTime.toString()) + .stakeAmount(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.stakeAmountNoOutput) + .delegationFeeRate(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.delegationFeeRate) + .nodeID(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.nodeId) + .blsPublicKey(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.blsPublicKey) + .blsSignature(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.blsSignature) + .utxos(testData.BUILD_AND_SIGN_ADD_PERMISSIONLESS_VALIDATOR_SAMPLE.utxos); + await txBuilder.build().catch((error) => { + assert(error.message === ERROR_CHANGE_AMOUNT); + }); + }); });