UNPKG

@gnosis.pm/pm-js

Version:

A javascript library for building applications on top of Gnosis, the Ethereum prediction market platform

627 lines (477 loc) 22.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.sendTransactionAndGetResult = exports.TransactionError = exports.Decimal = undefined; var _from = require('babel-runtime/core-js/array/from'); var _from2 = _interopRequireDefault(_from); var _promise = require('babel-runtime/core-js/promise'); var _promise2 = _interopRequireDefault(_promise); var _assign = require('babel-runtime/core-js/object/assign'); var _assign2 = _interopRequireDefault(_assign); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _stringify = require('babel-runtime/core-js/json/stringify'); var _stringify2 = _interopRequireDefault(_stringify); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); 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 _typeof2 = require('babel-runtime/helpers/typeof'); var _typeof3 = _interopRequireDefault(_typeof2); var _functionsIn2 = require('lodash/functionsIn'); var _functionsIn3 = _interopRequireDefault(_functionsIn2); var _filter2 = require('lodash/filter'); var _filter3 = _interopRequireDefault(_filter2); var _clone2 = require('lodash/clone'); var _clone3 = _interopRequireDefault(_clone2); var _defaults2 = require('lodash/defaults'); var _defaults3 = _interopRequireDefault(_defaults2); var _forOwn2 = require('lodash/forOwn'); var _forOwn3 = _interopRequireDefault(_forOwn2); var _isNumber2 = require('lodash/isNumber'); var _isNumber3 = _interopRequireDefault(_isNumber2); var _isBoolean2 = require('lodash/isBoolean'); var _isBoolean3 = _interopRequireDefault(_isBoolean2); var _isString2 = require('lodash/isString'); var _isString3 = _interopRequireDefault(_isString2); var _has2 = require('lodash/has'); var _has3 = _interopRequireDefault(_has2); var _isArray2 = require('lodash/isArray'); var _isArray3 = _interopRequireDefault(_isArray2); var sendTransactionAndGetResult = exports.sendTransactionAndGetResult = function () { var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(opts) { var caller, txHash, txResult, matchingLog, contractInstance, _caller$opts$methodNa; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: opts = opts || {}; caller = void 0, txHash = void 0, txResult = void 0, matchingLog = void 0, contractInstance = void 0; _context3.prev = 2; caller = opts.callerContract; if (!(0, _has3.default)(caller, 'deployed')) { _context3.next = 8; break; } _context3.next = 7; return caller.deployed(); case 7: caller = _context3.sent; case 8: _context3.next = 10; return (_caller$opts$methodNa = caller[opts.methodName]).sendTransaction.apply(_caller$opts$methodNa, (0, _toConsumableArray3.default)(opts.methodArgs)); case 10: txHash = _context3.sent; if (opts.log != null) { opts.log('got tx hash ' + txHash + ' for call ' + formatCallSignature({ caller: caller, methodName: opts.methodName, methodArgs: opts.methodArgs })); } _context3.next = 14; return caller.constructor.syncTransaction(txHash); case 14: txResult = _context3.sent; matchingLog = requireEventFromTXResult(txResult, opts.eventName); if (!(opts.resultContract == null)) { _context3.next = 20; break; } return _context3.abrupt('return', matchingLog.args[opts.eventArgName]); case 20: opts.log('tx hash ' + txHash.slice(0, 6) + '..' + txHash.slice(-4) + ' returned ' + opts.resultContract.contractName + '(' + matchingLog.args[opts.eventArgName] + ')'); _context3.next = 23; return opts.resultContract.at(matchingLog.args[opts.eventArgName]); case 23: contractInstance = _context3.sent; // Set the resulting transaction hash on the contract instance contractInstance.transactionHash = txHash; return _context3.abrupt('return', contractInstance); case 26: _context3.next = 31; break; case 28: _context3.prev = 28; _context3.t0 = _context3['catch'](2); throw new TransactionError((0, _assign2.default)({ caller: caller, txHash: txHash, txResult: txResult, matchingLog: matchingLog, subError: _context3.t0 }, opts)); case 31: case 'end': return _context3.stop(); } } }, _callee3, this, [[2, 28]]); })); return function sendTransactionAndGetResult(_x) { return _ref9.apply(this, arguments); }; }(); // I know bluebird does this, but it's heavy // Also, as of Node v8.5.0, `util.promisify` doesn't call the function with the same `this` exports.normalizeWeb3Args = normalizeWeb3Args; exports.wrapWeb3Function = wrapWeb3Function; exports.requireEventFromTXResult = requireEventFromTXResult; exports.formatCallSignature = formatCallSignature; exports.promisify = promisify; exports.promisifyAll = promisifyAll; var _decimal = require('decimal.js'); var _decimal2 = _interopRequireDefault(_decimal); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function makeWeb3Compatible(value, type, argName) { if (type == null) { throw new Error('type must be specified for argument ' + argName); } var match = /^(.*)\[(\d*)\]$/.exec(type); if (match != null) { if (!(0, _isArray3.default)(value)) { throw new Error('expected ' + value + ' to be convertable to ' + type + ' ' + argName); } if (match[2] !== '' && value.length !== Number(match[2])) { throw new Error(value + ' has ' + value.length + ' items but should be ' + type + ' ' + argName); } return value.map(function (v) { return makeWeb3Compatible(v, match[1], argName); }); } if (type === 'address') { // if it quacks like a TruffleContract if ((0, _has3.default)(value, 'address')) { value = value.address; } if (!(0, _isString3.default)(value)) { throw new Error(value + ' must be string for ' + type + ' ' + argName); } if (!/^(0x)?[0-9a-f]{40}$/i.test(value)) { throw new Error(value + ' has wrong format for ' + type + ' ' + argName); } return value; } if (type === 'bool') { if (!(0, _isBoolean3.default)(value)) { throw new Error('expected ' + value + ' to be a bool for ' + type + ' ' + argName); } return value; } if (type === 'bytes' || type === 'string') { if ((0, _isString3.default)(value)) { return value; } throw new Error('could not format ' + value + ' for ' + type + ' ' + argName); } match = /^bytes(\d+)$/.exec(type); if (match != null) { var bytesLength = Number(match[1]); if (bytesLength > 32 || bytesLength === 0 || match[1].startsWith('0')) { throw new Error('invalid type ' + type + ' specified for ' + argName); } if ((0, _isString3.default)(value)) { // TODO: refine this check to account for things like '\uACDC'.length if (value.length > bytesLength) { throw new Error('value ' + value + ' too long for ' + type + ' ' + argName); } return value; } throw new Error('could not format ' + value + ' for ' + type + ' ' + argName); } match = /^(u?)int(\d+)$/.exec(type); if (match != null) { var signed = match[1] === ''; var numBits = Number(match[2]); if (numBits % 8 !== 0) { throw new Error('number of bits for ' + type + ' ' + argName + ' not divisible by 8'); } if (numBits > 256) { throw new Error('number of bits for ' + type + ' ' + argName + ' is too large'); } value = value.valueOf(); if ((0, _isString3.default)(value) && /^-?(0x[\da-f]+|\d+)$/i.test(value) || (0, _isNumber3.default)(value)) { if ((0, _isString3.default)(value) && value.startsWith('0x') && value.slice(2) === Number(value).toString(16) || value == Number(value).toString()) { value = Number(value); } if (!signed && value.toString().startsWith('-')) { throw new Error('cannot pass negative value ' + value + ' for ' + type + ' ' + argName); } return value; } throw new Error('could not normalize ' + value + ' for ' + type + ' ' + argName); } throw new Error('unsupported type ' + type + ' for ' + argName); } function getOptsFromArgs(args) { return (0, _typeof3.default)(args[args.length - 1]) === 'object' ? args[args.length - 1] : {}; } function getTruffleArgsWhileMutatingOptions(argInfo, opts, argAliases) { opts = opts == null ? {} : opts; if (argAliases != null) { (0, _forOwn3.default)(argAliases, function (name, alias) { if ((0, _has3.default)(opts, alias)) { if ((0, _has3.default)(opts, name)) { throw new Error('both name ' + name + ' and its alias ' + alias + ' specified in ' + opts); } opts[name] = opts[alias]; delete opts[alias]; } }); } return argInfo.map(function (_ref) { var name = _ref.name, type = _ref.type; if (!(0, _has3.default)(opts, name)) { throw new Error('missing argument ' + name); } var ret = makeWeb3Compatible(opts[name], type, name); delete opts[name]; return ret; }); } var Decimal = exports.Decimal = _decimal2.default.clone({ precision: 80, toExpPos: 9999 }); function normalizeWeb3Args(args, opts) { var functionInputs = opts.functionInputs, methodName = opts.methodName, argAliases = opts.argAliases, defaults = opts.defaults; // Format arguments in a way that web3 likes var methodArgs = void 0, methodOpts = void 0; if (functionInputs.length === 1 && args.length === 1) { // if there is one input, user could have supplied either the argument with no options // or the argument inside of an options object if ((0, _typeof3.default)(args[0]) === 'object' && (0, _has3.default)(args[0], functionInputs[0].name)) { // we consider argument to be an options object if it has the parameter name as a key on it methodOpts = (0, _defaults3.default)((0, _clone3.default)(args[0]), defaults); methodArgs = getTruffleArgsWhileMutatingOptions(functionInputs, methodOpts, argAliases); } else { methodOpts = null; methodArgs = functionInputs.map(function (_ref2, i) { var name = _ref2.name, type = _ref2.type; return makeWeb3Compatible(args[i], type, name); }); } } else if (functionInputs.length === args.length) { methodOpts = null; methodArgs = functionInputs.map(function (_ref3, i) { var name = _ref3.name, type = _ref3.type; return makeWeb3Compatible(args[i], type, name); }); } else if (functionInputs.length + 1 === args.length && (0, _typeof3.default)(args[functionInputs.length]) === 'object') { methodOpts = args[args.length - 1]; // this map should not hit the last element of args methodArgs = functionInputs.map(function (_ref4, i) { var name = _ref4.name, type = _ref4.type; return makeWeb3Compatible(args[i], type, name); }); } else if (args.length === 1 && (0, _typeof3.default)(args[0]) === 'object') { methodOpts = (0, _defaults3.default)((0, _clone3.default)(args[0]), defaults); methodArgs = getTruffleArgsWhileMutatingOptions(functionInputs, methodOpts, argAliases); } else { throw new Error(methodName + '(' + functionInputs.map(function (_ref5) { var name = _ref5.name, type = _ref5.type; return type + ' ' + name; }).join(', ') + ') can\'t be called with args (' + args.join(', ') + ')'); } return [methodArgs, methodOpts]; } function getWeb3CallMetadata(args, opts, speccedOpts) { var callerContract = speccedOpts.callerContract, callerABI = speccedOpts.callerABI, methodName = speccedOpts.methodName, eventName = speccedOpts.eventName, eventArgName = speccedOpts.eventArgName, resultContract = speccedOpts.resultContract, argAliases = speccedOpts.argAliases, validators = speccedOpts.validators; if (callerABI == null) { callerABI = callerContract.abi; } var functionCandidates = callerABI.filter(function (_ref6) { var name = _ref6.name; return name === methodName; }); if (functionCandidates.length === 0) { throw new Error('could not find function ' + methodName + ' in abi ' + callerABI); } else if (functionCandidates.length > 1) { // eslint-disable-next-line no-console console.warn('function ' + methodName + ' has multiple candidates in abi ' + callerABI + ' -- using last candidate'); } var functionInputs = functionCandidates.pop().inputs; var _normalizeWeb3Args = normalizeWeb3Args(args, { functionInputs: functionInputs, methodName: methodName, argAliases: argAliases }), _normalizeWeb3Args2 = (0, _slicedToArray3.default)(_normalizeWeb3Args, 2), methodArgs = _normalizeWeb3Args2[0], methodOpts = _normalizeWeb3Args2[1]; if (validators != null) { validators.forEach(function (validator) { validator(methodArgs); }); } // Pass extra options down to the web3 layer if (methodOpts != null) { methodArgs.push(methodOpts); } return { callerContract: callerContract, methodName: methodName, methodArgs: methodArgs, eventName: eventName, eventArgName: eventArgName, resultContract: resultContract }; } function wrapWeb3Function(spec) { var wrappedFn = function () { var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() { var opts, speccedOpts, callMetadata, _args = arguments; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: opts = getOptsFromArgs(_args); speccedOpts = spec(this, opts); callMetadata = getWeb3CallMetadata(_args, opts, speccedOpts); callMetadata.log = this.log; _context.next = 6; return sendTransactionAndGetResult(callMetadata); case 6: return _context.abrupt('return', _context.sent); case 7: case 'end': return _context.stop(); } } }, _callee, this); })); return function wrappedFn() { return _ref7.apply(this, arguments); }; }(); wrappedFn.estimateGas = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() { var opts, speccedOpts, callerContract, methodName, _caller$methodName, _getWeb3CallMetadata, methodArgs, caller, _args2 = arguments; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: opts = getOptsFromArgs(_args2); speccedOpts = spec(this, opts); callerContract = speccedOpts.callerContract, methodName = speccedOpts.methodName; if (!(opts.using === 'stats')) { _context2.next = 5; break; } return _context2.abrupt('return', callerContract.gasStats[methodName].averageGasUsed); case 5: if (!(opts.using === 'rpc')) { _context2.next = 15; break; } _getWeb3CallMetadata = getWeb3CallMetadata(_args2, opts, speccedOpts), methodArgs = _getWeb3CallMetadata.methodArgs; caller = callerContract; if (!(0, _has3.default)(caller, 'deployed')) { _context2.next = 12; break; } _context2.next = 11; return caller.deployed(); case 11: caller = _context2.sent; case 12: _context2.next = 14; return (_caller$methodName = caller[methodName]).estimateGas.apply(_caller$methodName, (0, _toConsumableArray3.default)(methodArgs)); case 14: return _context2.abrupt('return', _context2.sent); case 15: throw new Error('unsupported gas estimation source ' + opts.using); case 16: case 'end': return _context2.stop(); } } }, _callee2, this); })); return wrappedFn; } /** * Looks for a single event in the logs of a transaction result. If no such events or multiple matching events are found, throws an error. Otherwise returns the matching event log. * * @param {Transaction} result Result of sending a transaction * @param {string} eventName Name of the event * @return {Object} The matching event log found * @alias Gnosis.requireEventFromTXResult */ function requireEventFromTXResult(result, eventName) { var matchingLogs = (0, _filter3.default)(result.logs, function (l) { return l.event === eventName; }); if (matchingLogs.length < 1) { throw new Error('could not find any logs in transaction ' + result.tx + ' corresponding to event ' + eventName); } else if (matchingLogs.length > 1) { throw new Error('found too many logs in transaction ' + result.tx + ' corresponding to event ' + eventName); } return matchingLogs[0]; } function formatCallSignature(opts) { return opts.caller.constructor.contractName + '(' + opts.caller.address.slice(0, 6) + '..' + opts.caller.address.slice(-4) + ').' + opts.methodName + '(' + opts.methodArgs.map(function (v) { try { return (0, _stringify2.default)(v); } catch (e) { return v; } }).join(', ') + ')'; } var TransactionError = exports.TransactionError = function (_Error) { (0, _inherits3.default)(TransactionError, _Error); function TransactionError(opts) { (0, _classCallCheck3.default)(this, TransactionError); var _this = (0, _possibleConstructorReturn3.default)(this, (TransactionError.__proto__ || (0, _getPrototypeOf2.default)(TransactionError)).call(this, '' + formatCallSignature(opts) + (opts.txHash == null ? '' : '\n\n with transaction hash ' + opts.txHash) + '\n\n failed with ' + opts.subError)); (0, _assign2.default)(_this, opts); _this.name = 'TransactionError'; return _this; } return TransactionError; }(Error); function promisify(fn) { return new Proxy(fn, { apply: function apply(target, thisArg, args) { return new _promise2.default(function (resolve, reject) { var newArgs = (0, _from2.default)(args); newArgs.push(function (err, result) { if (err != null) { reject(new Error('' + err + (result == null ? '' : ' (' + result + ')'))); } else { resolve(result); } }); target.apply(thisArg, newArgs); }); } }); } function promisifyAll(obj) { (0, _functionsIn3.default)(obj).forEach(function (fnName) { var asyncFnName = fnName + 'Async'; if (!(0, _has3.default)(obj, asyncFnName)) { obj[asyncFnName] = promisify(obj[fnName]); } }); return obj; } //# sourceMappingURL=utils.js.map