Skip to content

Commit 94e1361

Browse files
committed
fix: address review feedback and fix adding stakeouts
1 parent 141294d commit 94e1361

File tree

7 files changed

+98
-127
lines changed

7 files changed

+98
-127
lines changed

src/vms/pvm/etna-builder/spend-reducers/useSpendableLockedUTXOs.test.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -162,30 +162,6 @@ describe('useSpendableLockedUTXOs', () => {
162162
expect(stakeOutputs).toHaveLength(0);
163163
});
164164

165-
it('should add stake outputs even with no UTXOs', () => {
166-
const toBurn = new Map();
167-
const toStake = new Map([[testContext.avaxAssetID, 1_000n]]);
168-
169-
const initialState = getInitialReducerState({
170-
excessAVAX: 0n,
171-
spendOptions: {
172-
minIssuanceTime: 100n,
173-
},
174-
toBurn,
175-
toStake,
176-
utxos: [],
177-
});
178-
179-
const spendHelper = getSpendHelper({ toBurn, toStake });
180-
181-
useSpendableLockedUTXOs(initialState, spendHelper, testContext);
182-
const { stakeOutputs } = spendHelper.getInputsOutputs();
183-
184-
expect(stakeOutputs).toHaveLength(1);
185-
expect(stakeOutputs[0].assetId.toString()).toEqual(testContext.avaxAssetID);
186-
expect(stakeOutputs[0].amount()).toEqual(1_000n);
187-
});
188-
189165
it('should add spendable locked UTXO with change', () => {
190166
const toBurn = new Map();
191167
const toStake = new Map([[testAvaxAssetID.toString(), 1_000n]]);

src/vms/pvm/etna-builder/spend-reducers/useSpendableLockedUTXOs.ts

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const getUsableUTXOsFilter =
3333
}
3434

3535
// 1b. Ensure UTXO is stakeable.
36-
if (!(state.spendOptions.minIssuanceTime < utxo.output.getLocktime())) {
36+
if (state.spendOptions.minIssuanceTime >= utxo.output.getLocktime()) {
3737
return false;
3838
}
3939

@@ -43,7 +43,7 @@ export const getUsableUTXOsFilter =
4343
}
4444

4545
// 1d. Filter out UTXOs that aren't needed for staking.
46-
if ((state.toStake.get(utxo.assetId.value()) ?? 0n) === 0n) {
46+
if (!state.toStake.has(utxo.assetId.value())) {
4747
return false;
4848
}
4949

@@ -85,19 +85,14 @@ export const useSpendableLockedUTXOs: SpendReducerFunction = (
8585
utxo.utxoId,
8686
utxo.assetId,
8787
new StakeableLockIn(
88-
// StakeableLockOut
8988
new BigIntPr(utxoInfo.stakeableLocktime),
90-
TransferInput.fromNative(
91-
// TransferOutput
92-
utxoInfo.amount,
93-
sigData.sigIndicies,
94-
),
89+
TransferInput.fromNative(utxoInfo.amount, sigData.sigIndicies),
9590
),
9691
),
9792
);
9893

9994
// 3c. Consume the locked asset and get the remaining amount.
100-
const remainingAmount = spendHelper.consumeLockedAsset(
95+
const [remainingAmount] = spendHelper.consumeLockedAsset(
10196
utxoInfo.assetId,
10297
utxoInfo.amount,
10398
);
@@ -133,20 +128,5 @@ export const useSpendableLockedUTXOs: SpendReducerFunction = (
133128
}
134129
}
135130

136-
// 4. Add all remaining stake amounts assuming they are unlocked.
137-
for (const [assetId, amount] of state.toStake) {
138-
if (amount === 0n) {
139-
continue;
140-
}
141-
142-
spendHelper.addStakedOutput(
143-
TransferableOutput.fromNative(
144-
assetId,
145-
amount,
146-
state.spendOptions.changeAddresses,
147-
),
148-
);
149-
}
150-
151131
return state;
152132
};

src/vms/pvm/etna-builder/spend-reducers/useUnlockedUTXOs.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ export const useUnlockedUTXOs: SpendReducerFunction = (
9090
{ otherVerifiedUsableUTXOs: [], avaxVerifiedUsableUTXOs: [] },
9191
);
9292

