PHP WebShell

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

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

import assert from 'assert';
import should from 'should';
import { TransactionType } from '@bitgo/sdk-core';
import { Transaction, KeyPair, ETHTransactionType, Fee, TransactionBuilder } from '../../../src';
import * as testData from '../../resources/eth';
import { getBuilder } from '../getBuilder';

describe('Eth Transaction builder wallet initialization', function () {
  const sourcePrv =
    'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2';
  const pub1 =
    'xpub661MyMwAqRbcGpyL5QvWah4XZYHuTK21mSQ4NVwYaX67A35Kzb42nmTdf2WArW4tettXrWpfpwFbEFdEVqcSvnHLB8F6p1D41ssmbnRMXpc';
  const pub2 =
    'xpub661MyMwAqRbcFWzoz8qnYRDYEFQpPLYwxVFoG6WLy3ck5ZupRGJTG4ju6yGb7Dj3ey6GsC4kstLRER2nKzgjLtmxyPgC4zHy7kVhUt6yfGn';
  const defaultKeyPair = new KeyPair({
    prv: 'FAC4D04AA0025ECF200D74BC9B5E4616E4B8338B69B61362AAAD49F76E68EF28',
  });

  interface WalletCreationDetails {
    fee?: Fee;
    chainId?: number;
    counter?: number;
    source?: string;
    owners?: string[];
    type?: TransactionType;
  }

  const buildTransaction = async function (details: WalletCreationDetails): Promise<Transaction> {
    const txBuilder: any = getBuilder('teth');
    if (details.type !== undefined) {
      txBuilder.type(details.type);
    }
    if (details.fee !== undefined) {
      txBuilder.fee(details.fee);
    }

    if (details.counter !== undefined) {
      txBuilder.counter(details.counter);
    }

    if (details.owners !== undefined) {
      for (const owner of details.owners) {
        txBuilder.owner(owner);
      }
    }

    return await txBuilder.build();
  };

  describe('should build', () => {
    it('a wallet initialization transaction', async () => {
      const tx = await buildTransaction({
        type: TransactionType.WalletInitialization,
        fee: {
          fee: '10',
          gasLimit: '1000',
        },
        source: new KeyPair({ prv: sourcePrv }).getAddress(),
        owners: [
          new KeyPair({ prv: sourcePrv }).getAddress(),
          new KeyPair({ pub: pub1 }).getAddress(),
          new KeyPair({ pub: pub2 }).getAddress(),
        ],
        counter: 1,
      });

      tx.type.should.equal(TransactionType.WalletInitialization);
      const txJson = tx.toJson();
      txJson.gasLimit.should.equal('1000');
      txJson._type.should.equals(ETHTransactionType.LEGACY);
      txJson.gasPrice!.should.equal('10');
      should.equal(txJson.nonce, 1);
      should.equal(txJson.chainId, 42);
      should.equal(txJson.v, '0x77');
    });

    it('a wallet initialization transaction with nonce 0', async () => {
      const tx = await buildTransaction({
        type: TransactionType.WalletInitialization,
        fee: {
          fee: '10',
          gasLimit: '1000',
        },
        owners: [
          new KeyPair({ prv: sourcePrv }).getAddress(),
          new KeyPair({ pub: pub1 }).getAddress(),
          new KeyPair({ pub: pub2 }).getAddress(),
        ],
        counter: 0,
      });

      tx.type.should.equal(TransactionType.WalletInitialization);
      const txJson = tx.toJson();
      txJson.gasLimit.should.equal('1000');
      txJson._type.should.equals(ETHTransactionType.LEGACY);
      txJson.gasPrice!.should.equal('10');
      should.equal(txJson.nonce, 0);
      should.equal(txJson.chainId, 42);
      should.equal(txJson.v, '0x77');
    });

    it('an unsigned init transaction from serialized with 0-prefixed address', async () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.type(TransactionType.WalletInitialization);
      txBuilder.counter(1);
      txBuilder.fee({
        fee: '10000000000',
        gasLimit: '2000000',
      });
      txBuilder.owner('0x6461EC4E9dB87CFE2aeEc7d9b02Aa264edFbf41f');
      txBuilder.owner('0xf10C8f42BD63D0AeD3338A6B2b661BC6D9fa7C44');
      txBuilder.owner('0x07ee8b845b8bf0e807e096d6b1599b121b82cbe1');
      const tx = await txBuilder.build();
      const serialized = tx.toBroadcastFormat();

      // now rebuild from the signed serialized tx and make sure it stays the same
      const newTxBuilder: any = getBuilder('eth');
      newTxBuilder.from(serialized);
      const newTx = await newTxBuilder.build();
      should.equal(newTx.toBroadcastFormat(), serialized);
    });

    it('an unsigned init transaction from serialized', async () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.type(TransactionType.WalletInitialization);
      txBuilder.counter(1);
      txBuilder.fee({
        fee: '10000000000',
        gasLimit: '2000000',
      });
      txBuilder.owner('0x6461EC4E9dB87CFE2aeEc7d9b02Aa264edFbf41f');
      txBuilder.owner('0xf10C8f42BD63D0AeD3338A6B2b661BC6D9fa7C44');
      txBuilder.owner('0xa4b5666FB4fFEA84Dd848845E1114b84146de4b3');
      const tx = await txBuilder.build();
      const serialized = tx.toBroadcastFormat();

      // now rebuild from the signed serialized tx and make sure it stays the same
      const newTxBuilder: any = getBuilder('eth');
      newTxBuilder.from(serialized);
      const newTx = await newTxBuilder.build();
      should.equal(newTx.toBroadcastFormat(), serialized);
    });

    it('a signed init transaction from serialized', async () => {
      const txBuilder: any = getBuilder('teth');
      txBuilder.type(TransactionType.WalletInitialization);
      txBuilder.counter(1);
      txBuilder.fee({
        fee: '10000000000',
        gasLimit: '2000000',
      });
      txBuilder.owner('0x6461EC4E9dB87CFE2aeEc7d9b02Aa264edFbf41f');
      txBuilder.owner('0xf10C8f42BD63D0AeD3338A6B2b661BC6D9fa7C44');
      txBuilder.owner('0xa4b5666FB4fFEA84Dd848845E1114b84146de4b3');
      txBuilder.sign({ key: defaultKeyPair.getKeys().prv });
      const tx = await txBuilder.build();
      const serialized = tx.toBroadcastFormat();

      // now rebuild from the signed serialized tx and make sure it stays the same
      const newTxBuilder: any = getBuilder('teth');
      newTxBuilder.from(serialized);
      const newTx = await newTxBuilder.build();
      should.equal(newTx.toBroadcastFormat(), serialized);
      should.equal(newTx.id, '0xef04334f21ba844100b9aee6485f14afe177e88308ad2b8c1b0878576a31d47b');
      const txJson = newTx.toJson();
      should.exist(txJson.v);
      should.exist(txJson.r);
      should.exist(txJson.s);
      should.exist(txJson.from);
    });

    it('correct transaction id', async () => {
      const newTxBuilder = getBuilder('eth') as TransactionBuilder;
      newTxBuilder.from(testData.WALLET_INITIALIZATION);
      const newTx = await newTxBuilder.build();
      should.equal(newTx.toJson().id, '0xc65f9802df3b559b297779ec06d3e71ba7f5b1b47cc961ad2efba54d82347bec');
    });
  });

  describe('should fail to build', () => {
    it('an unsupported type of transaction', async () => {
      await buildTransaction({
        type: TransactionType.AccountUpdate,
        fee: {
          fee: '10',
          gasLimit: '10',
        },
        source: new KeyPair({ prv: sourcePrv }).getAddress(),
        counter: 0,
      }).should.be.rejectedWith('Unsupported transaction type');
    });

    it('a transaction without fee', async () => {
      await buildTransaction({
        source: new KeyPair({ prv: sourcePrv }).getAddress(),
        counter: 0,
      }).should.be.rejectedWith('Invalid transaction: missing fee');
    });

    it('a wallet initialization the wrong number of owners', async () => {
      await buildTransaction({
        type: TransactionType.WalletInitialization,
        fee: {
          fee: '10',
          gasLimit: '10',
        },
        source: new KeyPair({ prv: sourcePrv }).getAddress(),
        owners: [new KeyPair({ pub: pub1 }).getAddress(), new KeyPair({ pub: pub2 }).getAddress()],
        counter: 0,
      }).should.be.rejectedWith('Invalid transaction: wrong number of owners -- required: 3, found: 2');

      await buildTransaction({
        type: TransactionType.WalletInitialization,
        fee: {
          fee: '10',
          gasLimit: '10',
        },
        source: new KeyPair({ prv: sourcePrv }).getAddress(),
        owners: [
          new KeyPair({ prv: sourcePrv }).getAddress(),
          new KeyPair({ pub: pub1 }).getAddress(),
          new KeyPair({ pub: pub1 }).getAddress(),
          new KeyPair({ pub: pub2 }).getAddress(),
        ],
        counter: 0,
      }).should.be.rejectedWith('Repeated owner address: ' + new KeyPair({ pub: pub1 }).getAddress());

      await buildTransaction({
        type: TransactionType.WalletInitialization,
        fee: {
          fee: '10',
          gasLimit: '10',
        },
        source: new KeyPair({ prv: sourcePrv }).getAddress(),
        owners: [],
        counter: 0,
      }).should.be.rejectedWith('Invalid transaction: wrong number of owners -- required: 3, found: 0');
    });

    it('a transaction with invalid counter', async () => {
      await buildTransaction({
        fee: {
          fee: '10',
          gasLimit: '10',
        },
        source: new KeyPair({ prv: sourcePrv }).getAddress(),
        counter: -1,
        owners: [
          new KeyPair({ prv: sourcePrv }).getAddress(),
          new KeyPair({ pub: pub1 }).getAddress(),
          new KeyPair({ pub: pub2 }).getAddress(),
        ],
      }).should.be.rejectedWith('Invalid counter: -1');
    });
  });

  describe('should fail to sign', () => {
    it('a wallet initialization transaction without owners', () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.type(TransactionType.WalletInitialization);
      txBuilder.fee({
        fee: '10',
        gasLimit: '1000',
      });
      txBuilder.counter(1);
      assert.throws(() => txBuilder.sign({ key: defaultKeyPair.getKeys().prv }));
    });

    it('a signed wallet initialization transaction', () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.type(TransactionType.WalletInitialization);
      txBuilder.fee({
        fee: '10',
        gasLimit: '1000',
      });
      txBuilder.counter(1);
      txBuilder.owner(new KeyPair({ pub: pub1 }).getAddress());
      txBuilder.owner(new KeyPair({ pub: pub2 }).getAddress());
      txBuilder.owner(new KeyPair({ prv: sourcePrv }).getAddress());
      txBuilder.sign({ key: defaultKeyPair.getKeys().prv });
      assert.throws(
        () => txBuilder.sign({ key: defaultKeyPair.getKeys().prv }),
        new RegExp('Cannot sign multiple times a non send-type transaction')
      );
    });
  });

  describe('should validate', () => {
    it('an address', async () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.validateAddress(testData.VALID_ADDRESS);
      assert.throws(
        () => txBuilder.validateAddress(testData.INVALID_ADDRESS),
        new RegExp('Invalid address ' + testData.INVALID_ADDRESS.address)
      );
    });

    it('value should be greater than zero', () => {
      const txBuilder: any = getBuilder('eth');
      assert.throws(() => txBuilder.fee({ fee: '-10' }));
      should.doesNotThrow(() => txBuilder.fee({ fee: '10' }));
    });

    it('a private key', () => {
      const txBuilder: any = getBuilder('eth');
      assert.throws(() => txBuilder.validateKey({ key: 'abc' }), /Invalid key/);
      assert.throws(() => txBuilder.validateKey({ key: testData.PUBLIC_KEY }), /Invalid key/);
      should.doesNotThrow(() => txBuilder.validateKey({ key: testData.PRIVATE_KEY }));
    });

    it('a raw transaction', async () => {
      const builder: any = getBuilder('eth');
      should.doesNotThrow(() => builder.from(testData.TX_BROADCAST));
      should.doesNotThrow(() => builder.from(testData.TX_JSON));
      assert.throws(() => builder.from('0x00001000'), /There was error in decoding the hex string/);
      assert.throws(() => builder.from(''), /Raw transaction is empty/);
      assert.throws(() => builder.from('pqrs'), /There was error in parsing the JSON string/);
      assert.throws(() => builder.from(1234), /Transaction is not a hex string or stringified json/);
    });

    it('a transaction to build', async () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.counter(undefined);
      txBuilder.type(TransactionType.WalletInitialization);
      assert.throws(() => txBuilder.validateTransaction(), /Invalid transaction: missing fee/);
      txBuilder.fee({
        fee: '10',
        gasLimit: '1000',
      });
      assert.throws(() => txBuilder.validateTransaction(), /Invalid transaction: missing address counter/);
      assert.throws(() => txBuilder.validateTransaction(), /Invalid transaction: missing address counter/);
      const source = {
        prv: sourcePrv,
      };
      const sourceKeyPair = new KeyPair(source);
      assert.throws(() => txBuilder.validateTransaction(), /Invalid transaction: missing address counter/);
      txBuilder.counter(1);
      assert.throws(() => txBuilder.validateTransaction(), /wrong number of owners -- required: 3, found: 0/);
      txBuilder.owner(sourceKeyPair.getAddress());
      assert.throws(() => txBuilder.validateTransaction(), /wrong number of owners -- required: 3, found: 1/);
      txBuilder.owner(new KeyPair({ pub: pub1 }).getAddress());
      assert.throws(() => txBuilder.validateTransaction(), /wrong number of owners -- required: 3, found: 2/);
      txBuilder.owner(new KeyPair({ pub: pub2 }).getAddress());
      should.doesNotThrow(() => txBuilder.validateTransaction());
    });
  });

  describe('set owner', () => {
    it('should be wallet initializaion', () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.type(TransactionType.Send);
      const sourceKeyPair = new KeyPair({ prv: sourcePrv });
      assert.throws(
        () => txBuilder.owner(sourceKeyPair.getAddress()),
        new RegExp('Multisig wallet owner can only be set for initialization transactions')
      );
    });

    it('should be only 3 owners', () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.type(TransactionType.WalletInitialization);
      txBuilder.fee({
        fee: '10',
        gasLimit: '1000',
      });
      const sourceKeyPair = new KeyPair({ prv: sourcePrv });
      txBuilder.counter(1);
      txBuilder.owner(sourceKeyPair.getAddress());
      txBuilder.owner('0x7325A3F7d4f9E86AE62Cf742426078C3755730d5');
      txBuilder.owner('0x603e077acd3F01e81b95fB92ce42FF60dFf3D4C7');
      assert.throws(
        () => txBuilder.owner('0x1A88Ee4Bc80BE080fC91AC472Af2F59260695060'),
        new RegExp('A maximum of 3 owners can be set for a multisig wallet')
      );
    });

    it('should be a valid address', () => {
      const txBuilder: any = getBuilder('eth');
      txBuilder.type(TransactionType.WalletInitialization);
      txBuilder.fee({
        fee: '10',
        gasLimit: '1000',
      });
      txBuilder.counter(1);
      assert.throws(() => txBuilder.owner('0x7325A3F7d4f9E86AE62C'), /Invalid address/);
    });
  });

  it('Should getting same tx hash from raw tx', async function () {
    const txBuilder: any = getBuilder('eth');
    txBuilder.fee({
      fee: '280000000000',
      gasLimit: '7000000',
    });
    txBuilder.counter(1);
    txBuilder.type(TransactionType.WalletInitialization);
    txBuilder.owner(new KeyPair({ pub: pub1 }).getAddress());
    txBuilder.owner(new KeyPair({ pub: pub2 }).getAddress());
    txBuilder.owner(new KeyPair({ prv: sourcePrv }).getAddress());
    txBuilder.sign({ key: defaultKeyPair.getKeys().prv });
    const tx = await txBuilder.build();
    const txBuiderFromRaw: any = getBuilder('eth');
    txBuiderFromRaw.from(tx.toBroadcastFormat());
    const txFromRaw = await txBuiderFromRaw.build();
    should.deepEqual(tx.id, txFromRaw.id);
  });

  describe('Recovery Wallet', function () {
    it('should build and sign using txHex', async () => {
      const txBuilder = getBuilder('gteth') as TransactionBuilder;
      const txHex = testData.RECOVERY_WALLET_DEPLOYMENT_TXHEX;
      txBuilder.from(txHex);
      const xpriv =
        'xprv9s21ZrQH143K2YKSzZa2fv7QTzZ5Ui45ZUzygDwhfeHodwHbWtUUSKqikMKZR9Z751NMekrjXKBykb8mhvoTrKMRodKBvNDH4XKPkccF1K4';
      txBuilder.sign({ key: xpriv });
      const signedTx = await txBuilder.build();
      should.exists(signedTx.toBroadcastFormat());
    });

    it('succeed when build via contract call', async () => {
      const txBuilder = getBuilder('hteth') as TransactionBuilder;
      txBuilder.type(TransactionType.ContractCall);
      txBuilder.fee({
        eip1559: {
          maxFeePerGas: '100',
          maxPriorityFeePerGas: '10',
        },
        fee: '100',
        gasLimit: '10000',
      });

      const data = testData.SEND_FUNDS_METHOD_CALL;
      txBuilder.counter(1);
      txBuilder.data(data);
      txBuilder.contract('0xd536f4b9f9127a39f19820ca18baac7cd157471f');
      const tx = await txBuilder.build();
      const txhex = tx.toBroadcastFormat();
      const builderFrom = getBuilder('hteth') as TransactionBuilder;
      builderFrom.from(txhex);
      const txFrom = await builderFrom.build();
      should.deepEqual(tx.id, txFrom.id);
      should.deepEqual(tx.toJson(), txFrom.toJson());
    });
  });
});

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


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