From 87299c34266ce70ddf969f6a64248f1b17e027b0 Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Wed, 23 Oct 2024 10:14:10 -0600 Subject: [PATCH 1/7] feat: add test transactions and complexity constants --- src/vms/pvm/txs/fee/constants.ts | 41 +++++++++++ src/vms/pvm/txs/fee/fixtures/transactions.ts | 73 ++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/src/vms/pvm/txs/fee/constants.ts b/src/vms/pvm/txs/fee/constants.ts index 4de5db5e1..d2be57f04 100644 --- a/src/vms/pvm/txs/fee/constants.ts +++ b/src/vms/pvm/txs/fee/constants.ts @@ -226,3 +226,44 @@ export const INTRINSIC_CONVERT_SUBNET_TX_COMPLEXITIES: Dimensions = { [FeeDimensions.DBWrite]: 2, // manager + weight [FeeDimensions.Compute]: 0, }; + +export const INTRINSIC_REGISTER_SUBNET_VALIDATOR_TX_COMPLEXITIES: Dimensions = { + [FeeDimensions.Bandwidth]: + INTRINSIC_BASE_TX_COMPLEXITIES[FeeDimensions.Bandwidth] + + LONG_LEN + // balance + SIGNATURE_LENGTH + // proof of possession + INT_LEN, // message length + [FeeDimensions.DBRead]: 0, // TODO: Implement + [FeeDimensions.DBWrite]: 0, // TODO: Implement + [FeeDimensions.Compute]: 0, // TODO: Include PoP verification time +}; + +export const INTRINSIC_SET_SUBNET_VALIDATOR_WEIGHT_TX_COMPLEXITIES: Dimensions = + { + [FeeDimensions.Bandwidth]: + INTRINSIC_BASE_TX_COMPLEXITIES[FeeDimensions.Bandwidth] + INT_LEN, // message length + [FeeDimensions.DBRead]: 0, // TODO: Implement + [FeeDimensions.DBWrite]: 0, // TODO: Implement + [FeeDimensions.Compute]: 0, + }; + +export const INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES: Dimensions = { + [FeeDimensions.Bandwidth]: + INTRINSIC_BASE_TX_COMPLEXITIES[FeeDimensions.Bandwidth] + + ID_LEN + // validationId + LONG_LEN, // balance + [FeeDimensions.DBRead]: 0, // TODO: Implement + [FeeDimensions.DBWrite]: 0, // TODO: Implement + [FeeDimensions.Compute]: 0, +}; + +export const INTRINSIC_DISABLE_SUBNET_VALIDATOR_TX_COMPLEXITIES: Dimensions = { + [FeeDimensions.Bandwidth]: + INTRINSIC_BASE_TX_COMPLEXITIES[FeeDimensions.Bandwidth] + + ID_LEN + // validationId + INT_LEN + // auth typeId + INT_LEN, // auth credential typeId + [FeeDimensions.DBRead]: 0, // TODO: Implement + [FeeDimensions.DBWrite]: 0, // TODO: Implement + [FeeDimensions.Compute]: 0, +}; diff --git a/src/vms/pvm/txs/fee/fixtures/transactions.ts b/src/vms/pvm/txs/fee/fixtures/transactions.ts index e822be07e..75f1b9224 100644 --- a/src/vms/pvm/txs/fee/fixtures/transactions.ts +++ b/src/vms/pvm/txs/fee/fixtures/transactions.ts @@ -19,6 +19,7 @@ import { INTRINSIC_CREATE_SUBNET_TX_COMPLEXITIES, INTRINSIC_EXPORT_TX_COMPLEXITIES, INTRINSIC_IMPORT_TX_COMPLEXITIES, + INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES, INTRINSIC_INPUT_DB_READ, INTRINSIC_INPUT_DB_WRITE, INTRINSIC_OUTPUT_DB_WRITE, @@ -296,6 +297,78 @@ export const TEST_TRANSACTIONS: ReadonlyArray<{ }), expectedDynamicFee: 365_600n, }, + + // { + // name: 'RegisterSubnetValidatorTx', + // txHex: + // '00000000002400003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b552a000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001ca44ad45a63381b07074be7f82005c41550c989b967f40020f3bedc4b02191f300000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f234262404000000010000000000000000000000003b9aca00ab5cb0516b7afdb13727f766185b2b8da44e2653eef63c85f196701083e649289cce1a23c39eb471b2473bc6872aa3ea190de0fe66296cbdd4132c92c3430ff22f28f0b341b15905a005bbd66cc0f4056bc4be5934e4f3a57151a60060f429190000012f000000003039705f3d4415f990225d3df5ce437d7af2aa324b1bbce854ee34ab6f39882250d20000009c000000000001000000000000008e000000000001a0673b4ee5ec44e57c8ab250dd7cd7b68d04421f64bd6559a4284a3ee358ff2b000000145efc86a11c5b12cc95b2cf527c023f9cf6e0e8f6b62034315c5d11cea4190f6ea8997821c02483d29adb5e4567843f7a44c39b2ffa20c8520dc358702fb1ec29f2746dcc000000006705af280000000000000000000000000000000000000000000000010000000000000001018e99dc6ed736089c03b9a1275e0cf801524ed341fb10111f29c0390fa2f96cf6aa78539ec767e5cd523c606c7ede50e60ba6065a3685e770d979b0df74e3541b61ed63f037463776098576e385767a695de59352b44e515831c5ee7a8cc728f9000000010000000900000001a0950b9e6e866130f0d09e2a7bfdd0246513295237258afa942b1850dab79824605c796bbfc9223cf91935fb29c66f8b927690220b9b1c24d6f078054a3e346201', + // expectedComplexity: createDimensions({ + // bandwidth: 710, + // dbRead: + // INTRINSIC_REGISTER_SUBNET_VALIDATOR_TX_COMPLEXITIES[FeeDimensions.DBRead] + + // INTRINSIC_INPUT_DB_READ, + // dbWrite: + // INTRINSIC_REGISTER_SUBNET_VALIDATOR_TX_COMPLEXITIES[FeeDimensions.DBWrite] + + // INTRINSIC_INPUT_DB_WRITE + + // INTRINSIC_OUTPUT_DB_WRITE, + // compute: 0, // TODO: Implement + // }), + // expectedDynamicFee: 151_000n, + // }, + + // { + // name: 'SetSubnetValidatorWeightTx', + // txHex: + // '00000000002500003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b5100000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001389c41b6ed301e4c118bd23673268fd2054b772efcf25685a117b74bab7ae5e400000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f1f88b552a000000010000000000000000000000d7000000003039705f3d4415f990225d3df5ce437d7af2aa324b1bbce854ee34ab6f39882250d200000044000000000001000000000000003600000000000338e6e9fe31c6d070a8c792dbacf6d0aefb8eac2aded49cc0aa9f422d1fdd9ecd0000000000000001000000000000000500000000000000010187f4bb2c42869c56f023a1ca81045aff034acd490b8f15b5069025f982e605e077007fc588f7d56369a65df7574df3b70ff028ea173739c789525ab7eebfcb5c115b13cca8f02b362104b700c75bc95234109f3f1360ddcb4ec3caf6b0e821cb0000000100000009000000010a29f3c86d52908bf2efbc3f918a363df704c429d66c8d6615712a2a584a2a5f264a9e7b107c07122a06f31cadc2f51285884d36fe8df909a07467417f1d64cf00', + // expectedComplexity: createDimensions({ + // bandwidth: 518, + // dbRead: + // INTRINSIC_SET_SUBNET_VALIDATOR_WEIGHT_TX_COMPLEXITIES[FeeDimensions.DBRead] + + // INTRINSIC_INPUT_DB_READ, + // dbWrite: + // INTRINSIC_SET_SUBNET_VALIDATOR_WEIGHT_TX_COMPLEXITIES[FeeDimensions.DBWrite] + + // INTRINSIC_INPUT_DB_WRITE + + // INTRINSIC_OUTPUT_DB_WRITE, + // compute: 0, // TODO: Implement + // }), + // expectedDynamicFee: 131_800n, + // }, + + { + name: 'IncreaseBalanceTx', + txHex: + '00000000002600003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b4e52000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001f61ea7e3bb6d33da9901644f3c623e4537b7d1c276e9ef23bcc8e4150e494d6600000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f1f88b510000000001000000000000000038e6e9fe31c6d070a8c792dbacf6d0aefb8eac2aded49cc0aa9f422d1fdd9ecd0000000000000002000000010000000900000001cb56b56387be9186d86430fad5418db4d13e991b6805b6ba178b719e3f47ce001da52d6ed3173bfdd8b69940a135432abce493a10332e881f6c34cea3617595e00', + expectedComplexity: createDimensions({ + bandwidth: 339, + dbRead: + INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES[FeeDimensions.DBRead] + + INTRINSIC_INPUT_DB_READ, + dbWrite: + INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES[FeeDimensions.DBWrite] + + INTRINSIC_INPUT_DB_WRITE + + INTRINSIC_OUTPUT_DB_WRITE, + compute: 0, // TODO: Implement + }), + expectedDynamicFee: 113_900n, + }, + + // { + // name: 'DisableSubnetValidatorTx', + // txHex: + // '00000000002700003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b4b9e000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001fd91c5c421468b13b09dda413bdbe1316c7c9417f2468b893071d4cb608a01da00000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f1f88b4e5200000001000000000000000038e6e9fe31c6d070a8c792dbacf6d0aefb8eac2aded49cc0aa9f422d1fdd9ecd0000000a00000000000000020000000900000001ff99bb626d898907a660701e2febaa311b4e644fe71add2d1a3f71748102c73f54d73c8370a9ae33e09c984bb8c03da4922bf208af836ec2daaa31cb42788bee010000000900000000', + // expectedComplexity: createDimensions({ + // bandwidth: 347, + // dbRead: + // INTRINSIC_DISABLE_SUBNET_VALIDATOR_TX_COMPLEXITIES[FeeDimensions.DBRead] + + // INTRINSIC_INPUT_DB_READ, + // dbWrite: + // INTRINSIC_DISABLE_SUBNET_VALIDATOR_TX_COMPLEXITIES[FeeDimensions.DBWrite] + + // INTRINSIC_INPUT_DB_WRITE + + // INTRINSIC_OUTPUT_DB_WRITE, + // compute: 0, // TODO: Implement + // }), + // expectedDynamicFee: 114_700n, + // }, ]; export const TEST_UNSUPPORTED_TRANSACTIONS = [ From 8afca52bca3767321ce4632a644387898445388a Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Thu, 24 Oct 2024 11:08:37 -0600 Subject: [PATCH 2/7] feat: add IncreaseBalanceTx --- src/fixtures/pvm.ts | 7 ++ src/serializable/constants.ts | 1 + .../fxs/pvm/convertSubnetValidator.test.ts | 2 + src/serializable/fxs/pvm/pChainOwner.test.ts | 9 +- src/serializable/pvm/codec.ts | 5 + src/serializable/pvm/increaseBalance.spec.ts | 12 ++ src/serializable/pvm/increaseBalanceTx.ts | 44 +++++++ src/serializable/pvm/index.ts | 2 + src/serializable/pvm/typeGuards.spec.ts | 4 + src/serializable/pvm/typeGuards.ts | 5 + .../validateBurnedAmount.ts | 6 +- src/vms/pvm/etna-builder/builder.ts | 109 +++++++++++++++--- src/vms/pvm/txs/fee/complexity.ts | 14 ++- 13 files changed, 202 insertions(+), 18 deletions(-) create mode 100644 src/serializable/pvm/increaseBalance.spec.ts create mode 100644 src/serializable/pvm/increaseBalanceTx.ts diff --git a/src/fixtures/pvm.ts b/src/fixtures/pvm.ts index a11efed35..50723fca3 100644 --- a/src/fixtures/pvm.ts +++ b/src/fixtures/pvm.ts @@ -19,6 +19,7 @@ import { RemoveSubnetValidatorTx, TransferSubnetOwnershipTx, TransformSubnetTx, + IncreaseBalanceTx, } from '../serializable/pvm'; import { baseTx, @@ -346,6 +347,12 @@ export const convertSubnetTxBytes = () => inputBytes(), ); +export const increaseBalanceTx = () => + new IncreaseBalanceTx(baseTx(), id(), bigIntPr()); + +export const increaseBalanceTxBytes = () => + concatBytes(baseTxbytes(), idBytes(), bigIntPrBytes()); + export const pChainOwner = () => new PChainOwner(int(), addresses()()); export const pChainOwnerBytes = () => diff --git a/src/serializable/constants.ts b/src/serializable/constants.ts index f99de8913..c11221cba 100644 --- a/src/serializable/constants.ts +++ b/src/serializable/constants.ts @@ -82,6 +82,7 @@ export enum TypeSymbols { ConvertSubnetTx = 'pvm.ConvertSubnetTx', ConvertSubnetValidator = 'pvm.ConvertSubnetValidator', + IncreaseBalanceTx = 'pvm.IncreaseBalanceTx', PChainOwner = 'pvm.PChainOwner', diff --git a/src/serializable/fxs/pvm/convertSubnetValidator.test.ts b/src/serializable/fxs/pvm/convertSubnetValidator.test.ts index 30f7d347a..302a3dbd2 100644 --- a/src/serializable/fxs/pvm/convertSubnetValidator.test.ts +++ b/src/serializable/fxs/pvm/convertSubnetValidator.test.ts @@ -1,3 +1,4 @@ +import { testPVMCodec } from '../../../fixtures/codec'; import { convertSubnetValidator, convertSubnetValidatorBytes, @@ -10,4 +11,5 @@ testSerialization( ConvertSubnetValidator, convertSubnetValidator, convertSubnetValidatorBytes, + testPVMCodec, ); diff --git a/src/serializable/fxs/pvm/pChainOwner.test.ts b/src/serializable/fxs/pvm/pChainOwner.test.ts index 8567a7a88..eb8b8ce51 100644 --- a/src/serializable/fxs/pvm/pChainOwner.test.ts +++ b/src/serializable/fxs/pvm/pChainOwner.test.ts @@ -1,5 +1,12 @@ +import { testPVMCodec } from '../../../fixtures/codec'; import { pChainOwner, pChainOwnerBytes } from '../../../fixtures/pvm'; import { testSerialization } from '../../../fixtures/utils/serializable'; import { PChainOwner } from './pChainOwner'; -testSerialization('PChainOwner', PChainOwner, pChainOwner, pChainOwnerBytes); +testSerialization( + 'PChainOwner', + PChainOwner, + pChainOwner, + pChainOwnerBytes, + testPVMCodec, +); diff --git a/src/serializable/pvm/codec.ts b/src/serializable/pvm/codec.ts index 7f9cf01f9..aeed25b5d 100644 --- a/src/serializable/pvm/codec.ts +++ b/src/serializable/pvm/codec.ts @@ -19,6 +19,7 @@ import { TransferSubnetOwnershipTx } from './transferSubnetOwnershipTx'; import { TransformSubnetTx } from './transformSubnetTx'; import { BaseTx } from './baseTx'; import { ConvertSubnetTx } from './convertSubnetTx'; +import { IncreaseBalanceTx } from './increaseBalanceTx'; /** * @see https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/txs/codec.go#L35 @@ -60,6 +61,10 @@ export const codec = new Codec([ BaseTx, // 34 ConvertSubnetTx, // 35 + // Replace these with the actual txs when they are implemented + ...new Array(2), // 36-37 RegisterSubnetValidatorTx, SetSubnetValidatorWeightTx + IncreaseBalanceTx, // 38 + // DisableSubnetValidatorTx, // 39 ]); let manager: Manager; diff --git a/src/serializable/pvm/increaseBalance.spec.ts b/src/serializable/pvm/increaseBalance.spec.ts new file mode 100644 index 000000000..529d954a3 --- /dev/null +++ b/src/serializable/pvm/increaseBalance.spec.ts @@ -0,0 +1,12 @@ +import { testPVMCodec } from '../../fixtures/codec'; +import { increaseBalanceTx, increaseBalanceTxBytes } from '../../fixtures/pvm'; +import { testSerialization } from '../../fixtures/utils/serializable'; +import { IncreaseBalanceTx } from './increaseBalanceTx'; + +testSerialization( + 'IncreaseBalanceTx', + IncreaseBalanceTx, + increaseBalanceTx, + increaseBalanceTxBytes, + testPVMCodec, +); diff --git a/src/serializable/pvm/increaseBalanceTx.ts b/src/serializable/pvm/increaseBalanceTx.ts new file mode 100644 index 000000000..b72431617 --- /dev/null +++ b/src/serializable/pvm/increaseBalanceTx.ts @@ -0,0 +1,44 @@ +import { pack, unpack } from '../../utils/struct'; +import { BaseTx } from '../avax'; +import type { Codec } from '../codec'; +import { serializable } from '../common/types'; +import { TypeSymbols } from '../constants'; +import { Id } from '../fxs/common'; +import { BigIntPr } from '../primitives'; +import { PVMTx } from './abstractTx'; + +@serializable() +export class IncreaseBalanceTx extends PVMTx { + _type = TypeSymbols.IncreaseBalanceTx; + + constructor( + public readonly baseTx: BaseTx, + /** + * The corresponding Validator ID. + */ + public readonly validationId: Id, + /** + * Balance <= sum of AVAX inputs - sum of AVAX outputs - Tx fee + */ + public readonly balance: BigIntPr, + ) { + super(); + } + + static fromBytes( + bytes: Uint8Array, + codec: Codec, + ): [increaseBalanceTx: IncreaseBalanceTx, rest: Uint8Array] { + const [baseTx, validationId, balance, rest] = unpack( + bytes, + [BaseTx, Id, BigIntPr], + codec, + ); + + return [new IncreaseBalanceTx(baseTx, validationId, balance), rest]; + } + + toBytes(codec: Codec) { + return pack([this.baseTx, this.validationId, this.balance], codec); + } +} diff --git a/src/serializable/pvm/index.ts b/src/serializable/pvm/index.ts index e5054f42b..37289c788 100644 --- a/src/serializable/pvm/index.ts +++ b/src/serializable/pvm/index.ts @@ -20,6 +20,7 @@ import { AbstractSubnetTx } from './abstractSubnetTx'; import { TransferSubnetOwnershipTx } from './transferSubnetOwnershipTx'; import { TransformSubnetTx } from './transformSubnetTx'; import { ConvertSubnetTx } from './convertSubnetTx'; +import { IncreaseBalanceTx } from './increaseBalanceTx'; export * from './typeGuards'; export { @@ -46,4 +47,5 @@ export { TransferSubnetOwnershipTx, TransformSubnetTx, ConvertSubnetTx, + IncreaseBalanceTx, }; diff --git a/src/serializable/pvm/typeGuards.spec.ts b/src/serializable/pvm/typeGuards.spec.ts index 920f1184e..5b901e89d 100644 --- a/src/serializable/pvm/typeGuards.spec.ts +++ b/src/serializable/pvm/typeGuards.spec.ts @@ -18,6 +18,8 @@ import { isPvmBaseTx, isTransferSubnetOwnershipTx, isTransformSubnetTx, + isConvertSubnetTx, + isIncreaseBalanceTx, } from './typeGuards'; const cases: [any, TypeSymbols][] = [ @@ -38,6 +40,8 @@ const cases: [any, TypeSymbols][] = [ [isRewardValidatorTx, TypeSymbols.RewardValidatorTx], [isTransferSubnetOwnershipTx, TypeSymbols.TransferSubnetOwnershipTx], [isTransformSubnetTx, TypeSymbols.TransformSubnetTx], + [isConvertSubnetTx, TypeSymbols.ConvertSubnetTx], + [isIncreaseBalanceTx, TypeSymbols.IncreaseBalanceTx], ]; const guards = cases.map((caseItem) => caseItem[0]); diff --git a/src/serializable/pvm/typeGuards.ts b/src/serializable/pvm/typeGuards.ts index 20517123d..f8b91327f 100644 --- a/src/serializable/pvm/typeGuards.ts +++ b/src/serializable/pvm/typeGuards.ts @@ -17,6 +17,7 @@ import type { TransferSubnetOwnershipTx } from './transferSubnetOwnershipTx'; import { TypeSymbols } from '../constants'; import type { TransformSubnetTx } from './transformSubnetTx'; import type { ConvertSubnetTx } from './convertSubnetTx'; +import type { IncreaseBalanceTx } from './increaseBalanceTx'; export function isPvmBaseTx(tx: Transaction): tx is BaseTx { return tx._type === TypeSymbols.PvmBaseTx; @@ -92,6 +93,10 @@ export function isConvertSubnetTx(tx: Transaction): tx is ConvertSubnetTx { return tx._type === TypeSymbols.ConvertSubnetTx; } +export function isIncreaseBalanceTx(tx: Transaction): tx is IncreaseBalanceTx { + return tx._type === TypeSymbols.IncreaseBalanceTx; +} + export function isEmptySigner( signer: Signer | SignerEmpty, ): signer is SignerEmpty { diff --git a/src/utils/validateBurnedAmount/validateBurnedAmount.ts b/src/utils/validateBurnedAmount/validateBurnedAmount.ts index 2c14c7b2d..438ecc14b 100644 --- a/src/utils/validateBurnedAmount/validateBurnedAmount.ts +++ b/src/utils/validateBurnedAmount/validateBurnedAmount.ts @@ -18,6 +18,7 @@ import { isConvertSubnetTx, isCreateChainTx, isCreateSubnetTx, + isIncreaseBalanceTx, isPvmBaseTx, isExportTx as isPvmExportTx, isImportTx as isPvmImportTx, @@ -31,7 +32,7 @@ const _getBurnedAmount = (tx: Transaction, context: Context) => { }; // Check supported pvm transactions for Etna -// Todo: add isAvmBaseTx, isAvmExportTx and isAvmImportTx when avm dynmamic fee is implemented +// Todo: add isAvmBaseTx, isAvmExportTx and isAvmImportTx when avm dynamic fee is implemented const isEtnaSupported = (tx: Transaction) => { return ( // isAvmBaseTx(tx) || // not implemented @@ -47,7 +48,8 @@ const isEtnaSupported = (tx: Transaction) => { isCreateSubnetTx(tx) || isRemoveSubnetValidatorTx(tx) || isTransferSubnetOwnershipTx(tx) || - isConvertSubnetTx(tx) + isConvertSubnetTx(tx) || + isIncreaseBalanceTx(tx) ); }; diff --git a/src/vms/pvm/etna-builder/builder.ts b/src/vms/pvm/etna-builder/builder.ts index c82a0923b..9572c489a 100644 --- a/src/vms/pvm/etna-builder/builder.ts +++ b/src/vms/pvm/etna-builder/builder.ts @@ -11,6 +11,7 @@ import { } from '../../../constants/networkIDs'; import type { TransferOutput } from '../../../serializable'; import { + BigIntPr, Input, NodeId, OutputOwners, @@ -40,6 +41,7 @@ import { SubnetValidator, TransferSubnetOwnershipTx, ConvertSubnetTx, + IncreaseBalanceTx, } from '../../../serializable/pvm'; import { createSignerOrSignerEmptyFromStrings } from '../../../serializable/pvm/signer'; import { @@ -77,6 +79,7 @@ import { import { spend } from './spend'; import { useSpendableLockedUTXOs, useUnlockedUTXOs } from './spend-reducers'; import type { ConvertSubnetValidator } from '../../../serializable/fxs/pvm/convertSubnetValidator'; +import { INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES } from '../txs/fee/constants'; /** * Creates OutputOwners used for change outputs with the specified @@ -1355,22 +1358,22 @@ export type NewConvertSubnetTxProps = TxProps<{ * Creates a new unsigned PVM convert subnet transaction * (`ConvertSubnetTx`) using calculated dynamic fees. * - * @param props {NewConvertSubnetTxProps} - * @param context {Context} - * @returns {UnsignedTx} An UnsignedTx. + * @param props + * @param context + * @returns An UnsignedTx. */ export const newConvertSubnetTx: TxBuilderFn = ( { - fromAddressesBytes, + address, + chainId, + changeAddressesBytes, feeState, - subnetId, - utxos, + fromAddressesBytes, memo = new Uint8Array(), minIssuanceTime = BigInt(Math.floor(new Date().getTime() / 1000)), - changeAddressesBytes, subnetAuth, - chainId, - address, + subnetId, + utxos, validators, }, context, @@ -1404,17 +1407,17 @@ export const newConvertSubnetTx: TxBuilderFn = ( const spendResults = spend( { + changeOutputOwners: getChangeOutputOwners({ + changeAddressesBytes, + fromAddressesBytes, + }), excessAVAX: 0n, feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, minIssuanceTime, - changeOutputOwners: getChangeOutputOwners({ - changeAddressesBytes, - fromAddressesBytes, - }), - utxos, toBurn, + utxos, }, [useUnlockedUTXOs], context, @@ -1447,3 +1450,81 @@ export const newConvertSubnetTx: TxBuilderFn = ( addressMaps, ); }; + +export type IncreaseBalanceTxProps = TxProps<{ + balance: bigint; + validationId: string; +}>; + +/** + * Creates a new unsigned PVM convert subnet transaction + * (`ConvertSubnetTx`) using calculated dynamic fees. + * + * @param props + * @param context + * @returns An UnsignedTx. + */ +export const newIncreaseBalanceTx: TxBuilderFn = ( + { + balance, + changeAddressesBytes, + feeState, + fromAddressesBytes, + memo = new Uint8Array(), + minIssuanceTime = BigInt(Math.floor(new Date().getTime() / 1000)), + utxos, + validationId, + }, + context, +) => { + const toBurn = new Map([[context.avaxAssetID, balance]]); + + const bytesComplexity = getBytesComplexity(memo); + + const complexity = addDimensions( + INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES, + bytesComplexity, + ); + + const spendResults = spend( + { + changeOutputOwners: getChangeOutputOwners({ + changeAddressesBytes, + fromAddressesBytes, + }), + excessAVAX: 0n, + feeState, + fromAddresses: addressesFromBytes(fromAddressesBytes), + initialComplexity: complexity, + minIssuanceTime, + toBurn, + utxos, + }, + [useUnlockedUTXOs], + context, + ); + + const { changeOutputs, inputs, inputUTXOs } = spendResults; + const addressMaps = getAddressMaps({ + inputs, + inputUTXOs, + minIssuanceTime, + fromAddressesBytes, + }); + + return new UnsignedTx( + new IncreaseBalanceTx( + AvaxBaseTx.fromNative( + context.networkID, + context.pBlockchainID, + changeOutputs, + inputs, + memo, + ), + Id.fromString(validationId), + new BigIntPr(balance), + ), + inputUTXOs, + addressMaps, + ); +}; diff --git a/src/vms/pvm/txs/fee/complexity.ts b/src/vms/pvm/txs/fee/complexity.ts index df91705b6..ad52e1740 100644 --- a/src/vms/pvm/txs/fee/complexity.ts +++ b/src/vms/pvm/txs/fee/complexity.ts @@ -26,6 +26,7 @@ import type { RemoveSubnetValidatorTx, TransferSubnetOwnershipTx, ConvertSubnetTx, + IncreaseBalanceTx, } from '../../../../serializable/pvm'; import type { Signer } from '../../../../serializable/pvm/signer'; import { @@ -33,10 +34,12 @@ import { isAddPermissionlessDelegatorTx, isAddPermissionlessValidatorTx, isAddSubnetValidatorTx, + isConvertSubnetTx, isCreateChainTx, isCreateSubnetTx, isExportTx, isImportTx, + isIncreaseBalanceTx, isPvmBaseTx, isRemoveSubnetValidatorTx, isTransferSubnetOwnershipTx, @@ -55,7 +58,6 @@ import { } from '../../../common/fees/dimensions'; import type { Serializable } from '../../../common/types'; import type { Transaction } from '../../../common'; -import { isConvertSubnetTx } from '../../../../serializable/pvm/typeGuards'; import { INTRINSIC_ADD_PERMISSIONLESS_DELEGATOR_TX_COMPLEXITIES, INTRINSIC_ADD_PERMISSIONLESS_VALIDATOR_TX_COMPLEXITIES, @@ -67,6 +69,7 @@ import { INTRINSIC_CREATE_SUBNET_TX_COMPLEXITIES, INTRINSIC_EXPORT_TX_COMPLEXITIES, INTRINSIC_IMPORT_TX_COMPLEXITIES, + INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES, INTRINSIC_INPUT_BANDWIDTH, INTRINSIC_INPUT_DB_READ, INTRINSIC_INPUT_DB_WRITE, @@ -383,6 +386,13 @@ const convertSubnetTx = (tx: ConvertSubnetTx): Dimensions => { ); }; +const increaseBalanceTx = (tx: IncreaseBalanceTx): Dimensions => { + return addDimensions( + INTRINSIC_INCREASE_BALANCE_TX_COMPLEXITIES, + getBaseTxComplexity(tx.baseTx), + ); +}; + export const getTxComplexity = (tx: Transaction): Dimensions => { if (isAddPermissionlessValidatorTx(tx)) { return addPermissionlessValidatorTx(tx); @@ -406,6 +416,8 @@ export const getTxComplexity = (tx: Transaction): Dimensions => { return baseTx(tx); } else if (isConvertSubnetTx(tx)) { return convertSubnetTx(tx); + } else if (isIncreaseBalanceTx(tx)) { + return increaseBalanceTx(tx); } else { throw new Error('Unsupported transaction type.'); } From 5710ba7064ff7ced8f363048b4b0fe67d456fc43 Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Thu, 24 Oct 2024 12:06:35 -0600 Subject: [PATCH 3/7] chore: update ts-jest and jest config --- jest.config.ts | 20 +++++------ package.json | 2 +- yarn.lock | 96 ++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 88 insertions(+), 30 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index fc32ddd1f..fd98c074d 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,21 +1,19 @@ -/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = { +import { createDefaultEsmPreset, type JestConfigWithTsJest } from 'ts-jest'; + +const preset = createDefaultEsmPreset(); + +const jestConfig: JestConfigWithTsJest = { + ...preset, moduleFileExtensions: ['js', 'json', 'ts'], rootDir: 'src', - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - useESM: true, - }, - ], - }, collectCoverageFrom: ['**/*.(t|j)s'], coverageDirectory: '../coverage', testEnvironment: 'node', coverageProvider: 'v8', - extensionsToTreatAsEsm: ['.ts'], // Experimental to fix issues with BigInt serialization // See: https://jestjs.io/docs/configuration#workerthreads + // @ts-expect-error - workerThreads is not in the type definition workerThreads: true, }; + +export default jestConfig; diff --git a/package.json b/package.json index bedaa1eeb..e6c336bb1 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "rollup": "4.9.6", "rollup-plugin-filesize": "10.0.0", "semantic-release": "21.0.1", - "ts-jest": "29.1.2", + "ts-jest": "29.2.5", "ts-node": "10.9.2", "typescript": "5.3.3" }, diff --git a/yarn.lock b/yarn.lock index b46e89d41..17739e146 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2438,6 +2438,11 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" @@ -2600,7 +2605,7 @@ browserslist@^4.21.3: node-releases "^2.0.8" update-browserslist-db "^1.0.10" -bs-logger@0.x: +bs-logger@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== @@ -2783,7 +2788,7 @@ chalk@^2.0.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3387,6 +3392,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + electron-to-chromium@^1.4.284: version "1.4.301" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.301.tgz#175d9fa1509a5b062752c6db321051e610fe2eae" @@ -3804,6 +3816,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + filesize@^6.1.0: version "6.4.0" resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.4.0.tgz#914f50471dd66fdca3cefe628bd0cde4ef769bcd" @@ -4786,6 +4805,16 @@ jackspeak@^2.3.5: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + java-properties@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211" @@ -5682,7 +5711,7 @@ lodash.map@^4.5.1: resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q== -lodash.memoize@4.x: +lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -5787,7 +5816,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -7584,6 +7613,11 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + serialize-javascript@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" @@ -7859,7 +7893,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7900,7 +7943,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8147,19 +8197,20 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== -ts-jest@29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" - integrity sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g== +ts-jest@29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" jest-util "^29.0.0" json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "^7.5.3" - yargs-parser "^21.0.1" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" ts-node@10.9.2, ts-node@^10.8.1: version "10.9.2" @@ -8487,7 +8538,16 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -8578,7 +8638,7 @@ yargs-parser@^20.2.3: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.0.1, yargs-parser@^21.1.1: +yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== From e442228dd4ed94ddb3230a585156f2501fe2d79f Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Thu, 24 Oct 2024 12:07:03 -0600 Subject: [PATCH 4/7] fix: circular dependencies Closes #873. --- .../fxs/pvm/convertSubnetValidator.ts | 2 +- src/serializable/pvm/increaseBalanceTx.ts | 2 +- src/serializable/pvm/validator.spec.ts | 9 ++- src/utils/addressMap.spec.ts | 71 +++++++++++++++++- src/utils/addressMap.ts | 43 ++++++++++- src/utils/builderUtils.ts | 2 +- src/utils/matchOwners.spec.ts | 73 ------------------- src/utils/matchOwners.ts | 43 ----------- src/vms/evm/builder.ts | 3 +- src/vms/pvm/etna-builder/builder.ts | 2 +- src/vms/pvm/txs/fee/complexity.ts | 2 +- .../utils/verifySignaturesMatch.ts | 4 +- 12 files changed, 127 insertions(+), 129 deletions(-) delete mode 100644 src/utils/matchOwners.spec.ts delete mode 100644 src/utils/matchOwners.ts diff --git a/src/serializable/fxs/pvm/convertSubnetValidator.ts b/src/serializable/fxs/pvm/convertSubnetValidator.ts index 1b8cc88f1..e10e0ef94 100644 --- a/src/serializable/fxs/pvm/convertSubnetValidator.ts +++ b/src/serializable/fxs/pvm/convertSubnetValidator.ts @@ -3,7 +3,7 @@ import type { Codec } from '../../codec'; import { serializable } from '../../common/types'; import { BigIntPr, Bytes } from '../../primitives'; import { TypeSymbols } from '../../constants'; -import { ProofOfPossession } from '../../pvm'; +import { ProofOfPossession } from '../../pvm/proofOfPossession'; import { NodeId } from '../common'; import { PChainOwner } from './pChainOwner'; diff --git a/src/serializable/pvm/increaseBalanceTx.ts b/src/serializable/pvm/increaseBalanceTx.ts index b72431617..a6df5452a 100644 --- a/src/serializable/pvm/increaseBalanceTx.ts +++ b/src/serializable/pvm/increaseBalanceTx.ts @@ -1,5 +1,5 @@ import { pack, unpack } from '../../utils/struct'; -import { BaseTx } from '../avax'; +import { BaseTx } from '../avax/baseTx'; import type { Codec } from '../codec'; import { serializable } from '../common/types'; import { TypeSymbols } from '../constants'; diff --git a/src/serializable/pvm/validator.spec.ts b/src/serializable/pvm/validator.spec.ts index b138e1866..2e60b611d 100644 --- a/src/serializable/pvm/validator.spec.ts +++ b/src/serializable/pvm/validator.spec.ts @@ -1,5 +1,12 @@ +import { testPVMCodec } from '../../fixtures/codec'; import { validator, validatorBytes } from '../../fixtures/pvm'; import { testSerialization } from '../../fixtures/utils/serializable'; import { Validator } from './validator'; -testSerialization('Validator', Validator, validator, validatorBytes); +testSerialization( + 'Validator', + Validator, + validator, + validatorBytes, + testPVMCodec, +); diff --git a/src/utils/addressMap.spec.ts b/src/utils/addressMap.spec.ts index 2f8b69c45..fecdb57c9 100644 --- a/src/utils/addressMap.spec.ts +++ b/src/utils/addressMap.spec.ts @@ -1,7 +1,10 @@ import { jest } from '@jest/globals'; import { address } from '../fixtures/common'; import { Address } from '../serializable/fxs/common'; -import { AddressMap, AddressMaps } from './addressMap'; +import { AddressMap, AddressMaps, matchOwners } from './addressMap'; +import { addressesFromBytes } from './addressesFromBytes'; +import { OutputOwners } from '../serializable'; +import { hexToBuffer } from './buffer'; describe('AddressMap', () => { const testAddress1 = address(); @@ -240,3 +243,69 @@ describe('AddressMaps', () => { ]); }); }); + +describe('matchOwners', () => { + it('matches owners', () => { + const owner1 = address(); + const owner2 = Address.fromHex('7db97c7cece249c2b98bdc0226cc4c2a57bf52fc'); + const ownerAddresses: Uint8Array[] = [owner1.toBytes(), owner2.toBytes()]; + const goodOwner = OutputOwners.fromNative(ownerAddresses, 0n, 1); + const threasholdTooHigh = OutputOwners.fromNative(ownerAddresses, 0n, 5); + const wrongOwner = OutputOwners.fromNative( + [hexToBuffer('0x12345123451234512345')], + 0n, + 5, + ); + const locked = OutputOwners.fromNative( + ownerAddresses, + 9999999999999999999999999999999999n, + 5, + ); + + const specs = [ + { + testCase: goodOwner, + expectedSigIndices: [0], + expectedAddressMap: new AddressMap([[owner1, 0]]), + }, + { + testCase: threasholdTooHigh, + expectedSigIndices: undefined, + expectedAddressMap: undefined, + }, + { + testCase: locked, + expectedSigIndices: undefined, + expectedAddressMap: undefined, + }, + { + testCase: wrongOwner, + expectedSigIndices: undefined, + expectedAddressMap: undefined, + }, + { + testCase: goodOwner, + sigindices: [1], + expectedSigIndices: [1], + expectedAddressMap: new AddressMap([[owner2, 1]]), + }, + { + testCase: goodOwner, + sigindices: [2], + expectedSigIndices: undefined, + expectedAddressMap: undefined, + }, + ]; + + specs.forEach((spec) => { + const result = matchOwners( + spec.testCase, + addressesFromBytes(ownerAddresses), + 50n, + spec.sigindices, + ); + expect(result?.sigIndicies).toEqual(spec.expectedSigIndices); + expect(result?.addressMap).toEqual(spec.expectedAddressMap); + }); + }); +}); diff --git a/src/utils/addressMap.ts b/src/utils/addressMap.ts index b1bba060a..8e3deb5a2 100644 --- a/src/utils/addressMap.ts +++ b/src/utils/addressMap.ts @@ -1,9 +1,48 @@ -import type { TransferableInput } from '../serializable'; +import type { OutputOwners, TransferableInput } from '../serializable'; import type { Utxo } from '../serializable/avax/utxo'; import { Address } from '../serializable/fxs/common'; import { addressesFromBytes } from './addressesFromBytes'; import { hexToBuffer } from './buffer'; -import { matchOwners } from './matchOwners'; + +export type MatchOwnerResult = { + sigIndicies: number[]; + addressMap: AddressMap; +}; +export const matchOwners = ( + owners: OutputOwners, + inputAddrs: Address[], + minIssuanceTime: bigint, + sigindices?: number[], +): MatchOwnerResult | undefined => { + if (owners.locktime.value() > minIssuanceTime) { + return undefined; + } + + const inputAddrSet = new Set(inputAddrs.map((a) => a.toString())); + const addressMap = owners.addrs.reduce((agg, addr, i) => { + if ( + agg.size() < owners.threshold.value() && + inputAddrSet.has(addr.value()) + ) { + // only add actual signer addresses if sigindices are known + if (sigindices?.length && !sigindices.includes(i)) { + return agg; + } + + return agg.set(addr, i); + } + return agg; + }, new AddressMap()); + + if (addressMap.size() < owners.threshold.value()) { + return undefined; + } + + return { + sigIndicies: Array.from(addressMap.values()), + addressMap: addressMap, + }; +}; export class AddressMap { constructor(initialData: [Address, number][] = []) { diff --git a/src/utils/builderUtils.ts b/src/utils/builderUtils.ts index d60be0387..2d8b16c87 100644 --- a/src/utils/builderUtils.ts +++ b/src/utils/builderUtils.ts @@ -1,7 +1,7 @@ import { TransferableInput } from '../serializable/avax'; import type { Utxo } from '../serializable/avax/utxo'; +import { matchOwners } from './addressMap'; import { addressesFromBytes } from './addressesFromBytes'; -import { matchOwners } from './matchOwners'; import { isTransferOut } from './typeGuards'; type GetImportedInputsFromUtxosOutput = { diff --git a/src/utils/matchOwners.spec.ts b/src/utils/matchOwners.spec.ts deleted file mode 100644 index 63f77ec64..000000000 --- a/src/utils/matchOwners.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Address } from '../serializable/fxs/common'; -import { address } from '../fixtures/common'; -import { OutputOwners } from '../serializable/fxs/secp256k1'; -import { addressesFromBytes } from './addressesFromBytes'; -import { AddressMap } from './addressMap'; -import { hexToBuffer } from './buffer'; -import { matchOwners } from './matchOwners'; - -describe('matchOwners', () => { - it('matches owners', () => { - const owner1 = address(); - const owner2 = Address.fromHex('7db97c7cece249c2b98bdc0226cc4c2a57bf52fc'); - const ownerAddresses: Uint8Array[] = [owner1.toBytes(), owner2.toBytes()]; - const goodOwner = OutputOwners.fromNative(ownerAddresses, 0n, 1); - const threasholdTooHigh = OutputOwners.fromNative(ownerAddresses, 0n, 5); - const wrongOwner = OutputOwners.fromNative( - [hexToBuffer('0x12345123451234512345')], - 0n, - 5, - ); - const locked = OutputOwners.fromNative( - ownerAddresses, - 9999999999999999999999999999999999n, - 5, - ); - - const specs = [ - { - testCase: goodOwner, - expectedSigIndices: [0], - expectedAddressMap: new AddressMap([[owner1, 0]]), - }, - { - testCase: threasholdTooHigh, - expectedSigIndices: undefined, - expectedAddressMap: undefined, - }, - { - testCase: locked, - expectedSigIndices: undefined, - expectedAddressMap: undefined, - }, - { - testCase: wrongOwner, - expectedSigIndices: undefined, - expectedAddressMap: undefined, - }, - { - testCase: goodOwner, - sigindices: [1], - expectedSigIndices: [1], - expectedAddressMap: new AddressMap([[owner2, 1]]), - }, - { - testCase: goodOwner, - sigindices: [2], - expectedSigIndices: undefined, - expectedAddressMap: undefined, - }, - ]; - - specs.forEach((spec) => { - const result = matchOwners( - spec.testCase, - addressesFromBytes(ownerAddresses), - 50n, - spec.sigindices, - ); - expect(result?.sigIndicies).toEqual(spec.expectedSigIndices); - expect(result?.addressMap).toEqual(spec.expectedAddressMap); - }); - }); -}); diff --git a/src/utils/matchOwners.ts b/src/utils/matchOwners.ts deleted file mode 100644 index 2a4bd55c6..000000000 --- a/src/utils/matchOwners.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Address } from '../serializable/fxs/common'; -import type { OutputOwners } from '../serializable/fxs/secp256k1'; -import { AddressMap } from './addressMap'; - -export type MatchOwnerResult = { - sigIndicies: number[]; - addressMap: AddressMap; -}; -export const matchOwners = ( - owners: OutputOwners, - inputAddrs: Address[], - minIssuanceTime: bigint, - sigindices?: number[], -): MatchOwnerResult | undefined => { - if (owners.locktime.value() > minIssuanceTime) { - return undefined; - } - - const inputAddrSet = new Set(inputAddrs.map((a) => a.toString())); - const addressMap = owners.addrs.reduce((agg, addr, i) => { - if ( - agg.size() < owners.threshold.value() && - inputAddrSet.has(addr.value()) - ) { - // only add actual signer addresses if sigindices are known - if (sigindices?.length && !sigindices.includes(i)) { - return agg; - } - - return agg.set(addr, i); - } - return agg; - }, new AddressMap()); - - if (addressMap.size() < owners.threshold.value()) { - return undefined; - } - - return { - sigIndicies: Array.from(addressMap.values()), - addressMap: addressMap, - }; -}; diff --git a/src/vms/evm/builder.ts b/src/vms/evm/builder.ts index 10629beb8..ba9e2605a 100644 --- a/src/vms/evm/builder.ts +++ b/src/vms/evm/builder.ts @@ -9,9 +9,8 @@ import { } from '../../serializable/fxs/secp256k1'; import { BigIntPr, Int } from '../../serializable/primitives'; import { addressesFromBytes } from '../../utils'; -import { AddressMap, AddressMaps } from '../../utils/addressMap'; +import { AddressMap, AddressMaps, matchOwners } from '../../utils/addressMap'; import { costCorethTx } from '../../utils/costs'; -import { matchOwners } from '../../utils/matchOwners'; import { compareEVMOutputs } from '../../utils/sort'; import { EVMUnsignedTx } from '../common/evmUnsignedTx'; import type { UnsignedTx } from '../common/unsignedTx'; diff --git a/src/vms/pvm/etna-builder/builder.ts b/src/vms/pvm/etna-builder/builder.ts index 9572c489a..749bb98fa 100644 --- a/src/vms/pvm/etna-builder/builder.ts +++ b/src/vms/pvm/etna-builder/builder.ts @@ -49,8 +49,8 @@ import { addressesFromBytes, bytesCompare, isTransferOut, + matchOwners, } from '../../../utils'; -import { matchOwners } from '../../../utils/matchOwners'; import { compareTransferableOutputs } from '../../../utils/sort'; import { baseTxUnsafePvm, UnsignedTx } from '../../common'; import { addDimensions, createDimensions } from '../../common/fees/dimensions'; diff --git a/src/vms/pvm/txs/fee/complexity.ts b/src/vms/pvm/txs/fee/complexity.ts index ad52e1740..64b2da25d 100644 --- a/src/vms/pvm/txs/fee/complexity.ts +++ b/src/vms/pvm/txs/fee/complexity.ts @@ -48,7 +48,7 @@ import { isStakeableLockIn, isStakeableLockOut, isTransferOut, -} from '../../../../utils'; +} from '../../../../utils/typeGuards'; import type { Dimensions } from '../../../common/fees/dimensions'; import { FeeDimensions, diff --git a/src/vms/utils/calculateSpend/utils/verifySignaturesMatch.ts b/src/vms/utils/calculateSpend/utils/verifySignaturesMatch.ts index 67f13ebd5..a01a1468b 100644 --- a/src/vms/utils/calculateSpend/utils/verifySignaturesMatch.ts +++ b/src/vms/utils/calculateSpend/utils/verifySignaturesMatch.ts @@ -1,6 +1,6 @@ -import type { MatchOwnerResult } from '../../../../utils/matchOwners'; -import { matchOwners } from '../../../../utils/matchOwners'; import type { Address, TransferOutput } from '../../../../serializable'; +import type { MatchOwnerResult } from '../../../../utils'; +import { matchOwners } from '../../../../utils'; export type verifySigMatchItem = Required<{ sigData: MatchOwnerResult; From 96041b694b341ed4fd69b0baaaf61c3eef354eec Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Thu, 24 Oct 2024 13:53:15 -0600 Subject: [PATCH 5/7] test: add newIncreaseBalanceTx builder tests --- src/vms/pvm/etna-builder/builder.test.ts | 145 +++++++++++++++++++---- src/vms/pvm/etna-builder/builder.ts | 17 ++- src/vms/pvm/txs/fee/constants.ts | 2 +- 3 files changed, 138 insertions(+), 26 deletions(-) diff --git a/src/vms/pvm/etna-builder/builder.test.ts b/src/vms/pvm/etna-builder/builder.test.ts index 0461c6dcb..6ce9c468d 100644 --- a/src/vms/pvm/etna-builder/builder.test.ts +++ b/src/vms/pvm/etna-builder/builder.test.ts @@ -40,6 +40,7 @@ import { AddPermissionlessDelegatorTx, ConvertSubnetTx, ProofOfPossession, + IncreaseBalanceTx, } from '../../../serializable/pvm'; import { BaseTx as AvaxBaseTx } from '../../../serializable/avax'; import { hexToBuffer } from '../../../utils'; @@ -55,6 +56,7 @@ import { newCreateSubnetTx, newExportTx, newImportTx, + newIncreaseBalanceTx, newRemoveSubnetValidatorTx, newTransferSubnetOwnershipTx, } from './builder'; @@ -127,6 +129,7 @@ const checkFeeIsCorrect = ({ feeState, additionalInputs = [], additionalOutputs = [], + additionalFee = 0n, }: { unsignedTx: UnsignedTx; inputs: readonly TransferableInput[]; @@ -134,6 +137,7 @@ const checkFeeIsCorrect = ({ feeState: FeeState; additionalInputs?: readonly TransferableInput[]; additionalOutputs?: readonly TransferableOutput[]; + additionalFee?: bigint; }): [ amountConsumed: Record, expectedAmountConsumed: Record, @@ -155,7 +159,7 @@ const checkFeeIsCorrect = ({ ); const expectedAmountBurned = addAmounts( - new Map([[testAvaxAssetID.toString(), expectedFee]]), + new Map([[testAvaxAssetID.toString(), expectedFee + additionalFee]]), ); const expectedAmountConsumed = addAmounts( @@ -1022,11 +1026,12 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { blsSignatureBytes(), ); const pChainOwner = PChainOwner.fromNative([testAddress1], 1); + const validatorBalanceAmount = BigInt(1 * 1e9); const validator = ConvertSubnetValidator.fromNative( nodeId, BigInt(1 * 1e9), - BigInt(0 * 1e9), + validatorBalanceAmount, signer, pChainOwner, pChainOwner, @@ -1055,6 +1060,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, feeState, + additionalFee: validatorBalanceAmount, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -1063,7 +1069,11 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { AvaxBaseTx.fromNative( testContext.networkID, testContext.pBlockchainID, - [getTransferableOutForTest(utxoInputAmt - expectedFee)], + [ + getTransferableOutForTest( + utxoInputAmt - expectedFee - validatorBalanceAmount, + ), + ], [getTransferableInputForTest(utxoInputAmt)], new Uint8Array(), ), @@ -1075,36 +1085,125 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { ); expectTxs(unsignedTx.getTx(), expectedTx); }); + + it('should throw error if weight on a validator is 0', () => { + const validator = ConvertSubnetValidator.fromNative( + nodeId, + BigInt(0 * 1e9), + BigInt(0 * 1e9), + new ProofOfPossession(blsPublicKeyBytes(), blsSignatureBytes()), + PChainOwner.fromNative([testAddress1], 1), + PChainOwner.fromNative([testAddress1], 1), + ); + const utxos = testUtxos(); + try { + newConvertSubnetTx( + { + fromAddressesBytes, + feeState, + utxos, + subnetAuth: [0], + subnetId: Id.fromHex(testSubnetId).toString(), + address: testAddress1, + validators: [validator], + chainId: 'h5vH4Zz53MTN2jf72axZCfo1VbG1cMR6giR4Ra2TTpEmqxDWB', + }, + testContext, + ); + } catch (error) { + expect((error as Error).message).toEqual( + 'Validator weight must be greater than 0', + ); + } + }); }); - it('should throw error if weight on a validator is 0', () => { - const validator = ConvertSubnetValidator.fromNative( - nodeId, - BigInt(0 * 1e9), - BigInt(0 * 1e9), - new ProofOfPossession(blsPublicKeyBytes(), blsSignatureBytes()), - PChainOwner.fromNative([testAddress1], 1), - PChainOwner.fromNative([testAddress1], 1), - ); + describe('IncreaseBalanceTx', () => { + it('should create an IncreaseBalanceTx', () => { + const validUtxoAmount = BigInt(30 * 1e9); + const balance = BigInt(1 * 1e9); + const validationId = 'test'; + + const utxos = [ + getLockedUTXO(), // Locked and should be ignored. + getNotTransferOutput(), // Invalid and should be ignored. + // AVAX Assets + getValidUtxo(new BigIntPr(validUtxoAmount), testAvaxAssetID), + // Non-AVAX Assets (Jupiter) + getValidUtxo(new BigIntPr(BigInt(15 * 1e9)), Id.fromString('jupiter')), + getValidUtxo(new BigIntPr(BigInt(11 * 1e9)), Id.fromString('jupiter')), + // Non-AVAX Asset (Mars) + getValidUtxo(new BigIntPr(BigInt(9 * 1e9)), Id.fromString('mars')), + ]; + + const unsignedTx = newIncreaseBalanceTx( + { + balance, + fromAddressesBytes, + feeState, + utxos, + validationId, + }, + testContext, + ); + + const { baseTx } = unsignedTx.getTx() as IncreaseBalanceTx; + const { inputs, outputs } = baseTx; + + const [amountConsumed, expectedAmountConsumed, expectedFee] = + checkFeeIsCorrect({ + unsignedTx, + inputs, + outputs, + feeState, + additionalFee: balance, + }); + + expect(amountConsumed).toEqual(expectedAmountConsumed); + + const expectedTx = new IncreaseBalanceTx( + AvaxBaseTx.fromNative( + testContext.networkID, + testContext.pBlockchainID, + [getTransferableOutForTest(validUtxoAmount - expectedFee - balance)], + [getTransferableInputForTest(validUtxoAmount)], + new Uint8Array(), + ), + Id.fromString(validationId), + new BigIntPr(balance), + ); + expectTxs(unsignedTx.getTx(), expectedTx); + }); + }); + + it('should throw an error if the balance is less than or equal to 0', () => { + const validationId = 'test'; const utxos = testUtxos(); - try { - newConvertSubnetTx( + + expect(() => { + newIncreaseBalanceTx( { + balance: 0n, fromAddressesBytes, feeState, utxos, - subnetAuth: [0], - subnetId: Id.fromHex(testSubnetId).toString(), - address: testAddress1, - validators: [validator], - chainId: 'h5vH4Zz53MTN2jf72axZCfo1VbG1cMR6giR4Ra2TTpEmqxDWB', + validationId, }, testContext, ); - } catch (error) { - expect((error as Error).message).toEqual( - 'Validator weight must be greater than 0', + }).toThrow('Balance must be greater than 0'); + + expect(() => { + newIncreaseBalanceTx( + { + balance: -1n, + fromAddressesBytes, + feeState, + utxos, + validationId, + }, + testContext, ); - } + }).toThrow('Balance must be greater than 0'); }); }); diff --git a/src/vms/pvm/etna-builder/builder.ts b/src/vms/pvm/etna-builder/builder.ts index 749bb98fa..8181f4843 100644 --- a/src/vms/pvm/etna-builder/builder.ts +++ b/src/vms/pvm/etna-builder/builder.ts @@ -1452,13 +1452,21 @@ export const newConvertSubnetTx: TxBuilderFn = ( }; export type IncreaseBalanceTxProps = TxProps<{ + /** + * Amount to increase the balance by. + * + * Must be greater than 0. + */ balance: bigint; + /** + * ID corresponding to the validator + */ validationId: string; }>; /** - * Creates a new unsigned PVM convert subnet transaction - * (`ConvertSubnetTx`) using calculated dynamic fees. + * Creates a new unsigned PVM increase balance transaction + * (`IncreaseBalanceTx`) using calculated dynamic fees. * * @param props * @param context @@ -1477,6 +1485,10 @@ export const newIncreaseBalanceTx: TxBuilderFn = ( }, context, ) => { + if (balance <= 0n) { + throw new Error('Balance must be greater than 0'); + } + const toBurn = new Map([[context.avaxAssetID, balance]]); const bytesComplexity = getBytesComplexity(memo); @@ -1505,6 +1517,7 @@ export const newIncreaseBalanceTx: TxBuilderFn = ( ); const { changeOutputs, inputs, inputUTXOs } = spendResults; + const addressMaps = getAddressMaps({ inputs, inputUTXOs, diff --git a/src/vms/pvm/txs/fee/constants.ts b/src/vms/pvm/txs/fee/constants.ts index d2be57f04..e926881bb 100644 --- a/src/vms/pvm/txs/fee/constants.ts +++ b/src/vms/pvm/txs/fee/constants.ts @@ -79,7 +79,7 @@ export const INTRINSIC_OUTPUT_DB_WRITE = 1; export const INTRINSIC_BASE_TX_COMPLEXITIES: Dimensions = { [FeeDimensions.Bandwidth]: - 2 + // codec version + SHORT_LEN + // codec version INT_LEN + // typeID INT_LEN + // networkID ID_LEN + // blockchainID From cc04f7676b81ab028568e3cfaddddf6628ca4125 Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Thu, 24 Oct 2024 14:01:56 -0600 Subject: [PATCH 6/7] docs: add example for increaseBalanceTx --- examples/p-chain/etna/convertSubnet.ts | 4 +-- examples/p-chain/etna/increaseBalanceTx.ts | 35 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 examples/p-chain/etna/increaseBalanceTx.ts diff --git a/examples/p-chain/etna/convertSubnet.ts b/examples/p-chain/etna/convertSubnet.ts index 13d526d1f..3cc927986 100644 --- a/examples/p-chain/etna/convertSubnet.ts +++ b/examples/p-chain/etna/convertSubnet.ts @@ -20,7 +20,7 @@ const BALANCE_AVAX: number = 1; * @param subnetId the ID of the subnet that is created via `createSubnetTx`. * @returns The resulting transaction's ID. */ -const convertSubnetTxExmaple = async () => { +const convertSubnetTxExample = async () => { const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, @@ -74,4 +74,4 @@ const convertSubnetTxExmaple = async () => { return pvmApi.issueSignedTx(tx.getSignedTx()); }; -convertSubnetTxExmaple().then(console.log); +convertSubnetTxExample().then(console.log); diff --git a/examples/p-chain/etna/increaseBalanceTx.ts b/examples/p-chain/etna/increaseBalanceTx.ts new file mode 100644 index 000000000..ec6152d18 --- /dev/null +++ b/examples/p-chain/etna/increaseBalanceTx.ts @@ -0,0 +1,35 @@ +import { addTxSignatures, pvm, utils } from '../../../src'; +import { setupEtnaExample } from './utils/etna-helper'; +import { getEnvVars } from '../../utils/getEnvVars'; + +const BALANCE_AVAX: number = 1; +const VALIDATION_ID: string = ''; + +const increaseBalanceTx = async () => { + const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars(); + const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL); + + const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); + + const testPAddr = utils.bech32ToBytes(P_CHAIN_ADDRESS); + + const unsignedTx = pvm.e.newIncreaseBalanceTx( + { + balance: BigInt(BALANCE_AVAX * 1e9), + feeState, + fromAddressesBytes: [testPAddr], + utxos, + validationId: VALIDATION_ID, + }, + context, + ); + + await addTxSignatures({ + unsignedTx, + privateKeys: [utils.hexToBuffer(PRIVATE_KEY)], + }); + + return pvmApi.issueSignedTx(unsignedTx.getSignedTx()); +}; + +await increaseBalanceTx().then(console.log); From 52ba14bf179278994b3762166b8ef399a58951e8 Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Fri, 25 Oct 2024 09:44:21 -0600 Subject: [PATCH 7/7] chore: adjust example envs --- examples/p-chain/etna/convertSubnet.ts | 2 +- examples/utils/getEnvVars.ts | 45 +++++++++++++++++++------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/examples/p-chain/etna/convertSubnet.ts b/examples/p-chain/etna/convertSubnet.ts index 3cc927986..069425ec3 100644 --- a/examples/p-chain/etna/convertSubnet.ts +++ b/examples/p-chain/etna/convertSubnet.ts @@ -28,7 +28,7 @@ const convertSubnetTxExample = async () => { NODE_ID, BLS_PUBLIC_KEY, BLS_SIGNATURE, - } = getEnvVars(); + } = getEnvVars(['BLS_PUBLIC_KEY', 'BLS_SIGNATURE', 'NODE_ID']); const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL); const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); diff --git a/examples/utils/getEnvVars.ts b/examples/utils/getEnvVars.ts index 9673ede90..30e01c53e 100644 --- a/examples/utils/getEnvVars.ts +++ b/examples/utils/getEnvVars.ts @@ -3,37 +3,58 @@ const P_CHAIN_ADDRESS = process.env['P_CHAIN_ADDRESS']; const PRIVATE_KEY = process.env['PRIVATE_KEY']; const X_CHAIN_ADDRESS = process.env['X_CHAIN_ADDRESS']; const C_CHAIN_ADDRESS = process.env['C_CHAIN_ADDRESS']; -const NODE_ID = process.env['NODE_ID']; -const BLS_PUBLIC_KEY = process.env['BLS_PUBLIC_KEY']; -const BLS_SIGNATURE = process.env['BLS_SIGNATURE']; const CORETH_ADDRESS = process.env['CORETH_ADDRESS']; -export const getEnvVars = () => { +type PrimaryEnvKeys = + | 'AVAX_PUBLIC_URL' + | 'P_CHAIN_ADDRESS' + | 'PRIVATE_KEY' + | 'X_CHAIN_ADDRESS' + | 'C_CHAIN_ADDRESS' + | 'CORETH_ADDRESS'; + +type ExampleEnvs = Record; + +export const getEnvVars = ( + additionalEnvsKeys: T[] = [], +): ExampleEnvs => { if ( !( AVAX_PUBLIC_URL && P_CHAIN_ADDRESS && PRIVATE_KEY && X_CHAIN_ADDRESS && - NODE_ID && - BLS_PUBLIC_KEY && - BLS_SIGNATURE && C_CHAIN_ADDRESS && CORETH_ADDRESS ) ) { - throw new Error('Missing environment variable(s).'); + throw new Error( + 'Missing required environment variable(s). Please check your .env file.', + ); } - return { + const additionalEnvs = additionalEnvsKeys.reduce((acc, key) => { + const env = process.env[key]; + + if (!env) { + throw new Error(`Missing environment variable: ${key}`); + } + + return { + ...acc, + [key]: env, + }; + }, {} as Record); + + const envs: ExampleEnvs = { + ...additionalEnvs, AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS, - NODE_ID, - BLS_PUBLIC_KEY, - BLS_SIGNATURE, C_CHAIN_ADDRESS, CORETH_ADDRESS, }; + + return envs; };