@colony/colony-js-contract-client
Version:
Method-like interface for Smart Contracts
544 lines (426 loc) • 17.4 kB
JavaScript
'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