├── .gitignore ├── networks.json ├── example ├── simple.sol └── math.lib.sol ├── lib ├── eth-utils.js ├── generate.js ├── compiler.js ├── file-utils.js ├── deployer.js ├── version-utils.js ├── cli-utils.js └── migration.js ├── TODO.md ├── LICENSE ├── package.json ├── abis ├── LibFund.json └── LiveLibs.json ├── contracts ├── LibFund.sol └── LiveLibs.sol ├── cli.js ├── test └── suite.js ├── README.md ├── browser └── index.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | sandbox.js 2 | node_modules -------------------------------------------------------------------------------- /networks.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainnet": "0x5f9192599fdfb9e662d79d717f84a29f39cddd23", 3 | "morden": "0x84160eba09d96c8b6581fc8d520a7efeab63e7e4" 4 | } -------------------------------------------------------------------------------- /example/simple.sol: -------------------------------------------------------------------------------- 1 | // Hook into compilation process to resolve this import correctly 2 | import "Math"; 3 | 4 | contract simple { 5 | uint public x; 6 | 7 | function calc(uint y) { 8 | x = Math.modExp(x, y, 3); 9 | } 10 | } -------------------------------------------------------------------------------- /lib/eth-utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | blankAddress: function(address) { 3 | return address == '0x0000000000000000000000000000000000000000'; 4 | }, 5 | toAscii: function(web3, string) { 6 | return web3.toAscii(string).replace(/\0/g, ''); 7 | } 8 | }; -------------------------------------------------------------------------------- /example/math.lib.sol: -------------------------------------------------------------------------------- 1 | // via https://github.com/ethereum/dapp-bin/blob/master/library/math.sol 2 | library Math { 3 | /// @dev Computes the modular exponential (x ** k) % m. 4 | function modExp(uint x, uint k, uint m) constant returns (uint r) { 5 | r = 1; 6 | for (uint s = 1; s <= k; s *= 2) { 7 | if (k & s != 0) 8 | r = mulmod(r, x, m); 9 | x = mulmod(x, x, m); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /lib/generate.js: -------------------------------------------------------------------------------- 1 | module.exports = function(libName, abiString) { 2 | var abi = JSON.parse(abiString); 3 | var libSource = 'library '+libName+' { '; 4 | abi.forEach(function(func) { 5 | if (func.type != 'function') return 6 | var inputs = []; 7 | func.inputs.forEach(function(input) { 8 | inputs.push(input.type+' '+input.name); 9 | }); 10 | 11 | // TODO: force constant? 12 | var constant = ''; 13 | if (func.constant) constant = ' constant'; 14 | 15 | var returns = ''; 16 | if (func.outputs.length > 0) { 17 | var outputs = []; 18 | func.outputs.forEach(function(output) { 19 | outputs.push(output.type+' '+output.name); 20 | }); 21 | returns = ' returns ('+outputs.join(',')+')'; 22 | } 23 | libSource += 'function '+func.name+'('+inputs.join(',')+')'+constant+returns+';'; 24 | }); 25 | return libSource + ' }'; 26 | } 27 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * Fork browser-solidity webapp to use LiveLibs 2 | * Report on all events in the log 3 | * Start sync'ing Live Libs versions (stored on-chain, checked in client) 4 | * Try to send signed/raw transactions (via @tcoulter: "You can use the provider-engine to do translations from `sendTransaction` to `sendRawTransaction` before they hit the server.") 5 | * Script for updating newly-deployed live-lib contract with existing network data (useful for when the live-libs contract is updated) 6 | * Script for updating morden live-libs contract with live network data 7 | * PoC using serpent with live-libs 8 | * Explore whether live-libs testing needs to be incorporated 9 | * Start backing up lib data in a repo, a place for contributors to put lib source, test source, documentation, as well as a convenient place for developers to grab registry data without having to run a full node 10 | * Extract environment migration into its own repo? /via @tcoulter 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Consensys LLC, and authors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live-libs", 3 | "namespace": "consensys", 4 | "version": "0.1.5", 5 | "description": "Providing reusable Solidity libraries that are live on the Ethereum blockchain.", 6 | "dependencies": { 7 | "solc": "^0.3.2-1", 8 | "web3": "^0.15.3", 9 | "yargs": "^4.7.0" 10 | }, 11 | "devDependencies": { 12 | "chai": "^3.5.0", 13 | "mocha": "^2.4.5", 14 | "ethereumjs-testrpc": "^2.0.7" 15 | }, 16 | "main": "./index.js", 17 | "bin": { 18 | "live-libs": "./cli.js" 19 | }, 20 | "scripts": { 21 | "test": "mocha --timeout 15000 --slow 1000" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/consensys/live-libs.git" 26 | }, 27 | "license": "MIT License. Copyright Consensys LLC, and authors. All rights reserved.", 28 | "homepage": "https://github.com/consensys/live-libs", 29 | "bugs": { 30 | "url": "https://github.com/consensys/live-libs/issues" 31 | }, 32 | "author": "consensys.net", 33 | "authors": [ 34 | { 35 | "name": "Dave Hoover", 36 | "email": "dave.hoover@gmail.com", 37 | "url": "https://github.com/redsquirrel" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /lib/compiler.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | function compile(sourceDir, contractName) { 4 | var compiledContracts = compileAll(sourceDir); 5 | 6 | var output = {}; 7 | Object.keys(compiledContracts).forEach(function(contractName) { 8 | var compiled = compiledContracts[contractName]; 9 | var abi = JSON.parse(compiled.interface); 10 | var code = '0x'+compiled.bytecode; 11 | output[contractName] = {abi: abi, code: code}; 12 | }); 13 | 14 | return output; 15 | } 16 | 17 | function compileAll(sourceDir) { 18 | // Requiring this just-in-time because it's heavy 19 | var solc = require('solc'); 20 | // TODO: Investigate this https://github.com/chriseth/browser-solidity/issues/167 21 | process.removeAllListeners("uncaughtException"); 22 | 23 | var input = {}; 24 | fs.readdirSync(sourceDir).forEach(function(fileName) { 25 | if (!fileName.match(/\.sol$/)) return; 26 | var source = fs.readFileSync(sourceDir+'/'+fileName, 'utf8'); 27 | input[fileName] = source; 28 | }); 29 | 30 | var result = solc.compile({sources: input}, 1); 31 | if (result.errors) throw(new Error(result.errors)); 32 | return result.contracts; 33 | } 34 | 35 | module.exports = { compile: compile }; 36 | -------------------------------------------------------------------------------- /lib/file-utils.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var dataDirPath = process.env.HOME+'/.live-libs/'; 5 | var testRpcAddressPath = dataDirPath+'testrpc-address.txt'; 6 | var dataFilePath = dataDirPath+'download-data.json'; 7 | 8 | function readSync(fullPath) { 9 | return fs.readFileSync(fullPath, 'utf8'); 10 | } 11 | 12 | function testRpcAddressExists() { 13 | return fs.existsSync(testRpcAddressPath); 14 | } 15 | 16 | function getTestRpcAddress() { 17 | return readSync(testRpcAddressPath); 18 | } 19 | 20 | function resolvePath(relativePath) { 21 | return path.resolve(path.join(__dirname, relativePath)); 22 | } 23 | 24 | function config(o) { 25 | if (!o) o = {}; 26 | o.liveLibsABIString = readSync(resolvePath('../abis/LiveLibs.json')); 27 | o.libFundABIString = readSync(resolvePath('../abis/LibFund.json')); 28 | o.networksJSONString = readSync(resolvePath('../networks.json')); 29 | 30 | if (testRpcAddressExists()) 31 | o.testRpcAddress = getTestRpcAddress(); 32 | 33 | o.reload = function() { return config(o) }; 34 | 35 | return o; 36 | } 37 | 38 | module.exports = { 39 | config: config, 40 | dataDirPath: dataDirPath, 41 | dataFilePath: dataFilePath, 42 | testRpcAddressPath: testRpcAddressPath 43 | }; 44 | -------------------------------------------------------------------------------- /lib/deployer.js: -------------------------------------------------------------------------------- 1 | function deploy(web3, name, abi, code, callback) { 2 | deploySetup(web3, abi, code, function(err, d) { 3 | if (err) return callback(err); 4 | // TODO: figure out a better gasEstimate 5 | d.contract.new({data: code, gas: d.gasEstimate*2}, deployCallback(name, callback)); 6 | }); 7 | 8 | } 9 | 10 | // TODO: need to figure out how to DRY this up more elegantly 11 | function deployWithArg(web3, name, arg, abi, code, callback) { 12 | deploySetup(web3, abi, code, function(err, d) { 13 | if (err) return callback(err); 14 | d.contract.new(arg, {data: code, gas: d.gasEstimate*2}, deployCallback(name, callback)); 15 | }); 16 | } 17 | 18 | function deploySetup(web3, abi, code, callback) { 19 | var contract = web3.eth.contract(abi); 20 | var contractData = contract.getData({data: code}); 21 | web3.eth.estimateGas({data: contractData}, function(err, gasEstimate) { 22 | if (err) return callback(err); 23 | callback(null, { contract: contract, gasEstimate: gasEstimate }); 24 | }); 25 | } 26 | 27 | function deployCallback(name, callback) { 28 | return function(err, contract) { 29 | if (err) { 30 | console.error('While attempting to deploy '+name+': '+err); 31 | callback(err, null); 32 | } else if(contract.address) { 33 | console.log(name+' address: '+contract.address); 34 | callback(null, contract); 35 | } else { 36 | console.log(name+' transmitted, waiting for mining...'); 37 | } 38 | }; 39 | } 40 | 41 | function deployLibCode(web3, libName, contractInfo, callback) { 42 | var contract = web3.eth.contract(contractInfo.abi); 43 | //via @chriseth: https://gitter.im/ethereum/solidity?at=57278b37944fc7ba04cc53a3 44 | var constructorByteCode = "606060405260138038038082600039806000f3"; 45 | var code = '0x' + constructorByteCode + contractInfo.code.replace(/^0x/, ''); 46 | 47 | deploy(web3, libName, contractInfo.abi, code, callback); 48 | } 49 | 50 | module.exports = { 51 | deploy: deploy, 52 | deployWithArg: deployWithArg, 53 | deployLibCode: deployLibCode 54 | }; 55 | -------------------------------------------------------------------------------- /lib/version-utils.js: -------------------------------------------------------------------------------- 1 | var ethUtils = require('./eth-utils'); 2 | 3 | function latest(libName, contract, callback) { 4 | contract.getVersions(libName, function(err, rawVersions) { 5 | if (err) return callback(err); 6 | if (rawVersions.length == 0) return callback(); 7 | 8 | var ints = rawVersions.map(function(raw) { return parseInt(raw); }); 9 | var sortedInts = ints.sort(function(a, b) { return b-a; }); 10 | findLatestUnlockedVersion(sortedInts, 0, libName, contract, callback); 11 | }); 12 | } 13 | 14 | function findLatestUnlockedVersion(sortedInts, index, libName, contract, callback) { 15 | if (sortedInts.length == 0) return callback(); 16 | if (sortedInts.length == index) return callback(); 17 | 18 | var versionNum = sortedInts[index]; 19 | var version = calc(versionNum); 20 | 21 | contract.get(libName, version.num, function(err, rawLibData) { 22 | if (err) return callback(err); 23 | 24 | if (rawLibData || !ethUtils.blankAddress(rawLibData[0])) { 25 | // TODO: need a more elegant approach, we're returning version, only to re-call get() with it 26 | callback(null, version); 27 | } else { 28 | findLatestUnlockedVersion(sortedInts, index+1, libName, contract, callback); 29 | } 30 | }); 31 | } 32 | 33 | function calc(versionNum) { 34 | var major = Math.floor(versionNum / 1000000); 35 | var minor = Math.floor((versionNum % 1000000) / 1000); 36 | var patch = versionNum % 1000; 37 | return new Version(major, minor, patch); 38 | } 39 | 40 | function parse(string) { 41 | var parts = string.split('.'); 42 | return new Version(parseInt(parts[0]), parseInt(parts[1]), parseInt(parts[2])); 43 | } 44 | 45 | function Version(major, minor, patch) { 46 | this.major = major; 47 | this.minor = minor; 48 | this.patch = patch; 49 | this.string = major+'.'+minor+'.'+patch; 50 | this.num = 1000000*major + 1000*minor + patch; 51 | this.equals = function(other) { 52 | return this.num == other.num; 53 | } 54 | } 55 | 56 | module.exports = { 57 | latest: latest, 58 | calc: calc, 59 | parse: parse 60 | }; 61 | -------------------------------------------------------------------------------- /abis/LibFund.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"creator","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"o","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"}],"name":"get","outputs":[{"name":"","type":"address"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"}],"name":"addTo","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"}],"name":"isLocked","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"},{"name":"threshold","type":"uint256"},{"name":"author","type":"address"}],"name":"setThreshold","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"},{"name":"","type":"uint256"}],"name":"funds","outputs":[{"name":"author","type":"address"},{"name":"threshold","type":"uint256"},{"name":"totalValue","type":"uint256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"libName","type":"bytes32"},{"indexed":false,"name":"versionNum","type":"uint256"},{"indexed":false,"name":"threshold","type":"uint256"},{"indexed":false,"name":"author","type":"address"}],"name":"Setup","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"libName","type":"bytes32"},{"indexed":false,"name":"versionNum","type":"uint256"},{"indexed":false,"name":"threshold","type":"uint256"}],"name":"Update","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"libName","type":"bytes32"},{"indexed":false,"name":"versionNum","type":"uint256"},{"indexed":false,"name":"contributor","type":"address"},{"indexed":false,"name":"contribution","type":"uint256"},{"indexed":false,"name":"totalValue","type":"uint256"}],"name":"FundsAdded","type":"event"}] -------------------------------------------------------------------------------- /contracts/LibFund.sol: -------------------------------------------------------------------------------- 1 | contract LibFund { 2 | struct Fund { 3 | address author; 4 | uint threshold; 5 | uint totalValue; 6 | } 7 | 8 | event Setup(bytes32 indexed libName, uint versionNum, uint threshold, address author); 9 | event Update(bytes32 indexed libName, uint versionNum, uint threshold); 10 | event FundsAdded(bytes32 indexed libName, uint versionNum, address contributor, uint contribution, uint totalValue); 11 | 12 | // libName versionNum 13 | mapping (bytes32 => mapping (uint => Fund)) public funds; 14 | 15 | address public creator = msg.sender; 16 | address public owner; 17 | 18 | function setOwner(address o) { 19 | if (creator != msg.sender) throw; 20 | owner = o; // Should be the LiveLibs instance 21 | } 22 | 23 | function setThreshold(bytes32 libName, uint versionNum, uint threshold, address author) { 24 | if (author == 0) throw; 25 | 26 | // Only accept the contract owner or the library author 27 | if (owner != msg.sender && funds[libName][versionNum].author != msg.sender) 28 | throw; 29 | 30 | if (funds[libName][versionNum].author == 0) { 31 | Setup(libName, versionNum, threshold, author); 32 | funds[libName][versionNum].threshold = threshold; 33 | funds[libName][versionNum].author = author; 34 | 35 | } else { 36 | // TODO: There is no interface that allows anyone to reset threshold 37 | Update(libName, versionNum, threshold); 38 | funds[libName][versionNum].threshold = threshold; 39 | } 40 | } 41 | 42 | function addTo(bytes32 libName, uint versionNum) { 43 | if (funds[libName][versionNum].author == 0) throw; 44 | 45 | funds[libName][versionNum].totalValue += msg.value; 46 | FundsAdded(libName, versionNum, msg.sender, msg.value, funds[libName][versionNum].totalValue); 47 | 48 | if (!funds[libName][versionNum].author.send(msg.value)) 49 | throw; 50 | } 51 | 52 | function isLocked(bytes32 libName, uint versionNum) constant returns (bool) { 53 | return funds[libName][versionNum].threshold > funds[libName][versionNum].totalValue; 54 | } 55 | 56 | function get(bytes32 libName, uint versionNum) constant returns(address, uint, uint) { 57 | Fund f = funds[libName][versionNum]; 58 | return (f.author, f.threshold, f.totalValue); 59 | } 60 | 61 | function () { throw; } 62 | } 63 | -------------------------------------------------------------------------------- /lib/cli-utils.js: -------------------------------------------------------------------------------- 1 | function libInfoMessage(libInfo) { 2 | var message = 'Version:\n'; 3 | message += libInfo.version+'\n'; 4 | message += '\nAddress:\n'; 5 | message += libInfo.address+'\n'; 6 | message += '\nABI:\n'; 7 | message += libInfo.abi+'\n'; 8 | message += '\nAbstract source:\n'; 9 | message += libInfo.abstractSource()+'\n'; 10 | 11 | var resourceKeys = Object.keys(libInfo.resources); 12 | if (resourceKeys.length > 0) 13 | message += '\nResources:\n'; 14 | resourceKeys.forEach(function(key) { 15 | message += key+': '+libInfo.resources[key]+'\n'; 16 | }); 17 | 18 | if (libInfo.thresholdWei > 0) { 19 | message += '\nUnlocked at (wei):\n'; 20 | message += libInfo.thresholdWei+'\n'; 21 | } 22 | message += '\nContributions (wei):\n'; 23 | message += libInfo.totalValue+'\n'; 24 | return message; 25 | } 26 | 27 | function eventMessage(log) { 28 | var func = EventMessage[log.type]; 29 | 30 | var message; 31 | if (func) { 32 | message = func(log); 33 | } else { 34 | message = 'not yet implemented.'; 35 | } 36 | 37 | return toDateTimeString(log.time)+' '+log.type+'! '+message; 38 | } 39 | 40 | var EventMessage = { 41 | NewLib: function(log) { 42 | return 'Registered by owner: '+log.args.owner; 43 | }, 44 | NewVersion: function(log) { 45 | var message = log.args.version; 46 | if (log.args.thresholdWei > 0) { 47 | message += ', threshold: '+log.args.thresholdWei.toString(); 48 | } 49 | return message; 50 | }, 51 | NewResource: function(log) { 52 | return log.args.version+' has '+log.args.key+': '+log.args.resourceURI; 53 | } 54 | }; 55 | 56 | function toDateTimeString(time){ 57 | function pad(n){return n<10 ? '0'+n : n;} 58 | var d = new Date(time*1000); 59 | return d.getUTCFullYear()+'-' 60 | + pad(d.getUTCMonth()+1)+'-' 61 | + pad(d.getUTCDate())+'T' 62 | + pad(d.getUTCHours())+':' 63 | + pad(d.getUTCMinutes())+':' 64 | + pad(d.getUTCSeconds())+'Z'; 65 | } 66 | 67 | function parseResourceURIs(resourceuriARGV) { 68 | var resources = {}; 69 | if (resourceuriARGV) { 70 | if (typeof resourceuriARGV === 'string') { 71 | merge(resources, resourceuriARGV); 72 | } else { 73 | resourceuriARGV.forEach(function(stringPair) { 74 | merge(resources, stringPair); 75 | }); 76 | } 77 | } 78 | return resources; 79 | } 80 | 81 | function merge(resources, string) { 82 | var pair = string.split('='); 83 | resources[pair[0]] = pair[1]; 84 | } 85 | 86 | module.exports = { 87 | libInfoMessage: libInfoMessage, 88 | eventMessage: eventMessage, 89 | parseResourceURIs: parseResourceURIs 90 | }; 91 | -------------------------------------------------------------------------------- /abis/LiveLibs.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"","type":"bytes32"},{"name":"","type":"uint256"}],"name":"versionMap","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"creator","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"ownerMap","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"}],"name":"get","outputs":[{"name":"","type":"address"},{"name":"","type":"string"},{"name":"","type":"bytes32[]"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"},{"name":"key","type":"bytes32"},{"name":"uri","type":"string"}],"name":"registerResource","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"names","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"lf","type":"address"}],"name":"setLibFund","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"allNames","outputs":[{"name":"","type":"bytes32[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"libName","type":"bytes32"}],"name":"getVersions","outputs":[{"name":"","type":"uint256[]"}],"type":"function"},{"constant":false,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"},{"name":"a","type":"address"},{"name":"abi","type":"string"},{"name":"thresholdWei","type":"uint256"}],"name":"register","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"libName","type":"bytes32"},{"name":"newOwner","type":"address"}],"name":"transferLibOwnership","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"libName","type":"bytes32"},{"name":"versionNum","type":"uint256"},{"name":"key","type":"bytes32"}],"name":"getResource","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"},{"name":"","type":"uint256"}],"name":"versions","outputs":[{"name":"a","type":"address"},{"name":"abi","type":"string"},{"name":"author","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"libFund","outputs":[{"name":"","type":"address"}],"type":"function"},{"inputs":[{"name":"lf","type":"address"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"libName","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewLib","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"libName","type":"bytes32"},{"indexed":false,"name":"versionNum","type":"uint256"},{"indexed":false,"name":"thresholdWei","type":"uint256"}],"name":"NewVersion","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"libName","type":"bytes32"},{"indexed":false,"name":"versionNum","type":"uint256"},{"indexed":false,"name":"key","type":"bytes32"},{"indexed":false,"name":"resourceURI","type":"string"}],"name":"NewResource","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"libName","type":"bytes32"},{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnershipChange","type":"event"}] -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var argv = require('yargs').option('address', {type: 'string'}).argv; 5 | 6 | var Web3 = require('web3'); 7 | var web3 = new Web3(); 8 | 9 | var rpcURL = argv.rpcurl || 'http://0.0.0.0:8545'; 10 | web3.setProvider(new web3.providers.HttpProvider(rpcURL)); 11 | 12 | var migration = require('./lib/migration'); 13 | var versionUtils = require('./lib/version-utils'); 14 | var fileUtils = require('./lib/file-utils'); 15 | var cliUtils = require('./lib/cli-utils'); 16 | 17 | var LiveLibs = require('./index'); 18 | 19 | var liveLibs = new LiveLibs(web3, fileUtils.config({verbose:true})); 20 | 21 | var cmd = argv._[0]; 22 | var libName = argv._[1]; 23 | var version = argv.v || argv.version; 24 | 25 | if (cmd == "get") { 26 | liveLibs.get(libName, version).then(function(libInfo) { 27 | console.log(cliUtils.libInfoMessage(libInfo)); 28 | }).catch(function(err) { 29 | console.error(err); 30 | }); 31 | } 32 | 33 | if (cmd == "log") { 34 | liveLibs.log(libName).then(function(logs) { 35 | console.log('Event log for '+libName+'...'); 36 | logs.forEach(function(log) { 37 | console.log(cliUtils.eventMessage(log)); 38 | }); 39 | }).catch(function(err) { 40 | console.error(err); 41 | }); 42 | } 43 | 44 | if (cmd == "register") { 45 | web3.eth.defaultAccount = argv.account || web3.eth.coinbase; 46 | console.log('Attempting to register '+libName+', please wait for mining.'); 47 | var resources = cliUtils.parseResourceURIs(argv.resourceuri); 48 | liveLibs.register(libName, argv.version, argv.address, argv.abi, resources, argv.unlockat).catch(function(err) { 49 | console.log(err); 50 | }); 51 | } 52 | 53 | if (cmd == "contribute") { 54 | web3.eth.defaultAccount = argv.account || web3.eth.coinbase; 55 | console.log('Attempting to contribute to '+libName+', please wait for mining.'); 56 | liveLibs.contributeTo(libName, version, argv.wei).catch(function(err) { 57 | console.log(err); 58 | }); 59 | } 60 | 61 | if (cmd == "env") { 62 | liveLibs.env(function(err, env) { 63 | if (err) throw(err); 64 | console.log(env); 65 | }); 66 | } 67 | 68 | 69 | if (cmd == "download") { 70 | liveLibs.findContract(function(err, contract) { 71 | if (err) return console.error(err); 72 | migration.downloadData(contract, web3); 73 | }); 74 | } 75 | 76 | if (cmd == "deploy") { 77 | web3.eth.defaultAccount = argv.account || web3.eth.coinbase; 78 | liveLibs.env(function(err, env) { 79 | var noInstanceFound = !!err; 80 | 81 | if (noInstanceFound || env == 'testrpc') { 82 | migration.deploy(web3, true).then(function() { 83 | return migration.registerAll(web3, liveLibs); 84 | }).catch(function(err) { 85 | console.log(err); 86 | }); 87 | } else { 88 | console.log('Deploy not available for '+env); 89 | } 90 | }); 91 | } 92 | 93 | if (cmd == "build-browser") { 94 | var inlineConfig = JSON.stringify(fileUtils.config()); 95 | var source = 'var LiveLibs = require("../index.js");\n'+ 96 | 'LiveLibs.config = '+inlineConfig+'\n'+ 97 | 'module.exports = LiveLibs;\n'; 98 | fs.writeFileSync('./browser/index.js', source); 99 | } 100 | 101 | // TODO: Handle case where cmd matches nothing 102 | // TODO: Handle case where extra/ignored stuff is passed in (such as when a flag is forgotten) 103 | -------------------------------------------------------------------------------- /contracts/LiveLibs.sol: -------------------------------------------------------------------------------- 1 | import "LibFund.sol"; 2 | 3 | contract LiveLibs { 4 | struct Version { 5 | address a; 6 | string abi; 7 | address author; 8 | bytes32[] resourceKeys; 9 | mapping (bytes32 => string) resources; 10 | } 11 | 12 | event NewLib(bytes32 indexed libName, address owner); 13 | event NewVersion(bytes32 indexed libName, uint versionNum, uint thresholdWei); 14 | event NewResource(bytes32 indexed libName, uint versionNum, bytes32 key, string resourceURI); 15 | event OwnershipChange(bytes32 indexed libName, address oldOwner, address newOwner); 16 | 17 | bytes32[] public names; 18 | 19 | // libName versionNum 20 | mapping (bytes32 => mapping (uint => Version)) public versions; 21 | 22 | // Allows people to grab all the versions for a specific lib. 23 | mapping (bytes32 => uint[]) public versionMap; 24 | 25 | // Helps enforce lib ownership 26 | mapping (bytes32 => address) public ownerMap; 27 | 28 | LibFund public libFund; 29 | address public creator = msg.sender; 30 | 31 | function LiveLibs(LibFund lf) { 32 | setLibFund(lf); 33 | } 34 | 35 | modifier onlyLibOwner(bytes32 libName) { 36 | if (ownerMap[libName] != msg.sender) throw; 37 | _ 38 | } 39 | 40 | // This provides flexibility to upgrade/migrate LibFund 41 | function setLibFund(LibFund lf) { 42 | if (creator != msg.sender) throw; 43 | libFund = lf; 44 | } 45 | 46 | function register(bytes32 libName, uint versionNum, address a, string abi, uint thresholdWei) { 47 | if (ownerMap[libName] == 0) { 48 | ownerMap[libName] = msg.sender; 49 | names.push(libName); 50 | NewLib(libName, msg.sender); 51 | } else if (ownerMap[libName] != msg.sender) { 52 | throw; // Once a lib has an owner, only they can release 53 | } 54 | 55 | if (versions[libName][versionNum].a == 0) { 56 | versionMap[libName].push(versionNum); 57 | Version v = versions[libName][versionNum]; 58 | v.a = a; 59 | v.abi = abi; 60 | v.author = msg.sender; 61 | libFund.setThreshold(libName, versionNum, thresholdWei, msg.sender); 62 | NewVersion(libName, versionNum, thresholdWei); 63 | } 64 | } 65 | 66 | function registerResource(bytes32 libName, uint versionNum, bytes32 key, string uri) { 67 | Version v = versions[libName][versionNum]; 68 | if (!stringsEqual(v.resources[key], "")) throw; // TODO: need to test this 69 | v.resourceKeys.push(key); 70 | v.resources[key] = uri; 71 | NewResource(libName, versionNum, key, uri); 72 | } 73 | 74 | // TODO: implement in CLI 75 | function transferLibOwnership(bytes32 libName, address newOwner) onlyLibOwner(libName) { 76 | OwnershipChange(libName, ownerMap[libName], newOwner); 77 | ownerMap[libName] = newOwner; 78 | } 79 | 80 | function get(bytes32 libName, uint versionNum) constant returns (address, string, bytes32[], uint, uint) { 81 | Version v = versions[libName][versionNum]; 82 | if (v.a == 0 || libFund.isLocked(libName, versionNum)) return; 83 | var (_, threshold, totalValue) = libFund.funds(libName, versionNum); 84 | return (v.a, v.abi, v.resourceKeys, threshold, totalValue); 85 | } 86 | 87 | function getResource(bytes32 libName, uint versionNum, bytes32 key) constant returns (string) { 88 | Version v = versions[libName][versionNum]; 89 | return v.resources[key]; 90 | } 91 | 92 | function getVersions(bytes32 libName) constant returns (uint[]) { 93 | return versionMap[libName]; 94 | } 95 | 96 | function allNames() constant returns (bytes32[]) { 97 | return names; 98 | } 99 | 100 | // Extract to library! :) 101 | function stringsEqual(string storage _a, string memory _b) internal returns (bool) { 102 | bytes storage a = bytes(_a); 103 | bytes memory b = bytes(_b); 104 | if (a.length != b.length) 105 | return false; 106 | for (uint i = 0; i < a.length; i ++) 107 | if (a[i] != b[i]) 108 | return false; 109 | return true; 110 | } 111 | 112 | function() { throw; } 113 | } 114 | -------------------------------------------------------------------------------- /lib/migration.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var compiler = require('./compiler'); 4 | var deployer = require('./deployer'); 5 | var versionUtils = require('./version-utils'); 6 | var fileUtils = require('./file-utils'); 7 | var ethUtils = require('./eth-utils'); 8 | 9 | function downloadData(contractInstance, web3) { 10 | ensureHiddenDirectory(); 11 | var dataToStore = extractRegistryData(contractInstance, web3); 12 | if (fs.existsSync(fileUtils.dataFilePath)) 13 | fs.unlinkSync(fileUtils.dataFilePath); 14 | console.log("Writing data"); 15 | fs.writeFileSync(fileUtils.dataFilePath, JSON.stringify(dataToStore)); 16 | } 17 | 18 | function ensureHiddenDirectory() { 19 | if (!fs.existsSync(fileUtils.dataDirPath)) 20 | fs.mkdirSync(fileUtils.dataDirPath); 21 | } 22 | 23 | function extractRegistryData(contractInstance, web3) { 24 | var dataToStore = {}; 25 | contractInstance.allNames().forEach(function(rawName) { 26 | var plainName = ethUtils.toAscii(web3, rawName); 27 | contractInstance.getVersions(plainName).forEach(function(rawVersion) { 28 | var v = versionUtils.calc(rawVersion); 29 | console.log("Pulling " + plainName + " " + v.string); 30 | var libData = contractInstance.get(plainName, v.num); 31 | if (libData[1]) { // easiest way to detect a miss (no abi) 32 | dataToStore[plainName] = { 33 | version: v.string, 34 | address: libData[0], 35 | abi: JSON.parse(libData[1]), 36 | code: web3.eth.getCode(libData[0]) 37 | }; 38 | } else { 39 | console.error('Skipping '+plainName+' '+v.string+' because it is locked.'); 40 | } 41 | }); 42 | }); 43 | return dataToStore; 44 | } 45 | 46 | // TODO: This should be split up so it's possible to deploy LibFund independently 47 | function deploy(web3, onTestrpc) { 48 | var output = compiler.compile('./contracts'); 49 | 50 | return new Promise(function(resolve, reject) { 51 | deployer.deploy(web3, 'LibFund', output.LibFund.abi, output.LibFund.code, function(err, contract) { 52 | if (err) { 53 | return reject(err); 54 | } 55 | 56 | resolve(contract); 57 | }); 58 | }).then(function(libFundContract) { 59 | return new Promise(function(resolve, reject) { 60 | deployer.deployWithArg(web3, 'LiveLibs', libFundContract.address, output.LiveLibs.abi, output.LiveLibs.code, function(err, contract) { 61 | if (err) { 62 | return reject(err); 63 | } 64 | 65 | if (onTestrpc) { 66 | ensureHiddenDirectory(); 67 | fs.writeFileSync(fileUtils.testRpcAddressPath, contract.address); 68 | console.log('Stored contract address.'); 69 | } 70 | 71 | resolve({libFundContract: libFundContract, liveLibsAddress: contract.address}); 72 | }); 73 | }); 74 | }).then(function(data) { 75 | return new Promise(function(resolve, reject) { 76 | data.libFundContract.setOwner( 77 | data.liveLibsAddress, 78 | {value: 0, gas: 2000000}, // TODO: need to estimate this 79 | function(err, txHash) { 80 | if (err) { 81 | reject(err); 82 | } else { 83 | resolve(txHash); 84 | } 85 | } 86 | ); 87 | }); 88 | }).then(function(txHash) { 89 | return new Promise(function(resolve, reject) { 90 | var interval = setInterval(function() { 91 | web3.eth.getTransactionReceipt(txHash, function(err, receipt) { 92 | if (err != null) { 93 | clearInterval(interval); 94 | reject(err); 95 | } 96 | if (receipt != null) { 97 | console.log('Set owner of LibFund!'); 98 | resolve(); 99 | clearInterval(interval); 100 | } 101 | }); 102 | }, 500); 103 | }); 104 | }); 105 | } 106 | 107 | function registerAll(web3, liveLibs) { 108 | if (!fs.existsSync(fileUtils.dataFilePath)) 109 | throw(Error('No data file detected!')); 110 | 111 | var data = fs.readFileSync(fileUtils.dataFilePath, 'utf8'); 112 | var jsonData = JSON.parse(data); 113 | var promises = []; 114 | 115 | Object.keys(jsonData).forEach(function(libName) { 116 | console.log("Deploying "+libName); 117 | promises.push( 118 | new Promise(function(resolve, reject) { 119 | deployer.deployLibCode(web3, libName, jsonData[libName], function(error, contract) { 120 | if (error) { 121 | console.error('Problem deploying '+libName+': '+error); 122 | reject(error); 123 | } else { 124 | resolve(contract); 125 | } 126 | }); 127 | }).then(function(contract) { 128 | return liveLibs.register( 129 | libName, 130 | jsonData[libName].version, 131 | contract.address, 132 | JSON.stringify(jsonData[libName].abi, 133 | 0 // This is the lib thresholdWei. 134 | // We need to set it at 0 since we have no way to force totalValue in LibFund. 135 | // Also, this migration process is meant for pulling code from the live network 136 | // down into morden and testrpc environments (not the other way around). 137 | )); 138 | }) 139 | ); 140 | }); 141 | 142 | return Promise.all(promises); 143 | } 144 | 145 | module.exports = { 146 | downloadData: downloadData, 147 | deploy: deploy, 148 | registerAll: registerAll 149 | }; -------------------------------------------------------------------------------- /test/suite.js: -------------------------------------------------------------------------------- 1 | var Web3 = require('web3'); 2 | var web3 = new Web3(); 3 | 4 | var TestRPC = require('ethereumjs-testrpc'); 5 | var fileUtils = require('../lib/file-utils'); 6 | var LiveLibs = require('../index.js'); 7 | var liveLibs; // we need to define this after deployment 8 | 9 | var migration = require('../lib/migration'); 10 | 11 | var assert = require('chai').assert; 12 | 13 | var accountConfig = [ 14 | {balance: 20000000}, 15 | {balance: 20000000} 16 | ]; 17 | web3.setProvider(TestRPC.provider({accounts: accountConfig})); 18 | 19 | var accounts; 20 | 21 | describe('Live Libs', function() { 22 | before(function(done) { 23 | setAccounts().then(function() { 24 | return migration.deploy(web3, true); // TODO: maybe silence these logs? 25 | }).then(function() { 26 | liveLibs = new LiveLibs(web3, fileUtils.config({testing:true})); 27 | }).then(done).catch(done); 28 | }); 29 | 30 | var fakeAddress = '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826'; 31 | var fakeAbi = '[]'; 32 | 33 | it('detects when name is too long', function() { 34 | // names can only be 32 bytes 35 | var longName = 'abcdefghijklmnopqrstuvwxyz1234567'; 36 | var truncName = 'abcdefghijklmnopqrstuvwxyz123456'; 37 | 38 | return liveLibs.register(longName, '0.1.2', fakeAddress, fakeAbi).catch(function(error) { 39 | assert.isDefined(error, 'should have detected name was too long'); 40 | liveLibs.allNames(function(err, allNames) { 41 | assert.isNull(err); 42 | assert.notInclude(allNames, truncName); 43 | }); 44 | }); 45 | }); 46 | 47 | it('gracefully handles a get miss', function() { 48 | return liveLibs.get('baz').then(function() { 49 | assert.fail('Should have rejected missing libName'); 50 | }).catch(function(e) { 51 | assert.equal(e, 'No versions of baz found'); 52 | }); 53 | }); 54 | 55 | it('gets what it sets', function() { 56 | var libName = 'foo'; 57 | var fakeDocs = 'http://example.com/docs'; 58 | return liveLibs.register(libName, '0.1.2', fakeAddress, fakeAbi, {docs: fakeDocs}, 0).then(function() { 59 | 60 | return liveLibs.get(libName).then(function(libInfo) { 61 | assert.equal(libInfo.address, fakeAddress); 62 | assert.equal(libInfo.version, '0.1.2'); 63 | assert.equal(libInfo.abi, fakeAbi); 64 | assert.equal(libInfo.resources.docs, fakeDocs); 65 | assert.equal(libInfo.thresholdWei, 0); 66 | assert.equal(libInfo.totalValue, 0); 67 | }); 68 | 69 | }); 70 | }); 71 | 72 | it('gets specific and latest versions', function() { 73 | var libName = 'bar'; 74 | return liveLibs.register(libName, '0.1.3', fakeAddress, fakeAbi).then(function() { 75 | return liveLibs.register(libName, '0.1.2', fakeAddress, fakeAbi); 76 | }).then(function() { 77 | return liveLibs.get(libName, '0.1.2').then(function(libInfo) { 78 | assert.equal(libInfo.version, '0.1.2'); 79 | }); 80 | }).then(function() { 81 | return liveLibs.get(libName).then(function(libInfo) { 82 | assert.equal(libInfo.version, '0.1.3'); 83 | }); 84 | }); 85 | }); 86 | 87 | it('does not allow version data to change', function() { 88 | var libName = 'dhh'; 89 | var updatedAbi = '[foo]'; 90 | return liveLibs.register(libName, '7.7.7', fakeAddress, fakeAbi).then(function() { 91 | return liveLibs.register(libName, '7.7.7', fakeAddress, updatedAbi); 92 | }).then(function() { 93 | return liveLibs.get(libName, '7.7.7').then(function(libInfo) { 94 | assert.equal(libInfo.abi, fakeAbi); 95 | }); 96 | }); 97 | }); 98 | 99 | it('locks unfunded libraries', function() { 100 | var libName = 'abc'; 101 | return liveLibs.register(libName, '0.1.2', fakeAddress, fakeAbi, {}, 1000).then(function() { 102 | return liveLibs.get(libName).then(function() { 103 | assert.fail('Should have rejected locked libName'); 104 | }).catch(function(e) { 105 | assert.equal(e, 'abc 0.1.2 is locked'); 106 | }); 107 | }); 108 | }); 109 | 110 | it('unlocks funded libraries', function() { 111 | var libName = 'xyz'; 112 | var version = '30.1.2'; 113 | var startingBalance; 114 | web3.eth.defaultAccount = accounts[1]; 115 | 116 | return liveLibs.register(libName, version, fakeAddress, fakeAbi, {}, 1000).then(function() { 117 | return getBalanceFor(accounts[1]); 118 | }).then(function(balance) { 119 | startingBalance = balance; 120 | web3.eth.defaultAccount = accounts[0]; 121 | return liveLibs.contributeTo(libName, version, 250); 122 | }).then(function() { 123 | return liveLibs.contributeTo(libName, version, 750); 124 | }).then(function() { 125 | return liveLibs.get(libName).then(function(libInfo) { 126 | assert.isDefined(libInfo); 127 | assert.equal(libInfo.address, fakeAddress); 128 | assert.equal(libInfo.totalValue, 1000); 129 | }); 130 | }).then(function() { 131 | return getBalanceFor(accounts[1]); 132 | }).then(function(balance) { 133 | assert.equal(balance - startingBalance, 1000); 134 | }); 135 | }); 136 | }); 137 | 138 | function setAccounts() { 139 | return new Promise(function(resolve, reject) { 140 | web3.eth.getAccounts(function(err, _accounts) { 141 | if (err) { 142 | reject(err); 143 | } else { 144 | accounts = _accounts; 145 | web3.eth.defaultAccount = accounts[0]; 146 | resolve(); 147 | } 148 | }); 149 | }); 150 | } 151 | 152 | function getBalanceFor(account) { 153 | return new Promise(function(resolve, reject) { 154 | web3.eth.getBalance(account, function(err, balance) { 155 | if (err) return reject(err); 156 | resolve(balance); 157 | }); 158 | }); 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live Libs for Solidity 2 | 3 | Providing reusable Solidity libraries that are live on the Ethereum blockchain. 4 | 5 | ## Install 6 | 7 | $ npm install -g live-libs 8 | 9 | ## Setting up your environment 10 | 11 | You will need to be connected to an Ethereum network (testrpc, morden, mainnet, etc) when interacting with live-libs. Follow [these instructions](https://ethereum.gitbooks.io/frontier-guide/content/getting_a_client.html) to install an Ethereum node. The live-libs command line interface defaults to `http://localhost:8545` to reach the Ethereum node's RPC interface. You can override this with `--rpcurl https://example:8765`. 12 | 13 | ## Getting a library's information 14 | 15 | It's important to note that live-libs does not store source code, but it does store a library's [ABI](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI). In order to compile contracts that use live-libs, you'll need to provide the [library interface](https://github.com/ethereum/wiki/wiki/Solidity-Features#interface-contracts) to the compiler. 16 | 17 | __Note__: If you don't specify the version, the latest version of the library is used. 18 | 19 | From the command line: 20 | 21 | $ live-libs get LibName [--version 3.5.8] 22 | Version: 23 | 3.5.8 24 | 25 | Address: 26 | 0x3f4845... 27 | 28 | ABI: 29 | [{"constant":false,"inputs":...}] 30 | 31 | Abstract source: 32 | library LibName {...} 33 | 34 | Via Javascript: 35 | 36 | var web3 = ... // setup web3 object 37 | 38 | var LiveLibs = require('live-libs'); 39 | var liveLibs = new LiveLibs(web3); 40 | var libName = "Foo"; 41 | var version = "3.5.8"; // optional 42 | liveLibs.get(libName, version).then(function(libInfo) { 43 | console.log(libInfo.version); 44 | console.log(libInfo.address); 45 | console.log(libInfo.abi); 46 | console.log(libInfo.abstractSource()); 47 | console.log(libInfo.docURL); 48 | console.log(libInfo.sourceURL); 49 | }); 50 | 51 | ## Getting a library's event log 52 | 53 | From the command line: 54 | 55 | $ live-libs log LibName 56 | Event log for Foo... 57 | 2016-04-17T12:34:56Z NewLib! Registered by owner: 0x28bc5a7226a82053aa29c0806c380cfb6a82bb0c 58 | 2016-04-17T12:34:56Z NewVersion! 0.0.1 59 | 2016-05-17T17:27:37Z NewVersion! 0.0.2 60 | 61 | Via Javascript: 62 | 63 | liveLibs.log(libName).then(function(events) { 64 | events.forEach(function(event) { 65 | console.log(event.type); 66 | console.log(event.time); 67 | console.log(event.args); 68 | }); 69 | }); 70 | 71 | ## Working with accounts 72 | 73 | `get` and `log` do not require a transaction, so those calls require no account nor cost any gas. (They use [eth_call](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_call).) `register` and `contribute` require a transaction, costing you gas, and therefore require an unlocked account. (These commands use [eth_sendTransaction](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendtransaction)). 74 | 75 | If not specified via `--account` on the command line, `web3.eth.coinbase` will be used as your account. Also, you will need to unlock the account in order to use it to spend Ether on transactions. For the command line, you can read the [geth docs](https://github.com/ethereum/go-ethereum/wiki/Managing-your-accounts). For JavaScript, assuming you've started geth with `--rpcapi "eth,web3,personal"`, you can call `web3.eth.personal.unlockAccount(address, password, durationSeconds)` to unlock it. ([source](https://github.com/ethereum/web3.js/blob/master/lib/web3/methods/personal.js)) 76 | 77 | ## How to register a live library 78 | 79 | From the command line: 80 | 81 | $ live-libs register YourLibName --version 3.5.8 --address 0x45e2... --abi '[...]' \ 82 | [--resourceuri docs=http://example.com/docs] [--resourceuri source=http://example.com/source/lib.sol] 83 | 84 | __Warning:__ There is no way to remove your library. Once it's live, it's live forever. 85 | 86 | __Warning:__ This software is under active development and the live-libs registries (morden and mainnet) will be abandoned without warning. (Other than this warning.) 87 | 88 | ## Setting up your testrpc environment 89 | 90 | Running your tests against [testrpc](https://github.com/ethereumjs/testrpc) is a standard way to speed up your development process. In order to execute the live-libs libraries on your testrpc node, you'll need to deploy the live-libs contract(s) and import the live-libs data. This will require a two-step process: 91 | 92 | 1. Download the live-libs data from morden (you will need to run a node that connects to that network). 93 | 2. Deploy the live-libs contract(s) and data to testrpc. 94 | 95 | From the command line: 96 | 97 | $ # running a morden node 98 | $ live-libs download 99 | $ # switch to testrpc 100 | $ live-libs deploy 101 | 102 | __Note__: If you restart your testrpc server, you'll need to re-deploy live-libs, but you won't need to re-download the data. 103 | 104 | ## Funding your library 105 | 106 | Library authors can receive ether for registering their libraries with live-libs. When an author registers a library, they can optionally pass in a `--unlockat` flag, followed by a number of wei. This will "lock" the specified version of the library until the specified amount of wei has been contributed. For example: 107 | 108 | $ live-libs register YourLibName --version 3.5.8 --address 0x45e2... --abi '[...]' --unlockat 10000000 109 | 110 | This will register `YourLibName 3.5.8` in live-libs, but when you look it up, it will not be available. To unlock it, people need to contribute ether, which gets immediatly redirected to the Ethereum account that registered this version. 111 | 112 | If someone wants to contribute ether in order to unlock a version of a library, they can: 113 | 114 | $ live-libs contribute LibName --version 3.5.8 --wei 200000 115 | 116 | __Note__: The idea for this came from Vitalik's story at 2:30 of [this podcast](http://futurethinkers.org/vitalik-buterin-ethereum-decentralized-future/). 117 | 118 | ## Where is this headed? 119 | 120 | * [TODO](https://github.com/ConsenSys/live-libs/blob/master/TODO.md): a few weeks out 121 | * [Roadmap](https://github.com/ConsenSys/live-libs/wiki/Roadmap): a few months out 122 | 123 | ## Author 124 | 125 | Dave Hoover 126 | -------------------------------------------------------------------------------- /browser/index.js: -------------------------------------------------------------------------------- 1 | var LiveLibs = require("../index.js"); 2 | LiveLibs.config = {"liveLibsABIString":"[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"versionMap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"creator\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"ownerMap\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"bytes32[]\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"},{\"name\":\"key\",\"type\":\"bytes32\"},{\"name\":\"uri\",\"type\":\"string\"}],\"name\":\"registerResource\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"names\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"lf\",\"type\":\"address\"}],\"name\":\"setLibFund\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"allNames\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32[]\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"}],\"name\":\"getVersions\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"},{\"name\":\"a\",\"type\":\"address\"},{\"name\":\"abi\",\"type\":\"string\"},{\"name\":\"thresholdWei\",\"type\":\"uint256\"}],\"name\":\"register\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferLibOwnership\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"},{\"name\":\"key\",\"type\":\"bytes32\"}],\"name\":\"getResource\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"versions\",\"outputs\":[{\"name\":\"a\",\"type\":\"address\"},{\"name\":\"abi\",\"type\":\"string\"},{\"name\":\"author\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"libFund\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"inputs\":[{\"name\":\"lf\",\"type\":\"address\"}],\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"libName\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"NewLib\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"libName\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"versionNum\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"thresholdWei\",\"type\":\"uint256\"}],\"name\":\"NewVersion\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"libName\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"versionNum\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"key\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"resourceURI\",\"type\":\"string\"}],\"name\":\"NewResource\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"libName\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipChange\",\"type\":\"event\"}]","libFundABIString":"[{\"constant\":true,\"inputs\":[],\"name\":\"creator\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"o\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"}],\"name\":\"addTo\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"}],\"name\":\"isLocked\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"libName\",\"type\":\"bytes32\"},{\"name\":\"versionNum\",\"type\":\"uint256\"},{\"name\":\"threshold\",\"type\":\"uint256\"},{\"name\":\"author\",\"type\":\"address\"}],\"name\":\"setThreshold\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"funds\",\"outputs\":[{\"name\":\"author\",\"type\":\"address\"},{\"name\":\"threshold\",\"type\":\"uint256\"},{\"name\":\"totalValue\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"libName\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"versionNum\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"threshold\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"author\",\"type\":\"address\"}],\"name\":\"Setup\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"libName\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"versionNum\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"threshold\",\"type\":\"uint256\"}],\"name\":\"Update\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"libName\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"versionNum\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"contributor\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"contribution\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"totalValue\",\"type\":\"uint256\"}],\"name\":\"FundsAdded\",\"type\":\"event\"}]","networksJSONString":"{\n \"mainnet\": \"0x5f9192599fdfb9e662d79d717f84a29f39cddd23\",\n \"morden\": \"0x84160eba09d96c8b6581fc8d520a7efeab63e7e4\"\n}","testRpcAddress":"0x84160eba09d96c8b6581fc8d520a7efeab63e7e4"} 3 | module.exports = LiveLibs; 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var generateAbstractLib = require('./lib/generate'); 4 | var versionUtils = require('./lib/version-utils'); 5 | var ethUtils = require('./lib/eth-utils'); 6 | 7 | function LiveLibs(web3, config) { 8 | config = buildConfig(config); 9 | 10 | var testing = config.testing; 11 | var logger = getLogger(config.verbose); 12 | 13 | this.env = function(callback) { 14 | findContract(function(err, contract) { 15 | if (err) return callback(err); 16 | if (contract) { 17 | callback(null, contract.env); 18 | } else { 19 | callback(Error('Unable to detect environment!')); 20 | } 21 | }); 22 | } 23 | 24 | this.get = function(libName, version) { 25 | return new Promise(function(resolve, reject) { 26 | if (version) { 27 | resolve(versionUtils.parse(version)); 28 | } else { 29 | findContract(function(error, contract) { 30 | if (error) return reject(error); 31 | versionUtils.latest(libName, contract, function(err, v) { 32 | if (err) return reject(err); 33 | if (!v) return reject('No versions of '+libName+' found'); 34 | resolve(v); 35 | }); 36 | }); 37 | } 38 | }).then(function(v) { 39 | return new Promise(function(resolve, reject) { 40 | findContract(function(error, contract) { 41 | if (error) return reject(error); 42 | contract.get(libName, v.num, function(err, rawLibData) { 43 | if (err) return reject(err); 44 | if (ethUtils.blankAddress(rawLibData[0])) { 45 | reject(libName+' '+v.string+' is locked'); 46 | } else { 47 | resolve({ 48 | v: v, 49 | address: rawLibData[0], 50 | abi: rawLibData[1], 51 | rawResourceKeys: rawLibData[2], 52 | thresholdWei: rawLibData[3].toString(), 53 | totalValue: rawLibData[4].toString(), 54 | abstractSource: function() { return generateAbstractLib(libName, rawLibData[1]); } 55 | }); 56 | } 57 | }); 58 | }); 59 | }); 60 | }).then(function(libInfo) { 61 | return new Promise(function(resolve, reject) { 62 | var promises = []; 63 | var resources = {}; 64 | libInfo.rawResourceKeys.forEach(function(rawKey) { 65 | var promise = new Promise(function(resolve, reject) { 66 | var key = ethUtils.toAscii(web3, rawKey); 67 | findContract(function(error, contract) { 68 | if (error) return reject(error); 69 | contract.getResource(libName, libInfo.v.num, key, function(err, uri) { 70 | if (err) return reject(err); 71 | resources[key] = uri; 72 | resolve(); 73 | }); 74 | }); 75 | }); 76 | promises.push(promise); 77 | }); 78 | return Promise.all(promises).then(function() { 79 | libInfo.version = libInfo.v.string; 80 | delete libInfo.v; 81 | delete libInfo.rawResourceKeys; 82 | libInfo.resources = resources; 83 | resolve(libInfo); 84 | }); 85 | }); 86 | }); 87 | }; 88 | 89 | this.log = function(libName) { 90 | return new Promise(function(resolve, reject) { 91 | filterEventsBy(libName, function(error, results) { 92 | if (error) return reject(error); 93 | return resolve(results); 94 | }); 95 | }).then(function(rawEvents) { 96 | var events = []; 97 | var promises = []; 98 | rawEvents.forEach(function(raw) { 99 | delete raw.args.libName; // since we're already filtering by name 100 | if (raw.args.versionNum) { 101 | raw.args.version = versionUtils.calc(raw.args.versionNum).string; 102 | delete raw.args.versionNum; 103 | } 104 | if (raw.args.key) { 105 | raw.args.key = ethUtils.toAscii(web3, raw.args.key); 106 | } 107 | 108 | var event = {type: raw.event, args: raw.args}; 109 | events.push(event); 110 | 111 | var promise = new Promise(function(resolve, reject) { 112 | web3.eth.getBlock(raw.blockNumber, function(err, block) { 113 | if (err) return reject(err); 114 | event.time = block.timestamp; 115 | resolve(); 116 | }); 117 | }); 118 | promises.push(promise); 119 | }); 120 | return Promise.all(promises).then(function() { return events; }); 121 | }); 122 | }; 123 | 124 | this.register = function(libName, version, address, abiString, resources, thresholdWei) { 125 | var parsedVersion; 126 | if (version) { 127 | parsedVersion = versionUtils.parse(version); 128 | } 129 | 130 | return new Promise(function(resolve, reject) { 131 | if (!libName || libName.length > 32) 132 | return reject('Library names must be between 1-32 characters.'); 133 | 134 | if (resources) { 135 | Object.keys(resources).forEach(function(key) { 136 | if (!key || key.length > 32) 137 | return reject('Resource keys must be between 1-32 characters.'); 138 | }); 139 | } else { 140 | resources = {}; 141 | } 142 | 143 | if (!parsedVersion) 144 | return reject('No version specified for '+libName); 145 | 146 | findContract(function(error, contract) { 147 | if (error) return reject(error); 148 | contract.register( 149 | libName, 150 | parsedVersion.num, 151 | address, 152 | abiString, 153 | (thresholdWei || 0), 154 | {value: 0, gas: 2000000}, // TODO: need to estimate this 155 | promiseHandler(resolve, reject) 156 | ); 157 | }); 158 | }).then(function(txHash) { 159 | var message = 'Registered '+libName+' '+version+'!'; 160 | if (thresholdWei > 0) 161 | message += ' Locked until '+thresholdWei+' wei contributed.'; 162 | return txHandler(txHash, message); 163 | }).then(function() { 164 | var promises = [] 165 | Object.keys(resources).forEach(function(key) { 166 | var promise = new Promise(function(resolve, reject) { 167 | findContract(function(err, contract) { 168 | if (err) return reject(err); 169 | contract.registerResource( 170 | libName, 171 | parsedVersion.num, 172 | key, 173 | resources[key], 174 | {value: 0, gas: 2000000}, // TODO: need to estimate this 175 | promiseHandler(resolve, reject) 176 | ); 177 | }); 178 | }).then(function(txHash) { 179 | var message = 'Registered resource for '+libName+' '+version+'!'; 180 | return txHandler(txHash, message); 181 | }); 182 | promises.push(promise); 183 | }); 184 | return Promise.all(promises); 185 | }); 186 | }; 187 | 188 | this.contributeTo = function(libName, version, wei) { 189 | return new Promise(function(resolve, reject) { 190 | if (!wei) 191 | return reject('No wei amount specified'); 192 | 193 | findContract(function(err, liveLibsContract) { 194 | if (err) return reject(err); 195 | liveLibsContract.libFund(function(error, libFundAddress) { 196 | if (error) return reject(error); 197 | liveAddress(libFundAddress, function(err, isLive) { 198 | if (err) return reject(err); 199 | if (!isLive) return reject('LibFund instance not found!'); 200 | 201 | var abi = JSON.parse(config.libFundABIString); 202 | var libFundContract = web3.eth.contract(abi); 203 | var instance = libFundContract.at(libFundAddress); 204 | var v = versionUtils.parse(version); 205 | 206 | instance.addTo( 207 | libName, 208 | v.num, 209 | {value: wei, gas: 2000000}, // TODO: need to estimate this 210 | function(err, txHash) { 211 | if (err) { 212 | reject(err); 213 | } else { 214 | resolve(txHash); 215 | } 216 | } 217 | ); 218 | }); 219 | }); 220 | }); 221 | }).then(function(txHash) { 222 | return txHandler(txHash, 'Contributed '+wei+' wei to '+libName+' '+version+'!'); 223 | }); 224 | }; 225 | 226 | this.allNames = function(callback) { 227 | findContract(function(err, contract) { 228 | if (err) return callback(err); 229 | contract.allNames(function(error, rawNames) { 230 | if (error) return callback(error); 231 | var names = []; 232 | rawNames.forEach(function(rawName) { 233 | var plainName = ethUtils.toAscii(web3, rawName); 234 | names.push(plainName); 235 | }); 236 | callback(null, names); 237 | }); 238 | }); 239 | }; 240 | 241 | this.allVersionsFor = function(libName) { 242 | var versionStrings = []; 243 | findContract(function(err, contract) { 244 | if (err) return callback(err); 245 | contract.getVersions(libName, function(error, rawVersions) { 246 | if (error) return callback(error); 247 | rawVersions.forEach(function(rawVersion) { 248 | var version = versionUtils.calc(rawVersion); 249 | versionStrings.push(version.string); 250 | }); 251 | callback(null, versionStrings); 252 | }); 253 | }); 254 | }; 255 | 256 | function findContract(callback) { 257 | var contract = web3.eth.contract(liveLibsABI()); 258 | 259 | if (testing) // short-curcuit if we know we're testing 260 | return findTestRPCInstance(contract, callback); 261 | 262 | detectLiveLibsInstance(contract, function(err, instance) { 263 | if (err) return callback(err); 264 | if (instance) { 265 | callback(null, instance); 266 | } else { 267 | findTestRPCInstance(contract, function(err, instance) { 268 | if (err) { 269 | callback(err); 270 | } else if (instance) { 271 | callback(null, instance); 272 | } else { 273 | callback(Error('No Live Libs instance found')); 274 | } 275 | }); 276 | } 277 | }); 278 | } 279 | this.findContract = findContract; // exposing it for the CLI 280 | 281 | function liveLibsABI() { 282 | // NOTE: before updating this file, download the latest registry from networks 283 | return JSON.parse(config.liveLibsABIString); 284 | } 285 | 286 | function detectLiveLibsInstance(contract, callback) { 287 | var config = parseNetworkConfig(); 288 | var promises = []; 289 | Object.keys(config).forEach(function(networkName) { 290 | var address = config[networkName]; 291 | promises.push(new Promise(function(resolve, reject) { 292 | liveAddress(address, function(err, isLive) { 293 | if (err) return reject(err); 294 | var instance; 295 | if (isLive) { 296 | instance = contract.at(address); 297 | instance.env = networkName; 298 | } 299 | resolve(instance); 300 | }); 301 | })); 302 | }); 303 | return Promise.all(promises).then(function(possibilities) { 304 | var instance = possibilities.find(function(instance) { 305 | return instance; 306 | }); 307 | callback(null, instance); 308 | }).catch(function(err) { 309 | callback(err); 310 | }); 311 | } 312 | 313 | function findTestRPCInstance(contract, callback) { 314 | // TODO: ./browser doesn't have config.reload() 315 | var address = config.testRpcAddress; 316 | if (config.reload) 317 | address = config.reload().testRpcAddress; 318 | 319 | if (!address) 320 | return callback(Error('Contract address not found for testrpc!')); 321 | 322 | liveAddress(address, function(err, isLive) { 323 | if (err) return callback(err); 324 | if (isLive) { 325 | var instance = contract.at(address); 326 | instance.env = 'testrpc'; 327 | callback(null, instance); 328 | } else { 329 | callback(Error('Contract not found for testrpc!')); 330 | } 331 | }); 332 | } 333 | 334 | function parseNetworkConfig() { 335 | return JSON.parse(config.networksJSONString); 336 | } 337 | 338 | function liveAddress(address, callback) { 339 | web3.eth.getCode(address, function(err, contractCode) { 340 | if (err) return callback(err); 341 | //geth //testrpc 342 | var isLive = contractCode != '0x' && contractCode != '0x0'; 343 | callback(null, isLive); 344 | }); 345 | } 346 | 347 | function promiseHandler(resolve, reject) { 348 | return function(err, txHash) { 349 | if (err) { 350 | reject(err); 351 | } else { 352 | resolve(txHash); 353 | } 354 | }; 355 | } 356 | 357 | function txHandler(txHash, successMessage) { 358 | return new Promise(function(resolve, reject) { 359 | var interval = setInterval(function() { 360 | web3.eth.getTransactionReceipt(txHash, function(err, receipt) { 361 | if (err != null) { 362 | clearInterval(interval); 363 | reject(err); 364 | } 365 | if (receipt != null) { 366 | logger.log(successMessage); 367 | clearInterval(interval); 368 | resolve(); 369 | } 370 | }); 371 | }, 500); 372 | }); 373 | } 374 | 375 | function getLogger(verbose) { 376 | if (verbose) { 377 | return console; 378 | } else { 379 | var noop = function() {}; 380 | return { log: noop, error: noop }; 381 | } 382 | } 383 | 384 | function filterEventsBy(libName, callback) { 385 | var searchString = web3.toHex(libName); 386 | 387 | // TODO: Isn't there a better way to ensure the string is zero-padded? 388 | // If it's not zero-padded, the topic doesn't work correctly 389 | while (searchString.length < 66) { 390 | searchString += "0"; 391 | } 392 | 393 | findContract(function(err, contract) { 394 | if (err) return callback(err); 395 | 396 | var filter = web3.eth.filter({ 397 | address: contract.address, 398 | fromBlock: 0, 399 | topics: [null, searchString] 400 | }); 401 | 402 | filter.get(function(error, results) { 403 | if (error) return callback(error); 404 | var abi = liveLibsABI(); 405 | var decodedResults = results.map(function(log) { return decode(log, abi) }); 406 | callback(null, decodedResults); 407 | }); 408 | }); 409 | } 410 | 411 | function decode(log, abi) { 412 | var SolidityEvent = require('web3/lib/web3/event'); 413 | 414 | var decoders = abi.filter(function (json) { 415 | return json.type === 'event'; 416 | }).map(function(json) { 417 | return new SolidityEvent(null, json, null); 418 | }); 419 | 420 | var decoder = decoders.find(function(decoder) { 421 | return decoder.signature() == log.topics[0].replace("0x",""); 422 | }); 423 | 424 | return decoder.decode(log); 425 | } 426 | 427 | function buildConfig(config) { 428 | if (!config) config = {}; 429 | if (!LiveLibs.config) return config; // set in ./browser 430 | for (var attrname in LiveLibs.config) { 431 | if (config[attrname] == undefined) 432 | config[attrname] = LiveLibs.config[attrname]; 433 | } 434 | return config; 435 | } 436 | } 437 | 438 | module.exports = LiveLibs; 439 | --------------------------------------------------------------------------------