PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-stx/test/unit/transactionBuilder

Просмотр файла: contractBuilder.ts

import assert from 'assert';
import should from 'should';
import BigNum from 'bn.js';
import { StacksTestnet, StacksMainnet } from '@stacks/network';
import { TransactionType } from '@bitgo/sdk-core';
import { bufferCV, noneCV, someCV, standardPrincipalCV, tupleCV, uintCV, intCV } from '@stacks/transactions';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { BitGoAPI } from '@bitgo/sdk-api';
import { coins } from '@bitgo/statics';

import { Stx, Tstx, StxLib } from '../../../src';
import * as testData from '../resources';

const { stringifyCv } = StxLib.Utils;

describe('Stacks: Contract Builder', function () {
  const coinName = 'stx';
  const coinNameTest = 'tstx';
  let bitgo: TestBitGoAPI;

  before(function () {
    bitgo = TestBitGo.decorate(BitGoAPI, {
      env: 'mock',
    });
    bitgo.initializeTestVars();
    bitgo.safeRegister('stx', Stx.createInstance);
    bitgo.safeRegister('tstx', Tstx.createInstance);
  });

  describe('Stx Contract call Builder', () => {
    const factory = new StxLib.TransactionBuilderFactory(coins.get(coinNameTest));
    const factoryProd = new StxLib.TransactionBuilderFactory(coins.get(coinName));

    const initTxBuilder = () => {
      const txBuilder = factory.getContractBuilder();
      txBuilder.fee({ fee: '180' });
      txBuilder.nonce(0);
      txBuilder.contractAddress(testData.CONTRACT_ADDRESS);
      txBuilder.contractName(testData.CONTRACT_NAME);
      txBuilder.functionName(testData.CONTRACT_FUNCTION_NAME);
      return txBuilder;
    };

    describe('contract builder environment', function () {
      it('should select the right network', function () {
        should.equal(factory.getTransferBuilder().coinName(), 'tstx');
        should.equal(factoryProd.getTransferBuilder().coinName(), 'stx');
        // used type any to access protected properties
        const txBuilder: any = factory.getTransferBuilder();
        const txBuilderProd: any = factoryProd.getTransferBuilder();

        txBuilder._network.should.deepEqual(new StacksTestnet());
        txBuilderProd._network.should.deepEqual(new StacksMainnet());
      });
    });

    describe('should build ', () => {
      it('an unsigned contract call transaction', async () => {
        const builder = initTxBuilder();
        builder.functionArgs([
          { type: 'uint128', val: '400000000' },
          { type: 'principal', val: testData.ACCOUNT_2.address },
          { type: 'optional', val: { type: 'uint128', val: '200' } },
          {
            type: 'optional',
            val: {
              type: 'tuple',
              val: [
                { key: 'hashbytes', type: 'buffer', val: Buffer.from('some-hash') },
                { key: 'version', type: 'buffer', val: new BigNum(1).toBuffer() },
              ],
            },
          },
        ]);
        builder.fromPubKey(testData.TX_SENDER.pub);
        builder.numberSignatures(1);
        const tx = await builder.build();

        const txJson = tx.toJson();
        should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS);
        should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME);
        should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME);
        should.deepEqual(txJson.nonce, 0);
        should.deepEqual(txJson.fee.toString(), '180');
        should.deepEqual(tx.toBroadcastFormat(), testData.UNSIGNED_CONTRACT_CALL);

        tx.type.should.equal(TransactionType.ContractCall);
        tx.outputs.length.should.equal(1);
        tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS);
        tx.outputs[0].value.should.equal('0');
        tx.inputs.length.should.equal(1);
        tx.inputs[0].address.should.equal(testData.TX_SENDER.address);
        tx.inputs[0].value.should.equal('0');
      });

      it('an unsigned self stacking contract call transaction', async () => {
        const builder = initTxBuilder();
        /* Contract call in clarity POX-4
        (define-public (stack-stx (amount-ustx uint)
                          (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32))))
                          (start-burn-ht uint)
                          (lock-period uint)
                          (signer-sig (optional (buff 65)))
                          (signer-key (buff 33))
                          (max-amount uint)
                          (auth-id uint))
         */
        builder.functionArgs([
          { type: 'uint128', val: '400000000' },
          {
            type: 'tuple',
            val: [
              { key: 'hashbytes', type: 'buffer', val: Buffer.from('some-hash') },
              { key: 'version', type: 'buffer', val: new BigNum(1).toBuffer() },
            ],
          },
          { type: 'uint128', val: '52800' },
          { type: 'uint128', val: '2' },
          // Nakamoto upgrade new 4 parameters
          // https://docs.stacks.co/nakamoto-upgrade/signing-and-stacking/stacking-flow#solo-stacker-flow
          { type: 'optional', val: { type: 'buffer', val: Buffer.from('some-hash') } },
          { type: 'buffer', val: Buffer.from('some-hash') },
          { type: 'uint128', val: '340282366920938463463374607431768211455' },
          { type: 'uint128', val: '123456' },
        ]);
        builder.fromPubKey(testData.TX_SENDER.pub);
        builder.numberSignatures(1);
        const tx = await builder.build();

        const txJson = tx.toJson();
        should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS);
        should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME);
        should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME);
        should.deepEqual(txJson.nonce, 0);
        should.deepEqual(txJson.fee.toString(), '180');
        should.deepEqual(tx.toBroadcastFormat(), testData.UNSIGNED_SELF_STACK_CONTRACT_CALL);

        tx.type.should.equal(TransactionType.ContractCall);
        tx.outputs.length.should.equal(1);
        tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS);
        tx.outputs[0].value.should.equal('0');
        tx.inputs.length.should.equal(1);
        tx.inputs[0].address.should.equal(testData.TX_SENDER.address);
        tx.inputs[0].value.should.equal('0');
      });

      it('a signed contract call with args', async () => {
        const builder = initTxBuilder();
        builder.functionArgs([
          { type: 'uint128', val: '400000000' },
          { type: 'principal', val: testData.ACCOUNT_2.address },
          { type: 'optional' },
          {
            type: 'optional',
            val: {
              type: 'tuple',
              val: [
                { key: 'hashbytes', type: 'buffer', val: Buffer.from('some-hash') },
                { key: 'version', type: 'buffer', val: new BigNum(1).toBuffer() },
              ],
            },
          },
        ]);
        builder.sign({ key: testData.TX_SENDER.prv });
        const tx = await builder.build();

        const txJson = tx.toJson();
        should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS);
        should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME);
        should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME);
        should.deepEqual(txJson.nonce, 0);
        should.deepEqual(txJson.fee.toString(), '180');
        should.deepEqual(tx.toBroadcastFormat(), testData.SIGNED_CONTRACT_WITH_ARGS);

        tx.type.should.equal(TransactionType.ContractCall);
        tx.outputs.length.should.equal(1);
        tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS);
        tx.outputs[0].value.should.equal('0');
        tx.inputs.length.should.equal(1);
        tx.inputs[0].address.should.equal(testData.TX_SENDER.address);
        tx.inputs[0].value.should.equal('0');
      });

      it('a signed self stacking contract call', async () => {
        const builder = initTxBuilder();
        /* Contract call in clarity POX-4
        (define-public (stack-stx (amount-ustx uint)
                          (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32))))
                          (start-burn-ht uint)
                          (lock-period uint)
                          (signer-sig (optional (buff 65)))
                          (signer-key (buff 33))
                          (max-amount uint)
                          (auth-id uint))
         */
        builder.functionArgs([
          { type: 'uint128', val: '400000000' },
          {
            type: 'tuple',
            val: [
              { key: 'hashbytes', type: 'buffer', val: Buffer.from('some-hash') },
              { key: 'version', type: 'buffer', val: new BigNum(1).toBuffer() },
            ],
          },
          { type: 'uint128', val: '52800' },
          { type: 'uint128', val: '2' },
          // Nakamoto upgrade new 4 parameters
          // https://docs.stacks.co/nakamoto-upgrade/signing-and-stacking/stacking-flow#solo-stacker-flow
          { type: 'optional', val: { type: 'buffer', val: Buffer.from('some-hash') } },
          { type: 'buffer', val: Buffer.from('some-hash') },
          { type: 'uint128', val: '340282366920938463463374607431768211455' },
          { type: 'uint128', val: '123456' },
        ]);
        builder.sign({ key: testData.TX_SENDER.prv });
        const tx = await builder.build();

        const txJson = tx.toJson();
        should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS);
        should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME);
        should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME);
        should.deepEqual(txJson.nonce, 0);
        should.deepEqual(txJson.fee.toString(), '180');
        should.deepEqual(tx.toBroadcastFormat(), testData.SIGNED_SELF_STACK_CONTRACT_CALL);

        tx.type.should.equal(TransactionType.ContractCall);
        tx.outputs.length.should.equal(1);
        tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS);
        tx.outputs[0].value.should.equal('0');
        tx.inputs.length.should.equal(1);
        tx.inputs[0].address.should.equal(testData.TX_SENDER.address);
        tx.inputs[0].value.should.equal('0');
      });

      it('a signed contract call transaction', async () => {
        const amount = 123;
        const builder = initTxBuilder();
        builder.functionArgs([{ type: 'optional', val: { type: 'int128', val: amount } }]);
        builder.sign({ key: testData.TX_SENDER.prv });
        const tx = await builder.build();

        const txJson = tx.toJson();
        should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS);
        should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME);
        should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME);
        should.deepEqual(txJson.nonce, 0);
        should.deepEqual(txJson.fee.toString(), '180');
        should.deepEqual(txJson.payload.functionArgs, [stringifyCv(someCV(intCV(amount)))]);
        should.deepEqual(tx.toBroadcastFormat(), testData.SIGNED_CONTRACT_CALL);
        tx.type.should.equal(TransactionType.ContractCall);
      });

      it('a signed serialized contract call transaction', async () => {
        const builder = factory.from(testData.SIGNED_CONTRACT_CALL);
        const tx = await builder.build();
        const txJson = tx.toJson();
        should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS);
        should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME);
        should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME);
        should.deepEqual(txJson.nonce, 0);
        should.deepEqual(txJson.fee.toString(), '180');
        should.deepEqual(txJson.payload.functionArgs, [stringifyCv(someCV(intCV(123)))]);
        should.deepEqual(tx.toBroadcastFormat(), testData.SIGNED_CONTRACT_CALL);
        tx.type.should.equal(TransactionType.ContractCall);
        tx.outputs.length.should.equal(1);
        tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS);
        tx.outputs[0].value.should.equal('0');
        tx.inputs.length.should.equal(1);
        tx.inputs[0].address.should.equal(testData.TX_SENDER.address);
        tx.inputs[0].value.should.equal('0');
      });

      it('a signed serialized self stacking contract call transaction', async () => {
        const builder = factory.from(testData.SIGNED_SELF_STACK_CONTRACT_CALL);
        const tx = await builder.build();
        const txJson = tx.toJson();
        should.deepEqual(txJson.payload.contractAddress, testData.CONTRACT_ADDRESS);
        should.deepEqual(txJson.payload.contractName, testData.CONTRACT_NAME);
        should.deepEqual(txJson.payload.functionName, testData.CONTRACT_FUNCTION_NAME);
        should.deepEqual(txJson.nonce, 0);
        should.deepEqual(txJson.fee.toString(), '180');
        // Now stacks-stx self-stacking supports 8 parameters
        // https://docs.stacks.co/nakamoto-upgrade/signing-and-stacking/stacking-flow#solo-stacker-flow
        should.deepEqual(txJson.payload.functionArgs.length, 8);
        should.deepEqual(tx.toBroadcastFormat(), testData.SIGNED_SELF_STACK_CONTRACT_CALL);
        tx.type.should.equal(TransactionType.ContractCall);
        tx.outputs.length.should.equal(1);
        tx.outputs[0].address.should.equal(testData.CONTRACT_ADDRESS);
        tx.outputs[0].value.should.equal('0');
        tx.inputs.length.should.equal(1);
        tx.inputs[0].address.should.equal(testData.TX_SENDER.address);
        tx.inputs[0].value.should.equal('0');
      });

      it('a multisig transfer transaction', async () => {
        const builder = initTxBuilder();
        builder.functionArgs([{ type: 'optional', val: { type: 'int128', val: '123' } }]);

        builder.sign({ key: testData.prv1 });
        builder.sign({ key: testData.prv2 });
        builder.fromPubKey([testData.pub1, testData.pub2, testData.pub3]);
        builder.numberSignatures(2);
        const tx = await builder.build();
        JSON.stringify(tx.toJson());
        should.deepEqual(tx.toBroadcastFormat(), testData.MULTI_SIG_CONTRACT_CALL);
      });

      describe('ParseCV test', () => {
        it('Optional with out value', () => {
          const amount = '400000000';
          const builder = initTxBuilder();
          builder.functionArgs([
            { type: 'uint128', val: amount },
            { type: 'principal', val: testData.ACCOUNT_2.address },
            { type: 'optional' },
            {
              type: 'optional',
              val: {
                type: 'tuple',
                val: [
                  { key: 'hashbytes', type: 'buffer', val: Buffer.from('some-hash') },
                  { key: 'version', type: 'buffer', val: new BigNum(1).toBuffer() },
                ],
              },
            },
          ]);
          should.deepEqual((builder as any)._functionArgs, [
            uintCV(amount),
            standardPrincipalCV(testData.ACCOUNT_2.address),
            noneCV(),
            someCV(
              tupleCV({
                hashbytes: bufferCV(Buffer.from('some-hash')),
                version: bufferCV(new BigNum(1).toBuffer()),
              })
            ),
          ]);
        });

        it('use ClarityValue', () => {
          const amount = '400000000';
          const builder = initTxBuilder();
          builder.functionArgs([
            uintCV(amount),
            standardPrincipalCV(testData.ACCOUNT_2.address),
            noneCV(),
            someCV(
              tupleCV({
                hashbytes: bufferCV(Buffer.from('some-hash')),
                version: bufferCV(new BigNum(1).toBuffer()),
              })
            ),
          ]);
          should.deepEqual((builder as any)._functionArgs, [
            uintCV(amount),
            standardPrincipalCV(testData.ACCOUNT_2.address),
            noneCV(),
            someCV(
              tupleCV({
                hashbytes: bufferCV(Buffer.from('some-hash')),
                version: bufferCV(new BigNum(1).toBuffer()),
              })
            ),
          ]);
        });

        it('Buffer as string', () => {
          const builder = initTxBuilder();
          builder.functionArgs([
            { type: 'buffer', val: 'some-hash' },
            { type: 'buffer', val: '1' },
          ]);
          should.deepEqual((builder as any)._functionArgs, [
            bufferCV(Buffer.from('some-hash')),
            bufferCV(new BigNum(1).toBuffer()),
          ]);
        });

        it('Buffer as number', () => {
          const builder = initTxBuilder();
          builder.functionArgs([
            { type: 'buffer', val: '1' },
            { type: 'buffer', val: 1 },
          ]);
          should.deepEqual((builder as any)._functionArgs, [
            bufferCV(new BigNum(1).toBuffer()),
            bufferCV(new BigNum(1).toBuffer()),
          ]);
        });

        it('invalid type', () => {
          const builder = initTxBuilder();

          assert.throws(
            () => builder.functionArgs([{ type: 'unknow', val: 'any-val' }]),
            new RegExp('Unexpected Clarity ABI type primitive: "unknow"')
          );
        });
      });

      describe('should fail', () => {
        it('a contract call with an invalid key', () => {
          const builder = initTxBuilder();
          assert.throws(() => builder.sign({ key: 'invalidKey' }), /Unsupported private key/);
        });
        it('a contract call with an invalid contract address', () => {
          const builder = initTxBuilder();
          assert.throws(() => builder.contractAddress(testData.ACCOUNT_1.address), /Invalid contract address/);
        });
        it('a contract call with an invalid contract name pox-2', () => {
          const builder = initTxBuilder();
          assert.throws(() => builder.contractName('pox-2'), /Only pox-4 and send-many-memo contracts supported/);
        });
        it('a contract call with an invalid contract name pox-3', () => {
          const builder = initTxBuilder();
          assert.throws(() => builder.contractName('pox-3'), /Only pox-4 and send-many-memo contracts supported/);
        });
        it('a contract call with an invalid contract function name', () => {
          const builder = initTxBuilder();
          assert.throws(
            () => builder.functionName('test-function'),
            new RegExp('test-function is not supported contract function name')
          );
        });
      });
    });
  });
});

Выполнить команду


Для локальной разработки. Не используйте в интернете!