PHP WebShell

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

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

import _ from 'lodash';
import nock from 'nock';
import { BitGoAPI } from '@bitgo/sdk-api';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { ITransactionRecipient, Wallet, Memo } from '@bitgo/sdk-core';

import { Sip10Token } from '../../src';
import * as testData from '../fixtures';
import { RecoveryInfo, RecoveryOptions, RecoveryTransaction } from '../../src/lib/iface';
import assert from 'assert';

describe('Sip10Token:', function () {
  const sip10TokenName = 'tstx:tsip6dp';
  let bitgo: TestBitGoAPI;
  let basecoin: Sip10Token;
  let newTxPrebuild: () => { txHex: string; txInfo: Record<string, unknown> };
  let newTxParams: () => { recipients: ITransactionRecipient[]; memo?: Memo };
  let wallet: Wallet;

  const txPreBuild = {
    txHex: testData.txForExplainFungibleTokenTransfer,
    txInfo: {},
  };

  const txParams = {
    recipients: testData.recipients,
  };

  const memo = {
    type: '',
    value: '1',
  };

  before(function () {
    bitgo = TestBitGo.decorate(BitGoAPI, {
      env: 'mock',
    });
    bitgo.initializeTestVars();
    Sip10Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
      bitgo.safeRegister(name, coinConstructor);
    });
    newTxPrebuild = () => {
      return _.cloneDeep(txPreBuild);
    };
    newTxParams = () => {
      return _.cloneDeep(txParams);
    };
    basecoin = bitgo.coin(sip10TokenName) as Sip10Token;
    wallet = new Wallet(bitgo, basecoin, {});
  });

  describe('Verify Transaction', function () {
    it('should succeed to verify transaction', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txParams.memo = memo;
      const verification = {};
      const isTransactionVerified = await basecoin.verifyTransaction({
        txParams,
        txPrebuild,
        verification,
        wallet,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should succeed to verify transaction when recipients amount are numbers', async function () {
      const txPrebuild = newTxPrebuild();
      const txParamsWithNumberAmounts = newTxParams();
      txParamsWithNumberAmounts.recipients = txParamsWithNumberAmounts.recipients.map(
        ({ address, amount, memo, tokenName }) => {
          return { address, amount: Number(amount), memo, tokenName };
        }
      );
      txParamsWithNumberAmounts.memo = memo;

      const verification = {};
      const isTransactionVerified = await basecoin.verifyTransaction({
        txParams: txParamsWithNumberAmounts,
        txPrebuild,
        verification,
        wallet,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should succeed to verify when memo is passed', async function () {
      const txPrebuild = newTxPrebuild();
      txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithMemoId10;
      const txParams = newTxParams();
      const verification = {};
      txParams.memo = {
        type: '',
        value: '10',
      };
      const isTransactionVerified = await basecoin.verifyTransaction({
        txParams: txParams,
        txPrebuild,
        verification,
        wallet,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should succeed to verify when memo is zero', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithMemoZero;
      txParams.memo = {
        type: '',
        value: '0',
      };
      const verification = {};
      const isTransactionVerified = await basecoin.verifyTransaction({
        txParams: txParams,
        txPrebuild,
        verification,
        wallet,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should succeed to verify when memo is passed inside recipient address', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txParams.recipients[0].address = 'SN2NN1JP9AEP5BVE19RNJ6T2MP7NDGRZYST1VDF3M?memoId=10';
      txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithMemoId10;
      const verification = {};
      const isTransactionVerified = await basecoin.verifyTransaction({
        txParams: txParams,
        txPrebuild,
        verification,
        wallet,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should succeed to verify when memo is not passed', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithoutMemo;
      const verification = {};
      const isTransactionVerified = await basecoin.verifyTransaction({
        txParams: txParams,
        txPrebuild,
        verification,
        wallet,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should fail to verify transaction with no recipients', async function () {
      const txPrebuild = {};
      const txParams = newTxParams();
      txParams.recipients = [];
      await basecoin
        .verifyTransaction({
          txParams,
          txPrebuild,
          wallet,
        })
        .should.rejectedWith('missing required tx prebuild property txHex');
    });

    it('should fail when more than 1 recipients are passed', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txParams.recipients.push({
        address: 'SN2NN1JP9AEP5BVE19RNJ6T2MP7NDGRZYST1VDF3N',
        amount: '10000',
        memo: '1',
        tokenName: 'tsip6dp-token',
      });
      await basecoin
        .verifyTransaction({
          txParams,
          txPrebuild,
          wallet,
        })
        .should.rejectedWith(
          "tstx:tsip6dp doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient."
        );
    });

    it('should fail to verify transaction with wrong address', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txParams.recipients[0].address = 'SN2NN1JP9AEP5BVE19RNJ6T2MP7NDGRZYST1VDF3N';
      const verification = {};
      await basecoin
        .verifyTransaction({
          txParams,
          txPrebuild,
          verification,
          wallet,
        })
        .should.rejectedWith('Tx outputs does not match with expected txParams recipients');
    });

    it('should fail to verify transaction with wrong amount', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txParams.recipients[0].amount = '100';
      const verification = {};
      await basecoin
        .verifyTransaction({
          txParams,
          txPrebuild,
          verification,
          wallet,
        })
        .should.rejectedWith('Tx outputs does not match with expected txParams recipients');
    });

    it('should fail to verify transaction with wrong memo', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txParams.recipients[0].memo = '2';
      const verification = {};
      await basecoin
        .verifyTransaction({
          txParams,
          txPrebuild,
          verification,
          wallet,
        })
        .should.rejectedWith('Tx memo does not match with expected txParams recipient memo');
    });

    it('should fail to verify transaction with wrong token', async function () {
      const txPrebuild = newTxPrebuild();
      const txParams = newTxParams();
      txParams.recipients[0].tokenName = 'tstx:tsip8dp';
      const verification = {};
      await basecoin
        .verifyTransaction({
          txParams,
          txPrebuild,
          verification,
          wallet,
        })
        .should.rejectedWith('Tx outputs does not match with expected txParams recipients');
    });
  });

  describe('Recover Transaction SIP10', () => {
    before(function () {
      nock.enableNetConnect();
    });
    beforeEach(function () {
      nock.cleanAll();
    });
    after(function () {
      nock.disableNetConnect();
    });

    it('should build a signed token recover transaction when private key data is passed', async () => {
      const rootAddress = testData.HOT_WALLET_ROOT_ADDRESS;
      nock(`https://api.testnet.hiro.so`)
        .get(`/extended/v2/addresses/${rootAddress}/balances/stx`)
        .reply(200, testData.ACCOUNT_BALANCE_RESPONSE);
      nock('https://api.testnet.hiro.so')
        .get(`/extended/v2/addresses/${rootAddress}/balances/ft/${testData.STX_TOKEN_ASSET_ID}`)
        .reply(200, testData.TOKEN_BALANCE_RESPONSE);
      nock(`https://api.testnet.hiro.so`)
        .get(`/extended/v1/address/${rootAddress}/nonces`)
        .reply(200, testData.ACCOUNT_NONCE_RESPONSE);
      nock(`https://api.testnet.hiro.so`, { allowUnmocked: true })
        .post(`/v2/fees/transaction`, testData.FEE_ESTIMATION_TOKEN_REQUEST)
        .reply(200, testData.FEE_ESTIMATION_TOKEN_RESPONSE);

      const recoveryOptions: RecoveryOptions = {
        backupKey: testData.HOT_WALLET_KEY_CARD_INFO.BACKUP_KEY,
        userKey: testData.HOT_WALLET_KEY_CARD_INFO.USER_KEY,
        rootAddress: rootAddress,
        recoveryDestination: testData.DESTINATION_ADDRESS_WRW,
        bitgoKey: testData.HOT_WALLET_KEY_CARD_INFO.BITGO_PUB_KEY,
        walletPassphrase: testData.HOT_WALLET_KEY_CARD_INFO.WALLET_PASSPHRASE,
        contractId: 'STAG18E45W613FZ3H4ZMF6QHH426EXM5QTSAVWYH.tsip6dp-token',
      };
      const response: RecoveryTransaction = await basecoin.recover(recoveryOptions);
      response.should.have.property('txHex');
      assert.deepEqual(response.txHex, testData.HOT_WALLET_TOKEN_RECOVERY_TX_HEX, 'tx hex not matching!');
    });

    it('should build an unsigned token transaction when public keys are passed', async () => {
      const rootAddress = testData.COLD_WALLET_ROOT_ADDRESS;
      nock(`https://api.testnet.hiro.so`)
        .get(`/extended/v2/addresses/${rootAddress}/balances/stx`)
        .reply(200, testData.ACCOUNT_BALANCE_RESPONSE);
      nock('https://api.testnet.hiro.so')
        .get(`/extended/v2/addresses/${rootAddress}/balances/ft/${testData.STX_TOKEN_ASSET_ID}`)
        .reply(200, testData.TOKEN_BALANCE_RESPONSE);
      nock(`https://api.testnet.hiro.so`, { allowUnmocked: true })
        .get(`/extended/v1/address/${rootAddress}/nonces`)
        .reply(200, testData.ACCOUNT_NONCE_RESPONSE);
      const feeEstimateRequest = testData.FEE_ESTIMATION_TOKEN_REQUEST;
      feeEstimateRequest.transaction_payload =
        '021a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e087472616e73666572000000040100000000000000000000000005f5e10005159f2f1aff6fa0062e1f7fa6096133e75f47a7e8f7051a1500a1c42f0c11bfe3893f479af18904677685be09';
      nock(`https://api.testnet.hiro.so`)
        .post(`/v2/fees/transaction`, feeEstimateRequest)
        .reply(200, testData.FEE_ESTIMATION_TOKEN_RESPONSE);

      const recoveryOptions: RecoveryOptions = {
        backupKey: testData.COLD_WALLET_PUBLIC_KEY_INFO.BACKUP_KEY,
        userKey: testData.COLD_WALLET_PUBLIC_KEY_INFO.USER_KEY,
        rootAddress: rootAddress,
        recoveryDestination: testData.DESTINATION_ADDRESS_WRW,
        bitgoKey: testData.COLD_WALLET_PUBLIC_KEY_INFO.BITGO_PUB_KEY,
        contractId: 'STAG18E45W613FZ3H4ZMF6QHH426EXM5QTSAVWYH.tsip6dp-token',
      };
      const response: RecoveryInfo = (await basecoin.recover(recoveryOptions)) as RecoveryInfo;
      response.should.have.property('txHex');
      response.should.have.property('coin');
      response.should.have.property('feeInfo');
      assert.deepEqual(response.txHex, testData.COLD_WALLET_TOKEN_UNSIGNED_SWEEP_TX_HEX, 'tx hex not matching!');
      assert.deepEqual(response.coin, 'tstx:tsip6dp', 'coin not matching!');
    });

    it('should fail with insufficient balance when native stx balance is lower than fee for sip10', async () => {
      const rootAddress = testData.HOT_WALLET_ROOT_ADDRESS;
      const accountBalance = JSON.parse(JSON.stringify(testData.ACCOUNT_BALANCE_RESPONSE));
      accountBalance.balance = '100'; // set balance lower than fee
      nock(`https://api.testnet.hiro.so`)
        .get(`/extended/v2/addresses/${rootAddress}/balances/stx`)
        .reply(200, accountBalance);
      nock('https://api.testnet.hiro.so')
        .get(`/extended/v2/addresses/${rootAddress}/balances/ft/${testData.STX_TOKEN_ASSET_ID}`)
        .reply(200, testData.TOKEN_BALANCE_RESPONSE);
      nock(`https://api.testnet.hiro.so`)
        .get(`/extended/v1/address/${rootAddress}/nonces`)
        .reply(200, testData.ACCOUNT_NONCE_RESPONSE);
      const feeRequestBody = testData.FEE_ESTIMATION_TOKEN_REQUEST;
      feeRequestBody.transaction_payload =
        '021a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e087472616e73666572000000040100000000000000000000000005f5e100051549857eb4b6dd4fee08c3ec04e3d0ed04ef67d324051a1500a1c42f0c11bfe3893f479af18904677685be09';
      nock(`https://api.testnet.hiro.so`, { allowUnmocked: true })
        .post(`/v2/fees/transaction`, feeRequestBody)
        .reply(200, testData.FEE_ESTIMATION_TOKEN_RESPONSE);
      const recoveryOptions: RecoveryOptions = {
        backupKey: testData.HOT_WALLET_KEY_CARD_INFO.BACKUP_KEY,
        userKey: testData.HOT_WALLET_KEY_CARD_INFO.USER_KEY,
        rootAddress: rootAddress,
        recoveryDestination: testData.DESTINATION_ADDRESS_WRW,
        bitgoKey: testData.HOT_WALLET_KEY_CARD_INFO.BITGO_PUB_KEY,
        walletPassphrase: testData.HOT_WALLET_KEY_CARD_INFO.WALLET_PASSPHRASE,
        contractId: 'STAG18E45W613FZ3H4ZMF6QHH426EXM5QTSAVWYH.tsip6dp-token',
      };
      await basecoin.recover(recoveryOptions).should.rejectedWith('insufficient balance to build the transaction');
    });

    it('should fail when only contract address is passed', async () => {
      const recoveryOptions: RecoveryOptions = {
        backupKey: testData.HOT_WALLET_KEY_CARD_INFO.BACKUP_KEY,
        userKey: testData.HOT_WALLET_KEY_CARD_INFO.USER_KEY,
        rootAddress: testData.HOT_WALLET_ROOT_ADDRESS,
        recoveryDestination: testData.DESTINATION_ADDRESS_WRW,
        bitgoKey: testData.HOT_WALLET_KEY_CARD_INFO.BITGO_PUB_KEY,
        walletPassphrase: testData.HOT_WALLET_KEY_CARD_INFO.WALLET_PASSPHRASE,
        contractId: 'STAG18E45W613FZ3H4ZMF6QHH426EXM5QTSAVWYH',
      };
      await basecoin
        .recover(recoveryOptions)
        .should.rejectedWith('invalid contract id, please provide it in the form (contractAddress.contractName)');
    });
  });
});

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


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