diff --git a/packages/neuron-wallet/src/controllers/skip-data-and-type.ts b/packages/neuron-wallet/src/controllers/skip-data-and-type.ts new file mode 100644 index 0000000000..5357dc450d --- /dev/null +++ b/packages/neuron-wallet/src/controllers/skip-data-and-type.ts @@ -0,0 +1,25 @@ +import { CatchControllerError } from 'decorators/errors' +import { ResponseCode } from 'utils/const' +import SkipDataAndType from 'services/settings/skip-data-and-type' + +export default class SkipDataAndTypeController { + @CatchControllerError + public static async update(skip: boolean): Promise> { + SkipDataAndType.getInstance().update(skip) + + return { + status: ResponseCode.Success, + result: skip, + } + } + + @CatchControllerError + public static async get(): Promise> { + const skip = SkipDataAndType.getInstance().get() + + return { + status: ResponseCode.Success, + result: skip, + } + } +} diff --git a/packages/neuron-wallet/src/database/address/dao.ts b/packages/neuron-wallet/src/database/address/dao.ts index 9428164b98..a6605c2f9d 100644 --- a/packages/neuron-wallet/src/database/address/dao.ts +++ b/packages/neuron-wallet/src/database/address/dao.ts @@ -19,6 +19,7 @@ export interface Address { sentBalance: string pendingBalance: string balance: string + totalBalance: string blake160: string version: AddressVersion description?: string @@ -51,6 +52,8 @@ export default class AddressDao { // sentBalance means balance of OutputStatus.Sent cells (sent to me but not committed) // pendingBalance means balance of OutputStatus.Pending cells (sent from me, but not committed) // so the final balance is (liveBalance + sentBalance - pendingBalance) + // balance is the balance of the cells those who don't hold data or type script + // totalBalance means balance of all cells, including those who hold data and type script public static updateTxCountAndBalance = async (address: string): Promise => { const addressEntities = await getConnection() .getRepository(AddressEntity) @@ -67,9 +70,12 @@ export default class AddressDao { const addressEntity = entity addressEntity.txCount = txCount const lockHashes: string[] = await LockUtils.addressToAllLockHashes(addressEntity.address) - addressEntity.liveBalance = await CellsService.getBalance(lockHashes, OutputStatus.Live) - addressEntity.sentBalance = await CellsService.getBalance(lockHashes, OutputStatus.Sent) - addressEntity.pendingBalance = await CellsService.getBalance(lockHashes, OutputStatus.Pending) + addressEntity.liveBalance = await CellsService.getBalance(lockHashes, OutputStatus.Live, true) + addressEntity.sentBalance = await CellsService.getBalance(lockHashes, OutputStatus.Sent, true) + addressEntity.pendingBalance = await CellsService.getBalance(lockHashes, OutputStatus.Pending, true) + const totalLiveBalance = await CellsService.getBalance(lockHashes, OutputStatus.Live, false) + const totalSentBalance = await CellsService.getBalance(lockHashes, OutputStatus.Sent, false) + addressEntity.totalBalance = (BigInt(totalLiveBalance) - BigInt(totalSentBalance)).toString() return addressEntity }) ) diff --git a/packages/neuron-wallet/src/database/address/entities/address.ts b/packages/neuron-wallet/src/database/address/entities/address.ts index 1cdd995d60..6ae67eb128 100644 --- a/packages/neuron-wallet/src/database/address/entities/address.ts +++ b/packages/neuron-wallet/src/database/address/entities/address.ts @@ -73,6 +73,9 @@ export default class Address extends BaseEntity { @Column() pendingBalance: string = '0' + @Column() + totalBalance: string = '0' + public balance = (): string => { return (BigInt(this.liveBalance) + BigInt(this.sentBalance)).toString() } @@ -91,6 +94,7 @@ export default class Address extends BaseEntity { sentBalance: this.sentBalance, pendingBalance: this.pendingBalance, balance: this.balance(), + totalBalance: this.totalBalance, description: this.description, } } diff --git a/packages/neuron-wallet/src/database/address/migrations/1562126909151-extendBalance.ts b/packages/neuron-wallet/src/database/address/migrations/1562126909151-extendBalance.ts index 6b6de96d16..516d5befbc 100644 --- a/packages/neuron-wallet/src/database/address/migrations/1562126909151-extendBalance.ts +++ b/packages/neuron-wallet/src/database/address/migrations/1562126909151-extendBalance.ts @@ -13,8 +13,8 @@ export class extendBalance1562126909151 implements MigrationInterface { } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropColumn('transaction', 'sentBalance') - await queryRunner.dropColumn('transaction', 'pendingBalance') + await queryRunner.dropColumn('address', 'sentBalance') + await queryRunner.dropColumn('address', 'pendingBalance') await queryRunner.changeColumn('address', 'liveBalance', new TableColumn({ name: 'balance', type: 'varchar', diff --git a/packages/neuron-wallet/src/database/address/migrations/1567485550388-AddTotalBalance.ts b/packages/neuron-wallet/src/database/address/migrations/1567485550388-AddTotalBalance.ts new file mode 100644 index 0000000000..03b3e5df9b --- /dev/null +++ b/packages/neuron-wallet/src/database/address/migrations/1567485550388-AddTotalBalance.ts @@ -0,0 +1,17 @@ +import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; + +export class AddTotalBalance1567485550388 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn('address', new TableColumn({ + name: 'totalBalance', + type: 'varchar', + default: '0', + })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('address', 'totalBalance') + } + +} diff --git a/packages/neuron-wallet/src/database/address/ormconfig.ts b/packages/neuron-wallet/src/database/address/ormconfig.ts index 7e4731830e..63a6ee6765 100644 --- a/packages/neuron-wallet/src/database/address/ormconfig.ts +++ b/packages/neuron-wallet/src/database/address/ormconfig.ts @@ -8,6 +8,7 @@ import Address from './entities/address' import { AddAddress1561461669542 } from './migrations/1561461669542-AddAddress' import { extendBalance1562126909151 } from './migrations/1562126909151-extendBalance' +import { AddTotalBalance1567485550388 } from './migrations/1567485550388-AddTotalBalance' const dbPath = path.join(env.fileBasePath, 'address.sqlite') @@ -20,7 +21,7 @@ const connectOptions = (): SqliteConnectionOptions => { type: 'sqlite', database, entities: [Address], - migrations: [AddAddress1561461669542, extendBalance1562126909151], + migrations: [AddAddress1561461669542, extendBalance1562126909151, AddTotalBalance1567485550388], synchronize: false, migrationsRun: true, logging: ['error'], diff --git a/packages/neuron-wallet/src/database/chain/entities/output.ts b/packages/neuron-wallet/src/database/chain/entities/output.ts index 23e5606b53..8ec0559ebd 100644 --- a/packages/neuron-wallet/src/database/chain/entities/output.ts +++ b/packages/neuron-wallet/src/database/chain/entities/output.ts @@ -35,6 +35,17 @@ export default class Output extends BaseEntity { }) status!: string + @Column({ + type: 'simple-json', + nullable: true, + }) + typeScript: Script | null = null + + @Column({ + type: 'boolean', + }) + hasData!: boolean + public outPoint(): OutPoint { return { txHash: this.outPointTxHash, diff --git a/packages/neuron-wallet/src/database/chain/migrations/1567144517514-AddTypeAndHasData.ts b/packages/neuron-wallet/src/database/chain/migrations/1567144517514-AddTypeAndHasData.ts new file mode 100644 index 0000000000..f6c6c2da0f --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1567144517514-AddTypeAndHasData.ts @@ -0,0 +1,24 @@ +import {MigrationInterface, QueryRunner, TableColumn} from "typeorm"; + +export class AddTypeAndHasData1567144517514 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn('output', new TableColumn({ + name: 'typeScript', + type: 'text', + isNullable: true, + })) + + await queryRunner.addColumn('output', new TableColumn({ + name: 'hasData', + type: 'boolean', + default: false, + })) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('output', 'hasData') + await queryRunner.dropColumn('output', 'typeScript') + } + +} diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index d7732905f5..af68e415ed 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -10,6 +10,7 @@ import Input from './entities/input' import Output from './entities/output' import SyncInfo from './entities/sync-info' import { InitMigration1566959757554 } from './migrations/1566959757554-InitMigration' +import { AddTypeAndHasData1567144517514 } from './migrations/1567144517514-AddTypeAndHasData' export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError' @@ -20,6 +21,7 @@ const dbPath = (networkName: string): string => { const connectOptions = async (genesisBlockHash: string): Promise => { const connectionOptions = await getConnectionOptions() + const database = env.isTestMode ? ':memory:' : dbPath(genesisBlockHash) const logging: boolean | ('query' | 'schema' | 'error' | 'warn' | 'info' | 'log' | 'migration')[] = process.env.SHOW_CHAIN_DB_LOG && (env.isDevMode || env.isTestMode) ? true : ['warn', 'error'] @@ -27,9 +29,9 @@ const connectOptions = async (genesisBlockHash: string): Promise { + private static toAddress = (addressMetaInfo: AddressMetaInfo): AddressInterface[] => { const path: string = Address.pathFor(addressMetaInfo.addressType, addressMetaInfo.addressIndex) const testnetAddress: string = addressMetaInfo.accountExtendedPublicKey.address( addressMetaInfo.addressType, @@ -167,7 +167,7 @@ export default class AddressService { const blake160: string = LockUtils.addressToBlake160(testnetAddress) - const testnetAddressInfo = { + const testnetAddressInfo: AddressInterface = { walletId: addressMetaInfo.walletId, address: testnetAddress, path, @@ -178,6 +178,7 @@ export default class AddressService { sentBalance: '0', pendingBalance: '0', balance: '0', + totalBalance: '0', blake160, version: AddressVersion.Testnet, } diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 666ea6d7a9..9e20de0160 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -3,6 +3,7 @@ import OutputEntity from 'database/chain/entities/output' import { Cell, OutPoint, Input } from 'types/cell-types' import { CapacityNotEnough } from 'exceptions' import { OutputStatus } from './tx/params' +import SkipDataAndType from './settings/skip-data-and-type' export const MIN_CELL_CAPACITY = '6100000000' @@ -10,14 +11,28 @@ export const MIN_CELL_CAPACITY = '6100000000' /* eslint no-await-in-loop: "warn" */ /* eslint no-restricted-syntax: "warn" */ export default class CellsService { - public static getBalance = async (lockHashes: string[], status: OutputStatus): Promise => { + // exclude hasData = true and typeScript != null + public static getBalance = async ( + lockHashes: string[], + status: OutputStatus, + skipDataAndType: boolean + ): Promise => { + const queryParams = { + lockHash: In(lockHashes), + status, + } + + if (skipDataAndType) { + Object.assign(queryParams, { + hasData: false, + typeScript: null, + }) + } + const cells: OutputEntity[] = await getConnection() .getRepository(OutputEntity) .find({ - where: { - lockHash: In(lockHashes), - status, - }, + where: queryParams, }) const capacity: bigint = cells.map(c => BigInt(c.capacity)).reduce((result, c) => result + c, BigInt(0)) @@ -67,14 +82,23 @@ export default class CellsService { throw new Error(`capacity can't be less than ${MIN_CELL_CAPACITY}`) } - // only live cells + const queryParams = { + lockHashes: In(lockHashes), + status: OutputStatus.Live, + } + const skipDataAndType = SkipDataAndType.getInstance().get() + if (skipDataAndType) { + Object.assign(queryParams, { + hasData: false, + typeScript: null, + }) + } + + // only live cells, skip which has data or type const cellEntities: OutputEntity[] = await getConnection() .getRepository(OutputEntity) .find({ - where: { - lockHash: In(lockHashes), - status: 'live', - }, + where: queryParams, }) cellEntities.sort((a, b) => { const result = BigInt(a.capacity) - BigInt(b.capacity) diff --git a/packages/neuron-wallet/src/services/settings/base.ts b/packages/neuron-wallet/src/services/settings/base.ts new file mode 100644 index 0000000000..10ebce5bcf --- /dev/null +++ b/packages/neuron-wallet/src/services/settings/base.ts @@ -0,0 +1,48 @@ +import FileService from '../file' + +export default class BaseSettings { + private static moduleName = '' + private static fileName = 'settings.json' + + private static instance: BaseSettings + + public static getInstance(): BaseSettings { + if (!BaseSettings.instance) { + BaseSettings.instance = new BaseSettings() + } + + return BaseSettings.instance + } + + public updateSetting = (key: string, value: any) => { + let settings = this.read() + if (settings === undefined) { + settings = {} + } + Object.assign(settings, { [key]: value }) + FileService.getInstance().writeFileSync(BaseSettings.moduleName, BaseSettings.fileName, JSON.stringify(settings)) + } + + public getSetting = (key: string) => { + const info = this.read() + + if (info) { + return info[key] + } + + return undefined + } + + public read = () => { + const fileService = FileService.getInstance() + const { moduleName, fileName } = BaseSettings + + if (fileService.hasFile(moduleName, fileName)) { + const info = FileService.getInstance().readFileSync(moduleName, fileName) + const value = JSON.parse(info) + return value + } + + return undefined + } +} diff --git a/packages/neuron-wallet/src/services/settings/skip-data-and-type.ts b/packages/neuron-wallet/src/services/settings/skip-data-and-type.ts new file mode 100644 index 0000000000..c4b0e40a50 --- /dev/null +++ b/packages/neuron-wallet/src/services/settings/skip-data-and-type.ts @@ -0,0 +1,39 @@ +import BaseSettings from './base' + +export default class SkipDataAndType { + private skip: boolean | undefined = undefined + private keyName = 'skip' + + private static instance: SkipDataAndType + + static getInstance(): SkipDataAndType { + if (!SkipDataAndType.instance) { + SkipDataAndType.instance = new SkipDataAndType() + } + + return SkipDataAndType.instance + } + + // skip means can use cells with data and type + public update(skip: boolean) { + BaseSettings.getInstance().updateSetting(this.keyName, skip) + // cache this variable + this.skip = skip + } + + public get(): boolean { + // if cached, don't read file + if (this.skip !== undefined) { + return this.skip + } + + const skip = BaseSettings.getInstance().getSetting(this.keyName) + + if (skip === false) { + return false + } + + // default is true + return true + } +} diff --git a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts index 0508fe0b77..e0e5e8f4fa 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-persistor.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-persistor.ts @@ -133,6 +133,7 @@ export class TransactionPersistor { } } + const outputsData = transaction.outputsData! const outputs: OutputEntity[] = await Promise.all( transaction.outputs!.map(async (o, index) => { const output = new OutputEntity() @@ -143,6 +144,15 @@ export class TransactionPersistor { output.lockHash = o.lockHash! output.transaction = tx output.status = outputStatus + if (o.type) { + output.typeScript = o.type + } + const data = outputsData[index] + if (data && data !== '0x') { + output.hasData = true + } else { + output.hasData = false + } return output }) ) diff --git a/packages/neuron-wallet/src/types/type-convert.ts b/packages/neuron-wallet/src/types/type-convert.ts index 7512600cdb..35b382b07a 100644 --- a/packages/neuron-wallet/src/types/type-convert.ts +++ b/packages/neuron-wallet/src/types/type-convert.ts @@ -41,6 +41,7 @@ export default class TypeConvert { witnesses: transaction.witnesses, inputs: transaction.inputs.map(input => TypeConvert.toInput(input)), outputs: transaction.outputs.map(output => TypeConvert.toOutput(output)), + outputsData: transaction.outputsData, } if (blockHeader) { tx.timestamp = blockHeader.timestamp @@ -72,9 +73,14 @@ export default class TypeConvert { } static toOutput(output: CKBComponents.CellOutput): Cell { + let type: Script | undefined + if (output.type) { + type = TypeConvert.toScript(output.type) + } return { capacity: output.capacity.toString(), lock: TypeConvert.toScript(output.lock), + type, } } diff --git a/packages/neuron-wallet/tests/database/address/balance.test.ts b/packages/neuron-wallet/tests/database/address/balance.test.ts index c4279a664d..ebbfba8df6 100644 --- a/packages/neuron-wallet/tests/database/address/balance.test.ts +++ b/packages/neuron-wallet/tests/database/address/balance.test.ts @@ -18,7 +18,11 @@ describe('balance', () => { await connection.synchronize() }) - const generateAddress = (liveBalance: string | bigint, sentBalance: string | bigint, pendingBalance: string | bigint) => { + const generateAddress = ( + liveBalance: string | bigint, + sentBalance: string | bigint, + pendingBalance: string | bigint + ) => { const addr = Math.round(Math.random() * 100000000).toString() const address: Address = { walletId: '1', @@ -31,6 +35,7 @@ describe('balance', () => { sentBalance: sentBalance.toString(), pendingBalance: pendingBalance.toString(), balance: '0', + totalBalance: '0', blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', version: AddressVersion.Testnet, } @@ -53,15 +58,10 @@ describe('balance', () => { it('sent to others', async () => { // have 1000, sent to others 200, and refund 800 - const addresses = [ - generateAddress('0', '0', '1000'), - generateAddress('0', '800', '0'), - ] + const addresses = [generateAddress('0', '0', '1000'), generateAddress('0', '800', '0')] const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs - .map(addr => BigInt(addr.balance())) - .reduce((result, c) => result + c, BigInt(0)) + const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(800)) }) @@ -74,24 +74,17 @@ describe('balance', () => { generateAddress('0', '800', '0'), ] const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs - .map(addr => BigInt(addr.balance())) - .reduce((result, c) => result + c, BigInt(0)) + const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(1000)) }) it('sent to others with 10 shannon fee', async () => { // have 1000, sent to others 200, and refund 790, with 10 shannon fee - const addresses = [ - generateAddress('0', '0', '1000'), - generateAddress('0', '790', '0'), - ] + const addresses = [generateAddress('0', '0', '1000'), generateAddress('0', '790', '0')] const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs - .map(addr => BigInt(addr.balance())) - .reduce((result, c) => result + c, BigInt(0)) + const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(790)) }) @@ -105,9 +98,7 @@ describe('balance', () => { ] const addrs: AddressEntity[] = await AddressDao.create(addresses) - const balance: bigint = addrs - .map(addr => BigInt(addr.balance())) - .reduce((result, c) => result + c, BigInt(0)) + const balance: bigint = addrs.map(addr => BigInt(addr.balance())).reduce((result, c) => result + c, BigInt(0)) expect(balance).toEqual(BigInt(990)) }) diff --git a/packages/neuron-wallet/tests/database/address/dao.test.ts b/packages/neuron-wallet/tests/database/address/dao.test.ts index 2f05b462d5..adb931c074 100644 --- a/packages/neuron-wallet/tests/database/address/dao.test.ts +++ b/packages/neuron-wallet/tests/database/address/dao.test.ts @@ -15,6 +15,7 @@ describe('Address Dao tests', () => { sentBalance: '0', pendingBalance: '0', balance: '0', + totalBalance: '0', blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', version: AddressVersion.Testnet, } @@ -30,6 +31,7 @@ describe('Address Dao tests', () => { sentBalance: '0', pendingBalance: '0', balance: '0', + totalBalance: '0', blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', version: AddressVersion.Testnet, } @@ -45,6 +47,7 @@ describe('Address Dao tests', () => { sentBalance: '0', pendingBalance: '0', balance: '0', + totalBalance: '0', blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', version: AddressVersion.Testnet, } diff --git a/packages/neuron-wallet/tests/services/address.test.ts b/packages/neuron-wallet/tests/services/address.test.ts index 2fae0cdda5..7450c0beee 100644 --- a/packages/neuron-wallet/tests/services/address.test.ts +++ b/packages/neuron-wallet/tests/services/address.test.ts @@ -54,6 +54,7 @@ describe('Key tests with db', () => { sentBalance: '0', pendingBalance: '0', balance: '0', + totalBalance: '0', blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', version: AddressVersion.Testnet, } @@ -69,6 +70,7 @@ describe('Key tests with db', () => { sentBalance: '0', pendingBalance: '0', balance: '0', + totalBalance: '0', blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', version: AddressVersion.Testnet, } @@ -84,6 +86,7 @@ describe('Key tests with db', () => { sentBalance: '0', pendingBalance: '0', balance: '0', + totalBalance: '0', blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', version: AddressVersion.Testnet, } diff --git a/packages/neuron-wallet/tests/services/cells.test.ts b/packages/neuron-wallet/tests/services/cells.test.ts new file mode 100644 index 0000000000..442aef7c82 --- /dev/null +++ b/packages/neuron-wallet/tests/services/cells.test.ts @@ -0,0 +1,201 @@ +import { getConnection } from 'typeorm' +import { initConnection } from '../../src/database/chain/ormconfig' +import OutputEntity from '../../src/database/chain/entities/output' +import { OutputStatus } from '../../src/services/tx/params' +import { ScriptHashType, Script } from '../../src/types/cell-types' +import CellsService from '../../src/services/cells' +import { CapacityNotEnough } from '../../src/exceptions/wallet' +import SkipDataAndType from '../../src/services/settings/skip-data-and-type' + +const randomHex = (length: number = 64): string => { + const str: string = Array.from({ length }) + .map(() => Math.floor(Math.random() * 16).toString(16)) + .join('') + + return `0x${str}` +} + +describe('CellsService', () => { + beforeAll(async () => { + await initConnection('0x1234') + }) + + afterAll(async () => { + await getConnection().close() + }) + + beforeEach(async () => { + const connection = getConnection() + await connection.synchronize(true) + }) + + const bob = { + lockScript: { + codeHash: '0x68d5438ac952d2f584abf879527946a537e82c7f3c1cbf6d8ebf9767437d8e88', + args: ['0x36c329ed630d6ce750712a477543672adab57f4c'], + hashType: ScriptHashType.Type, + }, + lockHash: '0x024b0fd0c4912e98aab6808f6474cacb1969255d526b3cac5d3bdd15962a8818', + address: 'ckt1qyqrdsefa43s6m882pcj53m4gdnj4k440axqswmu83', + blake160: '0x36c329ed630d6ce750712a477543672adab57f4c', + } + + const generateCell = (capacity: string, status: OutputStatus, hasData: boolean, typeScript: Script | null) => { + const output = new OutputEntity() + output.outPointTxHash = randomHex() + output.outPointIndex = '0' + output.capacity = capacity + output.lock = bob.lockScript + output.lockHash = bob.lockHash + output.status = status + output.hasData = hasData + output.typeScript = typeScript + + return output + } + + const createCell = async (capacity: string, status: OutputStatus, hasData: boolean, typeScript: Script | null) => { + const cell = generateCell(capacity, status, hasData, typeScript) + await getConnection().manager.save(cell) + return cell + } + + const typeScript: Script = { + codeHash: randomHex(), + args: [], + hashType: ScriptHashType.Data, + } + + it('getLiveCell', async () => { + const capacity = '1000' + const entity = await createCell(capacity, OutputStatus.Live, false, null) + const outPoint = entity.outPoint() + const cell = await CellsService.getLiveCell(outPoint) + expect(cell!.capacity).toEqual(capacity) + }) + + it('getLiveCell in Sent', async () => { + const capacity = '1000' + const entity = await createCell(capacity, OutputStatus.Sent, false, null) + const outPoint = entity.outPoint() + const cell = await CellsService.getLiveCell(outPoint) + expect(cell).toBeUndefined() + }) + + it('allBlake160s', async () => { + await createCell('1000', OutputStatus.Sent, false, null) + await createCell('1000', OutputStatus.Sent, false, null) + const blake160s = await CellsService.allBlake160s() + expect(blake160s).toEqual([bob.blake160]) + }) + + + + const lockHashes = [bob.lockHash] + + describe('getBalance', () => { + const createCells = async () => { + const cells: OutputEntity[] = [ + generateCell('100', OutputStatus.Live, false, null), + generateCell('200', OutputStatus.Sent, false, null), + generateCell('300', OutputStatus.Pending, false, null), + generateCell('400', OutputStatus.Dead, false, null), + generateCell('1000', OutputStatus.Live, true, null), + generateCell('2000', OutputStatus.Sent, true, null), + generateCell('3000', OutputStatus.Pending, true, null), + generateCell('4000', OutputStatus.Dead, true, null), + generateCell('10000', OutputStatus.Live, false, typeScript), + generateCell('20000', OutputStatus.Sent, false, typeScript), + generateCell('30000', OutputStatus.Pending, false, typeScript), + generateCell('40000', OutputStatus.Dead, false, typeScript), + ] + await getConnection().manager.save(cells) + } + + it('getBalance, Live, skip', async () => { + await createCells() + + const balance: string = await CellsService.getBalance(lockHashes, OutputStatus.Live, true) + expect(balance).toEqual('100') + }) + + it('getBalance, Sent, skip', async () => { + await createCells() + + const balance: string = await CellsService.getBalance(lockHashes, OutputStatus.Sent, true) + expect(balance).toEqual('200') + }) + + it('getBalance, Live, not skip', async () => { + await createCells() + + const balance: string = await CellsService.getBalance(lockHashes, OutputStatus.Live, false) + expect(balance).toEqual('11100') + }) + + it('getBalance, Pending, not skip', async () => { + await createCells() + + const balance: string = await CellsService.getBalance(lockHashes, OutputStatus.Pending, false) + expect(balance).toEqual('33300') + }) + }) + + describe('gatherInputs', () => { + const toShannon = (ckb: string) => `${ckb}00000000` + const createCells = async () => { + const cells: OutputEntity[] = [ + generateCell(toShannon('1000'), OutputStatus.Live, false, null), + generateCell(toShannon('200'), OutputStatus.Sent, false, null), + generateCell(toShannon('2000'), OutputStatus.Live, true, null), + generateCell(toShannon('3000'), OutputStatus.Live, false, typeScript), + ] + await getConnection().manager.save(cells) + } + + it('1000, skip', async () => { + SkipDataAndType.getInstance().update(true) + await createCells() + + const result = await CellsService.gatherInputs(toShannon('1000'), lockHashes) + + expect(result.capacities).toEqual('100000000000') + }) + + it('1001, skip', async () => { + SkipDataAndType.getInstance().update(true) + await createCells() + + let error + try { + await CellsService.gatherInputs(toShannon('1001'), lockHashes) + } catch (e) { + error = e + } + expect(error).toBeInstanceOf(CapacityNotEnough) + }) + + it('6000, not skip', async () => { + SkipDataAndType.getInstance().update(false) + await createCells() + + const ckb = toShannon('6000') + const result = await CellsService.gatherInputs(ckb, lockHashes) + + expect(result.capacities).toEqual(ckb) + }) + + it('6001, not skip', async () => { + SkipDataAndType.getInstance().update(false) + await createCells() + + let error + try { + await CellsService.gatherInputs(toShannon('6001'), lockHashes) + } catch (e) { + error = e + } + expect(error).toBeInstanceOf(CapacityNotEnough) + }) + }) +}) diff --git a/packages/neuron-wallet/tests/services/settings/base.test.ts b/packages/neuron-wallet/tests/services/settings/base.test.ts new file mode 100644 index 0000000000..58d2b1a3c6 --- /dev/null +++ b/packages/neuron-wallet/tests/services/settings/base.test.ts @@ -0,0 +1,89 @@ +import BaseSettings from '../../../src/services/settings/base' +import FileService from '../../../src/services/file' + +describe('BaseSettings', () => { + let base: BaseSettings | undefined + + beforeEach(() => { + const fileService = FileService.getInstance() + // @ts-ignore: Private method + const { moduleName, fileName } = BaseSettings + if (fileService.hasFile(moduleName, fileName)) { + fileService.deleteFileSync(moduleName, fileName) + } + + base = new BaseSettings() + }) + + const key = 'testKey' + const value = 'testValue' + + const key2 = 'testKey2' + const value2 = 'testValue2' + + it('getInstance', () => { + const baseSettings = BaseSettings.getInstance() + expect(baseSettings).toBeInstanceOf(BaseSettings) + }) + + it('update', () => { + expect(() => { + base!.updateSetting(key, value) + }).not.toThrowError() + }) + + it('read empty', () => { + const settings = base!.read() + expect(settings).toBeUndefined() + }) + + it('update and read', () => { + base!.updateSetting(key, value) + const settings = base!.read() + expect(settings).toEqual({ [key]: value }) + }) + + it('update and get', () => { + base!.updateSetting(key, value) + const result = base!.getSetting(key) + expect(result).toEqual(value) + }) + + it('update multi', () => { + base!.updateSetting(key, value) + base!.updateSetting(key2, value2) + const result = base!.read() + expect(result).toEqual({ + [key]: value, + [key2]: value2, + }) + }) + + it('update multi and get', () => { + base!.updateSetting(key, value) + base!.updateSetting(key2, value2) + const result = base!.getSetting(key) + expect(result).toEqual(value) + }) + + it('update key multi times', () => { + base!.updateSetting(key, value) + base!.updateSetting(key, value2) + + const result = base!.getSetting(key) + expect(result).toEqual(value2) + }) + + it('new instance', () => { + base!.updateSetting(key, value) + const newInstance = new BaseSettings() + const result = newInstance.getSetting(key) + expect(result).toEqual(value) + }) + + it('getSetting empty', () => { + expect(base!.read()).toBeUndefined() + const result = base!.getSetting(key) + expect(result).toBeUndefined() + }) +}) diff --git a/packages/neuron-wallet/tests/services/settings/skip-data-and-type.test.ts b/packages/neuron-wallet/tests/services/settings/skip-data-and-type.test.ts new file mode 100644 index 0000000000..99b7851e06 --- /dev/null +++ b/packages/neuron-wallet/tests/services/settings/skip-data-and-type.test.ts @@ -0,0 +1,57 @@ +import SkipDataAndType from '../../../src/services/settings/skip-data-and-type' +import FileService from '../../../src/services/file' +import BaseSettings from '../../../src/services/settings/base' + +describe(`SkipDataAndType`, () => { + let skipDataAndType: SkipDataAndType | undefined + + beforeEach(() => { + const fileService = FileService.getInstance() + // @ts-ignore: Private method + const { moduleName, fileName } = BaseSettings + if (fileService.hasFile(moduleName, fileName)) { + fileService.deleteFileSync(moduleName, fileName) + } + + skipDataAndType = new SkipDataAndType() + }) + + it('getInstance', () => { + const skip = SkipDataAndType.getInstance() + expect(skip).toBeInstanceOf(SkipDataAndType) + }) + + it('update', () => { + expect(() => { + skipDataAndType!.update(true) + }).not.toThrowError() + }) + + describe('with instance cache', () => { + it('get true', () => { + skipDataAndType!.update(true) + const skip = skipDataAndType!.get() + expect(skip).toBe(true) + }) + + it('get false', () => { + skipDataAndType!.update(false) + const skip = skipDataAndType!.get() + expect(skip).toBe(false) + }) + }) + + describe('without cache', () => { + it('first time open', () => { + const skip = skipDataAndType!.get() + expect(skip).toBe(true) + }) + + it('new instance', () => { + skipDataAndType!.update(false) + const newInstance = new SkipDataAndType() + const skip = newInstance.get() + expect(skip).toBe(false) + }) + }) +})