web3.js/lib/web3/contract.js

309 lines
9.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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
* @author Marek Kotewicz <marek@ethdev.com>
* @date 2014
*/
var utils = require('../utils/utils');
var coder = require('../solidity/coder');
var SolidityEvent = require('./event');
var SolidityFunction = require('./function');
var AllEvents = require('./allevents');
/**
* Should be called to encode constructor params
*
* @method encodeConstructorParams
* @param {Array} abi
* @param {Array} constructor params
*/
var encodeConstructorParams = function (abi, params) {
return abi.filter(function (json) {
return json.type === 'constructor' && json.inputs.length === params.length;
}).map(function (json) {
return json.inputs.map(function (input) {
return input.type;
});
}).map(function (types) {
return coder.encodeParams(types, params);
})[0] || '';
};
/**
* Should be called to add functions to contract object
*
* @method addFunctionsToContract
* @param {Contract} contract
* @param {Array} abi
*/
var addFunctionsToContract = function (contract) {
contract.abi.filter(function (json) {
return json.type === 'function';
}).map(function (json) {
return new SolidityFunction(contract._eth, json, contract.address);
}).forEach(function (f) {
f.attachToContract(contract);
});
};
/**
* Should be called to add events to contract object
*
* @method addEventsToContract
* @param {Contract} contract
* @param {Array} abi
*/
var addEventsToContract = function (contract) {
var events = contract.abi.filter(function (json) {
return json.type === 'event';
});
var All = new AllEvents(contract._eth._requestManager, events, contract.address);
All.attachToContract(contract);
events.map(function (json) {
return new SolidityEvent(contract._eth._requestManager, json, contract.address);
}).forEach(function (e) {
e.attachToContract(contract);
});
};
/**
* Should be called to check if the contract gets properly deployed on the blockchain.
*
* @method checkForContractAddress
* @param {Object} contract
* @param {Function} callback
* @returns {Undefined}
*/
var checkForContractAddress = function(contract, callback){
var count = 0,
callbackFired = false;
// wait for receipt
var filter = contract._eth.filter('latest', function(e){
if (!e && !callbackFired) {
count++;
// stop watching after 50 blocks (timeout)
if (count > 50) {
filter.stopWatching();
callbackFired = true;
if (callback)
callback(new Error('Contract transaction couldn\'t be found after 50 blocks'));
else
throw new Error('Contract transaction couldn\'t be found after 50 blocks');
} else {
contract._eth.getTransactionReceipt(contract.transactionHash, function(e, receipt){
if(receipt && !callbackFired) {
contract._eth.getCode(receipt.contractAddress, function(e, code){
/*jshint maxcomplexity: 6 */
if(callbackFired || !code)
return;
filter.stopWatching();
callbackFired = true;
if(code.length > 3) {
// console.log('Contract code deployed!');
contract.address = receipt.contractAddress;
// attach events and methods again after we have
addFunctionsToContract(contract);
addEventsToContract(contract);
// call callback for the second time
if(callback)
callback(null, contract);
} else {
if(callback)
callback(new Error('The contract code couldn\'t be stored, please check your gas amount.'));
else
throw new Error('The contract code couldn\'t be stored, please check your gas amount.');
}
});
}
});
}
}
});
};
/**
* Should be called to create new ContractFactory instance
*
* @method ContractFactory
* @param {Array} abi
*/
var ContractFactory = function (eth, abi) {
this.eth = eth;
this.abi = abi;
/**
* Should be called to create new contract on a blockchain
*
* @method new
* @param {Any} contract constructor param1 (optional)
* @param {Any} contract constructor param2 (optional)
* @param {Object} contract transaction object (required)
* @param {Function} callback
* @returns {Contract} returns contract instance
*/
this.new = function () {
var contract = new Contract(this.eth, this.abi);
// parse arguments
var options = {}; // required!
var callback;
var args = Array.prototype.slice.call(arguments);
if (utils.isFunction(args[args.length - 1])) {
callback = args.pop();
}
var last = args[args.length - 1];
if (utils.isObject(last) && !utils.isArray(last)) {
options = args.pop();
}
if (options.value > 0) {
var constructorAbi = abi.filter(function (json) {
return json.type === 'constructor' && json.inputs.length === args.length;
})[0] || {};
if (!constructorAbi.payable) {
throw new Error('Cannot send value to non-payable constructor');
}
}
var bytes = encodeConstructorParams(this.abi, args);
options.data += bytes;
if (callback) {
// wait for the contract address adn check if the code was deployed
this.eth.sendTransaction(options, function (err, hash) {
if (err) {
callback(err);
} else {
// add the transaction hash
contract.transactionHash = hash;
// call callback for the first time
callback(null, contract);
checkForContractAddress(contract, callback);
}
});
} else {
var hash = this.eth.sendTransaction(options);
// add the transaction hash
contract.transactionHash = hash;
checkForContractAddress(contract);
}
return contract;
};
this.new.getData = this.getData.bind(this);
};
/**
* Should be called to create new ContractFactory
*
* @method contract
* @param {Array} abi
* @returns {ContractFactory} new contract factory
*/
//var contract = function (abi) {
//return new ContractFactory(abi);
//};
/**
* Should be called to get access to existing contract on a blockchain
*
* @method at
* @param {Address} contract address (required)
* @param {Function} callback {optional)
* @returns {Contract} returns contract if no callback was passed,
* otherwise calls callback function (err, contract)
*/
ContractFactory.prototype.at = function (address, callback) {
var contract = new Contract(this.eth, this.abi, address);
// this functions are not part of prototype,
// because we dont want to spoil the interface
addFunctionsToContract(contract);
addEventsToContract(contract);
if (callback) {
callback(null, contract);
}
return contract;
};
/**
* Gets the data, which is data to deploy plus constructor params
*
* @method getData
*/
ContractFactory.prototype.getData = function () {
var options = {}; // required!
var args = Array.prototype.slice.call(arguments);
var last = args[args.length - 1];
if (utils.isObject(last) && !utils.isArray(last)) {
options = args.pop();
}
var bytes = encodeConstructorParams(this.abi, args);
options.data += bytes;
return options.data;
};
/**
* Should be called to create new contract instance
*
* @method Contract
* @param {Array} abi
* @param {Address} contract address
*/
var Contract = function (eth, abi, address) {
this._eth = eth;
this.transactionHash = null;
this.address = address;
this.abi = abi;
};
module.exports = ContractFactory;