diff --git a/src/helpers/blockTime.js b/src/helpers/blockTime.js new file mode 100644 index 0000000..4e53df6 --- /dev/null +++ b/src/helpers/blockTime.js @@ -0,0 +1,23 @@ +import { format } from 'date-fns' +const MILLISECONDS_IN_A_SECOND = 1000 + +export default (eth) => + async (blockNumber, showBlock = true, averageBlockTime = 15, formatString = 'yyyy-MM-dd', dateFmtOptions = {}) => { + const { number: currentBlock, timestamp: currentTimestamp } = await eth.getBlock('latest') + const futureBlock = currentBlock < blockNumber + let rawTimestamp + + if (!futureBlock) { + rawTimestamp = (await eth.getBlock(blockNumber)).timestamp * MILLISECONDS_IN_A_SECOND + } else { + const blockDuration = (blockNumber - currentBlock) * averageBlockTime + rawTimestamp = (currentTimestamp + blockDuration) * MILLISECONDS_IN_A_SECOND + } + + const formattedTime = format(rawTimestamp, formatString, dateFmtOptions) + + return { + type: 'string', + value: `${formattedTime}${futureBlock ? ' (estimated)' : ''}${showBlock ? ` (block number: ${blockNumber})` : ''}` + } + } diff --git a/src/helpers/index.js b/src/helpers/index.js index c2c3575..d227990 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -1,31 +1,37 @@ import HelperManager from './HelperManager' +import blockTime from './blockTime' import echo from './echo' import formatDate from './formatDate' import fromHex from './fromHex' import formatPct from './formatPct' +import isBlockMined from './isBlockMined' +import radspec from './radspec' import tokenAmount from './tokenAmount' import transformTime from './transformTime' -import radspec from './radspec' const defaultHelpers = { + blockTime, + echo, formatDate, - transformTime, - tokenAmount, formatPct, fromHex, + isBlockMined, radspec, - echo + tokenAmount, + transformTime } export { HelperManager, defaultHelpers, + blockTime, echo, formatDate, formatPct, fromHex, + isBlockMined, radspec, - transformTime, - tokenAmount + tokenAmount, + transformTime } diff --git a/src/helpers/isBlockMined.js b/src/helpers/isBlockMined.js new file mode 100644 index 0000000..9655e15 --- /dev/null +++ b/src/helpers/isBlockMined.js @@ -0,0 +1,8 @@ +export default (eth) => + async (blockNumber) => { + const { number: currentBlock } = await eth.getBlock('latest') + return { + type: 'bool', + value: currentBlock >= blockNumber + } + } diff --git a/test/examples/examples.js b/test/examples/examples.js index 363caf3..465f8c3 100644 --- a/test/examples/examples.js +++ b/test/examples/examples.js @@ -207,7 +207,19 @@ const helperCases = [ source: 'Bar `@bar(shift)` foo `@foo(n)`', bindings: { shift: bool(true), n: int(7) }, options: { userHelpers: { bar: () => shift => ({ type: 'string', value: shift ? 'BAR' : 'bar' }), foo: () => n => ({ type: 'number', value: n * 7 }) } } - }, 'Bar BAR foo 49'] + }, 'Bar BAR foo 49'], + [{ + source: 'get a past date: `@blockTime(block, showBlock)`', + bindings: { block: int('8765432'), showBlock: bool(false) } + }, 'get a past date: 2019-10-18'], + [{ + source: 'get a past date: `@blockTime(block, showBlock)`', + bindings: { block: int('8765432'), showBlock: bool(true) } + }, 'get a past date: 2019-10-18 (block number: 8765432)'], + [{ + source: 'See if block is mined: `@isBlockMined(block)`', + bindings: { block: int('98765430') } + }, 'See if block is mined: false'] ] const dataDecodeCases = [ @@ -430,3 +442,33 @@ cases.forEach(([input, expected], index) => { ) }) }) + +const fuzzyCases = [ + [{ + source: 'Get a future date: `@blockTime(block)`', + bindings: { block: int('20976543') } + }, /^Get a future date: 202\d(?:-\d\d){2} \(estimated\) \(block number: 20976543\)$/], + [{ + source: 'Get a future date without showing block number: `@blockTime(block, false)`', + bindings: { block: int('20976543') } + }, /^Get a future date without showing block number: 202\d(?:-\d\d){2} \(estimated\)$/] +] + +fuzzyCases.forEach(([input, regExp], index) => { + test(`${index} - ${input.source}`, async (t) => { + const { userHelpers } = input.options || {} + const actual = await evaluateRaw( + input.source, + input.bindings, + { + ...input.options, + availableHelpers: { ...defaultHelpers, ...userHelpers } + } + ) + t.regex( + actual, + regExp, + `Expected "${input.source}" to match "${regExp}", but evaluated to "${actual}"` + ) + }) +})