Skip to content

Commit 074b4cd

Browse files
committed
feat(core): calculate Nervos DAO profit as input capacity
This fixes fee calculation for DAO withdrawal txs. If devs don't change the tx composing logic, sending tx will fail.
1 parent f58d398 commit 074b4cd

File tree

12 files changed

+133
-40
lines changed

12 files changed

+133
-40
lines changed

.changeset/twenty-pants-itch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ckb-ccc/core": minor
3+
---
4+
5+
feat(core): calculate Nervos DAO profit as input capacity

packages/core/src/ckb/script.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Bytes, BytesLike, bytesFrom } from "../bytes/index.js";
2-
import { Client, KnownScript } from "../client/index.js";
2+
import type { Client } from "../client/index.js";
3+
import { KnownScript } from "../client/knownScript.js";
34
import { Hex, HexLike, hexFrom } from "../hex/index.js";
45
import { mol } from "../molecule/index.js";
56
import {

packages/core/src/ckb/transaction.ts

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Bytes, BytesLike, bytesFrom } from "../bytes/index.js";
22
import type { ClientCollectableSearchKeyFilterLike } from "../client/clientTypes.advanced.js";
3-
import type { CellDepInfoLike, Client, KnownScript } from "../client/index.js";
3+
import type { CellDepInfoLike, Client } from "../client/index.js";
4+
import { KnownScript } from "../client/knownScript.js";
45
import {
56
Zero,
67
fixedPointFrom,
@@ -526,13 +527,15 @@ export class CellInput extends mol.Entity.Base<CellInputLike, CellInput>() {
526527
}
527528

528529
/**
529-
* Complete extra infos in the input. Like the output of the out point.
530+
* Complete extra infos in the input. Including
531+
* - Previous cell output
532+
* - Previous cell data
530533
* The instance will be modified.
531534
*
532535
* @returns true if succeed.
533536
* @example
534537
* ```typescript
535-
* await cellInput.completeExtraInfos();
538+
* await cellInput.completeExtraInfos(client);
536539
* ```
537540
*/
538541
async completeExtraInfos(client: Client): Promise<void> {
@@ -549,6 +552,69 @@ export class CellInput extends mol.Entity.Base<CellInputLike, CellInput>() {
549552
this.outputData = cell.outputData;
550553
}
551554

555+
/**
556+
* The extra capacity created when consume this input.
557+
* This is usually NervosDAO interest, see https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md.
558+
* And it can also be miners' income. (But this is not implemented yet)
559+
*/
560+
async getExtraCapacity(client: Client): Promise<Num> {
561+
return this.getDaoProfit(client);
562+
}
563+
564+
/**
565+
* Gets confirmed Nervos DAO profit of a CellInput
566+
* It returns non-zero value only when the cell is in withdrawal phase 2
567+
* See https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md
568+
*
569+
* @param client - A client for searching DAO related headers
570+
* @returns Profit
571+
*
572+
* @example
573+
* ```typescript
574+
* const profit = await input.getDaoProfit(client);
575+
* ```
576+
*/
577+
async getDaoProfit(client: Client): Promise<Num> {
578+
await this.completeExtraInfos(client);
579+
if (!this.cellOutput || !this.outputData) {
580+
throw new Error("Unable to complete input");
581+
}
582+
583+
const daoType = await client.getKnownScript(KnownScript.NervosDao);
584+
const type = this.cellOutput.type;
585+
if (
586+
!type ||
587+
type.codeHash !== daoType.codeHash ||
588+
type.hashType !== daoType.hashType ||
589+
!this.outputData ||
590+
numFrom(this.outputData) === Zero
591+
) {
592+
// Not a withdrawal phase 2 cell
593+
return Zero;
594+
}
595+
596+
const [depositHeader, withdrawRes] = await Promise.all([
597+
client.getHeaderByNumber(numFromBytes(this.outputData)),
598+
client.getCellWithHeader(this.previousOutput),
599+
]);
600+
if (!withdrawRes?.header || !depositHeader) {
601+
throw new Error(
602+
`Unable to get headers of a Nervos DAO input ${this.previousOutput.txHash}:${this.previousOutput.index.toString()}`,
603+
);
604+
}
605+
const withdrawHeader = withdrawRes.header;
606+
607+
const occupiedSize = fixedPointFrom(
608+
this.cellOutput.occupiedSize + bytesFrom(this.outputData).length,
609+
);
610+
const profitableSize = this.cellOutput.capacity - occupiedSize;
611+
612+
return (
613+
(profitableSize * withdrawHeader.dao.ar) / depositHeader.dao.ar -
614+
profitableSize
615+
);
616+
}
617+
552618
clone(): CellInput {
553619
const cloned = super.clone();
554620
cloned.cellOutput = this.cellOutput;
@@ -1426,15 +1492,33 @@ export class Transaction extends mol.Entity.Base<
14261492
this.setWitnessArgsAt(position, witness);
14271493
}
14281494

1495+
async getInputsCapacityExtra(client: Client): Promise<Num> {
1496+
return reduceAsync(
1497+
this.inputs,
1498+
async (acc, input) => {
1499+
const extraCapacity = await input.getExtraCapacity(client);
1500+
if (extraCapacity === undefined) {
1501+
throw new Error("Unable to complete input");
1502+
}
1503+
1504+
return acc + extraCapacity;
1505+
},
1506+
numFrom(0),
1507+
);
1508+
}
1509+
1510+
// This also includes extra amount
14291511
async getInputsCapacity(client: Client): Promise<Num> {
14301512
return reduceAsync(
14311513
this.inputs,
14321514
async (acc, input) => {
1515+
const extraCapacity = await input.getExtraCapacity(client);
14331516
await input.completeExtraInfos(client);
14341517
if (!input.cellOutput) {
14351518
throw new Error("Unable to complete input");
14361519
}
1437-
return acc + input.cellOutput.capacity;
1520+
1521+
return acc + input.cellOutput.capacity + extraCapacity;
14381522
},
14391523
numFrom(0),
14401524
);

packages/core/src/client/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ import {
3333
ClientTransactionResponse,
3434
ErrorClientMaxFeeRateExceeded,
3535
ErrorClientWaitTransactionTimeout,
36-
KnownScript,
3736
OutputsValidator,
3837
ScriptInfo,
3938
} from "./clientTypes.js";
39+
import { KnownScript } from "./knownScript.js";
4040

4141
function hasHeaderConfirmed(header: ClientBlockHeader): boolean {
4242
return numFrom(Date.now()) - header.timestamp >= CONFIRMED_BLOCK_TIME;

packages/core/src/client/clientPublicMainnet.advanced.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { KnownScript, ScriptInfoLike } from "./clientTypes.js";
1+
import { ScriptInfoLike } from "./clientTypes.js";
2+
import { KnownScript } from "./knownScript.js";
23

34
export const MAINNET_SCRIPTS: Record<KnownScript, ScriptInfoLike | undefined> =
45
Object.freeze({

packages/core/src/client/clientPublicMainnet.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import WebSocket from "isomorphic-ws";
22
import { MAINNET_SCRIPTS } from "./clientPublicMainnet.advanced.js";
3-
import { KnownScript, ScriptInfo, ScriptInfoLike } from "./clientTypes.js";
3+
import { ScriptInfo, ScriptInfoLike } from "./clientTypes.js";
44
import { ClientJsonRpc, ClientJsonRpcConfig } from "./jsonRpc/index.js";
5+
import { KnownScript } from "./knownScript.js";
56

67
/**
78
* @public

packages/core/src/client/clientPublicTestnet.advanced.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { KnownScript, ScriptInfoLike } from "./clientTypes.js";
1+
import { ScriptInfoLike } from "./clientTypes.js";
2+
import { KnownScript } from "./knownScript.js";
23

34
export const TESTNET_SCRIPTS: Record<KnownScript, ScriptInfoLike> =
45
Object.freeze({

packages/core/src/client/clientPublicTestnet.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import WebSocket from "isomorphic-ws";
22
import { TESTNET_SCRIPTS } from "./clientPublicTestnet.advanced.js";
3-
import { KnownScript, ScriptInfo, ScriptInfoLike } from "./clientTypes.js";
3+
import { ScriptInfo, ScriptInfoLike } from "./clientTypes.js";
44
import { ClientJsonRpc, ClientJsonRpcConfig } from "./jsonRpc/index.js";
5+
import { KnownScript } from "./knownScript.js";
56

67
/**
78
* @public

packages/core/src/client/clientTypes.ts

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,6 @@ import {
2424
clientSearchKeyRangeFrom,
2525
} from "./clientTypes.advanced.js";
2626

27-
/**
28-
* @public
29-
*/
30-
export enum KnownScript {
31-
NervosDao = "NervosDao",
32-
Secp256k1Blake160 = "Secp256k1Blake160",
33-
Secp256k1Multisig = "Secp256k1Multisig",
34-
AnyoneCanPay = "AnyoneCanPay",
35-
TypeId = "TypeId",
36-
XUdt = "XUdt",
37-
JoyId = "JoyId",
38-
COTA = "COTA",
39-
PWLock = "PWLock",
40-
OmniLock = "OmniLock",
41-
NostrLock = "NostrLock",
42-
UniqueType = "UniqueType",
43-
44-
// ckb-proxy-locks https://github.com/ckb-devrel/ckb-proxy-locks
45-
AlwaysSuccess = "AlwaysSuccess",
46-
InputTypeProxyLock = "InputTypeProxyLock",
47-
OutputTypeProxyLock = "OutputTypeProxyLock",
48-
LockProxyLock = "LockProxyLock",
49-
SingleUseLock = "SingleUseLock",
50-
TypeBurnLock = "TypeBurnLock",
51-
EasyToDiscoverType = "EasyToDiscoverType",
52-
TimeLock = "TimeLock",
53-
}
54-
5527
/**
5628
* @public
5729
*/

packages/core/src/client/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from "./clientPublicMainnet.js";
44
export * from "./clientPublicTestnet.js";
55
export * from "./clientTypes.js";
66
export * from "./jsonRpc/index.js";
7+
export * from "./knownScript.js";

0 commit comments

Comments
 (0)