From 23cc87ec738f38f13f91040b20a190786c389de8 Mon Sep 17 00:00:00 2001 From: pierregee Date: Fri, 20 Jan 2023 14:07:45 +0800 Subject: [PATCH 1/5] add txn builder and dftx for minting token --- .../txn/txn_builder_token_burn_token.test.ts | 2 +- .../txn/txn_builder_token_mint_token.test.ts | 181 ++++++++++++++++++ .../src/txn/txn_builder_token.ts | 16 +- .../script/dftx/dftx_token/TokenMint.test.ts | 121 +++++++++--- .../src/script/dftx/dftx_token.ts | 4 +- 5 files changed, 291 insertions(+), 33 deletions(-) create mode 100644 packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_mint_token.test.ts diff --git a/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_burn_token.test.ts b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_burn_token.test.ts index d59a18f54e..3c6e3ac572 100644 --- a/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_burn_token.test.ts +++ b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_burn_token.test.ts @@ -65,7 +65,7 @@ describe('burnToken', () => { await setupGovs() - // Fund 10 DFI UTXO + // Fund 100 DFI UTXO await fundEllipticPair(testing.container, providers.ellipticPair, 100) await providers.setupMocks() diff --git a/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_mint_token.test.ts b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_mint_token.test.ts new file mode 100644 index 0000000000..60444f1c2e --- /dev/null +++ b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_token_mint_token.test.ts @@ -0,0 +1,181 @@ +import { StartFlags } from '@defichain/testcontainers' +import { getProviders, MockProviders } from '../provider.mock' +import { P2WPKHTransactionBuilder } from '../../src' +import { fundEllipticPair, sendTransaction } from '../test.utils' +import BigNumber from 'bignumber.js' +import { TestingGroup } from '@defichain/jellyfish-testing' +import { RegTest } from '@defichain/jellyfish-network' +import { P2WPKH } from '@defichain/jellyfish-address' +import { OP_CODES, TokenMint } from '@defichain/jellyfish-transaction' +import { Bech32 } from '@defichain/jellyfish-crypto' + +const attributeKey = 'ATTRIBUTES' +const symbolDBTC = 'BTC' + +describe.only('Consortium', () => { + const tGroup = TestingGroup.create(4) + let account0: string, account1: string, account2: string, account3: string + let idBTC: string//, idDOGE: string + const symbolBTC = 'BTC' + const symbolDOGE = 'DOGE' + + let providers: MockProviders + let builder: P2WPKHTransactionBuilder + + const startFlags: StartFlags[] = [{ name: 'regtest-minttoken-simulate-mainnet', value: 1 }] + + beforeEach(async () => { + await tGroup.start({ startFlags }) + + account0 = await tGroup.get(0).generateAddress() + account1 = await tGroup.get(1).generateAddress() + account2 = await tGroup.get(2).generateAddress() + account3 = await tGroup.get(3).generateAddress() + + await tGroup.get(0).token.create({ + symbol: symbolBTC, + name: symbolBTC, + isDAT: true, + mintable: true, + tradeable: true, + collateralAddress: account0 + }) + await tGroup.get(0).generate(1) + + await tGroup.get(0).token.create({ + symbol: symbolDOGE, + name: symbolDOGE, + isDAT: true, + mintable: true, + tradeable: true, + collateralAddress: account0 + }) + await tGroup.get(0).generate(1) + + idBTC = await tGroup.get(0).token.getTokenId(symbolDBTC) + + providers = await getProviders(tGroup.get(0).container) + // providers.setEllipticPair(WIF.asEllipticPair(RegTestFoundationKeys[0].owner.privKey)) + + // Fund 10 DFI UTXO + await tGroup.get(0).container.fundAddress(account0, 10) + await tGroup.get(0).container.fundAddress(account1, 10) + await tGroup.get(0).container.fundAddress(account2, 10) + await tGroup.get(0).container.fundAddress(account3, 10) + await fundEllipticPair(tGroup.get(0).container, providers.ellipticPair, 100) + + await tGroup.get(0).generate(1) + await tGroup.waitForSync() + + builder = new P2WPKHTransactionBuilder(providers.fee, providers.prevout, providers.elliptic, RegTest) + + await providers.setupMocks() + await setupGovs() + }) + + afterEach(async () => { + await tGroup.stop() + }) + + async function setupGovs (): Promise { + // await tGroup.get(0).rpc.masternode.setGov({ + // [attributeKey]: + // { + // // Enable consortium + // 'v0/params/feature/consortium': 'true', + + // // Set a consortium global limit for dBTC + // [`v0/consortium/${idBTC}/mint_limit`]: '50', + // [`v0/consortium/${idBTC}/mint_limit_daily`]: '5', + + // // Set a consortium member for dBTC + // [`v0/consortium/${idBTC}/members`]: { + // '01': { + // name: 'Waves HQ', + // ownerAddress: RegTestFoundationKeys[0].owner.address, + // backingId: 'backing_address_btc_1_c', + // mintLimitDaily: '5.00000000', + // mintLimit: '50.00000000' + // } + // } + // } + // }) + // await tGroup.get(0).generate(1) + } + + it.only('should be able to mint tokens to an address', async () => { + await tGroup.get(0).rpc.masternode.setGov({ + [attributeKey]: + { + // Enable consortium + 'v0/params/feature/consortium': 'true', + 'v0/params/feature/mint-tokens-to-address': 'true', + + // Set a consortium global limit for dBTC + [`v0/consortium/${idBTC}/mint_limit`]: '50', + [`v0/consortium/${idBTC}/mint_limit_daily`]: '5', + + // Set a consortium member for dBTC + [`v0/consortium/${idBTC}/members`]: { + '01': { + name: 'Waves HQ', + ownerAddress: account0, + backingId: 'backing_address_btc_1_c', + mintLimitDaily: '5.00000000', + mintLimit: '50.00000000' + } + } + } + }) + await tGroup.get(0).generate(1) + await tGroup.waitForSync() + + const toAddress = await tGroup.get(0).container.getNewAddress() + const wavesColScript = P2WPKH.fromAddress(RegTest, toAddress, P2WPKH).getScript() + const script = await providers.elliptic.script() + + const tokenMint: TokenMint = { + // Mint 10.4 BTC + balances: [{ token: Number(idBTC), amount: new BigNumber(10.4) }], + to: wavesColScript + } + const txn = await builder.tokens.mint(tokenMint, script) + + // Ensure the created txn is correct + const outs = await sendTransaction(tGroup.get(0).container, txn) + const encoded: string = OP_CODES.OP_DEFI_TX_TOKEN_MINT(tokenMint).asBuffer().toString('hex') + const pubKey = await providers.ellipticPair.publicKey() + const address = Bech32.fromPubKey(pubKey, 'bcrt') + + expect(outs).toStrictEqual([{ + n: 0, + scriptPubKey: { + asm: expect.stringMatching(/^OP_RETURN 446654784d/), + hex: `6a${encoded}`, + type: 'nulldata' + }, + tokenId: 0, + value: 0 + }, { + n: 1, + scriptPubKey: { + addresses: [address], + asm: expect.any(String), + hex: expect.any(String), + reqSigs: 1, + type: 'witness_v0_keyhash' + }, + tokenId: 0, + value: 99.9999918 + }]) + await tGroup.get(0).generate(1) + + const attr = await tGroup.get(0).rpc.masternode.getGov(attributeKey) + expect(attr[`v0/live/economy/consortium/${idBTC}/minted`]).toStrictEqual(new BigNumber('10.4')) + console.log({ attributes: JSON.stringify(attr), idBTC }) + + const accAfter = (await tGroup.get(0).rpc.account.getAccount(toAddress)) + + expect(accAfter).toStrictEqual(['10.40000000@BTC']) + }) +}) diff --git a/packages/jellyfish-transaction-builder/src/txn/txn_builder_token.ts b/packages/jellyfish-transaction-builder/src/txn/txn_builder_token.ts index 3afdf87b95..c7316cfedb 100644 --- a/packages/jellyfish-transaction-builder/src/txn/txn_builder_token.ts +++ b/packages/jellyfish-transaction-builder/src/txn/txn_builder_token.ts @@ -1,5 +1,5 @@ import { - OP_CODES, Script, TransactionSegWit, TokenBurn + OP_CODES, Script, TransactionSegWit, TokenBurn, TokenMint } from '@defichain/jellyfish-transaction' import { P2WPKHTxnBuilder } from './txn_builder' @@ -17,4 +17,18 @@ export class TxnBuilderTokens extends P2WPKHTxnBuilder { changeScript ) } + + /** + * Mint tokens + * + * @param {TokenMint} tokenMint txn to create + * @param {Script} changeScript to send unspent to after deducting the (converted + fees) + * @returns {Promise} + */ + async mint (tokenMint: TokenMint, changeScript: Script): Promise { + return await super.createDeFiTx( + OP_CODES.OP_DEFI_TX_TOKEN_MINT(tokenMint), + changeScript + ) + } } diff --git a/packages/jellyfish-transaction/__tests__/script/dftx/dftx_token/TokenMint.test.ts b/packages/jellyfish-transaction/__tests__/script/dftx/dftx_token/TokenMint.test.ts index 55baa7f008..f6c9c92d00 100644 --- a/packages/jellyfish-transaction/__tests__/script/dftx/dftx_token/TokenMint.test.ts +++ b/packages/jellyfish-transaction/__tests__/script/dftx/dftx_token/TokenMint.test.ts @@ -7,8 +7,9 @@ import { CTokenMint, TokenMint } from '../../../../src/script/dftx/dftx_token' it('should bi-directional buffer-object-buffer', () => { const fixtures = [ - '6a12446654784d010200000000ca9a3b00000000', - '6a12446654784d010200000000ea56fa00000000' + '6a13446654784d010200000000ca9a3b0000000000', + '6a13446654784d010200000000ea56fa0000000000', + '6a29446654784d010100000000e1f50500000000160014dd527be30bedb3de69fee5ebe32af430686cfe3f' ] fixtures.forEach(hex => { @@ -21,40 +22,100 @@ it('should bi-directional buffer-object-buffer', () => { }) }) -const header = '6a12446654784d' // OP_RETURN, PUSH_DATA(44665478, 4d) -const data = '01010000006050da6001000000' -const tokenMint: TokenMint = { - balances: [ +const tokenMintData: Array<{ header: string, data: string, tokenMint: TokenMint }> = [ + { + header: '6a13446654784d', // OP_RETURN(0x6a) (length 19 = 0x13) CDfTx.SIGNATURE(0x44665478) CTokenMint.OP_CODE(0x4d) + // TokenMint.balances(0x01010000006050da6001000000) + // TokenMint.to[LE](00) + data: '01010000006050da600100000000', + tokenMint: { + balances: [ + { + token: 1, + amount: new BigNumber('59.19887456') + } + ], + to: { + stack: [] + } + } + }, + { + header: '6a29446654784d', // OP_RETURN(0x6a) (length 69 = 0x29) CDfTx.SIGNATURE(0x44665478) CTokenMint.OP_CODE(0x4d) + // TokenMint.balances(0x010100000000e1f50500000000) + // TokenMint.to(160014ad54d71e8681e0c990349070cbd17a5c567a9b9e) + data: '010100000000e1f50500000000160014ad54d71e8681e0c990349070cbd17a5c567a9b9e', + tokenMint: { - token: 1, - amount: new BigNumber('59.19887456') + balances: [ + { + token: 1, + amount: new BigNumber('1') + } + ], + to: { + stack: [ + OP_CODES.OP_0, + OP_CODES.OP_PUSHDATA_HEX_LE('ad54d71e8681e0c990349070cbd17a5c567a9b9e') + ] + } } - ] -} + } +] -it('should craft dftx with OP_CODES._()', () => { - const stack = [ - OP_CODES.OP_RETURN, - OP_CODES.OP_DEFI_TX_TOKEN_MINT(tokenMint) - ] +describe.each(tokenMintData)('should craft and compose dftx', + ({ header, tokenMint, data }: { header: string, data: string, tokenMint: TokenMint }) => { + it('should craft dftx with OP_CODES._()', () => { + const stack = [ + OP_CODES.OP_RETURN, + OP_CODES.OP_DEFI_TX_TOKEN_MINT(tokenMint) + ] - const buffer = toBuffer(stack) - expect(buffer.toString('hex')).toStrictEqual(header + data) -}) + const buffer = toBuffer(stack) + expect(buffer.toString('hex')).toStrictEqual(header + data) + }) -describe('Composable', () => { - it('should compose from buffer to composable', () => { - const buffer = SmartBuffer.fromBuffer(Buffer.from(data, 'hex')) - const composable = new CTokenMint(buffer) + describe('Composable', () => { + it('should compose from buffer to composable', () => { + const buffer = SmartBuffer.fromBuffer(Buffer.from(data, 'hex')) + const composable = new CTokenMint(buffer) - expect(composable.toObject()).toStrictEqual(tokenMint) - }) + expect(composable.toObject()).toStrictEqual(tokenMint) + }) + + it('should compose from composable to buffer', () => { + const composable = new CTokenMint(tokenMint) + const buffer = new SmartBuffer() + composable.toBuffer(buffer) - it('should compose from composable to buffer', () => { - const composable = new CTokenMint(tokenMint) - const buffer = new SmartBuffer() - composable.toBuffer(buffer) + expect(buffer.toBuffer().toString('hex')).toStrictEqual(data) + }) + }) - expect(buffer.toBuffer().toString('hex')).toStrictEqual(data) + it('should craft dftx with OP_CODES._()', () => { + const stack = [ + OP_CODES.OP_RETURN, + OP_CODES.OP_DEFI_TX_TOKEN_MINT(tokenMint) + ] + + const buffer = toBuffer(stack) + expect(buffer.toString('hex')).toStrictEqual(header + data) + }) + + describe('Composable', () => { + it('should compose from buffer to composable', () => { + const buffer = SmartBuffer.fromBuffer(Buffer.from(data, 'hex')) + const composable = new CTokenMint(buffer) + + expect(composable.toObject()).toStrictEqual(tokenMint) + }) + + it('should compose from composable to buffer', () => { + const composable = new CTokenMint(tokenMint) + const buffer = new SmartBuffer() + composable.toBuffer(buffer) + + expect(buffer.toBuffer().toString('hex')).toStrictEqual(data) + }) + }) }) -}) diff --git a/packages/jellyfish-transaction/src/script/dftx/dftx_token.ts b/packages/jellyfish-transaction/src/script/dftx/dftx_token.ts index 44f2309ab4..c464cc8d20 100644 --- a/packages/jellyfish-transaction/src/script/dftx/dftx_token.ts +++ b/packages/jellyfish-transaction/src/script/dftx/dftx_token.ts @@ -9,6 +9,7 @@ import BigNumber from 'bignumber.js' */ export interface TokenMint { balances: TokenBalanceUInt32[] // ----------| c = VarUInt{1-9 bytes}, + c x TokenBalance + to: Script } /** @@ -21,7 +22,8 @@ export class CTokenMint extends ComposableBuffer { composers (tm: TokenMint): BufferComposer[] { return [ - ComposableBuffer.compactSizeArray(() => tm.balances, v => tm.balances = v, v => new CTokenBalance(v)) + ComposableBuffer.compactSizeArray(() => tm.balances, v => tm.balances = v, v => new CTokenBalance(v)), + ComposableBuffer.single