diff --git a/packages/jellyfish-transaction/__tests__/buffer/buffer_composer.test.ts b/packages/jellyfish-transaction/__tests__/buffer/buffer_composer.test.ts index 854f8a236a..0c2f55ab90 100644 --- a/packages/jellyfish-transaction/__tests__/buffer/buffer_composer.test.ts +++ b/packages/jellyfish-transaction/__tests__/buffer/buffer_composer.test.ts @@ -1007,3 +1007,122 @@ describe('ComposableBuffer.varUInt', () => { }).toThrow('out of Number.MAX_SAFE_INTEGER range') }) }) + +describe('ComposableBuffer.bitmask1Byte', () => { + describe('bitmask 1 byte with 3 positional bits', () => { + const obj = { isA: false, isB: true, isC: false } + + const composer = ComposableBuffer.bitmask1Byte( + 3, + () => [obj.isA, obj.isB, obj.isC], + v => { + obj.isA = v[0] + obj.isB = v[1] + obj.isC = v[2] + } + ) + + it('should fromBuffer', () => { + shouldFromBuffer(composer, '02', [false, true, false], () => [obj.isA, obj.isB, obj.isC]) + }) + + it('should toBuffer', () => { + shouldToBuffer(composer, '02', [false, true, false], v => { + obj.isA = v[0] + obj.isB = v[1] + obj.isC = v[2] + }) + }) + }) + + describe('bitmask 1 byte with 8 positional bits', () => { + const obj = { + isA: true, + isB: true, + isC: true, + isD: true, + isE: true, + isF: true, + isG: true, + isH: true + } + + const composer = ComposableBuffer.bitmask1Byte( + 8, () => Object.values(obj), v => { + obj.isA = v[0] + obj.isB = v[1] + obj.isC = v[2] + obj.isD = v[3] + obj.isE = v[4] + obj.isF = v[5] + obj.isG = v[6] + obj.isH = v[7] + }) + + it('should fromBuffer', () => { + shouldFromBuffer(composer, 'FF', + [true, true, true, true, true, true, true, true], + () => Object.values(obj)) + }) + + it('should toBuffer', () => { + shouldToBuffer( + composer, 'FF', + [true, true, true, true, true, true, true, true], + v => { + obj.isA = v[0] + obj.isB = v[1] + obj.isC = v[2] + obj.isD = v[3] + obj.isE = v[4] + obj.isF = v[5] + obj.isG = v[6] + obj.isH = v[7] + }) + }) + }) + + describe('bitmask 1 byte with 7 positional bits', () => { + const obj = { + isA: false, + isB: true, + isC: false, + isD: true, + isE: false, + isF: true, + isG: false + } + + const composer = ComposableBuffer.bitmask1Byte( + 7, () => Object.values(obj), v => { + obj.isA = v[0] + obj.isB = v[1] + obj.isC = v[2] + obj.isD = v[3] + obj.isE = v[4] + obj.isF = v[5] + obj.isG = v[6] + }) + + it('should fromBuffer', () => { + shouldFromBuffer(composer, '2A', + [false, true, false, true, false, true, false], + () => Object.values(obj)) + }) + + it('should toBuffer', () => { + shouldToBuffer( + composer, '2A', + [false, true, false, true, false, true, false], + v => { + obj.isA = v[0] + obj.isB = v[1] + obj.isC = v[2] + obj.isD = v[3] + obj.isE = v[4] + obj.isF = v[5] + obj.isG = v[6] + }) + }) + }) +}) diff --git a/packages/jellyfish-transaction/__tests__/script/defi/dftx_token/TokenCreate.test.ts b/packages/jellyfish-transaction/__tests__/script/defi/dftx_token/TokenCreate.test.ts new file mode 100644 index 0000000000..a516f63bf1 --- /dev/null +++ b/packages/jellyfish-transaction/__tests__/script/defi/dftx_token/TokenCreate.test.ts @@ -0,0 +1,75 @@ +import { SmartBuffer } from 'smart-buffer' +import { OP_DEFI_TX } from '../../../../src/script/defi' +import { CTokenCreate, TokenCreate } from '../../../../src/script/defi/dftx_token' +import { OP_CODES } from '../../../../src/script' +import { toBuffer, toOPCodes } from '../../../../src/script/_buffer' +import BigNumber from 'bignumber.js' + +/** + * using createToken sample from + * https://explorer.defichain.com/#/DFI/mainnet/tx/8a5066b4ea77c8d0b705ba94f47585f944ae587700f0f43f8655d01f38921f40 + * https://explorer.defichain.com/#/DFI/mainnet/tx/baeddea27199a9e9001133f18942353dc79a765f0c437a1eda550f7675dc6b8b + * https://explorer.defichain.com/#/DFI/mainnet/tx/11fb90953bbd2a8f8649c116e6071dc73d428c5eba97c5a4a6dac550df2ab78c + */ +it('should bi-directional buffer-object-buffer', () => { + const fixtures = [ + '6a1b44665478540342544307426974636f696e08000000000000000003', + '6a19446654785404474f4c4404476f6c6408000000000000000003', + '6a224466547854035753420e77616c6c7374726565746265747308000000000000000003', + + // regtest fixtures + '6a174466547854034748490347484908000000000000000004', + '6a174466547854034a4b4c034a4b4c08000000000000000000', + '6a174466547854034d4e4f034d4e4f08000000000000000001', + '6a174466547854035051520350515208000000000000000007', + '6a174466547854034142430341424308000000000000000005' + ] + + fixtures.forEach(hex => { + const stack: any = toOPCodes( + SmartBuffer.fromBuffer(Buffer.from(hex, 'hex')) + ) + const buffer = toBuffer(stack) + expect(buffer.toString('hex')).toBe(hex) + expect((stack[1] as OP_DEFI_TX).tx.type).toBe(0x54) + }) +}) + +const header = '6a224466547854' // OP_RETURN, PUSH_DATA(44665478, 54) +const data = '035753420e77616c6c7374726565746265747308000000000000000003' +const tokenCreate: TokenCreate = { + symbol: 'WSB', + name: 'wallstreetbets', + decimal: 8, + limit: new BigNumber('0'), + mintable: true, + tradeable: true, + isDAT: false +} + +it('should craft dftx with OP_CODES._()', () => { + const stack = [ + OP_CODES.OP_RETURN, + OP_CODES.OP_DEFI_TX_TOKEN_CREATE(tokenCreate) + ] + + const buffer = toBuffer(stack) + expect(buffer.toString('hex')).toBe(header + data) +}) + +describe('Composable', () => { + it('should compose from buffer to composable', () => { + const buffer = SmartBuffer.fromBuffer(Buffer.from(data, 'hex')) + const composable = new CTokenCreate(buffer) + + expect(composable.toObject()).toEqual(tokenCreate) + }) + + it('should compose from composable to buffer', () => { + const composable = new CTokenCreate(tokenCreate) + const buffer = new SmartBuffer() + composable.toBuffer(buffer) + + expect(buffer.toBuffer().toString('hex')).toEqual(data) + }) +}) diff --git a/packages/jellyfish-transaction/src/buffer/buffer_bitmask.ts b/packages/jellyfish-transaction/src/buffer/buffer_bitmask.ts new file mode 100644 index 0000000000..effe882216 --- /dev/null +++ b/packages/jellyfish-transaction/src/buffer/buffer_bitmask.ts @@ -0,0 +1,12 @@ +/** + * Extracts the "truthiness" of a bit given a position + * @param {number} binaryNum - The number to query from + * @param {number} position - This is the zero-indexed position of the bit from the right + * @returns {boolean} - "Truthiness" of the bit we're interested in + */ +export function getBitsFrom (binaryNum: number, position: number): boolean { + // Bit-shifts according to zero-indexed position + const mask = 1 << position + const query = binaryNum & mask + return Boolean(query) +} diff --git a/packages/jellyfish-transaction/src/buffer/buffer_composer.ts b/packages/jellyfish-transaction/src/buffer/buffer_composer.ts index 444add9d23..abe91289a2 100644 --- a/packages/jellyfish-transaction/src/buffer/buffer_composer.ts +++ b/packages/jellyfish-transaction/src/buffer/buffer_composer.ts @@ -1,6 +1,7 @@ import BigNumber from 'bignumber.js' import { SmartBuffer } from 'smart-buffer' import { writeVarUInt, readVarUInt } from './buffer_varuint' +import { getBitsFrom } from './buffer_bitmask' import { ONE_HUNDRED_MILLION, readBigNumberUInt64, writeBigNumberUInt64 } from './buffer_bignumber' export interface BufferComposer { @@ -438,4 +439,33 @@ export abstract class ComposableBuffer implements BufferComposer { } } } + + /** + * Imposing mask over bits method, 1 byte + * + * @param length of the input array to read/set + * @param getter to read from to buffer + * @param setter to set to from buffer + */ + static bitmask1Byte ( + length: number, + getter: () => boolean[], + setter: (data: boolean[]) => void + ): BufferComposer { + return { + fromBuffer: (buffer: SmartBuffer): void => { + const num = buffer.readUInt8() + const array: boolean[] = [] + for (let i = 0; i < length; i += 1) { + array.unshift(getBitsFrom(num, i)) + } + setter(array) + }, + toBuffer: (buffer: SmartBuffer): void => { + const bools = getter().map(bool => bool.toString().toLowerCase() === 'true' ? 1 : 0) + const num = parseInt(bools.join(''), 2) + buffer.writeBuffer(Buffer.from([num])) + } + } + } } diff --git a/packages/jellyfish-transaction/src/script/defi/dftx.ts b/packages/jellyfish-transaction/src/script/defi/dftx.ts index 8f1ccef67b..ce6e0f58d4 100644 --- a/packages/jellyfish-transaction/src/script/defi/dftx.ts +++ b/packages/jellyfish-transaction/src/script/defi/dftx.ts @@ -15,7 +15,7 @@ import { CPoolAddLiquidity, CPoolRemoveLiquidity, CPoolSwap, PoolAddLiquidity, PoolRemoveLiquidity, PoolSwap } from './dftx_pool' -import { CTokenMint, TokenMint } from './dftx_token' +import { CTokenCreate, CTokenMint, TokenCreate, TokenMint } from './dftx_token' import { CAppointOracle, AppointOracle, @@ -111,6 +111,8 @@ export class CDfTx extends ComposableBuffer> { return compose(CPoolRemoveLiquidity.OP_NAME, d => new CPoolRemoveLiquidity(d)) case CTokenMint.OP_CODE: return compose(CTokenMint.OP_NAME, d => new CTokenMint(d)) + case CTokenCreate.OP_CODE: + return compose(CTokenCreate.OP_NAME, d => new CTokenCreate(d)) case CUtxosToAccount.OP_CODE: return compose(CUtxosToAccount.OP_NAME, d => new CUtxosToAccount(d)) case CAccountToUtxos.OP_CODE: diff --git a/packages/jellyfish-transaction/src/script/defi/dftx_token.ts b/packages/jellyfish-transaction/src/script/defi/dftx_token.ts index d03be064dc..0632faef1b 100644 --- a/packages/jellyfish-transaction/src/script/defi/dftx_token.ts +++ b/packages/jellyfish-transaction/src/script/defi/dftx_token.ts @@ -1,5 +1,6 @@ import { BufferComposer, ComposableBuffer } from '../../buffer/buffer_composer' import { TokenBalance, CTokenBalance } from './dftx_balance' +import BigNumber from 'bignumber.js' // Disabling no-return-assign makes the code cleaner with the setter and getter */ /* eslint-disable no-return-assign */ @@ -25,3 +26,39 @@ export class CTokenMint extends ComposableBuffer { ] } } + +/** + * TokenCreate DeFi Transaction + */ +export interface TokenCreate { + symbol: string // ---------------------| VarUInt{1-9 bytes}, + n bytes + name: string // -----------------------| VarUInt{1-9 bytes}, + n bytes + decimal: number // --------------------| 1 byte + limit: BigNumber // -------------------| 8 bytes + isDAT: boolean // ---------------------| 1 byte bitmask start, position 0 + tradeable: boolean // -----------------| 1 byte bitmask, position 1 + mintable: boolean // ------------------| 1 byte bitmask end, position 2 +} + +/** + * Composable TokenMint, C stands for Composable. + * Immutable by design, bi-directional fromBuffer, toBuffer deep composer. + */ +export class CTokenCreate extends ComposableBuffer { + static OP_CODE = 0x54 /// 'T' + static OP_NAME = 'OP_DEFI_TX_TOKEN_CREATE' + + composers (tc: TokenCreate): BufferComposer[] { + return [ + ComposableBuffer.varUIntUtf8BE(() => tc.symbol, v => tc.symbol = v), + ComposableBuffer.varUIntUtf8BE(() => tc.name, v => tc.name = v), + ComposableBuffer.uInt8(() => tc.decimal, v => tc.decimal = v), + ComposableBuffer.bigNumberUInt64(() => tc.limit, v => tc.limit = v), + ComposableBuffer.bitmask1Byte(3, () => [tc.isDAT, tc.tradeable, tc.mintable], v => { + tc.isDAT = v[0] + tc.tradeable = v[1] + tc.mintable = v[2] + }) + ] + } +} diff --git a/packages/jellyfish-transaction/src/script/mapping.ts b/packages/jellyfish-transaction/src/script/mapping.ts index 2f9412de51..fc9a4f29cc 100644 --- a/packages/jellyfish-transaction/src/script/mapping.ts +++ b/packages/jellyfish-transaction/src/script/mapping.ts @@ -18,7 +18,7 @@ import { PoolRemoveLiquidity, PoolSwap } from './defi/dftx_pool' -import { CTokenMint, TokenMint } from './defi/dftx_token' +import { CTokenCreate, CTokenMint, TokenCreate, TokenMint } from './defi/dftx_token' import { AccountToAccount, AccountToUtxos, @@ -161,6 +161,14 @@ export const OP_CODES = { data: tokenMint }) }, + OP_DEFI_TX_TOKEN_CREATE: (tokenCreate: TokenCreate): OP_DEFI_TX => { + return new OP_DEFI_TX({ + signature: CDfTx.SIGNATURE, + type: CTokenCreate.OP_CODE, + name: CTokenCreate.OP_NAME, + data: tokenCreate + }) + }, OP_DEFI_TX_UTXOS_TO_ACCOUNT: (utxosToAccount: UtxosToAccount): OP_DEFI_TX => { return new OP_DEFI_TX({ signature: CDfTx.SIGNATURE,