'use strict';Object.defineProperty(exports, "__esModule", { value: true });exports.default = undefined;var _slicedToArray = function () {function sliceIterator(arr, i) {var _arr = [];var _n = true;var _d = false;var _e = undefined;try {for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {_arr.push(_s.value);if (i && _arr.length === i) break;}} catch (err) {_d = true;_e = err;} finally {try {if (!_n && _i["return"]) _i["return"]();} finally {if (_d) throw _e;}}return _arr;}return function (arr, i) {if (Array.isArray(arr)) {return arr;} else if (Symbol.iterator in Object(arr)) {return sliceIterator(arr, i);} else {throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}(); // Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity.  If not, see <http://www.gnu.org/licenses/>.

var _lodash = require('lodash');

var _abi = require('@parity/abi');var _abi2 = _interopRequireDefault(_abi);
var _contract = require('@parity/api/lib/contract');var _contract2 = _interopRequireDefault(_contract);
var _format = require('@parity/api/lib/util/format');

var _validation = require('../validation');
var _foundationMultisigWallet = require('../../contracts/abi/foundation-multisig-wallet.json');var _foundationMultisigWallet2 = _interopRequireDefault(_foundationMultisigWallet);
var _oldWallet = require('../../contracts/abi/old-wallet.json');var _oldWallet2 = _interopRequireDefault(_oldWallet);

var _pendingContracts = require('./pending-contracts');var _pendingContracts2 = _interopRequireDefault(_pendingContracts);
var _updates = require('./updates');function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}

var WALLET_CONTRACT = new _contract2.default({}, _foundationMultisigWallet2.default);
var WALLET_ABI = new _abi2.default(_foundationMultisigWallet2.default);
var OLD_WALLET_ABI = new _abi2.default(_oldWallet2.default);

var walletEvents = WALLET_ABI.events.reduce(function (events, event) {
  events[event.name] = event;
  return events;
}, {});

var oldWalletEvents = OLD_WALLET_ABI.events.reduce(function (events, event) {
  events[event.name] = event;
  return events;
}, {});

var WalletSignatures = {
  OwnerChanged: (0, _format.toHex)(walletEvents.OwnerChanged.signature),
  OwnerAdded: (0, _format.toHex)(walletEvents.OwnerAdded.signature),
  OwnerRemoved: (0, _format.toHex)(walletEvents.OwnerRemoved.signature),
  RequirementChanged: (0, _format.toHex)(walletEvents.RequirementChanged.signature),
  Confirmation: (0, _format.toHex)(walletEvents.Confirmation.signature),
  Revoke: (0, _format.toHex)(walletEvents.Revoke.signature),
  Deposit: (0, _format.toHex)(walletEvents.Deposit.signature),
  SingleTransact: (0, _format.toHex)(walletEvents.SingleTransact.signature),
  MultiTransact: (0, _format.toHex)(walletEvents.MultiTransact.signature),
  ConfirmationNeeded: (0, _format.toHex)(walletEvents.ConfirmationNeeded.signature),

  Old: {
    SingleTransact: (0, _format.toHex)(oldWalletEvents.SingleTransact.signature),
    MultiTransact: (0, _format.toHex)(oldWalletEvents.MultiTransact.signature) } };var



FoundationWalletUtils = function () {function FoundationWalletUtils() {_classCallCheck(this, FoundationWalletUtils);}_createClass(FoundationWalletUtils, null, [{ key: 'fetchConfirmations', value: function fetchConfirmations(
    walletContract, operation) {var _owners = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
      var ownersPromise = _owners ?
      Promise.resolve(_owners) :
      FoundationWalletUtils.fetchOwners(walletContract);

      return ownersPromise.
      then(function (owners) {
        var promises = owners.map(function (owner) {
          return walletContract.instance.hasConfirmed.call({}, [operation, owner]);
        });

        return Promise.
        all(promises).
        then(function (data) {
          return owners.filter(function (_, index) {return data[index];});
        });
      });
    } }, { key: 'fetchDailylimit', value: function fetchDailylimit(

    walletContract) {
      var walletInstance = walletContract.instance;

      return Promise.
      all([
      walletInstance.m_dailyLimit.call(),
      walletInstance.m_spentToday.call(),
      walletInstance.m_lastDay.call()]).

      then(function (_ref) {var _ref2 = _slicedToArray(_ref, 3),limit = _ref2[0],spent = _ref2[1],last = _ref2[2];return {
          limit: limit, spent: spent, last: last };});

    } }, { key: 'fetchOwners', value: function fetchOwners(

    walletContract) {
      var walletInstance = walletContract.instance;

      return walletInstance.
      m_numOwners.call().
      then(function (mNumOwners) {
        var promises = (0, _lodash.range)(mNumOwners.toNumber()).
        map(function (idx) {return walletInstance.getOwner.call({}, [idx]);});

        return Promise.
        all(promises).
        then(function (_owners) {
          var owners = validateOwners(_owners);

          // If all owners are the zero account : must be Mist wallet contract
          if (!owners) {
            return fetchMistOwners(walletContract, mNumOwners.toNumber());
          }

          return owners;
        });
      });
    } }, { key: 'fetchPendingTransactions', value: function fetchPendingTransactions(

    walletContract) {var cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};var
      owners = cache.owners,transactions = cache.transactions;

      return walletContract.
      instance.
      ConfirmationNeeded.
      getAllLogs().
      then(function (logs) {
        return logs.
        filter(function (log) {
          if (!log.blockNumber) {
            console.warn('got a log without blockNumber', log);
            return false;
          }

          if (!log.transactionIndex) {
            console.warn('got a log without transactionIndex', log);
            return false;
          }

          return true;
        }).
        map(function (log) {return {
            initiator: log.params.initiator.value,
            to: log.params.to.value,
            data: log.params.data.value,
            value: log.params.value.value,
            operation: (0, _format.bytesToHex)(log.params.operation.value),
            transactionIndex: log.transactionIndex,
            transactionHash: log.transactionHash,
            blockNumber: log.blockNumber,
            confirmedBy: [] };});

      }).
      then(function (logs) {
        return logs.sort(function (logA, logB) {
          var bnA = logA.blockNumber;
          var bnB = logA.blockNumber;

          if (!bnA) {
            console.warn('could not find block number in log', logA);
            return 1;
          }

          if (!bnB) {
            console.warn('could not find block number in log', logB);
            return -1;
          }

          var comp = bnA.comparedTo(bnB);

          if (comp !== 0) {
            return comp;
          }

          var txIdxA = logA.transactionIndex;
          var txIdxB = logB.transactionIndex;

          if (!txIdxA) {
            console.warn('could not find transaction index in log', logA);
            return 1;
          }

          if (!txIdxB) {
            console.warn('could not find transaction index in log', logB);
            return -1;
          }

          return txIdxA.comparedTo(txIdxB);
        });
      }).
      then(function (pendingTxs) {
        if (pendingTxs.length === 0) {
          return pendingTxs;
        }

        // Only fetch confirmations for operations not
        // yet confirmed (ie. not yet a transaction)
        if (transactions) {
          var operations = transactions.
          filter(function (t) {return t.operation;}).
          map(function (t) {return t.operation;});

          return pendingTxs.filter(function (pendingTx) {
            return !operations.includes(pendingTx.operation);
          });
        }

        return pendingTxs;
      }).
      then(function (pendingTxs) {
        var promises = pendingTxs.map(function (tx) {
          return FoundationWalletUtils.
          fetchConfirmations(walletContract, tx.operation, owners).
          then(function (confirmedBy) {
            tx.confirmedBy = confirmedBy;

            return tx;
          });
        });

        return Promise.all(promises);
      });
    } }, { key: 'fetchRequire', value: function fetchRequire(

    wallet) {
      return wallet.instance.m_required.call();
    } }, { key: 'fetchTransactions', value: function fetchTransactions(

    walletContract) {var
      api = walletContract.api;

      return walletContract.
      getAllLogs({
        topics: [[
        WalletSignatures.SingleTransact,
        WalletSignatures.MultiTransact,
        WalletSignatures.Deposit,
        WalletSignatures.Old.SingleTransact,
        WalletSignatures.Old.MultiTransact]] }).


      then(function (logs) {
        var transactions = logs.
        map(function (log) {
          var signature = (0, _format.toHex)(log.topics[0]);

          var value = log.params.value.value;
          var from = signature === WalletSignatures.Deposit ?
          log.params['_from'].value :
          walletContract.address;

          var to = signature === WalletSignatures.Deposit ?
          walletContract.address :
          log.params.to.value;

          var transaction = {
            transactionHash: log.transactionHash,
            transactionIndex: log.transactionIndex,
            blockNumber: log.blockNumber,
            from: from, to: to, value: value };


          if (!transaction.blockNumber) {
            console.warn('log without block number', log);
            return null;
          }

          if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
            transaction.creates = log.params.created.value;
            delete transaction.to;
          }

          if (log.params.operation) {
            transaction.operation = (0, _format.bytesToHex)(log.params.operation.value);
            checkPendingOperation(api, log, transaction.operation);
          }

          if (log.params.data) {
            transaction.data = log.params.data.value;
          }

          return transaction;
        }).
        filter(function (tx) {return tx;});

        return transactions;
      });
    } }, { key: 'getChangeMethod', value: function getChangeMethod(

    api, address, change) {
      var wallet = new _contract2.default(api, _foundationMultisigWallet2.default).at(address);
      var walletInstance = wallet.instance;

      if (change.type === 'require') {
        return {
          method: walletInstance.changeRequirement,
          values: [change.value] };

      }

      if (change.type === 'dailylimit') {
        return {
          method: walletInstance.setDailyLimit,
          values: [change.value] };

      }

      if (change.type === 'add_owner') {
        return {
          method: walletInstance.addOwner,
          values: [change.value] };

      }

      if (change.type === 'change_owner') {
        return {
          method: walletInstance.changeOwner,
          values: [change.value.from, change.value.to] };

      }

      if (change.type === 'remove_owner') {
        return {
          method: walletInstance.removeOwner,
          values: [change.value] };

      }
    } }, { key: 'getModifyOperationMethod', value: function getModifyOperationMethod(

    modification) {
      switch (modification) {
        case 'confirm':
          return 'confirm';

        case 'revoke':
          return 'revoke';

        default:
          return '';}

    } }, { key: 'getSubmitMethod', value: function getSubmitMethod()

    {
      return 'execute';
    } }, { key: 'getWalletContract', value: function getWalletContract(

    api) {
      return new _contract2.default(api, _foundationMultisigWallet2.default);
    } }, { key: 'getWalletSignatures', value: function getWalletSignatures()

    {
      return WalletSignatures;
    } }, { key: 'isWallet', value: function isWallet(

    api, address) {
      var walletContract = new _contract2.default(api, _foundationMultisigWallet2.default);

      return walletContract.
      at(address).
      instance.
      m_numOwners.
      call().
      then(function (result) {
        if (!result || result.equals(0)) {
          return false;
        }

        return true;
      });
    } }, { key: 'logToUpdate', value: function logToUpdate(

    log) {
      var eventSignature = (0, _format.toHex)(log.topics[0]);

      switch (eventSignature) {
        case WalletSignatures.OwnerChanged:
        case WalletSignatures.OwnerAdded:
        case WalletSignatures.OwnerRemoved:
          return _defineProperty({}, _updates.UPDATE_OWNERS, true);

        case WalletSignatures.RequirementChanged:
          return _defineProperty({}, _updates.UPDATE_REQUIRE, true);

        case WalletSignatures.ConfirmationNeeded:
        case WalletSignatures.Confirmation:
        case WalletSignatures.Revoke:
          var parsedLog = WALLET_CONTRACT.parseEventLogs([log])[0];
          var operation = (0, _format.bytesToHex)(parsedLog.params.operation.value);

          return _defineProperty({}, _updates.UPDATE_CONFIRMATIONS, operation);

        case WalletSignatures.Deposit:
        case WalletSignatures.SingleTransact:
        case WalletSignatures.MultiTransact:
        case WalletSignatures.Old.SingleTransact:
        case WalletSignatures.Old.MultiTransact:
          return _defineProperty({}, _updates.UPDATE_TRANSACTIONS, true);

        default:
          return {};}

    } }, { key: 'parseLogs', value: function parseLogs(

    api) {var logs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
      var walletContract = new _contract2.default(api, _foundationMultisigWallet2.default);

      return walletContract.parseEventLogs(logs);
    } }, { key: 'parseTransactionLogs', value: function parseTransactionLogs(

    api, options, rawLogs) {var
      metadata = options.metadata;
      var address = options.from;
      var logs = FoundationWalletUtils.parseLogs(api, rawLogs);

      var confirmationLog = logs.find(function (log) {return log.event === 'ConfirmationNeeded';});
      var transactionLog = logs.find(function (log) {return log.event === 'SingleTransact';});

      if (!confirmationLog && !transactionLog) {
        return null;
      }

      // Confirmations are needed from the other owners
      if (confirmationLog) {
        var operationHash = (0, _format.bytesToHex)(confirmationLog.params.operation.value);

        // Add the contract to pending contracts
        _pendingContracts2.default.addPendingContract(address, operationHash, metadata);

        return { pending: true };
      }

      return { contractAddress: transactionLog.params.created.value };
    } }]);return FoundationWalletUtils;}();exports.default = FoundationWalletUtils;


