UNPKG

@realitio/realitio-lib

Version:

Tools for handling questions in the Realitio fact verification platform

270 lines (243 loc) 9.22 kB
'use strict;'; const BN = require('bn.js'); const BigNumber = require('bignumber.js'); const ethereumjs_abi = require('ethereumjs-abi') const vsprintf = require("sprintf-js").vsprintf const QUESTION_MAX_OUTCOMES = 128; exports.delimiter = function() { return '\u241f'; // Thought about '\u0000' but it seems to break something; } exports.contentHash = function(template_id, opening_ts, content) { return "0x" + ethereumjs_abi.soliditySHA3( ["uint256", "uint32", "string"], [ new BN(template_id), new BN(opening_ts), content] ).toString('hex'); } exports.questionID = function(template_id, question, arbitrator, timeout, opening_ts, sender, nonce) { var content_hash = this.contentHash(template_id, opening_ts, question); // The seems to be something wrong with how soliditySHA3 handles bytes32, so tell it we gave it uint256 return "0x" + ethereumjs_abi.soliditySHA3( //["bytes32", "address", "uint256", "address", "uint256"], ["uint256", "address", "uint32", "address", "uint256"], [ new BN(content_hash.replace(/^0x/, ''), 16), arbitrator, new BN(timeout), sender, new BN(nonce)] ).toString('hex'); } exports.minNumber = function(qjson) { var is_signed = (qjson['type'] == 'int'); if (!is_signed) { return new BigNumber(0); } return this.maxNumber(qjson).neg(); } exports.maxNumber = function(qjson) { var is_signed = (qjson['type'] == 'int'); var divby = new BigNumber(1); if (qjson['decimals']) { divby = new BigNumber(10).pow(new BigNumber(qjson['decimals'])); } if (is_signed) { divby = divby.times(2); } return new BigNumber("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").dividedBy(divby); } exports.arrayToBitmaskBigNumber = function(selections) { // console.log('selections are ', selections); var bitstr = ''; for (var i=0; i<selections.length; i++) { var item = selections[i] ? '1' : '0'; bitstr = item + bitstr; } //console.log('bitstr',bitstr); return new BigNumber(bitstr, 2); } exports.answerToBytes32 = function(answer, qjson) { var qtype = qjson['type']; if (qtype == 'multiple-select') { answer = this.arrayToBitmaskBigNumber(answer); } var decimals = parseInt(qjson['decimals']); if (!decimals) { decimals = 0; } if (decimals > 0) { var multiplier = new BigNumber(10).pow(new BigNumber(decimals)); answer = new BigNumber(answer).times(multiplier).toString(16); } //console.log('muliplied to ',answer.toString()); var bn; if (qtype == 'int') { bn = new BN(answer, 16).toTwos(256); } else if (qtype == 'uint') { bn = new BN(answer, 16); } else { return this.padToBytes32(new BigNumber(answer).toString(16)); } return this.padToBytes32(bn.toString(16)); } exports.bytes32ToString = function(bytes32str, qjson) { var qtype = qjson['type']; var decimals = parseInt(qjson['decimals']); if (!decimals) { decimals = 0; } bytes32str = bytes32str.replace(/^0x/, ''); var bn; if (qtype == 'int') { var bn = new BN(bytes32str, 16).fromTwos(256); } else if (qtype == 'uint' || qtype == 'datetime') { var bn = new BN(bytes32str, 16); } else { throw Error("Unrecognized answer type " + qtype); } var ans = bn.toString(); // Do the decimals with BigNumber as it seems to work better if (decimals > 0) { var multiplier = new BigNumber(10).pow(new BigNumber(decimals)); ans = new BigNumber(ans).dividedBy(multiplier).toString(); } return ans.toString(); } exports.padToBytes32 = function(n, raw) { while (n.length < 64) { n = "0" + n; } if (raw) { return n; } return "0x" + n; } exports.convertTsToString = function(ts) { if (typeof ts.toNumber === 'function') { ts = ts.toNumber(); } let date = new Date(); date.setTime(ts * 1000); return date.toISOString(); } exports.secondsTodHms = function(sec) { sec = Number(sec); let d = Math.floor(sec / (3600 * 24)); let h = Math.floor(sec % (3600 * 24) / 3600); let m = Math.floor(sec % (3600 * 24) % 3600 / 60); let s = Math.floor(sec % (3600 * 24) % 3600 % 60); let dDisplay = d > 0 ? d + (d == 1 ? " day " : " days ") : ""; let hDisplay = h > 0 ? h + (h == 1 ? " hour " : " hours ") : ""; let mDisplay = m > 0 ? m + (m == 1 ? " minute " : " minutes ") : ""; let sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : ""; return dDisplay + hDisplay + mDisplay + sDisplay; } exports.parseQuestionJSON = function(data) { var question_json; try { question_json = JSON.parse(data); } catch(e) { question_json = { 'title': '[Badly formatted question]: ' + data, 'type': 'broken-question' }; } if (question_json['outcomes'] && question_json['outcomes'].length > QUESTION_MAX_OUTCOMES) { throw Error("Too many outcomes"); } return question_json; } exports.populatedJSONForTemplate = function(template, question) { var qbits = question.split(this.delimiter()); //console.log('pp', template); //console.log('qbits', qbits); var interpolated = vsprintf(template, qbits); //console.log('resulting template', interpolated); return this.parseQuestionJSON(interpolated); } exports.encodeText = function(qtype, txt, outcomes, category, lang) { var qtext = JSON.stringify(txt).replace(/^"|"$/g, ''); var delim = this.delimiter(); //console.log('using template_id', template_id); if (qtype == 'single-select' || qtype == 'multiple-select') { var outcome_str = JSON.stringify(outcomes).replace(/^\[/, '').replace(/\]$/, ''); //console.log('made outcome_str', outcome_str); qtext = qtext + delim + outcome_str; //console.log('made qtext', qtext); } if (typeof lang == 'undefined' || lang == '') { lang = 'en_US'; } qtext = qtext + delim + category + delim + lang; return qtext; } // A value used to denote that the question is invalid or can't be answered exports.getInvalidValue = function(question_json) { // equivalent to -1 in twos complement return '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; } exports.getLanguage = function(question_json) { if ( typeof question_json['lang'] == 'undefined' || question_json['lang'] == '') { return 'en_US'; } return question_json['lang']; } exports.getAnswerString = function(question_json, answer) { if (answer === null) { return 'null'; } if (answer == this.getInvalidValue(question_json)) { return 'Invalid'; } var label = ''; switch (question_json['type']) { case 'uint': label = this.bytes32ToString(answer, question_json); break; case 'int': label = this.bytes32ToString(answer, question_json); break; case 'bool': if (new BigNumber(answer).toNumber() === 1) { label = 'Yes'; } else if (new BigNumber(answer).toNumber() === 0) { label = 'No'; } break; case 'single-select': if (typeof question_json['outcomes'] !== 'undefined' && question_json['outcomes'].length > 0) { var idx = new BigNumber(answer).toNumber(); label = question_json['outcomes'][idx]; } break; case 'multiple-select': if (typeof question_json['outcomes'] !== 'undefined' && question_json['outcomes'].length > 0) { var answer_bits = new BigNumber(answer).toString(2); var length = answer_bits.length; var entries = []; for (var i = question_json['outcomes'].length - 1; i >= 0; i--) { if (answer_bits[i] === '1') { var idx = answer_bits.length - 1 - i; entries.push(question_json['outcomes'][idx]); } } return entries.join(' / '); } break; case 'datetime': let ts = parseInt(this.bytes32ToString(answer, question_json)); let dateObj = new Date(ts * 1000); let year = dateObj.getFullYear(); let month = dateObj.getMonth() + 1; let date = dateObj.getDate(); label = year + '/' + month + '/' + date; break; } return label; } exports.commitmentID = function(question_id, answer_hash, bond) { return "0x" + ethereumjs_abi.soliditySHA3( ["uint256", "uint256", "uint256"], [ new BN(question_id.replace(/^0x/, ''), 16), new BN(answer_hash.replace(/^0x/, ''), 16), new BN(bond.toString(16), 16)] ).toString('hex'); } exports.answerHash = function(answer_plaintext, nonce) { return "0x" + ethereumjs_abi.soliditySHA3( ["uint256", "uint256"], [ new BN(answer_plaintext.replace(/^0x/, ''), 16), new BN(nonce.replace(/^0x/, ''), 16)] ).toString('hex'); }