UNPKG

@colony/colony-js-contract-client

Version:

Method-like interface for Smart Contracts

544 lines (426 loc) 17.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); var _defineProperty3 = _interopRequireDefault(_defineProperty2); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _web3Utils = require('web3-utils'); var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert); var _colonyJsUtils = require('@colony/colony-js-utils'); var _lodash = require('lodash.isplainobject'); var _lodash2 = _interopRequireDefault(_lodash); var _lodash3 = require('lodash.isequal'); var _lodash4 = _interopRequireDefault(_lodash3); var _ContractMethodMultisigSender = require('./ContractMethodMultisigSender'); var _ContractMethodMultisigSender2 = _interopRequireDefault(_ContractMethodMultisigSender); var _ContractClient = require('./ContractClient'); var _ContractClient2 = _interopRequireDefault(_ContractClient); var _constants = require('../constants'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* eslint-disable import/no-cycle */ var MultisigOperation = function () { (0, _createClass3.default)(MultisigOperation, null, [{ key: '_validatePayload', value: function _validatePayload(payload) { var assert = (0, _colonyJsUtils.makeAssert)('Invalid payload'); assert((0, _lodash2.default)(payload), 'Payload must be an object'); var _ref = payload || {}, data = _ref.data, destinationAddress = _ref.destinationAddress, sourceAddress = _ref.sourceAddress, value = _ref.value; assert((0, _web3Utils.isHexStrict)(data), 'data must be a hex string'); assert((0, _colonyJsUtils.isValidAddress)(destinationAddress), 'destinationAddress must be a valid address'); assert((0, _colonyJsUtils.isValidAddress)(sourceAddress), 'sourceAddress must be a valid address'); return assert(Number(value) === value && value >= 0, 'value must be a positive number'); } // Immutable }, { key: '_validateSignature', value: function _validateSignature(signature) { var assert = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _assert2.default; assert((0, _lodash2.default)(signature), 'Signature must be an object'); var sigV = signature.sigV, sigR = signature.sigR, sigS = signature.sigS, mode = signature.mode; return assert([27, 28].includes(sigV), 'v must be 27 or 28') && assert((0, _web3Utils.isHexStrict)(sigR), 'r must be a hex string') && assert((0, _web3Utils.isHexStrict)(sigS), 's must be a hex string') && assert(Object.values(_constants.SIGNING_MODES).includes(mode), 'mode must be a valid signing mode'); } }, { key: '_validateSigners', value: function _validateSigners(signers) { var _this = this; var assert = (0, _colonyJsUtils.makeAssert)('Invalid _signers'); assert((0, _lodash2.default)(signers), 'Signers must be an object'); return Object.entries(signers || {}).every(function (_ref2) { var _ref3 = (0, _slicedToArray3.default)(_ref2, 2), address = _ref3[0], signature = _ref3[1]; return assert((0, _colonyJsUtils.isValidAddress)(address), '"' + address + '" is not a valid address') && _this._validateSignature(signature, assert); }); } }]); function MultisigOperation(sender, args) { (0, _classCallCheck3.default)(this, MultisigOperation); var payload = args.payload, _args$signers = args.signers, signers = _args$signers === undefined ? {} : _args$signers, nonce = args.nonce, onReset = args.onReset; // eslint-disable-next-line no-underscore-dangle this.constructor._validatePayload(payload); // eslint-disable-next-line no-underscore-dangle this.constructor._validateSigners(signers); (0, _assert2.default)(nonce == null || Number.isInteger(nonce), 'The optional `nonce` parameter should be an integer'); this.sender = sender; this.payload = Object.freeze(Object.assign({}, payload)); this._signers = signers; if (onReset) this._onReset = onReset; if (nonce !== undefined && Number(nonce) === nonce) this._nonce = nonce; } (0, _createClass3.default)(MultisigOperation, [{ key: 'toJSON', value: function toJSON() { var nonce = this._nonce, payload = this.payload, signers = this._signers; return JSON.stringify({ nonce: nonce, payload: payload, signers: signers }); } /** * Given the state of an operation as JSON, validate the parsed state and * add in the signers. */ }, { key: 'addSignersFromJSON', value: function addSignersFromJSON(json) { var parsed = {}; try { parsed = JSON.parse(json); } catch (error) { throw new Error('Unable to add signers: could not parse JSON'); } var _parsed = parsed, payload = _parsed.payload, signers = _parsed.signers; (0, _assert2.default)((0, _lodash4.default)(this.payload, payload), 'Unable to add state; incompatible payloads'); // eslint-disable-next-line no-underscore-dangle this.constructor._validateSigners(signers); this._signers = Object.assign({}, this._signers, signers); return this; } }, { key: '_getMessageDigest', value: function _getMessageDigest(mode) { return mode === _constants.SIGNING_MODES.TREZOR ? this._signedTrezorMessageDigest : this._signedMessageDigest; } /** * Given a signature and a wallet address, determine the signing mode by * trying different digests with `ecRecover` until the wallet address matches * the recovered address. */ }, { key: '_findSignatureMode', value: function _findSignatureMode(signature, address) { var _this2 = this; var foundMode = void 0; var adapter = this.sender.client.adapter; Object.keys(_constants.SIGNING_MODES).map(function (key) { return _constants.SIGNING_MODES[key]; }).forEach(function (mode) { var digest = _this2._getMessageDigest(mode); var recovered = adapter.ecRecover(digest, signature); if (address.toLowerCase() === recovered.toLowerCase()) foundMode = mode; }); if (foundMode !== undefined) return foundMode; throw new Error('Unable to confirm signature mode for address ' + address); } /** * Given multiple signers, combine each part of the signatures together. */ }, { key: '_combineSignatures', value: function _combineSignatures() { var _this3 = this; var combined = { sigV: [], sigR: [], sigS: [], mode: [] }; // Sort by address so that the order is always the same Object.keys(this._signers).sort().forEach(function (address) { var _signers$address = _this3._signers[address], sigV = _signers$address.sigV, sigR = _signers$address.sigR, sigS = _signers$address.sigS, mode = _signers$address.mode; combined.sigV.push(sigV); combined.sigR.push(sigR); combined.sigS.push(sigS); combined.mode.push(mode); }); return combined; } /** * Given the payload and signatures for this operation, combine the signatures * and return the arguments in the order the contract expects. */ }, { key: '_getArgs', value: function _getArgs() { var _payload = this.payload, value = _payload.value, data = _payload.data; var _combineSignatures2 = this._combineSignatures(), sigV = _combineSignatures2.sigV, sigR = _combineSignatures2.sigR, sigS = _combineSignatures2.sigS, mode = _combineSignatures2.mode; return [sigV, sigR, sigS, mode, value, data]; } /** * Ensure that there are no missing signees (based on the input values for * this operation). */ }, { key: '_validateRequiredSignees', value: function _validateRequiredSignees() { var missing = this.missingSignees; (0, _assert2.default)(missing.length === 0, 'Missing signatures (from address' + (missing.length > 1 ? 'es' : '') + ' ' + missing.join(', ') + ')'); return true; } /** * Given send options, ensure that the necessary signees have signed the * operation, then get the arguments and send the transaction. */ }, { key: 'send', value: function () { var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(options) { return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return this.refresh(); case 2: this._validateRequiredSignees(); return _context.abrupt('return', this.sender.sendMultisig(this._getArgs(), options)); case 4: case 'end': return _context.stop(); } } }, _callee, this); })); function send(_x2) { return _ref4.apply(this, arguments); } return send; }() /** * Given a signature and an address, find the signature mode and * add the address/signature to the signers. */ }, { key: '_addSignature', value: function _addSignature(signature, address) { var normalisedAddress = address.toLowerCase(); var mode = this._findSignatureMode(signature, normalisedAddress); this._signers = Object.assign({}, this._signers, (0, _defineProperty3.default)({}, normalisedAddress, (0, _extends3.default)({ mode: mode }, signature))); return this; } /** * Sign the message hash with the current wallet and add the signature. */ }, { key: 'sign', value: function () { var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() { var adapter, signature, address; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _context2.next = 2; return this.refresh(); case 2: adapter = this.sender.client.adapter; _context2.next = 5; return adapter.signMessage(this._messageHash); case 5: signature = _context2.sent; _context2.next = 8; return adapter.wallet.getAddress(); case 8: address = _context2.sent; this._addSignature(signature, address); return _context2.abrupt('return', this); case 11: case 'end': return _context2.stop(); } } }, _callee2, this); })); function sign() { return _ref5.apply(this, arguments); } return sign; }() /** * Refresh the required signees, nonce value and message hash. * If the nonce value has changed, `_signers` will be reset. */ }, { key: 'refresh', value: function () { var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() { return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: _context3.next = 2; return this._refreshNonce(); case 2: _context3.next = 4; return this._refreshRequiredSignees(); case 4: this._refreshMessageHash(); return _context3.abrupt('return', this); case 6: case 'end': return _context3.stop(); } } }, _callee3, this); })); function refresh() { return _ref6.apply(this, arguments); } return refresh; }() }, { key: '_refreshNonce', value: function () { var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4() { var oldNonce, newNonce; return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: if (Object.hasOwnProperty.call(this, '_nonce')) { _context4.next = 5; break; } _context4.next = 3; return this.sender.getNonce(this.payload.inputValues); case 3: this._nonce = _context4.sent; return _context4.abrupt('return'); case 5: oldNonce = Number(this._nonce); _context4.next = 8; return this.sender.getNonce(this.payload.inputValues); case 8: newNonce = _context4.sent; if (oldNonce !== newNonce) { this._nonce = newNonce; // If the nonce changed, the signers are no longer valid this._signers = {}; // We will also trigger onReset, if it exists if (this._onReset) this._onReset(); } case 10: case 'end': return _context4.stop(); } } }, _callee4, this); })); function _refreshNonce() { return _ref7.apply(this, arguments); } return _refreshNonce; }() }, { key: '_refreshRequiredSignees', value: function () { var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() { return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _context5.next = 2; return this.sender.getRequiredSignees(this.payload.inputValues); case 2: this._requiredSignees = _context5.sent; case 3: case 'end': return _context5.stop(); } } }, _callee5, this); })); function _refreshRequiredSignees() { return _ref8.apply(this, arguments); } return _refreshRequiredSignees; }() /** * Given the payload and nonce, use this input to create an ERC191-compatible * message hash */ }, { key: '_refreshMessageHash', value: function _refreshMessageHash() { var _payload2 = this.payload, data = _payload2.data, destinationAddress = _payload2.destinationAddress, sourceAddress = _payload2.sourceAddress, value = _payload2.value, _nonce = this._nonce; // Follows ERC191 signature scheme: https://github.com/ethereum/EIPs/issues/191 var addresses = '' + sourceAddress.slice(2) + destinationAddress.slice(2); var paddedValue = (0, _web3Utils.padLeft)(value.toString(16), 64, '0'); var paddedNonce = (0, _web3Utils.padLeft)(_nonce.toString(16), 64, '0'); this._messageHash = (0, _web3Utils.soliditySha3)('0x' + addresses + paddedValue + data.slice(2) + paddedNonce); } }, { key: 'requiredSignees', get: function get() { (0, _assert2.default)(Array.isArray(this._requiredSignees), 'Required signees not defined; call `.refresh` to refresh signees'); return this._requiredSignees; } }, { key: 'missingSignees', get: function get() { var _this4 = this; return this.requiredSignees.filter(function (address) { return !_this4._signers[address]; }); } }, { key: '_signedMessageDigest', get: function get() { return (0, _web3Utils.hexToBytes)((0, _web3Utils.soliditySha3)('\x19Ethereum Signed Message:\n32', this._messageHash)); } }, { key: '_signedTrezorMessageDigest', get: function get() { return (0, _web3Utils.hexToBytes)((0, _web3Utils.soliditySha3)('\x19Ethereum Signed Message:\n\x20', this._messageHash)); } }]); return MultisigOperation; }(); exports.default = MultisigOperation; //# sourceMappingURL=MultisigOperation.js.map