From 244712400429e47a028d91a2f3eda0a7b5ca435f Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Wed, 21 Aug 2024 16:42:52 +0800 Subject: [PATCH 1/4] bug: fix injectNeededCapacity rare border capacity issue --- packages/core/src/helpers/capacity.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/core/src/helpers/capacity.ts b/packages/core/src/helpers/capacity.ts index 4114643..d9fa431 100644 --- a/packages/core/src/helpers/capacity.ts +++ b/packages/core/src/helpers/capacity.ts @@ -6,6 +6,7 @@ import { Address, Script, Cell } from '@ckb-lumos/base/lib'; import { common, FromInfo } from '@ckb-lumos/common-scripts/lib'; import { fromInfoToAddress } from './address'; import { isScriptValueEquals } from './script'; +import {} from '@ckb-lumos/lumos/helpers'; /** * Calculate target cell's minimal occupied capacity by lock script. @@ -166,6 +167,7 @@ export function calculateNeededCapacity(props: { snapshot: CapacitySnapshot; neededCapacity: BI; exceedCapacity: BI; + add_minimal_change_cell: boolean; } { let txSkeleton = props.txSkeleton; @@ -177,6 +179,7 @@ export function calculateNeededCapacity(props: { let exceedCapacity = snapshot.inputsRemainCapacity; let neededCapacity = snapshot.outputsRemainCapacity.add(extraCapacity); + let add_minimal_change_cell = false; // Collect one more cell if: // 1. Has sufficient capacity for transaction construction @@ -184,8 +187,9 @@ export function calculateNeededCapacity(props: { const sufficientForTransaction = neededCapacity.lte(0) && exceedCapacity.gt(0); const insufficientForChangeCell = exceedCapacity.lt(minChangeCapacity); if (sufficientForTransaction && insufficientForChangeCell) { - neededCapacity = minChangeCapacity; + neededCapacity = minChangeCapacity.sub(exceedCapacity); exceedCapacity = BI.from(0); + add_minimal_change_cell = true; } if (neededCapacity.lt(0)) { @@ -199,6 +203,7 @@ export function calculateNeededCapacity(props: { snapshot, neededCapacity, exceedCapacity, + add_minimal_change_cell, }; } @@ -234,6 +239,24 @@ export async function injectNeededCapacity(props: { const before: CapacitySnapshot = calculated.snapshot; let after: CapacitySnapshot | undefined; + let enableDeductCapacity = props.enableDeductCapacity; + + // If the exceeded capaicty cannot cover the new minimal change cell, we need to add one more cell to collect + // the new more needed capacity + if (calculated.add_minimal_change_cell) { + txSkeleton = txSkeleton.update('outputs', (outputs) => { + let changeLock = helpers.parseAddress(changeAddress, { config }); + const minimalChangeCell: Cell = { + cellOutput: { + capacity: minimalCellCapacityByLock(changeLock).toHexString(), + lock: changeLock, + }, + data: '0x', + }; + return outputs.push(minimalChangeCell); + }); + enableDeductCapacity = true; + } // Collect needed capacity using `common.injectCapacity` API from lumos if (calculated.neededCapacity.gt(0)) { @@ -244,7 +267,7 @@ export async function injectNeededCapacity(props: { props.changeAddress, void 0, { - enableDeductCapacity: props.enableDeductCapacity, + enableDeductCapacity, config: props.config, }, ); From 592c2587b34349d7d1c4413b8028ce4f0ab19cd5 Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Wed, 21 Aug 2024 22:38:53 +0800 Subject: [PATCH 2/4] feat: enforce enableDeductCapacity always to be true --- .../api/joints/spore/injectLiveSporeCell.ts | 8 +-- packages/core/src/helpers/capacity.ts | 52 ++++++------------- packages/core/src/helpers/fee.ts | 21 +++++++- 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/packages/core/src/api/joints/spore/injectLiveSporeCell.ts b/packages/core/src/api/joints/spore/injectLiveSporeCell.ts index 2a11111..e657cab 100644 --- a/packages/core/src/api/joints/spore/injectLiveSporeCell.ts +++ b/packages/core/src/api/joints/spore/injectLiveSporeCell.ts @@ -48,12 +48,14 @@ export async function injectLiveSporeCell(props: { input: sporeCell, addOutput: props.addOutput, updateOutput(cell) { - if (props.capacityMargin !== void 0) { - cell = setAbsoluteCapacityMargin(cell, props.capacityMargin); - } + // May contain code about changing scripts, which causes the change of cell's occupied capacity, + // so here should be processed at first if (props.updateOutput instanceof Function) { cell = props.updateOutput(cell); } + if (props.capacityMargin !== void 0) { + cell = setAbsoluteCapacityMargin(cell, props.capacityMargin); + } return cell; }, defaultWitness: props.defaultWitness, diff --git a/packages/core/src/helpers/capacity.ts b/packages/core/src/helpers/capacity.ts index d9fa431..38d4213 100644 --- a/packages/core/src/helpers/capacity.ts +++ b/packages/core/src/helpers/capacity.ts @@ -167,30 +167,27 @@ export function calculateNeededCapacity(props: { snapshot: CapacitySnapshot; neededCapacity: BI; exceedCapacity: BI; - add_minimal_change_cell: boolean; } { let txSkeleton = props.txSkeleton; const snapshot = createCapacitySnapshotFromTransactionSkeleton(txSkeleton); - const changeLock = helpers.parseAddress(props.changeAddress, { config: props.config }); + // const changeLock = helpers.parseAddress(props.changeAddress, { config: props.config }); const extraCapacity = BI.from(props.extraCapacity ?? 0); - const minChangeCapacity = minimalCellCapacityByLock(changeLock).add(extraCapacity); + // const minChangeCapacity = minimalCellCapacityByLock(changeLock).add(extraCapacity); let exceedCapacity = snapshot.inputsRemainCapacity; let neededCapacity = snapshot.outputsRemainCapacity.add(extraCapacity); - let add_minimal_change_cell = false; - - // Collect one more cell if: - // 1. Has sufficient capacity for transaction construction - // 2. Has insufficient capacity for adding a change cell to Transaction.outputs - const sufficientForTransaction = neededCapacity.lte(0) && exceedCapacity.gt(0); - const insufficientForChangeCell = exceedCapacity.lt(minChangeCapacity); - if (sufficientForTransaction && insufficientForChangeCell) { - neededCapacity = minChangeCapacity.sub(exceedCapacity); - exceedCapacity = BI.from(0); - add_minimal_change_cell = true; - } + + // // Collect one more cell if: + // // 1. Has sufficient capacity for transaction construction + // // 2. Has insufficient capacity for adding a change cell to Transaction.outputs + // const sufficientForTransaction = neededCapacity.lte(0) && exceedCapacity.gt(0); + // const insufficientForChangeCell = exceedCapacity.lt(minChangeCapacity); + // if (sufficientForTransaction && insufficientForChangeCell) { + // neededCapacity = minChangeCapacity.sub(exceedCapacity); + // exceedCapacity = BI.from(0); + // } if (neededCapacity.lt(0)) { neededCapacity = BI.from(0); @@ -203,7 +200,6 @@ export function calculateNeededCapacity(props: { snapshot, neededCapacity, exceedCapacity, - add_minimal_change_cell, }; } @@ -218,7 +214,6 @@ export async function injectNeededCapacity(props: { config?: Config; extraCapacity?: BIish; changeAddress?: Address; - enableDeductCapacity?: boolean; }): Promise<{ txSkeleton: helpers.TransactionSkeletonType; before: CapacitySnapshot; @@ -239,24 +234,6 @@ export async function injectNeededCapacity(props: { const before: CapacitySnapshot = calculated.snapshot; let after: CapacitySnapshot | undefined; - let enableDeductCapacity = props.enableDeductCapacity; - - // If the exceeded capaicty cannot cover the new minimal change cell, we need to add one more cell to collect - // the new more needed capacity - if (calculated.add_minimal_change_cell) { - txSkeleton = txSkeleton.update('outputs', (outputs) => { - let changeLock = helpers.parseAddress(changeAddress, { config }); - const minimalChangeCell: Cell = { - cellOutput: { - capacity: minimalCellCapacityByLock(changeLock).toHexString(), - lock: changeLock, - }, - data: '0x', - }; - return outputs.push(minimalChangeCell); - }); - enableDeductCapacity = true; - } // Collect needed capacity using `common.injectCapacity` API from lumos if (calculated.neededCapacity.gt(0)) { @@ -267,7 +244,7 @@ export async function injectNeededCapacity(props: { props.changeAddress, void 0, { - enableDeductCapacity, + enableDeductCapacity: true, // Set adding extra capacity to the last output cell as a default option config: props.config, }, ); @@ -375,6 +352,9 @@ export function returnExceededCapacity(props: { }; const minimalCapacity = helpers.minimalCellCapacityCompatible(changeCell); if (snapshot.inputsRemainCapacity.lt(minimalCapacity)) { + console.warn( + "The change cell's capacity is less than the minimal capacity, it may cause a failure in the future", + ); unreturnedCapacity = snapshot.inputsRemainCapacity; } else { createdChangeCell = true; diff --git a/packages/core/src/helpers/fee.ts b/packages/core/src/helpers/fee.ts index 5ec37ff..e652cb7 100644 --- a/packages/core/src/helpers/fee.ts +++ b/packages/core/src/helpers/fee.ts @@ -3,9 +3,10 @@ import { Address, Transaction } from '@ckb-lumos/base'; import { BI, Header, helpers, RPC } from '@ckb-lumos/lumos'; import { BIish } from '@ckb-lumos/bi'; import { getSporeConfig, SporeConfig } from '../config'; -import { injectNeededCapacity, returnExceededCapacity } from './capacity'; +import { injectNeededCapacity, minimalCellCapacityByLock, returnExceededCapacity } from './capacity'; import { CapacitySnapshot, createCapacitySnapshotFromTransactionSkeleton } from './capacity'; import { getTransactionSize, getTransactionSkeletonSize } from './transaction'; +import { fromInfoToAddress } from './address'; /** * Get minimal acceptable fee rate from RPC. @@ -154,7 +155,7 @@ export async function injectCapacityAndPayFee(props: { feeRate?: BIish; extraCapacity?: BIish; changeAddress?: Address; - enableDeductCapacity?: boolean; + addChangeCell?: boolean; updateTxSkeletonAfterCollection?: ( txSkeleton: helpers.TransactionSkeletonType, ) => Promise | helpers.TransactionSkeletonType; @@ -165,6 +166,22 @@ export async function injectCapacityAndPayFee(props: { }> { // Env const config = props.config ?? getSporeConfig(); + const addChangeCell = props.addChangeCell ?? true; + + // Add new change cell if marked + if (addChangeCell) { + const changeAddress = fromInfoToAddress(props.changeAddress ?? props.fromInfos[0], config.lumos); + const changeLock = helpers.addressToScript(changeAddress, { config: config.lumos }); + props.txSkeleton = props.txSkeleton.update('outputs', (outputs) => { + return outputs.push({ + cellOutput: { + capacity: minimalCellCapacityByLock(changeLock).toHexString(), + lock: changeLock, + }, + data: '0x', + }); + }); + } // Collect capacity const injectNeededCapacityResult = await injectNeededCapacity({ From acab4b24831b128c23e8cf52682ccf2012bdfa40 Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Wed, 21 Aug 2024 23:09:27 +0800 Subject: [PATCH 3/4] feat: remove addChangeCell parameter --- packages/core/src/helpers/fee.ts | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/core/src/helpers/fee.ts b/packages/core/src/helpers/fee.ts index e652cb7..2168ade 100644 --- a/packages/core/src/helpers/fee.ts +++ b/packages/core/src/helpers/fee.ts @@ -155,7 +155,6 @@ export async function injectCapacityAndPayFee(props: { feeRate?: BIish; extraCapacity?: BIish; changeAddress?: Address; - addChangeCell?: boolean; updateTxSkeletonAfterCollection?: ( txSkeleton: helpers.TransactionSkeletonType, ) => Promise | helpers.TransactionSkeletonType; @@ -166,22 +165,19 @@ export async function injectCapacityAndPayFee(props: { }> { // Env const config = props.config ?? getSporeConfig(); - const addChangeCell = props.addChangeCell ?? true; - // Add new change cell if marked - if (addChangeCell) { - const changeAddress = fromInfoToAddress(props.changeAddress ?? props.fromInfos[0], config.lumos); - const changeLock = helpers.addressToScript(changeAddress, { config: config.lumos }); - props.txSkeleton = props.txSkeleton.update('outputs', (outputs) => { - return outputs.push({ - cellOutput: { - capacity: minimalCellCapacityByLock(changeLock).toHexString(), - lock: changeLock, - }, - data: '0x', - }); + // Add a new change cell for receiving change capacity as default + const changeAddress = fromInfoToAddress(props.changeAddress ?? props.fromInfos[0], config.lumos); + const changeLock = helpers.addressToScript(changeAddress, { config: config.lumos }); + props.txSkeleton = props.txSkeleton.update('outputs', (outputs) => { + return outputs.push({ + cellOutput: { + capacity: minimalCellCapacityByLock(changeLock).toHexString(), + lock: changeLock, + }, + data: '0x', }); - } + }); // Collect capacity const injectNeededCapacityResult = await injectNeededCapacity({ From 56ac8d37e61562181f3cce14cac0a8a4bc4b20a3 Mon Sep 17 00:00:00 2001 From: ashuralyk Date: Wed, 21 Aug 2024 23:23:08 +0800 Subject: [PATCH 4/4] chore: remove warn log --- packages/core/src/helpers/capacity.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/core/src/helpers/capacity.ts b/packages/core/src/helpers/capacity.ts index 38d4213..1c9f226 100644 --- a/packages/core/src/helpers/capacity.ts +++ b/packages/core/src/helpers/capacity.ts @@ -6,7 +6,6 @@ import { Address, Script, Cell } from '@ckb-lumos/base/lib'; import { common, FromInfo } from '@ckb-lumos/common-scripts/lib'; import { fromInfoToAddress } from './address'; import { isScriptValueEquals } from './script'; -import {} from '@ckb-lumos/lumos/helpers'; /** * Calculate target cell's minimal occupied capacity by lock script. @@ -352,9 +351,6 @@ export function returnExceededCapacity(props: { }; const minimalCapacity = helpers.minimalCellCapacityCompatible(changeCell); if (snapshot.inputsRemainCapacity.lt(minimalCapacity)) { - console.warn( - "The change cell's capacity is less than the minimal capacity, it may cause a failure in the future", - ); unreturnedCapacity = snapshot.inputsRemainCapacity; } else { createdChangeCell = true;