Skip to content

Commit

Permalink
feat(sdk): basic cobuild support for legacy locks
Browse files Browse the repository at this point in the history
  • Loading branch information
ShookLyngs committed Jan 17, 2024
1 parent f0beb99 commit b89681c
Show file tree
Hide file tree
Showing 40 changed files with 1,843 additions and 52 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-planets-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@spore-sdk/core': minor
---

Support basic Cobuild feature with legacy locks
42 changes: 40 additions & 2 deletions packages/core/src/__tests__/ClusterProxyAgent.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { afterAll, describe, expect, it } from 'vitest';
import { BI } from '@ckb-lumos/lumos';
import { BI, utils } from '@ckb-lumos/lumos';
import { getSporeScript } from '../config';
import { bytifyRawString, minimalCellCapacityByLock } from '../helpers';
import { createSpore, createCluster, getClusterByOutPoint, getClusterById } from '../api';
import { packRawClusterAgentDataToHash, unpackToRawClusterProxyArgs } from '../codec';
import { createClusterProxy, transferClusterProxy, meltClusterProxy, getClusterProxyByOutPoint } from '../api';
import { createClusterAgent, transferClusterAgent, meltClusterAgent, getClusterAgentByOutPoint } from '../api';
import { getClusterAgentOutput, getSporeOutput, getClusterProxyOutput, IdRecord } from './helpers';
import {
getClusterAgentOutput,
getSporeOutput,
getClusterProxyOutput,
IdRecord,
getActionsFromCobuildWitnessLayout,
} from './helpers';
import { signAndSendTransaction, retryQuery, popRecord, OutPointRecord } from './helpers';
import { expectCellDep, expectLockCell, expectTypeCell, expectTypeId, expectCellLock } from './helpers';
import {
Expand All @@ -19,6 +25,7 @@ import {
CLUSTER_AGENT_OUTPOINT_RECORDS,
cleanupRecords,
} from './shared';
import { createSporeScriptInfoFromTemplate, ScriptInfo } from '../cobuild';

describe('ClusterProxy and ClusterAgent', () => {
const { rpc, config } = TEST_ENV;
Expand Down Expand Up @@ -209,6 +216,37 @@ describe('ClusterProxy and ClusterAgent', () => {
expectTypeCell(txSkeleton, 'both', clusterProxy.cell.cellOutput.type!);
expectCellDep(txSkeleton, clusterProxy.script.cellDep);

const witness = txSkeleton.get('witnesses').last();
expect(witness).toBeDefined();
const actions = getActionsFromCobuildWitnessLayout(witness!);
expect(actions[0]).toBeDefined();
expect(actions[0]).toHaveProperty('sporeActionData');
expect(actions[0].sporeActionData).toHaveProperty('type', 'TransferClusterProxy');

const clusterProxyType = clusterProxy.cell.cellOutput.type!;
const clusterProxyTypeHash = utils.computeScriptHash(clusterProxyType);
const scriptInfo = createSporeScriptInfoFromTemplate({
scriptHash: clusterProxyTypeHash,
});
const scriptInfoHash = utils.ckbHash(ScriptInfo.pack(scriptInfo));
expect(actions[0].scriptHash).toEqual(clusterProxyTypeHash);
expect(actions[0].scriptInfoHash).toEqual(scriptInfoHash);

const sporeActionData = actions[0].sporeActionData;
expect(sporeActionData).toHaveProperty('type');
expect(sporeActionData).toHaveProperty('type', 'TransferClusterProxy');
expect(sporeActionData).toHaveProperty('value');
expect(sporeActionData.value).toHaveProperty('clusterId', clusterProxy.data);
expect(sporeActionData.value).toHaveProperty('clusterProxyId', clusterProxy.id);
expect(sporeActionData.value).toHaveProperty('from', {
type: 'Script',
value: clusterProxyRecord.account.lock,
});
expect(sporeActionData.value).toHaveProperty('to', {
type: 'Script',
value: clusterProxy.cell.cellOutput.lock,
});

const hash = await signAndSendTransaction({
account: clusterProxyRecord.account,
txSkeleton,
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/__tests__/Codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
import { bytes } from '@ckb-lumos/codec';
import { BI, HexString } from '@ckb-lumos/lumos';
import { bytifyRawString } from '../helpers';
import { packRawSporeData, unpackToRawSporeData, RawSporeData } from '../codec';
import { packRawSporeData, unpackToRawSporeData, RawSporeData, RawString } from '../codec';
import { packRawClusterData, unpackToRawClusterData, RawClusterData } from '../codec';
import { packRawClusterProxyArgs, unpackToRawClusterProxyArgs, RawClusterProxyArgs } from '../codec';
import { packRawClusterAgentDataToHash, RawClusterAgentData } from '../codec';
Expand All @@ -13,6 +13,14 @@ interface PackableTest<T> {
}

describe('Codec', function () {
/**
* RawString
*/
it('Encode via RawString codec', () => {
expect(RawString.pack('content')).toEqual(bytes.bytify('0x07000000636f6e74656e74'));
expect(RawString.unpack('0x07000000636f6e74656e74')).toEqual('content');
});

/**
* SporeData
*/
Expand Down
19 changes: 18 additions & 1 deletion packages/core/src/__tests__/helpers/check.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { expect } from 'vitest';
import { helpers } from '@ckb-lumos/lumos';
import { helpers, HexString } from '@ckb-lumos/lumos';
import { Cell, CellDep, Hash, Script } from '@ckb-lumos/base';
import { getSporeScript, isSporeScriptSupported, SporeConfig, SporeScript } from '../../config';
import { unpackToRawSporeData, RawSporeData } from '../../codec';
import { unpackToRawClusterData, RawClusterData } from '../../codec';
import { unpackToRawClusterProxyArgs, RawClusterProxyArgs } from '../../codec';
import { generateTypeId, isScriptValueEquals } from '../../helpers';
import { getWitnessType, SighashAll, SporeAction, WitnessLayout } from '../../cobuild';
import { UnpackResult } from '@ckb-lumos/codec';

export function getSporeOutput(
txSkeleton: helpers.TransactionSkeletonType,
Expand Down Expand Up @@ -214,3 +216,18 @@ export function findCellByLock(lock: Script) {
export function findCellByType(type: Script) {
return (cell: Cell) => !!cell.cellOutput.type && isScriptValueEquals(type, cell.cellOutput.type);
}

export function getActionsFromCobuildWitnessLayout(witness: HexString) {
const witnessType = getWitnessType(witness);
expect(witnessType).toEqual('SighashAll');

const witnessLayout = WitnessLayout.unpack(witness);
return (witnessLayout.value as UnpackResult<typeof SighashAll>).message.actions.map((action) => {
return {
scriptInfoHash: action.scriptInfoHash,
scriptHash: action.scriptHash,
data: action.data,
sporeActionData: SporeAction.unpack(action.data),
};
});
}
16 changes: 16 additions & 0 deletions packages/core/src/__tests__/helpers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export function generateDevnetSporeConfig(config: Record<any, any>): SporeConfig
},
depType: config.SCRIPTS.SPORE.DEP_TYPE,
},
behaviors: {
lockProxy: true,
cobuild: true,
},
},
],
},
Expand All @@ -56,6 +60,10 @@ export function generateDevnetSporeConfig(config: Record<any, any>): SporeConfig
},
depType: config.SCRIPTS.CLUSTER.DEP_TYPE,
},
behaviors: {
lockProxy: true,
cobuild: true,
},
},
],
},
Expand All @@ -74,6 +82,10 @@ export function generateDevnetSporeConfig(config: Record<any, any>): SporeConfig
},
depType: config.SCRIPTS.CLUSTER_PROXY.DEP_TYPE,
},
behaviors: {
lockProxy: true,
cobuild: true,
},
},
],
},
Expand All @@ -92,6 +104,10 @@ export function generateDevnetSporeConfig(config: Record<any, any>): SporeConfig
},
depType: config.SCRIPTS.CLUSTER_AGENT.DEP_TYPE,
},
behaviors: {
lockProxy: true,
cobuild: true,
},
},
],
},
Expand Down
35 changes: 27 additions & 8 deletions packages/core/src/api/composed/cluster/createCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Address, Script } from '@ckb-lumos/base';
import { FromInfo } from '@ckb-lumos/common-scripts';
import { BI, Cell, helpers, Indexer } from '@ckb-lumos/lumos';
import { RawClusterData } from '../../../codec';
import { getSporeConfig, SporeConfig } from '../../../config';
import { getSporeConfig, getSporeScript, SporeConfig } from '../../../config';
import { generateCreateClusterAction, injectCommonCobuildProof } from '../../../cobuild';
import { injectCapacityAndPayFee, assertTransactionSkeletonSize } from '../../../helpers';
import { injectNewClusterIds, injectNewClusterOutput } from '../..';

