-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement helper functions #42
Changes from 15 commits
8116071
8ff3081
0d7c770
b920e9f
2204535
274449c
8e22222
4028cdf
dc0de7f
f011f3d
625fa3e
b140217
f670e04
03c8478
c6cd403
973616d
4352ea7
fde131c
8c92fa8
8a2e04c
06c6811
6bc383f
b390a29
2b02323
fa9943c
035d1a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module.exports = () => | ||
/** | ||
* Repeats a string (testing helper) | ||
* | ||
* @param {string} echo The string | ||
* @param {integer} repeat Number of times to repeat the string | ||
* @return {Promise<radspec/evaluator/TypedValue>} | ||
*/ | ||
async (echo, repeat = 1) => { | ||
return { | ||
type: 'string', | ||
value: echo.repeat(repeat) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const formatDate = require('date-fns/format') | ||
|
||
module.exports = () => | ||
/** | ||
* Format a timestamp as a string (using date-fns) | ||
* | ||
* @param {integer} timestamp Unix timestamp | ||
* @param {string} format The format for the date (https://date-fns.org/v2.0.0-alpha.7/docs/format) | ||
* @return {Promise<radspec/evaluator/TypedValue>} | ||
*/ | ||
async (timestamp, format = 'MM-DD-YYYY') => { | ||
return { | ||
type: 'string', | ||
value: formatDate(new Date(timestamp.toNumber() * 1000), format) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const { formatBN, tenPow } = require('./lib/formatBN') | ||
|
||
module.exports = () => | ||
/** | ||
* Format a percentage amount | ||
* | ||
* @param {integer} value The absolute number that will be formatted as a percentage | ||
* @param {integer} base The number that is considered a 100% when calculating the percentage | ||
* @param {integer} precision The number of decimals that will be printed (if any) | ||
* @return {Promise<radspec/evaluator/TypedValue>} | ||
*/ | ||
async (value, base = tenPow(18), precision = 2) => { | ||
const oneHundred = tenPow(2) | ||
const formattedAmount = formatBN(value.mul(oneHundred), base, precision) | ||
|
||
return { | ||
type: 'string', | ||
value: `${formattedAmount}` | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const formatDate = require('./formatDate') | ||
const echo = require('./echo') | ||
const tokenAmount = require('./tokenAmount') | ||
const transformTime = require('./transformTime') | ||
const formatPct = require('./formatPct') | ||
|
||
const defaultHelpers = { | ||
formatDate, | ||
transformTime, | ||
tokenAmount, | ||
formatPct, | ||
echo | ||
} | ||
|
||
/** | ||
* Class for managing the execution of helper functions | ||
* | ||
* @class Helpers | ||
* @param {web3/eth} eth web3.eth instance | ||
* @param {Object.<string,helpers/Helper>} userHelpers User defined helpers | ||
*/ | ||
class Helpers { | ||
constructor (eth, userHelpers = {}) { | ||
this.eth = eth | ||
this.helpers = { ...defaultHelpers, ...userHelpers } | ||
} | ||
|
||
/** | ||
* Does a helper exist | ||
* | ||
* @param {string} helper Helper name | ||
* @return {bool} | ||
*/ | ||
exists (helper) { | ||
return !!this.helpers[helper] | ||
} | ||
|
||
/** | ||
* Execute a helper with some inputs | ||
* | ||
* @param {string} helper Helper name | ||
* @param {Array<radspec/evaluator/TypedValue>} inputs | ||
* @return {Promise<radspec/evaluator/TypedValue>} | ||
*/ | ||
execute (helper, inputs) { | ||
inputs = inputs.map(input => input.value) // pass values directly | ||
return this.helpers[helper](this.eth)(...inputs) | ||
} | ||
} | ||
|
||
module.exports = { | ||
Helpers, | ||
|
||
defaultHelpers | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const BN = require('bn.js') | ||
|
||
exports.tenPow = x => ( | ||
(new BN(10)).pow(new BN(x)) | ||
) | ||
|
||
exports.formatBN = (value, base, precision) => { | ||
// Inspired by: https://github.com/ethjs/ethjs-unit/blob/35d870eae1c32c652da88837a71e252a63a83ebb/src/index.js#L83 | ||
const baseLength = base.toString().length | ||
|
||
let fraction = value.mod(base).toString() | ||
const zeros = '0'.repeat(Math.max(0, baseLength - fraction.length - 1)) | ||
fraction = `${zeros}${fraction}` | ||
const whole = value.div(base).toString() | ||
|
||
return `${whole}${parseInt(fraction) === 0 ? '' : `.${fraction.slice(0, precision)}`}` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
exports.ETH = '0x0000000000000000000000000000000000000000' | ||
exports.ERC20_SYMBOL_DECIMALS_ABI = [ | ||
{ | ||
"constant":true, | ||
"inputs":[ | ||
|
||
], | ||
"name":"decimals", | ||
"outputs":[ | ||
{ | ||
"name":"", | ||
"type":"uint8" | ||
} | ||
], | ||
"payable":false, | ||
"stateMutability":"view", | ||
"type":"function" | ||
}, | ||
{ | ||
"constant":true, | ||
"inputs":[ | ||
|
||
], | ||
"name":"symbol", | ||
"outputs":[ | ||
{ | ||
"name":"", | ||
"type":"string" | ||
} | ||
], | ||
"payable":false, | ||
"stateMutability":"view", | ||
"type":"function" | ||
}, | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
const BN = require('bn.js') | ||
const { ERC20_SYMBOL_DECIMALS_ABI, ETH } = require('./lib/token') | ||
const { formatBN, tenPow } = require('./lib/formatBN') | ||
|
||
module.exports = (eth) => | ||
/** | ||
* Format token amounts taking decimals into account | ||
* | ||
* @param {string} tokenAddress The address of the token | ||
* @param {integer} amount The absolute amount for the token quantity (wei) | ||
* @param {bool} showSymbol Whether the token symbol will be printed after the amount | ||
* @param {integer} precision The number of decimals that will be printed (if any) | ||
* @return {Promise<radspec/evaluator/TypedValue>} | ||
*/ | ||
async (tokenAddress, amount, showSymbol = true, precision = new BN(2)) => { | ||
let decimals | ||
let symbol | ||
|
||
if (tokenAddress === ETH) { | ||
decimals = new BN(18) | ||
if (showSymbol) { | ||
symbol = 'ETH' | ||
} | ||
} else { | ||
const token = new eth.Contract(ERC20_SYMBOL_DECIMALS_ABI, tokenAddress) | ||
|
||
decimals = new BN(await token.methods.decimals().call()) | ||
if (showSymbol) { | ||
symbol = await token.methods.symbol().call() | ||
} | ||
} | ||
|
||
const formattedAmount = formatBN(amount, tenPow(decimals), precision) | ||
|
||
return { | ||
type: 'string', | ||
value: showSymbol ? `${formattedAmount} ${symbol}` : formattedAmount | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
const formatDistanceStrict = require('date-fns/formatDistanceStrict') | ||
|
||
const BEST_UNIT = 'best' | ||
const TO_UNIT_MAP = { | ||
seconds: 's', | ||
minutes: 'm', | ||
hours: 'h', | ||
days: 'd', | ||
months: 'M', | ||
years: 'Y' | ||
} | ||
const SUPPORTED_TO_UNITS = new Set(Object.keys(TO_UNIT_MAP).concat(BEST_UNIT)) | ||
const SUPPORTED_FROM_UNITS = new Set([ | ||
...Object.keys(TO_UNIT_MAP), | ||
'milliseconds', | ||
'weeks' | ||
]) | ||
|
||
module.exports = () => | ||
/** | ||
* Transform between time units. | ||
* | ||
* @param {integer} time The base time amount | ||
* @param {string} toUnit The unit to convert the time to (Supported units: 'seconds', 'minutes', 'hours', 'days', 'months', 'years') | ||
* @param {string} fromUnit The unit to convert the time from (Supported units: 'milliseconds', 'seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years') | ||
* @return {Promise<radspec/evaluator/TypedValue>} | ||
*/ | ||
async (time, toUnit = BEST_UNIT, fromUnit = 'seconds') => { | ||
if (!SUPPORTED_FROM_UNITS.has(fromUnit)) { | ||
throw new Error(`@transformTime: Time unit ${fromUnit} is not supported as a fromUnit`) | ||
} | ||
|
||
if (!SUPPORTED_TO_UNITS.has(toUnit)) { | ||
throw new Error(`@transformTime: Time unit ${toUnit} is not supported as a toUnit`) | ||
} | ||
|
||
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1) | ||
const add = require(`date-fns/add${capitalize(fromUnit)}`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsure if this dynamic require could cause us trouble There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty sure bundlers aren't going to like this :(. Best to avoid it and add it to a map or similar. |
||
|
||
const zeroDate = new Date(0) | ||
const duration = add(zeroDate, time.toNumber()) | ||
|
||
const options = toUnit === BEST_UNIT ? {} : { unit: TO_UNIT_MAP[toUnit] } | ||
return { | ||
type: 'string', | ||
value: formatDistanceStrict(zeroDate, duration, options) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn’t be a problem in modern JS engines, but it can still be a good idea to specify the radix as a second parameter of
parseInt()
for a while, just in case scripts are run on unsupported engines (e.g. old mobile browser or Node.js version) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Octal_interpretations_with_no_radix