PHP WebShell

Текущая директория: /opt/BitGoJS/modules/bitgo/test/integration

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

//
// Tests for a Pending Approval
//
// Copyright 2015, BitGo, Inc.  All Rights Reserved.
//

import * as _ from 'lodash';
import { strict as assert } from 'assert';
import 'should';
import * as utxolib from '@bitgo/utxo-lib';

const BitGoJS = require('../../src/index');
const TestBitGo = require('../lib/test_bitgo');
const TestUtil = require('./testutil');

const Q = require('q');

describe('PendingApproval', function () {
  let bitgo;
  let bitgoSharedKeyUser;
  let bitgoThirdUser;
  let sharedWallet;

  /**
   * There is a 0-limit policy on the shared wallet
   * Create a pending approval by attempting to send coins
   */
  const createTransactionPendingApproval = function () {
    return sharedWallet
      .sendCoins({
        address: TestBitGo.TEST_WALLET2_ADDRESS,
        amount: 0.0001 * 1e8,
        walletPassphrase: TestBitGo.TEST_PASSWORD,
        otp: bitgo.testUserOTP(),
        message: 'never gonna',
      })
      .then(function (result) {
        result.should.have.property('pendingApproval');
        return bitgo.pendingApprovals().get({ id: result.pendingApproval });
      });
  };

  /**
   * There is a 0-limit policy on the shared wallet
   * Create a pending approval by attempting to send to many
   */
  const createTransactionPendingApprovalToMultipleRecipients = function () {
    return sharedWallet
      .sendMany({
        recipients: [
          {
            address: TestBitGo.TEST_WALLET3_ADDRESS,
            amount: 0.0002 * 1e8,
          },
          {
            address: TestBitGo.TEST_WALLET2_ADDRESS,
            amount: 0.0001 * 1e8,
          },
          {
            address: TestBitGo.TEST_SHARED_WALLET_CHANGE_ADDRESS,
            amount: 0.0005 * 1e8,
          },
        ],
        walletPassphrase: TestBitGo.TEST_PASSWORD,
        otp: bitgo.testUserOTP(),
        message: 'never gonna',
      })
      .then(function (result) {
        result.should.have.property('pendingApproval');
        return bitgo.pendingApprovals().get({ id: result.pendingApproval });
      });
  };

  /**
   * Create a pending approval by attempting to add a user to the wallet
   */
  const createPolicyPendingApproval = function () {
    // it's ok to set up any tx limit since the daily limit is 0
    return sharedWallet
      .updatePolicyRule({
        action: {
          type: 'getApproval',
        },
        condition: {
          amount: 1e8 + Math.round(Math.random() * 1e8),
        },
        id: 'com.bitgo.limit.tx',
        type: 'transactionLimit',
      })
      .then(function (result) {
        result.should.have.property('pendingApproval');
        return bitgo.pendingApprovals().get({ id: result.pendingApproval.id });
      });
  };

  before(function () {
    bitgo = new TestBitGo();
    bitgo.initializeTestVars();
    bitgoSharedKeyUser = new TestBitGo();
    bitgoSharedKeyUser.initializeTestVars();
    bitgoThirdUser = new TestBitGo();
    bitgoThirdUser.initializeTestVars();

    return bitgo
      .authenticateTestUser(bitgo.testUserOTP())
      .then(function () {
        return bitgoSharedKeyUser.authenticate({
          username: TestBitGo.TEST_SHARED_KEY_USER,
          password: TestBitGo.TEST_SHARED_KEY_PASSWORD,
          otp: bitgo.testUserOTP(),
        });
      })
      .then(function () {
        return bitgo.unlock({ otp: bitgo.testUserOTP() });
      })
      .then(function () {
        return bitgoSharedKeyUser.unlock({ otp: bitgo.testUserOTP() });
      })
      .then(function () {
        return bitgo.wallets().get({ id: TestBitGo.TEST_SHARED_WALLET_ADDRESS });
      })
      .then(function (result) {
        sharedWallet = result;
      });
  });

  describe('Create and Get', function () {
    let pendingApproval;

    before(function () {
      return createTransactionPendingApproval().then(function (result) {
        pendingApproval = result;
      });
    });

    after(function () {
      return pendingApproval.reject();
    });

    it('arguments', function () {
      assert.throws(function () {
        bitgo.pendingApprovals().get({}, 'invalid');
      });
      assert.throws(function () {
        bitgo.pendingApprovals().get('invalid');
      });
      assert.throws(function () {
        bitgo.pendingApprovals().get({ id: 54321 }, 'invalid');
      });
    });

    it('get property methods', function () {
      pendingApproval.id().should.eql(pendingApproval.pendingApproval.id);
      pendingApproval.ownerType().should.eql('wallet');
      pendingApproval.walletId().should.eql(sharedWallet.id());
      assert.equal(pendingApproval.enterpriseId(), undefined);
      pendingApproval.state().should.eql('pending');
      pendingApproval.creator().should.eql(TestBitGo.TEST_USERID);
      pendingApproval.type().should.eql('transactionRequest');
      pendingApproval.info().transactionRequest.message.should.eql('never gonna');
      pendingApproval.info().transactionRequest.destinationAddress.should.eql(TestBitGo.TEST_WALLET2_ADDRESS);
    });

    it('get', function () {
      pendingApproval.get({}).then(function (result) {
        pendingApproval = result;
        pendingApproval.id().should.eql(pendingApproval.pendingApproval.id);
        pendingApproval.ownerType().should.eql('wallet');
        pendingApproval.walletId().should.eql(sharedWallet.id());
        assert.equal(pendingApproval.enterpriseId(), undefined);
        pendingApproval.state().should.eql('pending');
        pendingApproval.creator().should.eql(TestBitGo.TEST_USERID);
        pendingApproval.type().should.eql('transactionRequest');
        pendingApproval.info().transactionRequest.destinationAddress.should.eql(TestBitGo.TEST_WALLET2_ADDRESS);
      });
    });
  });

  describe('Approve', function () {
    let pendingApproval;

    before(function () {
      return createTransactionPendingApproval().then(function (result) {
        pendingApproval = result;
      });
    });

    after(function () {
      return pendingApproval.reject();
    });

    it('arguments', function () {
      assert.throws(function () {
        pendingApproval.approve({}, 'invalid');
      });
      assert.throws(function () {
        pendingApproval.approve('invalid');
      });
    });

    it('error when self approving', function () {
      return createPolicyPendingApproval()
        .then(function (pendingApproval) {
          return pendingApproval.approve({ walletPassphrase: TestBitGo.TEST_PASSWORD, otp: bitgo.testUserOTP() });
        })
        .catch(function (err) {
          err.message.should.containEql('cannot approve by self');
          return pendingApproval.reject();
        });
    });

    it('can approve when it does not require tx signing', function () {
      return createPolicyPendingApproval().then(function (pendingApproval) {
        return bitgoSharedKeyUser
          .pendingApprovals()
          .get({ id: pendingApproval.id() })
          .then(function (result) {
            return result.approve({ walletPassphrase: TestBitGo.TEST_PASSWORD, otp: bitgo.testUserOTP() });
          })
          .then(function (result) {
            result.state.should.eql('approved');
            result.info.policyRuleRequest.update.condition.amount.should.eql(
              pendingApproval.info().policyRuleRequest.update.condition.amount
            );
          });
      });
    });

    it('can approve when tx does not require reconstruction', function () {
      return createTransactionPendingApproval().then(function (pendingApproval) {
        return bitgoSharedKeyUser
          .pendingApprovals()
          .get({ id: pendingApproval.id() })
          .then(function (result) {
            return result.approve({ walletPassphrase: TestBitGo.TEST_PASSWORD, otp: bitgo.testUserOTP() });
          })
          .then(function (result) {
            result.state.should.eql('approved');
          });
      });
    });

    it('error when it does require tx signing but wrong passphrase', function () {
      return createTransactionPendingApproval()
        .then(function (pendingApproval) {
          return bitgoSharedKeyUser.pendingApprovals().get({ id: pendingApproval.id() });
        })
        .then(function (result) {
          return result.approve({ walletPassphrase: 'abcdef', otp: bitgo.testUserOTP() });
        })
        .catch(function (err) {
          err.message.should.containEql('Unable to decrypt user keychain');
          return pendingApproval.reject();
        });
    });

    it('can approve when it does require tx signing', function () {
      return createTransactionPendingApproval()
        .then(function (pendingApproval) {
          return bitgoSharedKeyUser.pendingApprovals().get({ id: pendingApproval.id() });
        })
        .then(function (result) {
          return result.approve({ walletPassphrase: TestBitGo.TEST_PASSWORD, otp: bitgo.testUserOTP() });
        })
        .then(function (result) {
          result.state.should.eql('approved');
        });
    });

    it('cannot approve when transaction needs reconstructing', function () {
      let approvals: any[] = [];
      return Q.all([createTransactionPendingApproval(), createTransactionPendingApproval()])
        .spread(function (approval1, approval2) {
          approvals = [approval1, approval2];
          return bitgoSharedKeyUser.pendingApprovals().get({ id: approval1.id() });
        })
        .then(function (result) {
          return result.approve({ walletPassphrase: TestBitGo.TEST_PASSWORD, otp: bitgo.testUserOTP() });
        })
        .then(function (result) {
          result.state.should.eql('approved');
          const approval2 = approvals[1];
          return bitgoSharedKeyUser.pendingApprovals().get({ id: approval2.id() });
        })
        .then(function (result) {
          return result.approve({ otp: bitgo.testUserOTP() });
        })
        .then(function () {
          throw new Error('approval success');
        })
        .catch(function (error) {
          error.message.should.equal('unspents expired, wallet passphrase or xprv required to recreate transaction');
        });
    });

    it('can approve when it does require tx signing (multiple recipients)', function () {
      return createTransactionPendingApprovalToMultipleRecipients()
        .then(function (pendingApproval) {
          return bitgoSharedKeyUser.pendingApprovals().get({ id: pendingApproval.id() });
        })
        .then(function (result) {
          return result.approve({ walletPassphrase: TestBitGo.TEST_PASSWORD, otp: bitgo.testUserOTP() });
        })
        .then(function (result) {
          result.state.should.eql('approved');

          // Parse the completed tx hex and make sure it was built with proper outputs
          const completedTxHex = result.info.transactionRequest.validTransaction;
          const transaction = utxolib.bitgo.createTransactionFromHex(completedTxHex, utxolib.networks.bitcoin);
          if (!transaction || !transaction.outs) {
            throw new Error('transaction had no outputs or failed to parse successfully');
          }
          const outputAddresses = _.map(transaction.outs, function (out) {
            return utxolib.address.fromOutputScript(out.script, BitGoJS.getNetworkObj());
          });

          // Output addresses should contain the 2 destinations, but not the change address
          outputAddresses.should.containEql(TestBitGo.TEST_WALLET3_ADDRESS);
          outputAddresses.should.containEql(TestBitGo.TEST_WALLET2_ADDRESS);
          outputAddresses.should.not.containEql(TestBitGo.TEST_SHARED_WALLET_CHANGE_ADDRESS);
        });
    });

    it('can manually pass in reconstructed tx', function () {
      let pendingApproval;
      return createTransactionPendingApprovalToMultipleRecipients()
        .then(function (pendingApproval) {
          return bitgoSharedKeyUser.pendingApprovals().get({ id: pendingApproval.id() });
        })
        .then(function (result) {
          pendingApproval = result;
          return pendingApproval.constructApprovalTx({
            walletPassphrase: TestBitGo.TEST_PASSWORD,
            otp: bitgo.testUserOTP(),
          });
        })
        .then(function (result) {
          return pendingApproval.approve({
            walletPassphrase: TestBitGo.TEST_PASSWORD,
            tx: result.tx,
            otp: bitgo.testUserOTP(),
          });
        })
        .then(function (result) {
          result.state.should.eql('approved');

          // Parse the completed tx hex and make sure it was built with proper outputs
          const completedTxHex = result.info.transactionRequest.validTransaction;
          const transaction = utxolib.bitgo.createTransactionFromHex(completedTxHex, utxolib.networks.bitcoin);
          if (!transaction || !transaction.outs) {
            throw new Error('transaction had no outputs or failed to parse successfully');
          }
          const outputAddresses = _.map(transaction.outs, function (out) {
            return utxolib.address.fromOutputScript(out.script, BitGoJS.getNetworkObj());
          });

          // Output addresses should contain the 2 destinations, but not the change address
          outputAddresses.should.containEql(TestBitGo.TEST_WALLET3_ADDRESS);
          outputAddresses.should.containEql(TestBitGo.TEST_WALLET2_ADDRESS);
          outputAddresses.should.not.containEql(TestBitGo.TEST_SHARED_WALLET_CHANGE_ADDRESS);
        });
    });
  });

  describe('Reject', function () {
    let pendingApproval;

    before(function () {
      return Q.delay(500)
        .then(function () {
          return createTransactionPendingApproval();
        })
        .then(function (result) {
          pendingApproval = result;
        });
    });

    it('arguments', function () {
      assert.throws(function () {
        pendingApproval.reject({}, 'invalid');
      });
      assert.throws(function () {
        pendingApproval.reject('invalid');
      });
    });

    it('can cancel', function () {
      return pendingApproval.reject().then(function (result) {
        result.state.should.eql('rejected');
      });
    });

    it('can reject', function () {
      return bitgoSharedKeyUser
        .pendingApprovals()
        .get({ id: pendingApproval.id() })
        .then(function (result) {
          return result.reject();
        })
        .then(function (result) {
          result.state.should.eql('rejected');
        });
    });
  });

  describe('Create, Get, Approve and Reject with Multiple Approvers', function () {
    // setup third user
    let multipleApproversWallet;
    let pendingApproval;
    before(function () {
      return bitgoThirdUser
        .authenticate({
          username: TestBitGo.TEST_THIRD_USER,
          password: TestBitGo.TEST_THIRD_PASSWORD,
          otp: bitgo.testUserOTP(),
        })
        .then(function () {
          return bitgoThirdUser.unlock({ otp: bitgoThirdUser.testUserOTP() });
        })
        .then(function () {
          return bitgoThirdUser.wallets().get({ id: TestBitGo.TEST_WALLETMULTAPPROVERS_ADDRESS });
        })
        .then(function (result) {
          multipleApproversWallet = result;

          if (multipleApproversWallet.approvalsRequired() === 2) {
            // we don't need to bother setting the number of approvals required
            return;
          }

          return multipleApproversWallet
            .updateApprovalsRequired({ approvalsRequired: 2 })
            .then(function (result) {
              return bitgoSharedKeyUser.pendingApprovals().get({ id: result.id });
            })
            .then(function (result) {
              pendingApproval = result;
              pendingApproval.approvalsRequired().should.equal(1);
              return result.approve({ otp: bitgo.testUserOTP() });
            })
            .then(function (result) {
              result.state.should.eql('approved');

              // update wallet variable with new approvalsRequired
              return bitgoThirdUser.wallets().get({ id: TestBitGo.TEST_WALLETMULTAPPROVERS_ADDRESS });
            })
            .then(function (result) {
              multipleApproversWallet = result;
            });
        });
    });

    after(function () {
      pendingApproval.reject();
    });

    it('should fail with too low approvalsRequired', function () {
      assert.throws(function () {
        multipleApproversWallet.updateApprovalsRequired({ approvalsRequired: 0 });
      }, 'invalid approvalsRequired');
    });

    it('should fail with too high approvalsRequired', function () {
      const promise = multipleApproversWallet.updateApprovalsRequired({ approvalsRequired: 3 });
      return TestUtil.throws(promise, 'approvalsRequired must be less than the number of admins on the wallet');
    });

    it('should be a no-op with same approvalsRequired', function () {
      return multipleApproversWallet.updateApprovalsRequired({ approvalsRequired: 2 }).then(function (wallet) {
        wallet.should.equal(multipleApproversWallet.wallet);
      });
    });

    it('should set approvals required to 1 after 2 approvals', function () {
      return multipleApproversWallet
        .updateApprovalsRequired({ approvalsRequired: 1 })
        .then(function (result) {
          return bitgoSharedKeyUser.pendingApprovals().get({ id: result.id });
        })
        .then(function (result) {
          pendingApproval = result;
          pendingApproval.approvalsRequired().should.equal(2);
          return result.approve({ otp: bitgo.testUserOTP() });
        })
        .then(function (result) {
          result.state.should.eql('pending');

          return bitgo.pendingApprovals().get({ id: result.id });
        })
        .then(function (result) {
          return result.approve({ otp: bitgo.testUserOTP() });
        })
        .then(function (result) {
          result.state.should.eql('approved');

          return bitgoThirdUser.wallets().get({ id: TestBitGo.TEST_WALLETMULTAPPROVERS_ADDRESS });
        })
        .then(function (wallet) {
          multipleApproversWallet = wallet;
          multipleApproversWallet.approvalsRequired().should.equal(1);
        });
    });
  });
});

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


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