/* This file is part of ethereum.js. ethereum.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. ethereum.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 ethereum.js. If not, see . */ /** * @file contract.js * @author Marek Kotewicz * @date 2014 */ var web3 = require('../web3'); 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, abi) { abi.filter(function (json) { return json.type === 'function'; }).map(function (json) { return new SolidityFunction(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, abi) { var events = abi.filter(function (json) { return json.type === 'event'; }); var All = new AllEvents(events, contract.address); All.attachToContract(contract); events.map(function (json) { return new SolidityEvent(json, contract.address); }).forEach(function (e) { e.attachToContract(contract); }); }; /** * 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 check if the contract gets properly deployed on the blockchain. * * @method checkForContractAddress * @param {Object} contract * @param {Function} callback * @returns {Undefined} */ var checkForContractAddress = function(contract, abi, callback){ var count = 0, callbackFired = false; // wait for receipt var filter = web3.eth.filter('latest', function(e){ if(!e && !callbackFired) { count++; // console.log('Checking for contract address', 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 { web3.eth.getTransactionReceipt(contract.transactionHash, function(e, receipt){ if(receipt && !callbackFired) { web3.eth.getCode(receipt.contractAddress, function(e, code){ /*jshint maxcomplexity: 5 */ if(callbackFired) return; filter.stopWatching(); callbackFired = true; if(code.length > 2) { // console.log('Contract code deployed!'); contract.address = receipt.contractAddress; // attach events and methods addFunctionsToContract(contract, abi); addEventsToContract(contract, abi); // 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 (abi) { 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 */ ContractFactory.prototype.new = function () { var _this = this; var contract = new Contract(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(); } // throw an error if there are no options var bytes = encodeConstructorParams(this.abi, args); options.data += bytes; if(callback) { // wait for the contract address adn check if the code was deployed web3.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, _this.abi, callback); } }); } else { var hash = web3.eth.sendTransaction(options); // add the transaction hash contract.transactionHash = hash; checkForContractAddress(contract, _this.abi); } return contract; }; /** * 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.abi, address); // TODO: address is required // attach functions addFunctionsToContract(contract, this.abi); addEventsToContract(contract, this.abi); if (callback) { callback(null, contract); } return contract; }; /** * Should be called to create new contract instance * * @method Contract * @param {Array} abi * @param {Address} contract address */ var Contract = function (abi, address) { this.address = address; }; module.exports = contract;