function checkPendingOperation(api, log, operation) {
  var pendingContracts = _pendingContracts2.default.getPendingContracts();

  // Add the pending contract to the contracts
  if (pendingContracts[operation]) {var
    metadata = pendingContracts[operation].metadata;
    var contractName = metadata.name;

    metadata.blockNumber = log.blockNumber;

    // The contract creation might not be in the same log,
    // but must be in the same transaction (eg. Contract creation
    // from Wallet within a Wallet)
    api.eth.
    getTransactionReceipt(log.transactionHash).
    then(function (transactionReceipt) {
      var transactionLogs = FoundationWalletUtils.parseLogs(api, transactionReceipt.logs);
      var creationLog = transactionLogs.find(function (log) {
        return log.params.created && !/^(0x)?0*$/.test(log.params.created.value);
      });

      if (!creationLog) {
        return false;
      }

      var contractAddress = creationLog.params.created.value;

      return Promise.
      all([
      api.parity.setAccountName(contractAddress, contractName),
      api.parity.setAccountMeta(contractAddress, metadata)]).

      then(function () {
        _pendingContracts2.default.removePendingContract(operation);
      });
    }).
    catch(function (error) {
      console.error('adding wallet contract', error);
    });
  }
}

function fetchMistOwners(walletContract, mNumOwners) {
  var walletAddress = walletContract.address;

  return getMistOwnersOffset(walletContract).
  then(function (result) {
    if (!result || result.offset === -1) {
      return [];
    }

    var owners = [result.address];

    if (mNumOwners === 1) {
      return owners;
    }

    var initOffset = result.offset + 1;
    var promise = Promise.resolve();

    (0, _lodash.range)(initOffset, initOffset + mNumOwners - 1).forEach(function (offset) {
      promise = promise.
      then(function () {
        return walletContract.api.eth.getStorageAt(walletAddress, offset);
      }).
      then(function (result) {
        var resultAddress = '0x' + (result || '').slice(-40);var _validateAddress =
        (0, _validation.validateAddress)(resultAddress),address = _validateAddress.address;

        owners.push(address);
      });
    });

    return promise.then(function () {return owners;});
  });
}

function getMistOwnersOffset(walletContract) {var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
  return walletContract.api.eth.
  getStorageAt(walletContract.address, offset).
  then(function (result) {
    if (result && !/^(0x)?0*$/.test(result)) {
      var resultAddress = '0x' + result.slice(-40);var _validateAddress2 =
      (0, _validation.validateAddress)(resultAddress),address = _validateAddress2.address,addressError = _validateAddress2.addressError;

      if (!addressError) {
        return { offset: offset, address: address };
      }
    }

    if (offset >= 100) {
      return { offset: -1 };
    }

    return getMistOwnersOffset(walletContract, offset + 1);
  });
}

function validateOwners(owners) {
  var uniqOwners = (0, _lodash.uniq)(owners);

  // If all owners are the zero account : must be Mist wallet contract
  if (uniqOwners.length === 1 && /^(0x)?0*$/.test(owners[0])) {
    return null;
  }

  return uniqOwners;
}