93+
const changeOwner = OutputOwners.fromNative(
94+
state.spendOptions.changeAddresses,
95+
0n,
96+
1,
97+
);
98+
9399
// 4. Handle all the non-AVAX asset UTXOs first.
94100
for (const { sigData, data: utxo } of otherVerifiedUsableUTXOs) {
95101
const utxoInfo = getUtxoInfo(utxo);
@@ -114,30 +120,27 @@ export const useUnlockedUTXOs: SpendReducerFunction = (
114120
);
115121

116122
// 4c. Consume the asset and get the remaining amount.
117-
const remainingAmount = spendHelper.consumeAsset(
123+
const [remainingAmount, amountToStake] = spendHelper.consumeAsset(
118124
utxoInfo.assetId,
119125
utxoInfo.amount,
120126
);
121127

122128
// 4d. If "amountToStake" is greater than 0, add the stake output.
123-
// TODO: Implement or determine if needed.
129+
if (amountToStake > 0n) {
130+
spendHelper.addStakedOutput(
131+
new TransferableOutput(
132+
utxo.assetId,
133+
new TransferOutput(new BigIntPr(amountToStake), changeOwner),
134+
),
135+
);
136+
}
124137

125138
// 4e. Add the change output if there is any remaining amount.
126139
if (remainingAmount > 0n) {
127140
spendHelper.addChangeOutput(
128141
new TransferableOutput(
129142
utxo.assetId,
130-
new TransferableOutput(
131-
utxo.assetId,
132-
new TransferOutput(
133-
new BigIntPr(remainingAmount),
134-
OutputOwners.fromNative(
135-
state.spendOptions.changeAddresses,
136-
0n,
137-
1,
138-
),
139-
),
140-
),
143+
new TransferOutput(new BigIntPr(remainingAmount), changeOwner),
141144
),
142145
);
143146
}
@@ -170,11 +173,20 @@ export const useUnlockedUTXOs: SpendReducerFunction = (
170173
),
171174
);
172175

173-
const remainingAmount = spendHelper.consumeAsset(
176+
const [remainingAmount, amountToStake] = spendHelper.consumeAsset(
174177
context.avaxAssetID,
175178
utxoInfo.amount,
176179
);
177180

181+
if (amountToStake > 0n) {
182+
spendHelper.addStakedOutput(
183+
new TransferableOutput(
184+
utxo.assetId,
185+
new TransferOutput(new BigIntPr(amountToStake), changeOwner),
186+
),
187+
);
188+
}
189+
178190
excessAVAX += remainingAmount;
179191

180192
// The ownerOverride is no longer needed. Clear it.

src/vms/pvm/etna-builder/spendHelper.test.ts

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -167,65 +167,65 @@ describe('src/vms/pvm/etna-builder/spendHelper', () => {
167167

168168
expect(spendHelper.shouldConsumeAsset('asset')).toBe(false);
169169
});
170+
});
170171

171-
describe('SpendHelper.consumeLockedAsset', () => {
172-
const testCases = [
173-
{
174-
description: 'consumes the full amount',
175-
toStake: new Map([['asset', 1n]]),
176-
asset: 'asset',
177-
amount: 1n,
178-
expected: 0n,
179-
},
180-
{
181-
description: 'consumes a partial amount',
182-
toStake: new Map([['asset', 1n]]),
183-
asset: 'asset',
184-
amount: 2n,
185-
expected: 1n,
186-
},
187-
{
188-
description: 'consumes nothing',
189-
toStake: new Map([['asset', 1n]]),
190-
asset: 'asset',
191-
amount: 0n,
192-
expected: 0n,
193-
},
194-
{
195-
description: 'consumes nothing when asset not in toStake',
196-
toStake: new Map(),
197-
asset: 'asset',
198-
amount: 1n,
199-
expected: 1n,
200-
},
201-
{
202-
description: 'consumes nothing when asset in toStake with 0 value',
203-
toStake: new Map([['asset', 0n]]),
204-
asset: 'asset',
205-
amount: 1n,
206-
expected: 1n,
207-
},
208-
];
209-
210-
test.each(testCases)(
211-
'$description',
212-
({ toStake, asset, amount, expected }) => {
213-
const spendHelper = new SpendHelper({
214-
...DEFAULT_PROPS,
215-
toStake,
216-
});
217-
218-
expect(spendHelper.consumeLockedAsset(asset, amount)).toBe(expected);
219-
},
220-
);
172+
describe('SpendHelper.consumeLockedAsset', () => {
173+
const testCases = [
174+
{
175+
description: 'consumes the full amount',
176+
toStake: new Map([['asset', 1n]]),
177+
asset: 'asset',
178+
amount: 1n,
179+
expected: 0n,
180+
},
181+
{
182+
description: 'consumes a partial amount',
183+
toStake: new Map([['asset', 1n]]),
184+
asset: 'asset',
185+
amount: 2n,
186+
expected: 1n,
187+
},
188+
{
189+
description: 'consumes nothing',
190+
toStake: new Map([['asset', 1n]]),
191+
asset: 'asset',
192+
amount: 0n,
193+
expected: 0n,
194+
},
195+
{
196+
description: 'consumes nothing when asset not in toStake',
197+
toStake: new Map(),
198+
asset: 'asset',
199+
amount: 1n,
200+
expected: 1n,
201+
},
202+
{
203+
description: 'consumes nothing when asset in toStake with 0 value',
204+
toStake: new Map([['asset', 0n]]),
205+
asset: 'asset',
206+
amount: 1n,
207+
expected: 1n,
208+
},
209+
];
221210

222-
test('throws an error when amount is negative', () => {
223-
const spendHelper = new SpendHelper(DEFAULT_PROPS);
211+
test.each(testCases)(
212+
'$description',
213+
({ toStake, asset, amount, expected }) => {
214+
const spendHelper = new SpendHelper({
215+
...DEFAULT_PROPS,
216+
toStake,
217+
});
224218

225-
expect(() => {
226-
spendHelper.consumeLockedAsset('asset', -1n);
227-
}).toThrow('Amount to consume must be greater than or equal to 0');
228-
});
219+
expect(spendHelper.consumeLockedAsset(asset, amount)[0]).toBe(expected);
220+
},
221+
);
222+
223+
test('throws an error when amount is negative', () => {
224+
const spendHelper = new SpendHelper(DEFAULT_PROPS);
225+
226+
expect(() => {
227+
spendHelper.consumeLockedAsset('asset', -1n);
228+
}).toThrow('Amount to consume must be greater than or equal to 0');
229229
});
230230
});
231231

@@ -284,7 +284,7 @@ describe('src/vms/pvm/etna-builder/spendHelper', () => {
284284
toBurn,
285285
});
286286

287-
expect(spendHelper.consumeAsset(asset, amount)).toBe(expected);
287+
expect(spendHelper.consumeAsset(asset, amount)[0]).toBe(expected);
288288
},
289289
);
290290

