Skip to content

Commit

Permalink
feat: Add omnilock and sudt actor.
Browse files Browse the repository at this point in the history
  • Loading branch information
yanguoyu committed Sep 19, 2023
1 parent 03e4016 commit 589382f
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 64 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,12 +425,12 @@ class OmnilockModel extends JSONStore<Record<string, never>> {

##### Lock

As in nervos network, each cell has its own `lock script`, so the `lock script` should be defined in `store`. We provide two default lock decorators, the `Omnilock` and the `Secp256k1Lock`, to use to define the most used `lock script`s in nervos network, and if you are using other `lock script`s in system, the `DefaultLock` could be used. Also we provide the `Lock` decorator for you to define your custom `lock script`.
As in nervos network, each cell has its own `lock script`, so the `lock script` should be defined in `store`. We provide two default lock decorators, the `Omnilock` and the `Secp256k1Lock`, to use to define the most used `lock script`s in nervos network, and if you are using other `lock script`s in system, the `DefaultScript` could be used. Also we provide the `Lock` decorator for you to define your custom `lock script`.

The Lock decorators we provided as below.

- `Lock`
- `DefaultLock`
- `DefaultScript`
- `Omnilock`
- `Secp256k1Lock`

Expand Down
3 changes: 2 additions & 1 deletion packages/core/sample-projects/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"ts-node": {
"files": true
},
"exclude": ["node_modules"]
"exclude": ["node_modules"],
"include": ["src"]
}
21 changes: 13 additions & 8 deletions packages/models/src/utils/decorator/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const Lock =
(ref?: ActorRef) => {
const codeHash = script?.codeHash ?? ref?.params?.codeHash
const hashType = script?.hashType ?? ref?.params?.hashType
const args = script?.args ?? ref?.params?.args ?? '0x'
const args = script?.args ?? ref?.params?.args ?? ref?.params?.lockArgs ?? '0x'
return codeHash && hashType ? { codeHash, hashType, args } : undefined
},
target,
Expand All @@ -125,25 +125,30 @@ export const Type =
(ref?: ActorRef) => {
const codeHash = script?.codeHash ?? ref?.params?.codeHash
const hashType = script?.hashType ?? ref?.params?.hashType
const args = script?.args ?? ref?.params?.args ?? '0x'
const args = script?.args ?? ref?.params?.args ?? ref?.params?.typeArgs ?? '0x'
return codeHash && hashType ? { codeHash, hashType, args } : undefined
},
target,
)

export const DefaultLock =
(lockName: keyof config.ScriptConfigs): ClassDecorator =>
export const DefaultScript =
(lockName: keyof config.ScriptConfigs, scriptType: keyof typeof ProviderKey = 'LockPattern'): ClassDecorator =>
(target: object) =>
Reflect.defineMetadata(
ProviderKey.LockPattern,
ProviderKey[scriptType],
(ref?: ActorRef) =>
createScriptRegistry(config.getConfig().SCRIPTS).newScript(lockName, ref?.params?.args ?? '0x'),
createScriptRegistry(config.getConfig().SCRIPTS).newScript(
lockName,
ref?.params?.args ?? ref?.params?.[scriptType === 'LockPattern' ? 'lockArgs' : 'typeArgs'] ?? '0x',
),
target,
)

export const Omnilock = (): ClassDecorator => DefaultLock('OMNILOCK')
export const Omnilock = (): ClassDecorator => DefaultScript('OMNILOCK')

export const Secp256k1Lock = (): ClassDecorator => DefaultScript('SECP256K1_BLAKE160')

export const Secp256k1Lock = (): ClassDecorator => DefaultLock('SECP256K1_BLAKE160')
export const Sudt = (): ClassDecorator => DefaultScript('SUDT', 'TypePattern')

/*
* Alias pattern to filter for better understanding in functional context
Expand Down
1 change: 1 addition & 0 deletions packages/samples/sudt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@ckb-js/kuai-core": "0.0.1-alpha.2",
"@ckb-js/kuai-io": "0.0.1-alpha.2",
"@ckb-js/kuai-models": "0.0.1-alpha.2",
"@ckb-lumos/lumos": "0.20.0",
"koa": "2.14.1",
"koa-body": "6.0.1"
},
Expand Down
33 changes: 0 additions & 33 deletions packages/samples/sudt/src/actors/app.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/samples/sudt/src/actors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ const appRegistry = new Registry();
appRegistry.load(__dirname);

export { appRegistry };
export { OmnilockModel } from './omnilock.model';
export { SudtModel } from './sudt.model';
110 changes: 110 additions & 0 deletions packages/samples/sudt/src/actors/omnilock.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @module src/actors/omnilock.model
* @description
* This is the actor model for omnilock, which is used to gather omnilock cells to generate record models.
*/

