mirror of
https://github.com/status-im/web3.js.git
synced 2025-02-24 20:18:18 +00:00
821 lines
26 KiB
JavaScript
821 lines
26 KiB
JavaScript
/*
|
|
This file is part of web3.js.
|
|
|
|
web3.js is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
web3.js 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/**
|
|
* @file contract.js
|
|
*
|
|
* To initialize a contract use:
|
|
*
|
|
* var Contract = require('web3-eth-contract');
|
|
* Contract.prototype._eth = needsAEthInstance;
|
|
* var contract = new Contract(abi, address, ...);
|
|
*
|
|
* @author Fabian Vogelsteller <fabian@frozeman.de>
|
|
* @date 2016
|
|
*/
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
var _ = require('underscore');
|
|
var Method = require('web3-core-method');
|
|
var utils = require('web3-utils');
|
|
var Subscription = require('web3-core-subscriptions').subscription;
|
|
var formatters = require('web3-core-helpers').formatters;
|
|
var promiEvent = require('web3-core-promiEvent');
|
|
|
|
|
|
var coder = require('./solidity/coder');
|
|
|
|
/**
|
|
* Should be called to create new contract instance
|
|
*
|
|
* @method Contract
|
|
* @constructor
|
|
* @param {Array} jsonInterface
|
|
* @param {String} address
|
|
* @param {Object} options
|
|
*/
|
|
var Contract = function Contract(jsonInterface, address, options) {
|
|
var _this = this,
|
|
args = Array.prototype.slice.call(arguments);
|
|
|
|
if(!(this instanceof Contract)) {
|
|
throw new Error('Please use the "new" keyword to instantiate a web3.eth.contract() object!');
|
|
}
|
|
|
|
if(!jsonInterface || !(jsonInterface instanceof Array)) {
|
|
throw new Error('You must provide the json interface of the contract when instatiating a contract object.');
|
|
}
|
|
|
|
|
|
// create the options object
|
|
this.options = {};
|
|
|
|
var lastArg = args[args.length - 1];
|
|
if(_.isObject(lastArg) && !_.isArray(lastArg)) {
|
|
options = lastArg;
|
|
|
|
this.options = _.extend(this.options, this._getOrSetDefaultOptions(options));
|
|
if(_.isObject(address)) {
|
|
address = null;
|
|
}
|
|
}
|
|
|
|
// set address
|
|
Object.defineProperty(this.options, 'address', {
|
|
set: function(value){
|
|
if(value) {
|
|
_this._address = utils.toChecksumAddress(formatters.inputAddressFormatter(value));
|
|
}
|
|
},
|
|
get: function(){
|
|
return _this._address;
|
|
},
|
|
enumerable: true
|
|
});
|
|
|
|
// add method and event signatures, when the jsonInterface gets set
|
|
Object.defineProperty(this.options, 'jsonInterface', {
|
|
set: function(value){
|
|
_this.methods = {};
|
|
_this.events = {};
|
|
|
|
_this._jsonInterface = value.map(function(method) {
|
|
var func,
|
|
funcName;
|
|
|
|
if(method.name)
|
|
funcName = utils._jsonInterfaceMethodToString(method);
|
|
|
|
|
|
// function
|
|
if (method.type === 'function') {
|
|
method.signature = utils.sha3(funcName).slice(0, 10);
|
|
func = _this._createTxObject.bind({
|
|
method: method,
|
|
parent: _this
|
|
});
|
|
|
|
|
|
// add method only if not one already exists
|
|
if(!_this.methods[method.name])
|
|
_this.methods[method.name] = func;
|
|
|
|
// definitely add the method based on its signature
|
|
_this.methods[method.signature] = func;
|
|
|
|
// add method by name
|
|
_this.methods[funcName] = func;
|
|
|
|
|
|
// event
|
|
} else if (method.type === 'event') {
|
|
method.signature = utils.sha3(funcName);
|
|
var event = _this._on.bind(_this, method.signature);
|
|
|
|
// add method only if not already exists
|
|
if(!_this.events[method.name] || _this.events[method.name].name === 'bound ')
|
|
_this.events[method.name] = event;
|
|
|
|
// definitely add the method based on its signature
|
|
_this.events[method.signature] = event;
|
|
|
|
// add event by name
|
|
_this.events[funcName] = event;
|
|
}
|
|
|
|
|
|
return method;
|
|
});
|
|
|
|
// add allEvents
|
|
_this.events.allEvents = _this._on.bind(_this, 'allevents');
|
|
|
|
return _this._jsonInterface;
|
|
},
|
|
get: function(){
|
|
return _this._jsonInterface;
|
|
},
|
|
enumerable: true
|
|
});
|
|
|
|
// properties
|
|
this.methods = {};
|
|
this.events = {};
|
|
|
|
this._address = null;
|
|
this._jsonInterface = [];
|
|
|
|
// set getter/setter properties
|
|
this.options.address = address;
|
|
this.options.jsonInterface = jsonInterface;
|
|
|
|
};
|
|
|
|
Contract.prototype._eth = {}; // eth is attached here in web3-eth/src/index.js
|
|
|
|
|
|
/**
|
|
* Get the callback and modiufy the array if necessary
|
|
*
|
|
* @method _getCallback
|
|
* @param {Array} args
|
|
* @return {Function} the callback
|
|
*/
|
|
Contract.prototype._getCallback = function getCallback(args) {
|
|
if (_.isFunction(args[args.length - 1])) {
|
|
return args.pop(); // modify the args array!
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Checks that no listener with name "newListener" or "removeListener" is added.
|
|
*
|
|
* @method _checkListener
|
|
* @param {String} type
|
|
* @param {String} event
|
|
* @return {Object} the contract instance
|
|
*/
|
|
Contract.prototype._checkListener = function(type, event){
|
|
if(event === type) {
|
|
throw new Error('The event "'+ type +'" is a reserved event name, you can\'t use it.');
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Use default values, if options are not available
|
|
*
|
|
* @method _getOrSetDefaultOptions
|
|
* @param {Object} options the options gived by the user
|
|
* @return {Object} the options with gaps filled by defaults
|
|
*/
|
|
Contract.prototype._getOrSetDefaultOptions = function getOrSetDefaultOptions(options) {
|
|
var gasPrice = options.gasPrice ? String(options.gasPrice): null;
|
|
var from = options.from ? utils.toChecksumAddress(formatters.inputAddressFormatter(options.from)) : null;
|
|
|
|
options.data = options.data || this.options.data;
|
|
|
|
options.from = from || this.options.from;
|
|
options.gasPrice = gasPrice || this.options.gasPrice;
|
|
options.gas = options.gas || options.gasLimit || this.options.gas;
|
|
|
|
// TODO replace with only gasLimit?
|
|
delete options.gasLimit;
|
|
|
|
return options;
|
|
};
|
|
|
|
|
|
/**
|
|
* Should be used to encode indexed params and options to one final object
|
|
*
|
|
* @method _encodeEventABI
|
|
* @param {Object} event
|
|
* @param {Object} options
|
|
* @return {Object} everything combined together and encoded
|
|
*/
|
|
Contract.prototype._encodeEventABI = function (event, options) {
|
|
options = options || {};
|
|
var filter = options.filter || {},
|
|
result = {};
|
|
|
|
['fromBlock', 'toBlock'].filter(function (f) {
|
|
return options[f] !== undefined;
|
|
}).forEach(function (f) {
|
|
result[f] = formatters.inputBlockNumberFormatter(options[f]);
|
|
});
|
|
|
|
// use given topics
|
|
if(_.isArray(options.topics)) {
|
|
result.topics = options.topics;
|
|
|
|
// create topics based on filter
|
|
} else {
|
|
|
|
result.topics = [];
|
|
|
|
// add event signature
|
|
if (event && !event.anonymous && event.name !== 'ALLEVENTS') {
|
|
result.topics.push(event.signature);
|
|
}
|
|
|
|
// add event topics (indexed arguments)
|
|
if (event.name !== 'ALLEVENTS') {
|
|
var indexedTopics = event.inputs.filter(function (i) {
|
|
return i.indexed === true;
|
|
}).map(function (i) {
|
|
var value = filter[i.name];
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
if (_.isArray(value)) {
|
|
return value.map(function (v) {
|
|
return '0x' + coder.encodeParam(i.type, v);
|
|
});
|
|
}
|
|
return '0x' + coder.encodeParam(i.type, value);
|
|
});
|
|
|
|
result.topics = result.topics.concat(indexedTopics);
|
|
}
|
|
|
|
if(!result.topics.length)
|
|
delete result.topics;
|
|
}
|
|
|
|
if(this.options.address) {
|
|
result.address = this.options.address.toLowerCase();
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Should be used to decode indexed params and options
|
|
*
|
|
* @method _decodeEventABI
|
|
* @param {Object} data
|
|
* @return {Object} result object with decoded indexed && not indexed params
|
|
*/
|
|
Contract.prototype._decodeEventABI = function (data) {
|
|
var event = this;
|
|
|
|
data.data = data.data || '';
|
|
data.topics = data.topics || [];
|
|
var result = formatters.outputLogFormatter(data);
|
|
|
|
// if allEvents get the right event
|
|
if(event.name === 'ALLEVENTS') {
|
|
event = event.jsonInterface.find(function (intf) {
|
|
return (intf.signature === data.topics[0]);
|
|
}) || {anonymous: true};
|
|
}
|
|
|
|
// create empty inputs if none are present (e.g. anonymous events on allEvents)
|
|
event.inputs = event.inputs || [];
|
|
|
|
|
|
var argTopics = event.anonymous ? data.topics : data.topics.slice(1);
|
|
var indexedTypes = event.inputs.filter(function (i) {
|
|
return i.indexed === true;
|
|
}).map(function (i) {
|
|
return i.type;
|
|
});
|
|
var notIndexedTypes = event.inputs.filter(function (i) {
|
|
return i.indexed === false;
|
|
}).map(function (i) {
|
|
return i.type;
|
|
});
|
|
|
|
var indexedData = argTopics.map(function (topics) { return topics.slice(2); }).join('');
|
|
// console.log('INDEXED', indexedTypes, indexedData);
|
|
var indexedParams = coder.decodeParams(indexedTypes, indexedData);
|
|
|
|
// console.log('NOT INDEXED', notIndexedTypes, data.data.slice(2));
|
|
var notIndexedParams = coder.decodeParams(notIndexedTypes, data.data.slice(2));
|
|
|
|
|
|
var count = 0;
|
|
result.returnValues = event.inputs.reduce(function (acc, current) {
|
|
var name = current.name || count++;
|
|
acc[name] = current.indexed ? indexedParams.shift() : notIndexedParams.shift();
|
|
return acc;
|
|
}, {});
|
|
|
|
result.event = event.name;
|
|
|
|
// move the data and topics to "raw"
|
|
result.raw = {
|
|
data: result.data,
|
|
topics: result.topics
|
|
};
|
|
delete result.data;
|
|
delete result.topics;
|
|
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Encodes an ABI for a method, including signature or the method.
|
|
* Or when constructor encodes only the constructor parameters.
|
|
*
|
|
* @method _encodeMethodABI
|
|
* @param {Mixed} args the arguments to encode
|
|
* @param {String} the encoded ABI
|
|
*/
|
|
Contract.prototype._encodeMethodABI = function _encodeMethodABI() {
|
|
var methodSignature = this._method.signature,
|
|
args = this.arguments;
|
|
|
|
var signature = false,
|
|
paramsABI = this._parent.options.jsonInterface.filter(function (json) {
|
|
return ((methodSignature === 'constructor' && json.type === methodSignature) ||
|
|
((json.signature === methodSignature || json.signature === methodSignature.replace('0x','') || json.name === methodSignature) && json.type === 'function'));
|
|
}).map(function (json) {
|
|
if(json.inputs.length !== args.length) {
|
|
throw new Error('The number of arguments is not matching the methods required number. You need to pass '+ json.inputs.length +' arguments.');
|
|
}
|
|
|
|
if(json.type === 'function') {
|
|
signature = json.signature;
|
|
}
|
|
return json.inputs.map(function (input) {
|
|
return input.type;
|
|
});
|
|
}).map(function (types) {
|
|
return coder.encodeParams(types, args);
|
|
})[0] || '';
|
|
|
|
// return constructor
|
|
if(methodSignature === 'constructor') {
|
|
if(!this._deployData)
|
|
throw new Error('The contract has no contract data option set. This is necessary to append the constructor parameters.');
|
|
|
|
return this._deployData + paramsABI;
|
|
|
|
// return method
|
|
} else {
|
|
|
|
var returnValue = (signature) ? signature + paramsABI : paramsABI;
|
|
|
|
if(!returnValue)
|
|
throw new Error('Couldn\'t find a matching contract method named "'+ this._method.name +'".');
|
|
else
|
|
return returnValue;
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Decode method return values
|
|
*
|
|
* @method _decodeMethodReturn
|
|
* @param {Array} outputs
|
|
* @param {String} returnValues
|
|
* @param {Array} decoded output return values
|
|
*/
|
|
Contract.prototype._decodeMethodReturn = function (outputs, returnValues) {
|
|
if (!returnValues) {
|
|
return;
|
|
}
|
|
|
|
var types = outputs.map(function (i) {
|
|
return i.type;
|
|
});
|
|
|
|
returnValues = returnValues.length >= 2 ? returnValues.slice(2) : returnValues;
|
|
var result = coder.decodeParams(types, returnValues);
|
|
result = result.length === 1 ? result[0] : result;
|
|
if(result === '0x')
|
|
result = null;
|
|
return result;
|
|
};
|
|
|
|
|
|
/**
|
|
* Deploys a contract and fire events based on its state: transactionHash, receipt
|
|
*
|
|
* All event listeners will be removed, once the last possible event is fired ("error", or "receipt")
|
|
*
|
|
* @method deploy
|
|
* @param {Object} options
|
|
* @param {Function} callback
|
|
* @return {Object} EventEmitter possible events are "error", "transactionHash" and "receipt"
|
|
*/
|
|
Contract.prototype.deploy = function(options, callback){
|
|
|
|
options = options || {};
|
|
|
|
options.arguments = options.arguments || [];
|
|
options = this._getOrSetDefaultOptions(options);
|
|
|
|
|
|
// return error, if no "data" is specified
|
|
if(!options.data) {
|
|
return utils._fireError(new Error('No "data" specified in neither the given options, nor the default options.'), null, null, callback);
|
|
}
|
|
|
|
var constructor = _.find(this.options.jsonInterface, function (method) {
|
|
return (method.type === 'constructor');
|
|
}) || {};
|
|
constructor.signature = 'constructor';
|
|
|
|
return this._createTxObject.apply({
|
|
method: constructor,
|
|
parent: this,
|
|
deployData: options.data
|
|
}, options.arguments);
|
|
|
|
};
|
|
|
|
/**
|
|
* Gets the event signature and outputformatters
|
|
*
|
|
* @method _generateEventOptions
|
|
* @param {Object} event
|
|
* @param {Object} options
|
|
* @param {Function} callback
|
|
* @return {Object} the event options object
|
|
*/
|
|
Contract.prototype._generateEventOptions = function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
// get the callback
|
|
var callback = this._getCallback(args);
|
|
|
|
// get the options
|
|
var options = (_.isObject(args[args.length - 1])) ? args.pop() : {};
|
|
|
|
var event = (_.isString(args[0])) ? args[0] : 'allevents';
|
|
event = (event.toLowerCase() === 'allevents') ? {
|
|
name: 'ALLEVENTS',
|
|
jsonInterface: this.options.jsonInterface
|
|
} : this.options.jsonInterface.find(function (json) {
|
|
return (json.type === 'event' && (json.name === event || json.signature === '0x'+ event.replace('0x','')));
|
|
});
|
|
|
|
if (!event) {
|
|
throw new Error('Event "' + event.name + '" doesn\'t exist in this contract.');
|
|
}
|
|
|
|
if (!utils.isAddress(this.options.address)) {
|
|
throw new Error('This contract object doesn\'t have address set yet, please set an address first.');
|
|
}
|
|
|
|
return {
|
|
params: this._encodeEventABI(event, options),
|
|
event: event,
|
|
callback: callback
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Adds event listeners and creates a subscription, and remove it once its fired.
|
|
*
|
|
* @method clone
|
|
* @return {Object} the event subscription
|
|
*/
|
|
Contract.prototype.clone = function() {
|
|
return new Contract(this.options.jsonInterface, this.options.address, this.options);
|
|
};
|
|
|
|
|
|
/**
|
|
* Adds event listeners and creates a subscription, and remove it once its fired.
|
|
*
|
|
* @method once
|
|
* @param {String} event
|
|
* @param {Object} options
|
|
* @param {Function} callback
|
|
* @return {Object} the event subscription
|
|
*/
|
|
Contract.prototype.once = function(event, options, callback) {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
// get the callback
|
|
callback = this._getCallback(args);
|
|
|
|
if (!callback) {
|
|
throw new Error('Once requires a callback as the second parameter.');
|
|
}
|
|
|
|
// don't allow fromBlock
|
|
if (options)
|
|
delete options.fromBlock;
|
|
|
|
// don't return as once shouldn't provide "on"
|
|
this._on(event, options, function (err, res, sub) {
|
|
sub.unsubscribe();
|
|
if(_.isFunction(callback)){
|
|
callback(err, res, sub);
|
|
}
|
|
});
|
|
|
|
return undefined;
|
|
};
|
|
|
|
/**
|
|
* Adds event listeners and creates a subscription.
|
|
*
|
|
* @method _on
|
|
* @param {String} event
|
|
* @param {Object} options
|
|
* @param {Function} callback
|
|
* @return {Object} the event subscription
|
|
*/
|
|
Contract.prototype._on = function(){
|
|
var subOptions = this._generateEventOptions.apply(this, arguments);
|
|
|
|
|
|
// prevent the event "newListener" and "removeListener" from being overwritten
|
|
this._checkListener('newListener', subOptions.event.name, subOptions.callback);
|
|
this._checkListener('removeListener', subOptions.event.name, subOptions.callback);
|
|
|
|
// TODO check if listener already exists? and reuse subscription if options are the same.
|
|
|
|
// create new subscription
|
|
var subscription = new Subscription({
|
|
subscription: {
|
|
params: 1,
|
|
inputFormatter: [formatters.inputLogFormatter],
|
|
outputFormatter: this._decodeEventABI.bind(subOptions.event),
|
|
// DUBLICATE, also in web3-eth
|
|
subscriptionHandler: function (output) {
|
|
if(output.removed) {
|
|
this.emit('changed', output);
|
|
} else {
|
|
this.emit('data', output);
|
|
}
|
|
|
|
if (_.isFunction(this.callback)) {
|
|
this.callback(null, output, this);
|
|
}
|
|
}
|
|
},
|
|
type: 'eth',
|
|
requestManager: this._eth._requestManager
|
|
});
|
|
subscription.subscribe('logs', subOptions.params, subOptions.callback || function () {});
|
|
|
|
return subscription;
|
|
};
|
|
|
|
/**
|
|
* Get past events from contracts
|
|
*
|
|
* @method getPastEvents
|
|
* @param {String} event
|
|
* @param {Object} options
|
|
* @param {Function} callback
|
|
* @return {Object} the promievent
|
|
*/
|
|
Contract.prototype.getPastEvents = function(){
|
|
var subOptions = this._generateEventOptions.apply(this, arguments);
|
|
|
|
var getPastLogs = new Method({
|
|
name: 'getPastLogs',
|
|
call: 'eth_getLogs',
|
|
params: 1,
|
|
inputFormatter: [formatters.inputLogFormatter],
|
|
outputFormatter: this._decodeEventABI.bind(subOptions.event)
|
|
});
|
|
getPastLogs.setRequestManager(this._eth._requestManager);
|
|
var call = getPastLogs.buildCall();
|
|
|
|
getPastLogs = null;
|
|
|
|
return call(subOptions.params, subOptions.callback);
|
|
};
|
|
|
|
|
|
/**
|
|
* returns the an object with call, send, estimate funcitons
|
|
*
|
|
* @method _createTxObject
|
|
* @returns {Object} an object with functions to call the methods
|
|
*/
|
|
Contract.prototype._createTxObject = function _createTxObject(){
|
|
var txObject = {};
|
|
|
|
if(this.method.type === 'function') {
|
|
|
|
txObject.call = this.parent._executeMethod.bind(txObject, 'call');
|
|
txObject.call.request = this.parent._executeMethod.bind(txObject, 'call', true); // to make batch requests
|
|
|
|
}
|
|
|
|
txObject.send = this.parent._executeMethod.bind(txObject, 'send');
|
|
txObject.send.request = this.parent._executeMethod.bind(txObject, 'send', true); // to make batch requests
|
|
txObject.encodeABI = this.parent._encodeMethodABI.bind(txObject);
|
|
txObject.estimateGas = this.parent._executeMethod.bind(txObject, 'estimate');
|
|
|
|
txObject.arguments = arguments;
|
|
txObject._method = this.method;
|
|
txObject._parent = this.parent;
|
|
|
|
if(this.deployData)
|
|
txObject._deployData = this.deployData;
|
|
|
|
return txObject;
|
|
};
|
|
|
|
|
|
/**
|
|
* Generates the options for the execute call
|
|
*
|
|
* @method _processExecuteArguments
|
|
* @param {Array} args
|
|
* @param {Promise} defer
|
|
*/
|
|
Contract.prototype._processExecuteArguments = function _processExecuteArguments(args, defer) {
|
|
var processedArgs = {};
|
|
|
|
processedArgs.type = args.shift();
|
|
|
|
// get the callback
|
|
processedArgs.callback = this._parent._getCallback(args);
|
|
|
|
// get block number to use for call
|
|
if(processedArgs.type === 'call' && args[args.length - 1] !== true && (_.isString(args[args.length - 1]) || isFinite(args[args.length - 1])))
|
|
processedArgs.defaultBlock = args.pop();
|
|
|
|
// get the options
|
|
processedArgs.options = (_.isObject(args[args.length - 1])) ? args.pop() : {};
|
|
|
|
// get the generateRequest argument for batch requests
|
|
processedArgs.generateRequest = (args[args.length - 1] === true)? args.pop() : false;
|
|
|
|
processedArgs.options = this._parent._getOrSetDefaultOptions(processedArgs.options);
|
|
processedArgs.options.data = this.encodeABI();
|
|
|
|
// add contract address
|
|
if(!this._deployData && !utils.isAddress(this._parent.options.address))
|
|
throw new Error('This contract object doesn\'t have address set yet, please set an address first.');
|
|
|
|
if(!this._deployData)
|
|
processedArgs.options.to = this._parent.options.address;
|
|
|
|
// return error, if no "data" is specified
|
|
if(!processedArgs.options.data)
|
|
return utils._fireError(new Error('Couldn\'t find a matching contract method, or the number of parameters is wrong.'), defer.eventEmitter, defer.reject, processedArgs.callback);
|
|
|
|
return processedArgs;
|
|
};
|
|
|
|
/**
|
|
* Executes a call, transact or estimateGas on a contract function
|
|
*
|
|
* @method _executeMethod
|
|
* @param {String} type the type this execute function should execute
|
|
* @param {Boolean} makeRequest if true, it simply returns the request parameters, rather than executing it
|
|
*/
|
|
Contract.prototype._executeMethod = function _executeMethod(){
|
|
var _this = this,
|
|
args = this._parent._processExecuteArguments.call(this, Array.prototype.slice.call(arguments), defer),
|
|
defer = promiEvent((args.type !== 'send'));
|
|
|
|
|
|
// simple return request for batch requests
|
|
if(args.generateRequest) {
|
|
|
|
var payload = {
|
|
params: [formatters.inputCallFormatter(args.options), formatters.inputDefaultBlockNumberFormatter(args.defaultBlock)],
|
|
callback: args.callback
|
|
};
|
|
|
|
if(args.type === 'call') {
|
|
payload.method = 'eth_call';
|
|
payload.format = this._parent._decodeMethodReturn.bind(null, this._method.outputs);
|
|
} else {
|
|
payload.method = 'eth_sendTransaction';
|
|
}
|
|
|
|
return payload;
|
|
|
|
} else {
|
|
|
|
switch (args.type) {
|
|
case 'estimate':
|
|
|
|
return this._parent._eth.estimateGas(args.options, args.callback);
|
|
|
|
case 'call':
|
|
|
|
// TODO check errors: missing "from" should give error on deploy and send, call ?
|
|
|
|
this._parent._eth.call(args.options, args.defaultBlock, function (err, result) {
|
|
|
|
// decode result
|
|
if(result) {
|
|
result = _this._parent._decodeMethodReturn(_this._method.outputs, result);
|
|
}
|
|
|
|
// throw error
|
|
if(err) {
|
|
return utils._fireError(err, null, defer.reject, args.callback);
|
|
}
|
|
|
|
if(_.isFunction(args.callback)) {
|
|
args.callback(null, result);
|
|
}
|
|
defer.resolve(result);
|
|
});
|
|
|
|
return defer.eventEmitter;
|
|
|
|
case 'send':
|
|
|
|
// return error, if no "from" is specified
|
|
if(!utils.isAddress(args.options.from)) {
|
|
return utils._fireError(new Error('No "from" address specified in neither the given options, nor the default options.'), defer.eventEmitter, defer.reject, args.callback);
|
|
}
|
|
|
|
if (_.isBoolean(this._method.payable) && !this._method.payable && args.options.value && args.options.value > 0) {
|
|
return utils._fireError(new Error('Can not send value to non-payable contract method or constructor'), defer.eventEmitter, defer.reject, args.callback);
|
|
}
|
|
|
|
|
|
// make sure receipt logs are decoded
|
|
var extraFormatters = {
|
|
receiptFormatter: function (receipt) {
|
|
if (_.isArray(receipt.logs)) {
|
|
|
|
// decode logs
|
|
var events = _.map(receipt.logs, function(log) {
|
|
return _this._parent._decodeEventABI.call({
|
|
name: 'ALLEVENTS',
|
|
jsonInterface: _this._parent.options.jsonInterface
|
|
}, log);
|
|
});
|
|
|
|
// make log names keys
|
|
receipt.events = {};
|
|
var count = 0;
|
|
events.forEach(function (ev) {
|
|
if (ev.event) {
|
|
receipt.events[ev.event] = ev;
|
|
} else {
|
|
receipt.events[count] = ev;
|
|
count++;
|
|
}
|
|
});
|
|
|
|
delete receipt.logs;
|
|
}
|
|
return receipt;
|
|
},
|
|
contractDeployFormatter: function (receipt) {
|
|
var newContract = _this._parent.clone();
|
|
newContract.options.address = receipt.contractAddress;
|
|
return newContract;
|
|
}
|
|
};
|
|
|
|
return this._parent._eth.sendTransaction.apply(extraFormatters, [args.options, args.callback]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
module.exports = Contract;
|