src/vms/pvm/etna-builder/spendHelper.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,12 @@ export class SpendHelper {
160160
*
161161
* @param {string} assetId - The ID of the asset to consume.
162162
* @param {bigint} amount - The amount of the asset to consume.
163-
* @returns {bigint} The remaining amount of the asset after consumption.
163+
* @returns A tuple of the remaining amount in the first position and the amount to stake in the second position.
164164
*/
165-
consumeLockedAsset(assetId: string, amount: bigint): bigint {
165+
consumeLockedAsset(
166+
assetId: string,
167+
amount: bigint,
168+
): [remainingAmount: bigint, amountToStake: bigint] {
166169
if (amount < 0n) {
167170
throw new Error('Amount to consume must be greater than or equal to 0');
168171
}
@@ -179,17 +182,20 @@ export class SpendHelper {
179182

180183
this.toStake.set(assetId, remainingAmountToStake - amountToStake);
181184

182-
return amount - amountToStake;
185+
return [amount - amountToStake, amountToStake];
183186
}
184187

185188
/**
186189
* Consumes an asset based on its asset ID and amount.
187190
*
188191
* @param {string} assetId - The ID of the asset to consume.
189192
* @param {bigint} amount - The amount of the asset to consume.
190-
* @returns {bigint} The remaining amount of the asset after consumption.
193+
* @returns A tuple of the remaining amount in the first position and the amount to stake in the second position.
191194
*/
192-
consumeAsset(assetId: string, amount: bigint): bigint {
195+
consumeAsset(
196+
assetId: string,
197+
amount: bigint,
198+
): [remainingAmount: bigint, amountToStake: bigint] {
193199
if (amount < 0n) {
194200
throw new Error('Amount to consume must be greater than or equal to 0');
195201
}

src/vms/pvm/txs/fee/calculator.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import { getTxComplexity } from './complexity';
1010
* transaction must pay for valid inclusion into a block.
1111
*/
1212
export const calculateFee = (
13-
// TODO: Do we need this to be UnsignedTx?
14-
// If so, we can use .getTx() to get the Transaction.
1513
tx: Transaction,
1614
weights: Dimensions,
1715
price: bigint,

src/vms/pvm/txs/fee/complexity.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ export const getOwnerComplexity = (outputOwners: OutputOwners): Dimensions => {
188188
* It does not include the typeID of the credential.
189189
*/
190190
export const getAuthComplexity = (input: Serializable): Dimensions => {
191-
// TODO: Not a fan of this. May be better to re-type `subnetAuth` as `Input` in `AddSubnetValidatorTx`?
192191
if (!(input instanceof Input)) {
193192
throw new Error(
194193
'Unable to calculate auth complexity of transaction. Expected Input as subnet auth.',

0 commit comments

Comments
 (0)