import type { Cell, HexString, Script } from '@ckb-lumos/base';
import {
ActorProvider,
Omnilock,
Param,
ActorReference,
CellPattern,
JSONStore,
OutPointString,
SchemaPattern,
UpdateStorageValue,
LockFilter,
DataFilter,
} from '@ckb-js/kuai-models';
import { BI } from '@ckb-lumos/bi';
import { InternalServerError } from 'http-errors';
import { number, bytes } from '@ckb-lumos/codec';
import { utils } from '@ckb-lumos/base';
import { getConfig } from '@ckb-lumos/config-manager';
import { TX_FEE } from '../const';

/**
* add business logic in an actor
*/
@ActorProvider({ ref: { name: 'omnilock', path: `/:args/` } })
@LockFilter()
@Omnilock()
@DataFilter('0x')
export class OmnilockModel extends JSONStore<Record<string, never>> {
constructor(
@Param('args') args: string,
_schemaOption?: void,
params?: {
states?: Record<OutPointString, never>;
chainData?: Record<OutPointString, UpdateStorageValue>;
cellPattern?: CellPattern;
schemaPattern?: SchemaPattern;
},
) {
super(undefined, { ...params, ref: ActorReference.newWithFilter(OmnilockModel, `/${args}/`) });
if (!this.lockScript) {
throw new Error('lock script is required');
}
this.registerResourceBinding();
}

get meta(): Record<'capacity', string> {
const cells = Object.values(this.chainData).filter((v) => !v.cell.cellOutput.type);
const capacity = cells.reduce((acc, cur) => BigInt(cur.cell.cellOutput.capacity ?? 0) + acc, BigInt(0)).toString();
return {
capacity,
};
}

mint(
lockScript: Script,
amount: HexString,
): {
inputs: Cell[];
outputs: Cell[];
witnesses: string[];
} {
const CONFIG = getConfig();
const sudtCell: Cell = {
cellOutput: {
capacity: BI.from('14400000000').toHexString(),
lock: lockScript,
type: {
codeHash: CONFIG.SCRIPTS.SUDT!.CODE_HASH,
hashType: CONFIG.SCRIPTS.SUDT!.HASH_TYPE,
args: utils.computeScriptHash(lockScript),
},
},
data: bytes.hexify(number.Uint128LE.pack(amount)),
};
const cells = Object.values(this.chainData);
let currentTotalCapacity: BI = BI.from(0);
// additional 0.001 ckb for tx fee
const needCapacity = BI.from(sudtCell.cellOutput.capacity).add(TX_FEE);
const inputs = cells.filter((v) => {
if (v.cell.cellOutput.type) return false;
if (currentTotalCapacity.gte(needCapacity)) return false;
currentTotalCapacity = currentTotalCapacity.add(BI.from(v.cell.cellOutput.capacity));
return true;
});
if (currentTotalCapacity.lt(needCapacity)) throw new InternalServerError('not enough capacity');

return {
inputs: inputs.map((v) => v.cell),
outputs: [
sudtCell,
{
cellOutput: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
lock: this.lockScript!,
capacity: currentTotalCapacity.sub(needCapacity).toHexString(),
},
data: '0x',
},
],
witnesses: [],
};
}
}
149 changes: 149 additions & 0 deletions packages/samples/sudt/src/actors/sudt.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* @module src/actors/omnilock.model
* @description
* This is the actor model for omnilock, which is used to gather omnilock cells to generate record models.
*/

import {
ActorProvider,
Omnilock,
Param,
ActorReference,
CellPattern,
JSONStore,
OutPointString,
SchemaPattern,
UpdateStorageValue,
LockFilter,
TypeFilter,
Sudt,
} from '@ckb-js/kuai-models';
import type { Cell, HexString, Script } from '@ckb-lumos/base';
import { number, bytes } from '@ckb-lumos/codec';
import { InternalServerError } from 'http-errors';
import { BI, utils, config } from '@ckb-lumos/lumos';
import { TX_FEE } from '../const';