Expand Down Expand Up @@ -47,16 +48,34 @@ export async function createCluster(props: {
txSkeleton,
fromInfos: props.fromInfos,
changeAddress: props.changeAddress,
config,
});
txSkeleton = injectCapacityAndPayFeeResult.txSkeleton;
updateTxSkeletonAfterCollection(_txSkeleton) {
// Generate ID for the new Cluster (if possible)
_txSkeleton = injectNewClusterIds({
txSkeleton: _txSkeleton,
outputIndices: [injectNewClusterResult.outputIndex],
config,
});

// Generate ID for the new Cluster (if possible)
txSkeleton = injectNewClusterIds({
outputIndices: [injectNewClusterResult.outputIndex],
txSkeleton,
// Inject CobuildProof
const clusterCell = txSkeleton.get('outputs').get(injectNewClusterResult.outputIndex)!;
const clusterScript = getSporeScript(config, 'Cluster', clusterCell.cellOutput.type!);
if (clusterScript.behaviors?.cobuild) {
const actionResult = generateCreateClusterAction({
txSkeleton: _txSkeleton,
outputIndex: injectNewClusterResult.outputIndex,
});
const injectCobuildProofResult = injectCommonCobuildProof({
txSkeleton: _txSkeleton,
actions: actionResult.actions,
});
_txSkeleton = injectCobuildProofResult.txSkeleton;
}

return _txSkeleton;
},
config,
});
txSkeleton = injectCapacityAndPayFeeResult.txSkeleton;