/**
* add business logic in an actor
*/
@ActorProvider({ ref: { name: 'sudt', path: `/:lockArgs/:typeArgs/` } })
@LockFilter()
@TypeFilter()
@Omnilock()
@Sudt()
export class SudtModel extends JSONStore<Record<string, never>> {
constructor(
@Param('lockArgs') lockArgs: string,
@Param('typeArgs') typeArgs: string,
_schemaOption?: void,
params?: {
states?: Record<OutPointString, never>;
chainData?: Record<OutPointString, UpdateStorageValue>;
cellPattern?: CellPattern;
schemaPattern?: SchemaPattern;
},
) {
super(undefined, { ...params, ref: ActorReference.newWithFilter(SudtModel, `/${lockArgs}/${typeArgs}/`) });
if (!this.lockScript || !this.typeScript) {
throw new Error('lock script and type script are required');
}
this.registerResourceBinding();
}

get meta(): Record<'capacity' | 'sudtBalance', string> {
let capacity = BigInt(0);
let sudtBalance = BigInt(0);
Object.values(this.chainData).forEach((v) => {
capacity += BigInt(v.cell.cellOutput.capacity ?? 0);
sudtBalance += number.Uint128LE.unpack(v.cell.data.slice(0, 34)).toBigInt();
});
return {
capacity: capacity.toString(),
sudtBalance: sudtBalance.toString(),
};
}

send(lockScript: Script, amount: HexString) {
const CONFIG = config.getConfig();
const sudtCell: Cell = {
cellOutput: {
capacity: BI.from('14400000000').toHexString(),
lock: lockScript,
type: {
codeHash: CONFIG.SCRIPTS.SUDT!.CODE_HASH,
hashType: CONFIG.SCRIPTS.SUDT!.HASH_TYPE,
args: utils.computeScriptHash(lockScript),
},
},
data: bytes.hexify(number.Uint128LE.pack(amount)),
};
const cells = Object.values(this.chainData);
let currentTotalSudt: BI = BI.from(0);
let currentTotalCapacity: BI = BI.from(0);
// additional 0.001 ckb for tx fee
const needCapacity = BI.from(sudtCell.cellOutput.capacity).add(TX_FEE);
const inputs = cells.filter((v) => {
if (currentTotalCapacity.gte(needCapacity) && currentTotalSudt.gte(amount)) return false;
currentTotalCapacity = currentTotalCapacity.add(BI.from(v.cell.cellOutput.capacity));
currentTotalSudt = currentTotalSudt.add(number.Uint128LE.unpack(v.cell.data.slice(0, 34)));
return true;
});
if (currentTotalCapacity.lt(needCapacity)) throw new InternalServerError('not enough capacity');
if (currentTotalSudt.lt(amount)) throw new InternalServerError('not enough sudt balance');

const leftSudt = currentTotalSudt.sub(amount);
const hasLeftSudt = leftSudt.gt(0);
return {
inputs: inputs.map((v) => v.cell),
outputs: [
sudtCell,
{
cellOutput: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
lock: this.lockScript!,
capacity: currentTotalCapacity.sub(needCapacity).toHexString(),
type: hasLeftSudt ? this.typeScript : undefined,
},
data: hasLeftSudt ? bytes.hexify(number.Uint128LE.pack(leftSudt)) : '0x',
},
],
witnesses: [],
};
}

destory(amount?: HexString) {
const cells = Object.values(this.chainData);
if (!cells.length) throw new InternalServerError('There is no sudt cell');
let useInputs = cells;
let currentTotalSudt: BI = BI.from(0);
let totalCapacity: BI = BI.from(0);
if (amount) {
useInputs = cells.filter((v) => {
if (currentTotalSudt.gte(amount)) return false;
totalCapacity = totalCapacity.add(BI.from(v.cell.cellOutput.capacity));
currentTotalSudt = currentTotalSudt.add(number.Uint128LE.unpack(v.cell.data.slice(0, 34)));
return true;
});
if (currentTotalSudt.lt(amount)) throw new InternalServerError('not enough sudt balance');
}

const leftSudt = currentTotalSudt.sub(amount ?? 0);
const hasLeftSudt = leftSudt.gt(0);
return {
inputs: useInputs.map((v) => v.cell),
outputs: [
{
cellOutput: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
lock: this.lockScript!,
capacity: totalCapacity.sub(TX_FEE).toHexString(),
type: hasLeftSudt ? this.typeScript : undefined,
},
data: hasLeftSudt ? bytes.hexify(number.Uint128LE.pack(leftSudt)) : '0x',
},
],
witnesses: [],
};
}
}
Loading

0 comments on commit 589382f

Please sign in to comment.