// Make sure the tx size is in range (if needed)
if (typeof maxTransactionSize === 'number') {
Expand Down
33 changes: 31 additions & 2 deletions packages/core/src/api/composed/cluster/transferCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { BIish } from '@ckb-lumos/bi';
import { FromInfo } from '@ckb-lumos/common-scripts';
import { Address, OutPoint, PackedSince, Script } from '@ckb-lumos/base';
import { BI, Cell, helpers, HexString, Indexer } from '@ckb-lumos/lumos';
import { getSporeConfig, SporeConfig } from '../../../config';
import { injectCapacityAndPayFee, payFeeByOutput } from '../../../helpers';
import { getSporeConfig, getSporeScript, SporeConfig } from '../../../config';
import { generateTransferClusterAction, injectCommonCobuildProof } from '../../../cobuild';
import { getClusterByOutPoint, injectLiveClusterCell } from '../..';

export async function transferCluster(props: {
Expand Down Expand Up @@ -43,6 +44,7 @@ export async function transferCluster(props: {

// Find Cluster by OutPoint
const clusterCell = await getClusterByOutPoint(props.outPoint, config);
const clusterScript = getSporeScript(config, 'Cluster', clusterCell.cellOutput.type!);

// Add Cluster to inputs and outputs of the Transaction
const injectLiveClusterCellResult = await injectLiveClusterCell({
Expand All @@ -64,16 +66,43 @@ export async function transferCluster(props: {
});
txSkeleton = injectLiveClusterCellResult.txSkeleton;

// Generate TransferSpore actions
const actionResult = generateTransferClusterAction({
txSkeleton,
inputIndex: injectLiveClusterCellResult.inputIndex,
outputIndex: injectLiveClusterCellResult.outputIndex,
});

if (!useCapacityMarginAsFee) {
// Inject needed capacity and pay fee
const injectCapacityAndPayFeeResult = await injectCapacityAndPayFee({
txSkeleton,
changeAddress: props.changeAddress,
fromInfos: props.fromInfos!,
changeAddress: props.changeAddress,
updateTxSkeletonAfterCollection(_txSkeleton) {
// Inject CobuildProof
if (clusterScript.behaviors?.cobuild) {
const injectCobuildProofResult = injectCommonCobuildProof({
txSkeleton: _txSkeleton,
actions: actionResult.actions,
});
_txSkeleton = injectCobuildProofResult.txSkeleton;
}
return _txSkeleton;
},
config,
});
txSkeleton = injectCapacityAndPayFeeResult.txSkeleton;
} else {
// Inject CobuildProof
if (clusterScript.behaviors?.cobuild) {
const injectCobuildProofResult = injectCommonCobuildProof({
txSkeleton: txSkeleton,
actions: actionResult.actions,
});
txSkeleton = injectCobuildProofResult.txSkeleton;
}

// Pay fee by the cluster cell's capacity margin
txSkeleton = await payFeeByOutput({
outputIndex: injectLiveClusterCellResult.outputIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { BIish } from '@ckb-lumos/bi';
import { FromInfo } from '@ckb-lumos/common-scripts';
import { Address, OutPoint, Script } from '@ckb-lumos/base';
import { BI, Cell, helpers, HexString, Indexer } from '@ckb-lumos/lumos';
import { getSporeConfig, SporeConfig } from '../../../config';
import { injectCapacityAndPayFee } from '../../../helpers';
import { getSporeConfig, getSporeScript, SporeConfig } from '../../../config';
import { generateCreateClusterAgentAction, injectCommonCobuildProof } from '../../../cobuild';
import { getClusterProxyByOutPoint, injectNewClusterAgentOutput } from '../..';
import { unpackToRawClusterProxyArgs } from '../../../codec';

export async function createClusterAgent(props: {
clusterProxyOutPoint: OutPoint;
Expand Down Expand Up @@ -61,6 +63,26 @@ export async function createClusterAgent(props: {
txSkeleton,
fromInfos: props.fromInfos,
changeAddress: props.changeAddress,
updateTxSkeletonAfterCollection(_txSkeleton) {
// Inject CobuildProof
const clusterAgentCell = txSkeleton.get('outputs').get(injectNewClusterAgentOutputResult.outputIndex)!;
const clusterAgentScript = getSporeScript(config, 'ClusterAgent', clusterAgentCell.cellOutput.type!);
if (clusterAgentScript.behaviors?.cobuild) {
const actionResult = generateCreateClusterAgentAction({
txSkeleton: _txSkeleton,
clusterProxyId: unpackToRawClusterProxyArgs(clusterProxyCell.cellOutput.type!.args).id,
outputIndex: injectNewClusterAgentOutputResult.outputIndex,
reference: injectNewClusterAgentOutputResult.reference,
});
const injectCobuildProofResult = injectCommonCobuildProof({
txSkeleton: _txSkeleton,
actions: actionResult.actions,
});
_txSkeleton = injectCobuildProofResult.txSkeleton;
}

return _txSkeleton;
},
config,
});
txSkeleton = injectCapacityAndPayFeeResult.txSkeleton;
Expand Down
17 changes: 16 additions & 1 deletion packages/core/src/api/composed/clusterAgent/meltClusterAgent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Address, OutPoint } from '@ckb-lumos/base';
import { helpers, HexString, Indexer } from '@ckb-lumos/lumos';
import { getSporeConfig, SporeConfig } from '../../../config';
import { returnExceededCapacityAndPayFee } from '../../../helpers';
import { getSporeConfig, getSporeScript, SporeConfig } from '../../../config';
import { generateMeltClusterAgentAction, injectCommonCobuildProof } from '../../../cobuild';
import { getClusterAgentByOutPoint, injectLiveClusterAgentCell } from '../..';

export async function meltClusterAgent(props: {
Expand All @@ -24,6 +25,7 @@ export async function meltClusterAgent(props: {

// Get ClusterAgent cell
const clusterAgentCell = await getClusterAgentByOutPoint(props.outPoint, config);
const clusterAgentScript = getSporeScript(config, 'ClusterAgent', clusterAgentCell.cellOutput.type!);

// Inject target cell to Transaction.inputs
const injectLiveClusterAgentCellResult = await injectLiveClusterAgentCell({
Expand All @@ -34,6 +36,19 @@ export async function meltClusterAgent(props: {
});
txSkeleton = injectLiveClusterAgentCellResult.txSkeleton;

// Inject CobuildProof
if (clusterAgentScript.behaviors?.cobuild) {
const actionResult = generateMeltClusterAgentAction({
txSkeleton,
inputIndex: injectLiveClusterAgentCellResult.inputIndex,
});
const injectCobuildProofResult = injectCommonCobuildProof({
txSkeleton,
actions: actionResult.actions,
});
txSkeleton = injectCobuildProofResult.txSkeleton;
}

// Redeem occupied capacity from the melted cell
const targetCellAddress = helpers.encodeToAddress(clusterAgentCell.cellOutput.lock, { config: config.lumos });
const returnExceededCapacityAndPayFeeResult = await returnExceededCapacityAndPayFee({
Expand Down
Loading

0 comments on commit b89681c

Please sign in to comment.