From ac1ffe53df90df9ecf4aca972a5625568f0b2a3a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 11:21:16 -0800 Subject: [PATCH 001/230] Send in rollbar environment --- packages/instant/webpack.config.js | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 41276809c9..161e7d1b07 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -11,21 +11,31 @@ const GIT_SHA = childProcess .toString() .trim(); -const HEAP_PRODUCTION_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION'; -const HEAP_DEVELOPMENT_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT'; -const getHeapAnalyticsId = modeName => { - if (modeName === 'production') { - return process.env[HEAP_PRODUCTION_ENV_VAR_NAME]; +const getEnvironmentName = (env, argv) => { + if (env && env.dogfood) { + return 'dogfood'; + } else if (env && env.staging) { + return 'staging'; } - if (modeName === 'development') { - return process.env[HEAP_DEVELOPMENT_ENV_VAR_NAME]; + // argv.mode should be 'development' or 'production' + return argv.mode; +}; + +const getHeapAnalyticsId = environmentName => { + if (environmentName === 'production') { + return process.env['INSTANT_HEAP_ANALYTICS_ID_PRODUCTION']; + } + + if (environmentName === 'development' || environmentName === 'dogfood' || environmentName === 'staging') { + return process.env['INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT']; } return undefined; }; module.exports = (env, argv) => { + const environmentName = getEnvironmentName(env, argv); const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; const config = { entry: { @@ -41,8 +51,10 @@ module.exports = (env, argv) => { new webpack.DefinePlugin({ 'process.env': { GIT_SHA: JSON.stringify(GIT_SHA), - HEAP_ANALYTICS_ID: getHeapAnalyticsId(argv.mode), NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), + HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName), + ROLLBAR_ENVIRONMENT: JSON.stringify(environmentName), + ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env.INSTANT_ROLLBAR_CLIENT_TOKEN), }, }), ], From d2dd5f93d21404ee5424792a4e29e843140a7fa1 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 11:39:31 -0800 Subject: [PATCH 002/230] Introduce rollbar plugin --- packages/instant/.dogfood.discharge.json | 4 +- packages/instant/.staging.discharge.json | 4 +- packages/instant/package.json | 3 ++ packages/instant/webpack.config.js | 63 +++++++++++++++++++----- yarn.lock | 15 ++++++ 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json index ca36b3861d..cea579c277 100644 --- a/packages/instant/.dogfood.discharge.json +++ b/packages/instant/.dogfood.discharge.json @@ -1,12 +1,12 @@ { "domain": "0x-instant-dogfood", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod --env.dogfood", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", "trailing_slashes": true, "cache": 3600, - "aws_profile": "default", + "aws_profile": "0xproject", "aws_region": "us-east-1", "cdn": false, "dns_configured": true diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json index c917a650b6..9e63cb1100 100644 --- a/packages/instant/.staging.discharge.json +++ b/packages/instant/.staging.discharge.json @@ -1,12 +1,12 @@ { "domain": "0x-instant-staging", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod --env.staging", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", "trailing_slashes": true, "cache": 3600, - "aws_profile": "default", + "aws_profile": "0xproject", "aws_region": "us-east-1", "cdn": false, "dns_configured": true diff --git a/packages/instant/package.json b/packages/instant/package.json index 1813d61e51..e613e028c9 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -64,6 +64,7 @@ "react-redux": "^5.0.7", "redux": "^4.0.0", "redux-devtools-extension": "^2.13.5", + "rollbar": "^2.5.0", "styled-components": "^4.0.2", "ts-optchain": "^0.1.1" }, @@ -88,7 +89,9 @@ "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", "nyc": "^11.0.1", + "rollbar-sourcemap-webpack-plugin": "^2.4.0", "shx": "^0.2.2", + "source-map-loader": "^0.2.4", "svg-react-loader": "^0.4.6", "ts-jest": "^23.10.3", "tslint": "5.11.0", diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 161e7d1b07..2375bbd31d 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -1,6 +1,7 @@ const childProcess = require('child_process'); const ip = require('ip'); const path = require('path'); +const RollbarSourceMapPlugin = require('rollbar-sourcemap-webpack-plugin'); const webpack = require('webpack'); // The common js bundle (not this one) is built using tsc. @@ -34,9 +35,59 @@ const getHeapAnalyticsId = environmentName => { return undefined; }; +const getRollbarPlugin = environmentName => { + if (!environmentName) { + return undefined; + } + + const publishToken = process.env.INSTANT_ROLLBAR_PUBLISH_TOKEN; + if (!publishToken) { + return undefined; + } + + let rollbarPublicPath; + if (environmentName === 'dogfood') { + rollbarPublicPath = 'http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com'; + } else if (environmentName === 'staging') { + rollbarPublicPath = 'http://0x-instant-staging.s3-website-us-east-1.amazonaws.com'; + } + + if (!rollbarPublicPath) { + console.log('No rollbar public path'); + return undefined; + } + + const rollbarPluginOptions = { + accessToken: publishToken, + version: GIT_SHA, + publicPath: rollbarPublicPath, + }; + return new RollbarSourceMapPlugin(rollbarPluginOptions); +}; + module.exports = (env, argv) => { const environmentName = getEnvironmentName(env, argv); const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; + + let plugins = [ + new webpack.DefinePlugin({ + 'process.env': { + GIT_SHA: JSON.stringify(GIT_SHA), + NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), + HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName), + ROLLBAR_ENVIRONMENT: JSON.stringify(environmentName), + ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env.INSTANT_ROLLBAR_CLIENT_TOKEN), + }, + }), + ]; + const rollbarPlugin = getRollbarPlugin(environmentName); + if (rollbarPlugin) { + console.log('Using rollbar plugin'); + plugins = plugins.concat(rollbarPlugin); + } else { + console.log('Not using rollbar plugin'); + } + const config = { entry: { instant: './src/index.umd.ts', @@ -47,17 +98,7 @@ module.exports = (env, argv) => { library: 'zeroExInstant', libraryTarget: 'umd', }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env': { - GIT_SHA: JSON.stringify(GIT_SHA), - NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), - HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName), - ROLLBAR_ENVIRONMENT: JSON.stringify(environmentName), - ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env.INSTANT_ROLLBAR_CLIENT_TOKEN), - }, - }), - ], + plugins, devtool: 'source-map', resolve: { extensions: ['.js', '.json', '.ts', '.tsx'], diff --git a/yarn.lock b/yarn.lock index a7a55a7b3d..f9d0362920 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13367,6 +13367,21 @@ rollbar@^2.4.7: optionalDependencies: decache "^3.0.5" +rollbar@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/rollbar/-/rollbar-2.5.0.tgz#dbb513eea34f1e3bae57611810c3e6df0ef50a12" + dependencies: + async "~1.2.1" + console-polyfill "0.3.0" + debug "2.6.9" + error-stack-parser "1.3.3" + json-stringify-safe "~5.0.0" + lru-cache "~2.2.1" + request-ip "~2.0.1" + uuid "3.0.x" + optionalDependencies: + decache "^3.0.5" + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" From 05d45e7146ce862ebf7318db636b1a1652348243 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 12:05:48 -0800 Subject: [PATCH 003/230] Use sourcemap loader --- packages/instant/webpack.config.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 2375bbd31d..e472824f5f 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -77,6 +77,7 @@ module.exports = (env, argv) => { HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName), ROLLBAR_ENVIRONMENT: JSON.stringify(environmentName), ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env.INSTANT_ROLLBAR_CLIENT_TOKEN), + ROLLBAR_FORCE_DEVELOPMENT_REPORT: JSON.stringify(process.env.INSTANT_ROLLBAR_FORCE_DEVELOPMENT_REPORT), }, }), ]; @@ -113,6 +114,15 @@ module.exports = (env, argv) => { test: /\.svg$/, loader: 'svg-react-loader', }, + { + test: /\.js$/, + loader: 'source-map-loader', + exclude: [ + // instead of /\/node_modules\// + path.join(process.cwd(), 'node_modules'), + path.join(process.cwd(), '../..', 'node_modules'), + ], + }, ], }, devServer: { From 728617fed2fee99b2accdcf6d2ddc9036c9ad266 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 12:06:10 -0800 Subject: [PATCH 004/230] feat(instant): Report errors to rollbar --- .../components/zero_ex_instant_container.tsx | 3 + packages/instant/src/constants.ts | 7 ++ packages/instant/src/util/error_reporter.ts | 68 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 packages/instant/src/util/error_reporter.ts diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index 47c938472b..6d7cef7fd0 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -12,12 +12,15 @@ import { SelectedAssetInstantHeading } from '../containers/selected_asset_instan import { ColorOption } from '../style/theme'; import { zIndex } from '../style/z_index'; import { OrderProcessState, SlideAnimationState } from '../types'; +import { setupRollbar } from '../util/error_reporter'; import { CSSReset } from './css_reset'; import { SlidingPanel } from './sliding_panel'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; +setupRollbar(); + export interface ZeroExInstantContainerProps { orderProcessState: OrderProcessState; } diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index be6077ca92..23660360d9 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -20,6 +20,13 @@ export const HEAP_ANALYTICS_ID = process.env.HEAP_ANALYTICS_ID; export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; export const PROGRESS_STALL_AT_WIDTH = '95%'; export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; +export const ROLLBAR_CLIENT_TOKEN = process.env.ROLLBAR_CLIENT_TOKEN; +export const ROLLBAR_ENVIRONMENT = process.env.ROLLBAR_ENVIRONMENT as + | 'dogfood' + | 'staging' + | 'development' + | 'production' + | undefined; export const COINBASE_WALLET_IOS_APP_STORE_URL = 'https://itunes.apple.com/us/app/coinbase-wallet/id1278383455?mt=8'; export const COINBASE_WALLET_ANDROID_APP_STORE_URL = 'https://play.google.com/store/apps/details?id=org.toshi&hl=en'; export const COINBASE_WALLET_SITE_URL = 'https://wallet.coinbase.com/'; diff --git a/packages/instant/src/util/error_reporter.ts b/packages/instant/src/util/error_reporter.ts new file mode 100644 index 0000000000..81a90e7f5a --- /dev/null +++ b/packages/instant/src/util/error_reporter.ts @@ -0,0 +1,68 @@ +import { logUtils } from '@0x/utils'; + +import { ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENVIRONMENT } from '../constants'; + +// Import version of Rollbar designed for embedded components +// See https://docs.rollbar.com/docs/using-rollbarjs-inside-an-embedded-component +// tslint:disable-next-line:no-var-requires +const Rollbar = require('rollbar/dist/rollbar.noconflict.umd'); + +const shouldAllowRollbar = () => { + if (ROLLBAR_ENVIRONMENT === 'development') { + return process.env.ROLLBAR_FORCE_DEVELOPMENT_REPORT ? true : false; + } + return true; +}; + +let rollbar: any; +if (ROLLBAR_CLIENT_TOKEN && ROLLBAR_ENVIRONMENT && shouldAllowRollbar()) { + rollbar = new Rollbar({ + accessToken: ROLLBAR_CLIENT_TOKEN, + captureUncaught: true, + captureUnhandledRejections: true, + enabled: true, + itemsPerMinute: 10, + maxItems: 500, + payload: { + environment: ROLLBAR_ENVIRONMENT, + client: { + javascript: { + source_map_enabled: true, + code_version: process.env.GIT_SHA, + guess_uncaught_frames: true, + }, + }, + }, + uncaughtErrorLevel: 'error', + ignoredMessages: [ + // Errors from the third-party scripts + 'Script error', + // Network errors or ad-blockers + 'TypeError: Failed to fetch', + 'Exchange has not been deployed to detected network (network/artifact mismatch)', + // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE + "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')", + // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging + 'SecurityError (DOM Exception 18)', + ], + }); +} + +export const setupRollbar = (): any => { + return rollbar as any; +}; + +export const errorReporter = { + report(err: Error): void { + if (!rollbar) { + logUtils.log('Not reporting to rollbar because not configured', err); + return; + } + + rollbar.error(err, (rollbarErr: Error) => { + if (rollbarErr) { + logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`); + } + }); + }, +}; From 748e3c0c5358b99f15baf4bf95da027c9b83eb89 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 13:14:20 -0800 Subject: [PATCH 005/230] Force source maps on staging and dogfood --- packages/instant/webpack.config.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index e472824f5f..e3ba59eb37 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -35,12 +35,14 @@ const getHeapAnalyticsId = environmentName => { return undefined; }; +const ROLLBAR_PUBLISH_TOKEN_ENV_NAME = 'INSTANT_ROLLBAR_PUBLISH_TOKEN'; +const ROLLBAR_CLIENT_TOKEN_ENV_NAME = 'INSTANT_ROLLBAR_CLIENT_TOKEN'; const getRollbarPlugin = environmentName => { if (!environmentName) { return undefined; } - const publishToken = process.env.INSTANT_ROLLBAR_PUBLISH_TOKEN; + const publishToken = process.env[ROLLBAR_PUBLISH_TOKEN_ENV_NAME]; if (!publishToken) { return undefined; } @@ -65,6 +67,26 @@ const getRollbarPlugin = environmentName => { return new RollbarSourceMapPlugin(rollbarPluginOptions); }; +const validateRollbar = (environmentName, rollbarPlugin) => { + const requiresRollbar = environmentName === 'dogfood' || environmentName === 'staging'; + + if (!requiresRollbar) { + return; + } + + if (!process.env[ROLLBAR_CLIENT_TOKEN_ENV_NAME]) { + throw new Error(`${ROLLBAR_CLIENT_TOKEN_ENV_NAME} must be set for ${environmentName}`); + } + + if (!rollbarPlugin) { + if (environmentName === 'dogfood' || environmentName === 'staging') { + throw new Error( + `Please set rollbar env var ${ROLLBAR_PUBLISH_TOKEN_ENV_NAME} to a Rollbar project access token with post_server_item permissions to deploy source maps to ${environmentName}`, + ); + } + } +}; + module.exports = (env, argv) => { const environmentName = getEnvironmentName(env, argv); const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; @@ -76,11 +98,12 @@ module.exports = (env, argv) => { NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName), ROLLBAR_ENVIRONMENT: JSON.stringify(environmentName), - ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env.INSTANT_ROLLBAR_CLIENT_TOKEN), + ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env[ROLLBAR_CLIENT_TOKEN_ENV_NAME]), ROLLBAR_FORCE_DEVELOPMENT_REPORT: JSON.stringify(process.env.INSTANT_ROLLBAR_FORCE_DEVELOPMENT_REPORT), }, }), ]; + const rollbarPlugin = getRollbarPlugin(environmentName); if (rollbarPlugin) { console.log('Using rollbar plugin'); @@ -88,6 +111,7 @@ module.exports = (env, argv) => { } else { console.log('Not using rollbar plugin'); } + validateRollbar(environmentName, rollbarPlugin); const config = { entry: { From 934570d12fae9c67ae18fc914577f02a51af3bca Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 13:18:47 -0800 Subject: [PATCH 006/230] Explicit error reporting --- packages/instant/src/redux/async_data.ts | 3 +++ packages/instant/src/util/buy_quote_updater.ts | 4 +++- packages/instant/src/util/gas_price_estimator.ts | 5 ++++- packages/instant/src/util/heap.ts | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 5d30388b85..29d441c755 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -9,6 +9,7 @@ import { assetUtils } from '../util/asset'; import { buyQuoteUpdater } from '../util/buy_quote_updater'; import { coinbaseApi } from '../util/coinbase_api'; import { errorFlasher } from '../util/error_flasher'; +import { errorReporter } from '../util/error_reporter'; import { actions } from './actions'; import { State } from './reducer'; @@ -22,6 +23,7 @@ export const asyncData = { const errorMessage = 'Error fetching ETH/USD price'; errorFlasher.flashNewErrorMessage(dispatch, errorMessage); dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO)); + errorReporter.report(e); } }, fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => { @@ -36,6 +38,7 @@ export const asyncData = { errorFlasher.flashNewErrorMessage(dispatch, errorMessage); // On error, just specify that none are available dispatch(actions.setAvailableAssets([])); + errorReporter.report(e); } }, fetchAccountInfoAndDispatchToStore: async ( diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 2fd16d7818..6cb5e41b6f 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -9,6 +9,7 @@ import { Action, actions } from '../redux/actions'; import { AffiliateInfo, ERC20Asset } from '../types'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; +import { errorReporter } from '../util/error_reporter'; export const buyQuoteUpdater = { updateBuyQuoteAsync: async ( @@ -49,8 +50,9 @@ export const buyQuoteUpdater = { } else { throw error; } + } else { + errorReporter.report(error); } - // TODO: report to error reporter on else return; } diff --git a/packages/instant/src/util/gas_price_estimator.ts b/packages/instant/src/util/gas_price_estimator.ts index 6b15809a39..332c8d00a5 100644 --- a/packages/instant/src/util/gas_price_estimator.ts +++ b/packages/instant/src/util/gas_price_estimator.ts @@ -7,6 +7,8 @@ import { GWEI_IN_WEI, } from '../constants'; +import { errorReporter } from './error_reporter'; + interface EthGasStationResult { average: number; fastestWait: number; @@ -42,8 +44,9 @@ export class GasPriceEstimator { let fetchedAmount: GasInfo | undefined; try { fetchedAmount = await fetchFastAmountInWeiAsync(); - } catch { + } catch (e) { fetchedAmount = undefined; + errorReporter.report(e); } if (fetchedAmount) { diff --git a/packages/instant/src/util/heap.ts b/packages/instant/src/util/heap.ts index 78ec3b3cc5..10670b2783 100644 --- a/packages/instant/src/util/heap.ts +++ b/packages/instant/src/util/heap.ts @@ -5,6 +5,7 @@ import * as _ from 'lodash'; import { HEAP_ANALYTICS_ID } from '../constants'; import { AnalyticsEventOptions, AnalyticsUserOptions } from './analytics'; +import { errorReporter } from './error_reporter'; export interface HeapAnalytics { loaded: boolean; @@ -105,8 +106,8 @@ export const heapUtil = { heapFunctionCall(curHeap); } catch (e) { // We never want analytics to crash our React component - // TODO(sk): error reporter here logUtils.log('Analytics error', e); + errorReporter.report(e); } } }, From d79c754a5bbc911856909b5d20079423e5e9c13f Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 13:18:53 -0800 Subject: [PATCH 007/230] TODO note --- packages/instant/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index e3ba59eb37..466385db96 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -52,7 +52,7 @@ const getRollbarPlugin = environmentName => { rollbarPublicPath = 'http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com'; } else if (environmentName === 'staging') { rollbarPublicPath = 'http://0x-instant-staging.s3-website-us-east-1.amazonaws.com'; - } + } // TODO(sk): When we decide on JS cdn, add public path here if (!rollbarPublicPath) { console.log('No rollbar public path'); From f5db4be52140e55045d6dc193065d83edeb85816 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 13:19:16 -0800 Subject: [PATCH 008/230] Ensure we publish Instant with rollbar settings --- packages/monorepo-scripts/src/prepublish_checks.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/monorepo-scripts/src/prepublish_checks.ts b/packages/monorepo-scripts/src/prepublish_checks.ts index fc550cf3a2..c1e2b702fc 100644 --- a/packages/monorepo-scripts/src/prepublish_checks.ts +++ b/packages/monorepo-scripts/src/prepublish_checks.ts @@ -186,7 +186,11 @@ async function checkPublishRequiredSetupAsync(): Promise { const checkRequiredEnvVariables = () => { utils.log('Checking required environment variables...'); - const requiredEnvVars = ['INSTANT_HEAP_ANALYTICS_ID_PRODUCTION']; + const requiredEnvVars = [ + 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION', + 'INSTANT_ROLLBAR_CLIENT_TOKEN', + 'INSTANT_ROLLBAR_PUBLISH_TOKEN', + ]; requiredEnvVars.forEach(requiredEnvVarName => { if (_.isUndefined(process.env[requiredEnvVarName])) { throw new Error(`Must have ${requiredEnvVarName} set`); From 094aabfcee8828eb5c5ba452edda306ef04757ce Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 20 Nov 2018 13:28:28 -0800 Subject: [PATCH 009/230] Linting --- packages/instant/src/util/error_reporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/src/util/error_reporter.ts b/packages/instant/src/util/error_reporter.ts index 81a90e7f5a..a03b5e65ee 100644 --- a/packages/instant/src/util/error_reporter.ts +++ b/packages/instant/src/util/error_reporter.ts @@ -49,7 +49,7 @@ if (ROLLBAR_CLIENT_TOKEN && ROLLBAR_ENVIRONMENT && shouldAllowRollbar()) { } export const setupRollbar = (): any => { - return rollbar as any; + return rollbar; }; export const errorReporter = { From 35b505114842b8c8eac838fff8588adf17f54b45 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 09:23:19 -0800 Subject: [PATCH 010/230] Always report unexpected errors. Move error message generation into helper function, and add tests --- packages/instant/src/util/asset.ts | 17 ++++++++++ .../instant/src/util/buy_quote_updater.ts | 29 +++++----------- packages/instant/test/util/asset.test.ts | 33 +++++++++++++++++-- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 40560d3eb1..08f3642e36 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -1,3 +1,4 @@ +import { AssetBuyerError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; @@ -106,4 +107,20 @@ export const assetUtils = { ); return _.compact(erc20sOrUndefined); }, + assetBuyerErrorMessage: (asset: ERC20Asset, error: Error): string | undefined => { + if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { + const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); + return `Not enough ${assetName} available`; + } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) { + return 'Not enough ZRX available'; + } else if ( + error.message === AssetBuyerError.StandardRelayerApiError || + error.message.startsWith(AssetBuyerError.AssetUnavailable) + ) { + const assetName = assetUtils.bestNameForAsset(asset, 'This asset'); + return `${assetName} is currently unavailable`; + } + + return undefined; + }, }; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 6cb5e41b6f..4f97622e12 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -30,30 +30,17 @@ export const buyQuoteUpdater = { try { newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { feePercentage }); } catch (error) { - if (options.dispatchErrors) { - dispatch(actions.setQuoteRequestStateFailure()); - let errorMessage; - if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { - const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); - errorMessage = `Not enough ${assetName} available`; - } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) { - errorMessage = 'Not enough ZRX available'; - } else if ( - error.message === AssetBuyerError.StandardRelayerApiError || - error.message.startsWith(AssetBuyerError.AssetUnavailable) - ) { - const assetName = assetUtils.bestNameForAsset(asset, 'This asset'); - errorMessage = `${assetName} is currently unavailable`; - } - if (!_.isUndefined(errorMessage)) { - errorFlasher.flashNewErrorMessage(dispatch, errorMessage); - } else { - throw error; - } - } else { + const errorMessage = assetUtils.assetBuyerErrorMessage(asset, error); + + if (_.isUndefined(errorMessage)) { + // This is an unknown error, report it to rollbar errorReporter.report(error); } + if (options.dispatchErrors) { + dispatch(actions.setQuoteRequestStateFailure()); + errorFlasher.flashNewErrorMessage(dispatch, errorMessage || 'Error fetching price, please try again'); + } return; } // We have a successful new buy quote diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index 4229b24ed2..fc4e4e2e47 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -1,6 +1,7 @@ +import { AssetBuyerError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; -import { Asset, AssetMetaData, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types'; +import { Asset, AssetMetaData, ERC20Asset, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types'; import { assetUtils } from '../../src/util/asset'; const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; @@ -11,7 +12,7 @@ const ZRX_META_DATA: ERC20AssetMetaData = { decimals: 18, name: '0x', }; -const ZRX_ASSET: Asset = { +const ZRX_ASSET: ERC20Asset = { assetData: ZRX_ASSET_DATA, metaData: ZRX_META_DATA, }; @@ -45,4 +46,32 @@ describe('assetDataUtil', () => { ).toThrowError(ZeroExInstantError.AssetMetaDataNotAvailable); }); }); + describe('assetBuyerErrorMessage', () => { + it('should return message for InsufficientAssetLiquidity', () => { + const insufficientAssetError = new Error(AssetBuyerError.InsufficientAssetLiquidity); + expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientAssetError)).toEqual( + 'Not enough ZRX available', + ); + }); + it('should return message for InsufficientAssetLiquidity', () => { + const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity); + expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientZrxError)).toEqual( + 'Not enough ZRX available', + ); + }); + it('should message for StandardRelayerApiError', () => { + const standardRelayerError = new Error(AssetBuyerError.StandardRelayerApiError); + expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, standardRelayerError)).toEqual( + 'ZRX is currently unavailable', + ); + }); + it('should return error for AssetUnavailable error', () => { + const assetUnavailableError = new Error( + `${AssetBuyerError.AssetUnavailable}: For assetData ${ZRX_ASSET_DATA}`, + ); + expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, assetUnavailableError)).toEqual( + 'ZRX is currently unavailable', + ); + }); + }); }); From 22a31246622e7139185d080cae9bef275087e245 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 09:35:24 -0800 Subject: [PATCH 011/230] Move rollbar setup into function, move setting up into provider --- .../components/zero_ex_instant_container.tsx | 3 - .../components/zero_ex_instant_provider.tsx | 2 + packages/instant/src/util/error_reporter.ts | 66 +++++++++---------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index 6d7cef7fd0..47c938472b 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -12,15 +12,12 @@ import { SelectedAssetInstantHeading } from '../containers/selected_asset_instan import { ColorOption } from '../style/theme'; import { zIndex } from '../style/z_index'; import { OrderProcessState, SlideAnimationState } from '../types'; -import { setupRollbar } from '../util/error_reporter'; import { CSSReset } from './css_reset'; import { SlidingPanel } from './sliding_panel'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; -setupRollbar(); - export interface ZeroExInstantContainerProps { orderProcessState: OrderProcessState; } diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 9435d8c7c6..00d3e95b2d 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -15,11 +15,13 @@ import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource } from import { analytics, disableAnalytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; +import { setupRollbar } from '../util/error_reporter'; import { gasPriceEstimator } from '../util/gas_price_estimator'; import { Heartbeater } from '../util/heartbeater'; import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory'; import { providerStateFactory } from '../util/provider_state_factory'; +setupRollbar(); fonts.include(); export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps & diff --git a/packages/instant/src/util/error_reporter.ts b/packages/instant/src/util/error_reporter.ts index a03b5e65ee..c5766b469f 100644 --- a/packages/instant/src/util/error_reporter.ts +++ b/packages/instant/src/util/error_reporter.ts @@ -1,4 +1,5 @@ import { logUtils } from '@0x/utils'; +import * as _ from 'lodash'; import { ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENVIRONMENT } from '../constants'; @@ -15,41 +16,40 @@ const shouldAllowRollbar = () => { }; let rollbar: any; -if (ROLLBAR_CLIENT_TOKEN && ROLLBAR_ENVIRONMENT && shouldAllowRollbar()) { - rollbar = new Rollbar({ - accessToken: ROLLBAR_CLIENT_TOKEN, - captureUncaught: true, - captureUnhandledRejections: true, - enabled: true, - itemsPerMinute: 10, - maxItems: 500, - payload: { - environment: ROLLBAR_ENVIRONMENT, - client: { - javascript: { - source_map_enabled: true, - code_version: process.env.GIT_SHA, - guess_uncaught_frames: true, +// Configures rollbar and sets up error catching +export const setupRollbar = (): any => { + if (_.isUndefined(rollbar) && ROLLBAR_CLIENT_TOKEN && ROLLBAR_ENVIRONMENT && shouldAllowRollbar()) { + rollbar = new Rollbar({ + accessToken: ROLLBAR_CLIENT_TOKEN, + captureUncaught: true, + captureUnhandledRejections: true, + enabled: true, + itemsPerMinute: 10, + maxItems: 500, + payload: { + environment: ROLLBAR_ENVIRONMENT, + client: { + javascript: { + source_map_enabled: true, + code_version: process.env.GIT_SHA, + guess_uncaught_frames: true, + }, }, }, - }, - uncaughtErrorLevel: 'error', - ignoredMessages: [ - // Errors from the third-party scripts - 'Script error', - // Network errors or ad-blockers - 'TypeError: Failed to fetch', - 'Exchange has not been deployed to detected network (network/artifact mismatch)', - // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE - "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')", - // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging - 'SecurityError (DOM Exception 18)', - ], - }); -} - -export const setupRollbar = (): any => { - return rollbar; + uncaughtErrorLevel: 'error', + ignoredMessages: [ + // Errors from the third-party scripts + 'Script error', + // Network errors or ad-blockers + 'TypeError: Failed to fetch', + 'Exchange has not been deployed to detected network (network/artifact mismatch)', + // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE + "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')", + // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging + 'SecurityError (DOM Exception 18)', + ], + }); + } }; export const errorReporter = { From ffa2f4554b959b9af67d385d85fe9a4c822ad482 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 09:41:44 -0800 Subject: [PATCH 012/230] Takeout redundant check, and make function name more clear --- packages/instant/webpack.config.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 466385db96..b08fabedfc 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -67,7 +67,7 @@ const getRollbarPlugin = environmentName => { return new RollbarSourceMapPlugin(rollbarPluginOptions); }; -const validateRollbar = (environmentName, rollbarPlugin) => { +const validateRollbarPresence = (environmentName, rollbarPlugin) => { const requiresRollbar = environmentName === 'dogfood' || environmentName === 'staging'; if (!requiresRollbar) { @@ -79,11 +79,9 @@ const validateRollbar = (environmentName, rollbarPlugin) => { } if (!rollbarPlugin) { - if (environmentName === 'dogfood' || environmentName === 'staging') { - throw new Error( - `Please set rollbar env var ${ROLLBAR_PUBLISH_TOKEN_ENV_NAME} to a Rollbar project access token with post_server_item permissions to deploy source maps to ${environmentName}`, - ); - } + throw new Error( + `Please set rollbar env var ${ROLLBAR_PUBLISH_TOKEN_ENV_NAME} to a Rollbar project access token with post_server_item permissions to deploy source maps to ${environmentName}`, + ); } }; @@ -111,7 +109,7 @@ module.exports = (env, argv) => { } else { console.log('Not using rollbar plugin'); } - validateRollbar(environmentName, rollbarPlugin); + validateRollbarPresence(environmentName, rollbarPlugin); const config = { entry: { From 70c99082496bbaed5b5ccc37bb259c0a9f02fab5 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 09:43:01 -0800 Subject: [PATCH 013/230] Report when cant update balance --- packages/instant/src/redux/async_data.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 29d441c755..5382494f2a 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -78,6 +78,7 @@ export const asyncData = { const ethBalanceInWei = await web3Wrapper.getBalanceInWeiAsync(address); dispatch(actions.updateAccountEthBalance({ address, ethBalanceInWei })); } catch (e) { + errorReporter.report(e); // leave balance as is return; } From e2a16f3f336c6787501c6a2366e7793a135009f8 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 10:25:10 -0800 Subject: [PATCH 014/230] Use ROLLBAR_ENABLED constant, and change ROLLBAR_ENVIRONMENT to INSTANT_ENVIRONMENT --- packages/instant/src/constants.ts | 3 ++- packages/instant/src/util/error_reporter.ts | 13 +++------- packages/instant/webpack.config.js | 28 +++++++++++++-------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 23660360d9..dfa7520f84 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -21,12 +21,13 @@ export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; export const PROGRESS_STALL_AT_WIDTH = '95%'; export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; export const ROLLBAR_CLIENT_TOKEN = process.env.ROLLBAR_CLIENT_TOKEN; -export const ROLLBAR_ENVIRONMENT = process.env.ROLLBAR_ENVIRONMENT as +export const INSTANT_ENVIRONMENT = process.env.INSTANT_ENVIRONMENT as | 'dogfood' | 'staging' | 'development' | 'production' | undefined; +export const ROLLBAR_ENABLED = process.env.ROLLBAR_ENABLED; export const COINBASE_WALLET_IOS_APP_STORE_URL = 'https://itunes.apple.com/us/app/coinbase-wallet/id1278383455?mt=8'; export const COINBASE_WALLET_ANDROID_APP_STORE_URL = 'https://play.google.com/store/apps/details?id=org.toshi&hl=en'; export const COINBASE_WALLET_SITE_URL = 'https://wallet.coinbase.com/'; diff --git a/packages/instant/src/util/error_reporter.ts b/packages/instant/src/util/error_reporter.ts index c5766b469f..89c5b6bd35 100644 --- a/packages/instant/src/util/error_reporter.ts +++ b/packages/instant/src/util/error_reporter.ts @@ -1,24 +1,17 @@ import { logUtils } from '@0x/utils'; import * as _ from 'lodash'; -import { ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENVIRONMENT } from '../constants'; +import { INSTANT_ENVIRONMENT, ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENABLED } from '../constants'; // Import version of Rollbar designed for embedded components // See https://docs.rollbar.com/docs/using-rollbarjs-inside-an-embedded-component // tslint:disable-next-line:no-var-requires const Rollbar = require('rollbar/dist/rollbar.noconflict.umd'); -const shouldAllowRollbar = () => { - if (ROLLBAR_ENVIRONMENT === 'development') { - return process.env.ROLLBAR_FORCE_DEVELOPMENT_REPORT ? true : false; - } - return true; -}; - let rollbar: any; // Configures rollbar and sets up error catching export const setupRollbar = (): any => { - if (_.isUndefined(rollbar) && ROLLBAR_CLIENT_TOKEN && ROLLBAR_ENVIRONMENT && shouldAllowRollbar()) { + if (_.isUndefined(rollbar) && ROLLBAR_CLIENT_TOKEN && INSTANT_ENVIRONMENT && ROLLBAR_ENABLED) { rollbar = new Rollbar({ accessToken: ROLLBAR_CLIENT_TOKEN, captureUncaught: true, @@ -27,7 +20,7 @@ export const setupRollbar = (): any => { itemsPerMinute: 10, maxItems: 500, payload: { - environment: ROLLBAR_ENVIRONMENT, + environment: INSTANT_ENVIRONMENT, client: { javascript: { source_map_enabled: true, diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index b08fabedfc..defdde2500 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -89,25 +89,31 @@ module.exports = (env, argv) => { const environmentName = getEnvironmentName(env, argv); const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; + const envVars = { + GIT_SHA: JSON.stringify(GIT_SHA), + NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), + HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName), + INSTANT_ENVIRONMENT: JSON.stringify(environmentName), + ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env[ROLLBAR_CLIENT_TOKEN_ENV_NAME]), + }; + + const canRollbarBeEnabled = + environmentName === 'development' ? process.env.INSTANT_ROLLBAR_FORCE_DEVELOPMENT_REPORT : true; + if (envVars.INSTANT_ENVIRONMENT && envVars.ROLLBAR_CLIENT_TOKEN && canRollbarBeEnabled) { + envVars['ROLLBAR_ENABLED'] = JSON.stringify(true); + } + let plugins = [ new webpack.DefinePlugin({ - 'process.env': { - GIT_SHA: JSON.stringify(GIT_SHA), - NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), - HEAP_ANALYTICS_ID: getHeapAnalyticsId(environmentName), - ROLLBAR_ENVIRONMENT: JSON.stringify(environmentName), - ROLLBAR_CLIENT_TOKEN: JSON.stringify(process.env[ROLLBAR_CLIENT_TOKEN_ENV_NAME]), - ROLLBAR_FORCE_DEVELOPMENT_REPORT: JSON.stringify(process.env.INSTANT_ROLLBAR_FORCE_DEVELOPMENT_REPORT), - }, + 'process.env': envVars, }), ]; - const rollbarPlugin = getRollbarPlugin(environmentName); if (rollbarPlugin) { - console.log('Using rollbar plugin'); + console.log('Using rollbar source map plugin'); plugins = plugins.concat(rollbarPlugin); } else { - console.log('Not using rollbar plugin'); + console.log('Not using rollbar source map plugin'); } validateRollbarPresence(environmentName, rollbarPlugin); From 95a80a0e757647f84515e7bc7208f18b875d4111 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 10:35:53 -0800 Subject: [PATCH 015/230] Shorter validateRollbarPresence logic --- packages/instant/webpack.config.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index defdde2500..adc1cb0000 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -66,21 +66,14 @@ const getRollbarPlugin = environmentName => { }; return new RollbarSourceMapPlugin(rollbarPluginOptions); }; - -const validateRollbarPresence = (environmentName, rollbarPlugin) => { +const validateRollbarPresence = (environmentName, rollbarEnabled, rollbarSourceMapPlugin) => { const requiresRollbar = environmentName === 'dogfood' || environmentName === 'staging'; - if (!requiresRollbar) { return; } - - if (!process.env[ROLLBAR_CLIENT_TOKEN_ENV_NAME]) { - throw new Error(`${ROLLBAR_CLIENT_TOKEN_ENV_NAME} must be set for ${environmentName}`); - } - - if (!rollbarPlugin) { + if (!rollbarEnabled || !rollbarSourceMapPlugin) { throw new Error( - `Please set rollbar env var ${ROLLBAR_PUBLISH_TOKEN_ENV_NAME} to a Rollbar project access token with post_server_item permissions to deploy source maps to ${environmentName}`, + `Rollbar env vars must be set to build for ${environmentName}. Please set ${ROLLBAR_CLIENT_TOKEN_ENV_NAME} to a rollbar access token with post_client_item permissions, and ${ROLLBAR_PUBLISH_TOKEN_ENV_NAME} to a rollbar access token with post_server_item permissions.`, ); } }; @@ -115,7 +108,7 @@ module.exports = (env, argv) => { } else { console.log('Not using rollbar source map plugin'); } - validateRollbarPresence(environmentName, rollbarPlugin); + validateRollbarPresence(environmentName, envVars['ROLLBAR_ENABLED'], rollbarPlugin); const config = { entry: { From b2e1be5cfe373f7cdafd5bd9a0c3ec1ea1e5f1d5 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 10:44:50 -0800 Subject: [PATCH 016/230] Better env var names for source map plugin --- packages/instant/webpack.config.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index adc1cb0000..7149793c48 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -37,7 +37,7 @@ const getHeapAnalyticsId = environmentName => { const ROLLBAR_PUBLISH_TOKEN_ENV_NAME = 'INSTANT_ROLLBAR_PUBLISH_TOKEN'; const ROLLBAR_CLIENT_TOKEN_ENV_NAME = 'INSTANT_ROLLBAR_CLIENT_TOKEN'; -const getRollbarPlugin = environmentName => { +const getRollbarSourceMapPlugin = environmentName => { if (!environmentName) { return undefined; } @@ -101,14 +101,14 @@ module.exports = (env, argv) => { 'process.env': envVars, }), ]; - const rollbarPlugin = getRollbarPlugin(environmentName); - if (rollbarPlugin) { + const rollbarSourceMapPlugin = getRollbarSourceMapPlugin(environmentName); + if (rollbarSourceMapPlugin) { console.log('Using rollbar source map plugin'); - plugins = plugins.concat(rollbarPlugin); + plugins = plugins.concat(rollbarSourceMapPlugin); } else { console.log('Not using rollbar source map plugin'); } - validateRollbarPresence(environmentName, envVars['ROLLBAR_ENABLED'], rollbarPlugin); + validateRollbarPresence(environmentName, envVars['ROLLBAR_ENABLED'], rollbarSourceMapPlugin); const config = { entry: { From 93672c01afd19f07daa89934fc8d29caefbec2f6 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 10:54:10 -0800 Subject: [PATCH 017/230] Linting --- packages/instant/src/util/buy_quote_updater.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 4f97622e12..172b50d2a4 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -1,4 +1,4 @@ -import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; +import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; From c6ae7b8d3f720f637db2d5ec9abee14cc00fb7a7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 21 Nov 2018 13:19:26 -0800 Subject: [PATCH 018/230] Host whitelist so we don't get errors from embedded site --- packages/instant/src/constants.ts | 9 +++++++++ packages/instant/src/util/error_reporter.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index dfa7520f84..eb785bc2b6 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -20,6 +20,15 @@ export const HEAP_ANALYTICS_ID = process.env.HEAP_ANALYTICS_ID; export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; export const PROGRESS_STALL_AT_WIDTH = '95%'; export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; +export const EMBEDDED_DOMAINS = [ + '0x-instant-staging.s3-website-us-east-1.amazonaws.com', + '0x-instant-dogfood.s3-website-us-east-1.amazonaws.com', + 'localhost', + '127.0.0.1', + '0.0.0.0', + 'unpkg.com', + 'jsdelivr.com', +]; export const ROLLBAR_CLIENT_TOKEN = process.env.ROLLBAR_CLIENT_TOKEN; export const INSTANT_ENVIRONMENT = process.env.INSTANT_ENVIRONMENT as | 'dogfood' diff --git a/packages/instant/src/util/error_reporter.ts b/packages/instant/src/util/error_reporter.ts index 89c5b6bd35..c0ef3e30e9 100644 --- a/packages/instant/src/util/error_reporter.ts +++ b/packages/instant/src/util/error_reporter.ts @@ -1,7 +1,7 @@ import { logUtils } from '@0x/utils'; import * as _ from 'lodash'; -import { INSTANT_ENVIRONMENT, ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENABLED } from '../constants'; +import { EMBEDDED_DOMAINS, INSTANT_ENVIRONMENT, ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENABLED } from '../constants'; // Import version of Rollbar designed for embedded components // See https://docs.rollbar.com/docs/using-rollbarjs-inside-an-embedded-component @@ -29,6 +29,7 @@ export const setupRollbar = (): any => { }, }, }, + hostWhiteList: EMBEDDED_DOMAINS, uncaughtErrorLevel: 'error', ignoredMessages: [ // Errors from the third-party scripts From 717a3bce8cd0d4cb782918b3ad806f6c1bdb825e Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 26 Nov 2018 09:12:51 -0800 Subject: [PATCH 019/230] EMBEDDED_DOMAINS -> HOST_DOMAINS --- packages/instant/src/constants.ts | 2 +- packages/instant/src/util/error_reporter.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index eb785bc2b6..677510a8ba 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -20,7 +20,7 @@ export const HEAP_ANALYTICS_ID = process.env.HEAP_ANALYTICS_ID; export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; export const PROGRESS_STALL_AT_WIDTH = '95%'; export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; -export const EMBEDDED_DOMAINS = [ +export const HOST_DOMAINS = [ '0x-instant-staging.s3-website-us-east-1.amazonaws.com', '0x-instant-dogfood.s3-website-us-east-1.amazonaws.com', 'localhost', diff --git a/packages/instant/src/util/error_reporter.ts b/packages/instant/src/util/error_reporter.ts index c0ef3e30e9..8e21c88818 100644 --- a/packages/instant/src/util/error_reporter.ts +++ b/packages/instant/src/util/error_reporter.ts @@ -1,7 +1,7 @@ import { logUtils } from '@0x/utils'; import * as _ from 'lodash'; -import { EMBEDDED_DOMAINS, INSTANT_ENVIRONMENT, ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENABLED } from '../constants'; +import { HOST_DOMAINS, INSTANT_ENVIRONMENT, ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENABLED } from '../constants'; // Import version of Rollbar designed for embedded components // See https://docs.rollbar.com/docs/using-rollbarjs-inside-an-embedded-component @@ -29,7 +29,7 @@ export const setupRollbar = (): any => { }, }, }, - hostWhiteList: EMBEDDED_DOMAINS, + hostWhiteList: HOST_DOMAINS, uncaughtErrorLevel: 'error', ignoredMessages: [ // Errors from the third-party scripts From faf80f85959ce157435f3bd500b00fd33e828084 Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 26 Nov 2018 17:37:03 -0500 Subject: [PATCH 020/230] feat: make package private --- packages/instant/package.json | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/instant/package.json b/packages/instant/package.json index d3a85a6461..af23af3b9b 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -5,13 +5,10 @@ "node": ">=6.12" }, "description": "0x Instant React Component", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "umd/instant.js", + "private": true, "scripts": { - "build": "yarn build:all", - "build:all": "run-p build:umd:prod build:commonjs", - "build:umd:prod": "webpack --mode production", - "build:commonjs": "tsc -b", + "build": "webpack --mode production", "build:ci": "yarn build", "watch_without_deps": "tsc -w", "dev": "webpack-dev-server --mode development", @@ -98,6 +95,6 @@ "webpack-dev-server": "^3.1.9" }, "publishConfig": { - "access": "public" + "access": "private" } } From 35d8525f5563e877752fd1bd9e5a5c7ffa395b78 Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 26 Nov 2018 17:37:21 -0500 Subject: [PATCH 021/230] feat: update README to reflect lack of commonjs module --- packages/instant/README.md | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/packages/instant/README.md b/packages/instant/README.md index b83a10508c..fd94d37d15 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -2,35 +2,7 @@ ## Installation -```bash -yarn add @0x/instant -``` - -**Import** - -**CommonJS module** - -```typescript -import { ZeroExInstant } from '@0x/instant'; -``` - -or - -```javascript -var ZeroExInstant = require('@0x/instant').ZeroExInstant; -``` - -If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`: - -```json -"compilerOptions": { - "typeRoots": ["node_modules/@0x/typescript-typings/types", "node_modules/@types"], -} -``` - -**UMD Module** - -The package is also available as a UMD module named `zeroExInstant`. +The package is available as a UMD module named `zeroExInstant`. ```html From b50187f59c09d4ecf7a840fd22f96bcecd14bb1b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 27 Nov 2018 11:25:51 -0800 Subject: [PATCH 022/230] Track install wallet clicked --- .../connected_account_payment_method.ts | 37 +++++++++++-------- packages/instant/src/types.ts | 5 +++ packages/instant/src/util/analytics.ts | 5 ++- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/packages/instant/src/containers/connected_account_payment_method.ts b/packages/instant/src/containers/connected_account_payment_method.ts index cdeb49a251..e9327a2885 100644 --- a/packages/instant/src/containers/connected_account_payment_method.ts +++ b/packages/instant/src/containers/connected_account_payment_method.ts @@ -11,7 +11,7 @@ import { import { Action, actions } from '../redux/actions'; import { asyncData } from '../redux/async_data'; import { State } from '../redux/reducer'; -import { Network, Omit, OperatingSystem, ProviderState, StandardSlidingPanelContent } from '../types'; +import { Network, Omit, OperatingSystem, ProviderState, StandardSlidingPanelContent, WalletSuggestion } from '../types'; import { analytics } from '../util/analytics'; import { envUtil } from '../util/env'; @@ -60,23 +60,28 @@ const mergeProps = ( onUnlockWalletClick: () => connectedDispatch.unlockWalletAndDispatchToStore(connectedState.providerState), onInstallWalletClick: () => { const isMobile = envUtil.isMobileOperatingSystem(); - if (!isMobile) { + const walletSuggestion: WalletSuggestion = isMobile + ? WalletSuggestion.CoinbaseWallet + : WalletSuggestion.MetaMask; + + analytics.trackInstallWalletClicked(walletSuggestion); + if (walletSuggestion === WalletSuggestion.MetaMask) { connectedDispatch.openInstallWalletPanel(); - return; + } else { + const operatingSystem = envUtil.getOperatingSystem(); + let url = COINBASE_WALLET_SITE_URL; + switch (operatingSystem) { + case OperatingSystem.Android: + url = COINBASE_WALLET_ANDROID_APP_STORE_URL; + break; + case OperatingSystem.iOS: + url = COINBASE_WALLET_IOS_APP_STORE_URL; + break; + default: + break; + } + window.open(url, '_blank'); } - const operatingSystem = envUtil.getOperatingSystem(); - let url = COINBASE_WALLET_SITE_URL; - switch (operatingSystem) { - case OperatingSystem.Android: - url = COINBASE_WALLET_ANDROID_APP_STORE_URL; - break; - case OperatingSystem.iOS: - url = COINBASE_WALLET_IOS_APP_STORE_URL; - break; - default: - break; - } - window.open(url, '_blank'); }, }); diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 999d50fed7..4ad9c9d4fc 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -149,6 +149,11 @@ export enum Browser { Other = 'OTHER', } +export enum WalletSuggestion { + CoinbaseWallet = 'Coinbase Wallet', + MetaMask = 'MetaMask', +} + export enum OperatingSystem { Android = 'ANDROID', iOS = 'IOS', diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 2bb9742545..e35a9e13f0 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -1,7 +1,7 @@ import { BuyQuote } from '@0x/asset-buyer'; import * as _ from 'lodash'; -import { AffiliateInfo, Asset, Network, OrderSource, ProviderState } from '../types'; +import { AffiliateInfo, Asset, Network, OrderSource, ProviderState, WalletSuggestion } from '../types'; import { EventProperties, heapUtil } from './heap'; @@ -30,6 +30,7 @@ enum EventNames { BUY_TX_SUBMITTED = 'Buy - Tx Submitted', BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded', BUY_TX_FAILED = 'Buy - Tx Failed', + INSTALL_WALLET_CLICKED = 'Install Wallet - Clicked', TOKEN_SELECTOR_OPENED = 'Token Selector - Opened', TOKEN_SELECTOR_CLOSED = 'Token Selector - Closed', TOKEN_SELECTOR_CHOSE = 'Token Selector - Chose', @@ -164,6 +165,8 @@ export const analytics = { expectedTxTimeMs: expectedEndTimeUnix - startTimeUnix, actualTxTimeMs: new Date().getTime() - startTimeUnix, }), + trackInstallWalletClicked: (walletSuggestion: WalletSuggestion) => + trackingEventFnWithPayload(EventNames.INSTALL_WALLET_CLICKED)({ walletSuggestion }), trackTokenSelectorOpened: trackingEventFnWithoutPayload(EventNames.TOKEN_SELECTOR_OPENED), trackTokenSelectorClosed: (closedVia: TokenSelectorClosedVia) => trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_CLOSED)({ closedVia }), From 462a5face9b2b24c1e9d20a6b894809faa512232 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 27 Nov 2018 13:30:28 -0800 Subject: [PATCH 023/230] feat(instant): Install Wallet analytics --- .../components/install_wallet_panel_content.tsx | 9 ++++++++- .../src/components/standard_panel_content.tsx | 13 ++++++++++++- packages/instant/src/redux/analytics_middleware.ts | 14 +++++++++++++- packages/instant/src/util/analytics.ts | 10 ++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/instant/src/components/install_wallet_panel_content.tsx b/packages/instant/src/components/install_wallet_panel_content.tsx index 88c26f59cf..481d82da0c 100644 --- a/packages/instant/src/components/install_wallet_panel_content.tsx +++ b/packages/instant/src/components/install_wallet_panel_content.tsx @@ -8,7 +8,9 @@ import { } from '../constants'; import { ColorOption } from '../style/theme'; import { Browser } from '../types'; +import { analytics } from '../util/analytics'; import { envUtil } from '../util/env'; +import { util } from '../util/util'; import { MetaMaskLogo } from './meta_mask_logo'; import { StandardPanelContent, StandardPanelContentProps } from './standard_panel_content'; @@ -45,6 +47,10 @@ export class InstallWalletPanelContent extends React.Component { + analytics.trackInstallWalletModalClickedGet(); + util.createOpenUrlInNewWindow(actionUrl)(); + }; return { image: , title: 'Install MetaMask', @@ -52,10 +58,11 @@ export class InstallWalletPanelContent extends React.Component void; } export interface StandardPanelContentProps { @@ -21,6 +23,15 @@ export interface StandardPanelContentProps { const SPACING_BETWEEN_PX = '20px'; +const onMoreInfoClick = (href: string, onClick?: () => void) => { + return () => { + if (onClick) { + onClick(); + } + util.createOpenUrlInNewWindow(href)(); + }; +}; + export const StandardPanelContent: React.StatelessComponent = ({ image, title, @@ -50,7 +61,7 @@ export const StandardPanelContent: React.StatelessComponent {moreInfoSettings.text} diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts index 8aa76eb776..3dc5fe924e 100644 --- a/packages/instant/src/redux/analytics_middleware.ts +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import { Middleware } from 'redux'; import { ETH_DECIMALS } from '../constants'; -import { Account, AccountState } from '../types'; +import { Account, AccountState, StandardSlidingPanelContent } from '../types'; import { analytics } from '../util/analytics'; import { Action, ActionTypes } from './actions'; @@ -77,6 +77,18 @@ export const analyticsMiddleware: Middleware = store => next => middlewareAction }); } break; + case ActionTypes.OPEN_STANDARD_SLIDING_PANEL: + const openSlidingContent = curState.standardSlidingPanelSettings.content; + if (openSlidingContent === StandardSlidingPanelContent.InstallWallet) { + analytics.trackInstallWalletModalOpened(); + } + break; + case ActionTypes.CLOSE_STANDARD_SLIDING_PANEL: + const closeSlidingContent = curState.standardSlidingPanelSettings.content; + if (closeSlidingContent === StandardSlidingPanelContent.InstallWallet) { + analytics.trackInstallWalletModalClosed(); + } + break; } return nextAction; diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index e35a9e13f0..f1ce158055 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -31,6 +31,10 @@ enum EventNames { BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded', BUY_TX_FAILED = 'Buy - Tx Failed', INSTALL_WALLET_CLICKED = 'Install Wallet - Clicked', + INSTALL_WALLET_MODAL_OPENED = 'Install Wallet - Modal - Opened', + INSTALL_WALLET_MODAL_CLICKED_EXPLANATION = 'Install Wallet - Modal - Clicked Explanation', + INSTALL_WALLET_MODAL_CLICKED_GET = 'Install Wallet - Modal - Clicked Get', + INSTALL_WALLET_MODAL_CLOSED = 'Install Wallet - Modal - Closed', TOKEN_SELECTOR_OPENED = 'Token Selector - Opened', TOKEN_SELECTOR_CLOSED = 'Token Selector - Closed', TOKEN_SELECTOR_CHOSE = 'Token Selector - Chose', @@ -167,6 +171,12 @@ export const analytics = { }), trackInstallWalletClicked: (walletSuggestion: WalletSuggestion) => trackingEventFnWithPayload(EventNames.INSTALL_WALLET_CLICKED)({ walletSuggestion }), + trackInstallWalletModalClickedExplanation: trackingEventFnWithoutPayload( + EventNames.INSTALL_WALLET_MODAL_CLICKED_EXPLANATION, + ), + trackInstallWalletModalClickedGet: trackingEventFnWithoutPayload(EventNames.INSTALL_WALLET_MODAL_CLICKED_GET), + trackInstallWalletModalOpened: trackingEventFnWithoutPayload(EventNames.INSTALL_WALLET_MODAL_OPENED), + trackInstallWalletModalClosed: trackingEventFnWithoutPayload(EventNames.INSTALL_WALLET_MODAL_CLOSED), trackTokenSelectorOpened: trackingEventFnWithoutPayload(EventNames.TOKEN_SELECTOR_OPENED), trackTokenSelectorClosed: (closedVia: TokenSelectorClosedVia) => trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_CLOSED)({ closedVia }), From 856f4b473b54ecb3dc707ea334890397c98606bf Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 27 Nov 2018 14:14:30 -0800 Subject: [PATCH 024/230] feat(instant): Close and View Transaction analytics events --- .../containers/selected_asset_buy_order_state_buttons.ts | 3 +++ packages/instant/src/index.umd.ts | 2 ++ packages/instant/src/util/analytics.ts | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts index 610335243e..0d49edc57a 100644 --- a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts +++ b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts @@ -10,6 +10,7 @@ import { BuyOrderStateButtons } from '../components/buy_order_state_buttons'; import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; import { AccountState, AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types'; +import { analytics } from '../util/analytics'; import { errorFlasher } from '../util/error_flasher'; import { etherscanUtil } from '../util/etherscan'; @@ -59,6 +60,8 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButt assetBuyer.networkId, ); if (etherscanUrl) { + analytics.trackTransactionViewed(state.buyOrderState.processState); + window.open(etherscanUrl, '_blank'); return; } diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 3a8694d6a8..667daf4416 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -4,6 +4,7 @@ import * as ReactDOM from 'react-dom'; import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_CLASS, INJECTED_DIV_ID } from './constants'; import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index'; +import { analytics } from './util/analytics'; import { assert } from './util/assert'; import { util } from './util/util'; @@ -57,6 +58,7 @@ const renderInstant = (config: ZeroExInstantConfig, selector: string) => { injectedDiv.setAttribute('class', INJECTED_DIV_CLASS); appendTo.appendChild(injectedDiv); const closeInstant = () => { + analytics.trackInstantClosed(); if (!_.isUndefined(config.onClose)) { config.onClose(); } diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 5bc9bb385f..b1a2d2b634 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -1,7 +1,7 @@ import { BuyQuote } from '@0x/asset-buyer'; import * as _ from 'lodash'; -import { AffiliateInfo, Asset, Network, OrderSource, ProviderState } from '../types'; +import { AffiliateInfo, Asset, Network, OrderProcessState, OrderSource, ProviderState } from '../types'; import { EventProperties, heapUtil } from './heap'; @@ -18,6 +18,7 @@ export const evaluateIfEnabled = (fnCall: () => void) => { enum EventNames { INSTANT_OPENED = 'Instant - Opened', + INSTANT_CLOSED = 'Instant - Closed', ACCOUNT_LOCKED = 'Account - Locked', ACCOUNT_READY = 'Account - Ready', ACCOUNT_UNLOCK_REQUESTED = 'Account - Unlock Requested', @@ -37,6 +38,7 @@ enum EventNames { TOKEN_SELECTOR_CLOSED = 'Token Selector - Closed', TOKEN_SELECTOR_CHOSE = 'Token Selector - Chose', TOKEN_SELECTOR_SEARCHED = 'Token Selector - Searched', + TRANSACTION_VIEWED = 'Transaction - Viewed', } const track = (eventName: EventNames, eventProperties: EventProperties = {}): void => { @@ -133,6 +135,7 @@ export const analytics = { return eventOptions; }, trackInstantOpened: trackingEventFnWithoutPayload(EventNames.INSTANT_OPENED), + trackInstantClosed: trackingEventFnWithoutPayload(EventNames.INSTANT_CLOSED), trackAccountLocked: trackingEventFnWithoutPayload(EventNames.ACCOUNT_LOCKED), trackAccountReady: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_READY)({ address }), trackAccountUnlockRequested: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_REQUESTED), @@ -177,4 +180,6 @@ export const analytics = { trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_CHOSE)(payload), trackTokenSelectorSearched: (searchText: string) => trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_SEARCHED)({ searchText }), + trackTransactionViewed: (orderProcesState: OrderProcessState) => + trackingEventFnWithPayload(EventNames.TRANSACTION_VIEWED)({ orderState: orderProcesState }), }; From c5d6b925e4e04d5b6cfd0623d0a1449ba69d3a30 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 27 Nov 2018 15:10:40 -0800 Subject: [PATCH 025/230] feat(instant): Quote fetch tracking --- .../src/components/zero_ex_instant_provider.tsx | 7 +++++-- .../containers/selected_erc20_asset_amount_input.ts | 3 ++- packages/instant/src/redux/async_data.ts | 11 ++++++++--- packages/instant/src/types.ts | 5 +++++ packages/instant/src/util/analytics.ts | 9 ++++++++- packages/instant/src/util/buy_quote_updater.ts | 11 +++++++++-- packages/instant/src/util/heartbeater_factory.ts | 2 ++ 7 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index e006a5a5fe..9f2c95e36e 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -11,7 +11,7 @@ import { asyncData } from '../redux/async_data'; import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; -import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource } from '../types'; +import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource, QuoteFetchedVia } from '../types'; import { analytics, disableAnalytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; @@ -115,7 +115,10 @@ export class ZeroExInstantProvider extends React.Component { const { buyOrderState, providerState, selectedAsset, selectedAssetUnitAmount, affiliateInfo } = state; const assetBuyer = providerState.assetBuyer; @@ -99,7 +99,12 @@ export const asyncData = { dispatch, selectedAsset as ERC20Asset, selectedAssetUnitAmount, - { setPending: !options.updateSilently, dispatchErrors: !options.updateSilently, affiliateInfo }, + { + setPending: !options.updateSilently, + dispatchErrors: !options.updateSilently, + fetchedVia: options.fetchedVia, + affiliateInfo, + }, ); } }, diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 999d50fed7..a8139c1857 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -21,6 +21,11 @@ export enum OrderProcessState { Failure = 'FAILURE', } +export enum QuoteFetchedVia { + Manual = 'Manual', + Heartbeat = 'Heartbeat', +} + export interface SimulatedProgress { startTimeUnix: number; expectedEndTimeUnix: number; diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 5bc9bb385f..204b6921d9 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -1,7 +1,7 @@ import { BuyQuote } from '@0x/asset-buyer'; import * as _ from 'lodash'; -import { AffiliateInfo, Asset, Network, OrderSource, ProviderState } from '../types'; +import { AffiliateInfo, Asset, Network, OrderSource, ProviderState, QuoteFetchedVia } from '../types'; import { EventProperties, heapUtil } from './heap'; @@ -37,6 +37,8 @@ enum EventNames { TOKEN_SELECTOR_CLOSED = 'Token Selector - Closed', TOKEN_SELECTOR_CHOSE = 'Token Selector - Chose', TOKEN_SELECTOR_SEARCHED = 'Token Selector - Searched', + QUOTE_FETCHED = 'Quote - Fetched', + QUOTE_ERROR = 'Quote - Error', } const track = (eventName: EventNames, eventProperties: EventProperties = {}): void => { @@ -177,4 +179,9 @@ export const analytics = { trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_CHOSE)(payload), trackTokenSelectorSearched: (searchText: string) => trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_SEARCHED)({ searchText }), + trackQuoteFetched: (buyQuote: BuyQuote, fetchedVia: QuoteFetchedVia) => + trackingEventFnWithPayload(EventNames.QUOTE_FETCHED)({ + ...buyQuoteEventProperties(buyQuote), + fetchedVia, + }), }; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 2fd16d7818..59d3a85af7 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -6,7 +6,8 @@ import { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; import { Action, actions } from '../redux/actions'; -import { AffiliateInfo, ERC20Asset } from '../types'; +import { AffiliateInfo, ERC20Asset, QuoteFetchedVia } from '../types'; +import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; @@ -16,7 +17,12 @@ export const buyQuoteUpdater = { dispatch: Dispatch, asset: ERC20Asset, assetUnitAmount: BigNumber, - options: { setPending: boolean; dispatchErrors: boolean; affiliateInfo?: AffiliateInfo }, + options: { + setPending: boolean; + dispatchErrors: boolean; + fetchedVia: QuoteFetchedVia; + affiliateInfo?: AffiliateInfo; + }, ): Promise => { // get a new buy quote. const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetUnitAmount, asset.metaData.decimals); @@ -58,5 +64,6 @@ export const buyQuoteUpdater = { errorFlasher.clearError(dispatch); // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(newBuyQuote)); + analytics.trackQuoteFetched(newBuyQuote, options.fetchedVia); }, }; diff --git a/packages/instant/src/util/heartbeater_factory.ts b/packages/instant/src/util/heartbeater_factory.ts index 2b852fb0d5..bf9e4291f8 100644 --- a/packages/instant/src/util/heartbeater_factory.ts +++ b/packages/instant/src/util/heartbeater_factory.ts @@ -1,5 +1,6 @@ import { asyncData } from '../redux/async_data'; import { Store } from '../redux/store'; +import { QuoteFetchedVia } from '../types'; import { Heartbeater } from './heartbeater'; @@ -19,6 +20,7 @@ export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): H return new Heartbeater(async () => { await asyncData.fetchCurrentBuyQuoteAndDispatchToStore(store.getState(), store.dispatch, { updateSilently: true, + fetchedVia: QuoteFetchedVia.Heartbeat, }); }, shouldPerformImmediatelyOnStart); }; From d3739488aed89ce98e805d48ec14a0d2608f85d7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 27 Nov 2018 15:28:38 -0800 Subject: [PATCH 026/230] Tracking quote errors --- packages/instant/src/util/analytics.ts | 16 ++++++++++++++++ packages/instant/src/util/buy_quote_updater.ts | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 204b6921d9..dd6021453c 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -1,4 +1,5 @@ import { BuyQuote } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { AffiliateInfo, Asset, Network, OrderSource, ProviderState, QuoteFetchedVia } from '../types'; @@ -184,4 +185,19 @@ export const analytics = { ...buyQuoteEventProperties(buyQuote), fetchedVia, }), + trackQuoteError: ( + errorMessage: string, + assetName: string, + assetData: string, + assetAmount: BigNumber, + fetchedVia: QuoteFetchedVia, + ) => { + trackingEventFnWithPayload(EventNames.QUOTE_ERROR)({ + errorMessage, + assetName, + assetData, + assetAmount: assetAmount.toString(), + fetchedVia, + }); + }, }; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 59d3a85af7..3c982ed1f4 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -37,6 +37,13 @@ export const buyQuoteUpdater = { } catch (error) { if (options.dispatchErrors) { dispatch(actions.setQuoteRequestStateFailure()); + analytics.trackQuoteError( + error.message ? error.message : 'other', + asset.metaData.name, + asset.assetData, + assetUnitAmount, + options.fetchedVia, + ); let errorMessage; if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); From 8b80d28029c6d840c7401b06a15652523f5faa60 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Wed, 7 Nov 2018 10:32:49 -0800 Subject: [PATCH 027/230] feat: Implement MultiAssetProxy --- packages/contracts/compiler.json | 1 + .../protocol/AssetProxy/MultiAssetProxy.sol | 249 ++++++++++++++++++ .../AssetProxy/interfaces/IAssetData.sol | 11 +- 3 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json index af3980b4ea..c824e4645a 100644 --- a/packages/contracts/compiler.json +++ b/packages/contracts/compiler.json @@ -38,6 +38,7 @@ "IValidator", "IWallet", "MixinAuthorizable", + "MultiAssetProxy", "MultiSigWallet", "MultiSigWalletWithTimeLock", "OrderValidator", diff --git a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol new file mode 100644 index 0000000000..9260ead8fd --- /dev/null +++ b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol @@ -0,0 +1,249 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../../utils/SafeMath/SafeMath.sol"; +import "../Exchange/MixinAssetProxyDispatcher.sol"; +import "./MixinAuthorizable.sol"; + + +contract MultiAssetProxy is + SafeMath, + MixinAssetProxyDispatcher, + MixinAuthorizable +{ + // Id of this proxy. + bytes4 constant internal PROXY_ID = bytes4(keccak256("MultiAsset(uint256[],bytes[])")); + + // solhint-disable-next-line payable-fallback + function () + external + { + assembly { + // The first 4 bytes of calldata holds the function selector + let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) + + // `transferFrom` will be called with the following parameters: + // assetData Encoded byte array. + // from Address to transfer asset from. + // to Address to transfer asset to. + // amount Amount of asset to transfer. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { + + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + mstore(0, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(32, authorized_slot) + + // Revert if authorized[msg.sender] == false + if iszero(sload(keccak256(0, 64))) { + // Revert with `Error("SENDER_NOT_AUTHORIZED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) + revert(0, 100) + } + + // `transferFrom`. + // The function is marked `external`, so no abi decodeding is done for + // us. Instead, we expect the `calldata` memory to contain the + // following: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + // + // (*): offset is computed from start of function parameters, so offset + // by an additional 4 bytes in the calldata. + // + // (**): see table below to compute length of assetData Contents + // + // WARNING: The ABIv2 specification allows additional padding between + // the Params and Data section. This will result in a larger + // offset to assetData. + + // Load offset to `assetData` + let assetDataOffset := calldataload(4) + + // Asset data itself is encoded as follows: + // + // | Area | Offset | Length | Contents | + // |----------|-------------|---------|-------------------------------------| + // | Header | 0 | 4 | assetProxyId | + // | Params | | 2 * 32 | function parameters: | + // | | 4 | | 1. offset to amounts (*) | + // | | 36 | | 2. offset to nestedAssetData (*) | + // | Data | | | amounts: | + // | | 68 | 32 | amounts Length | + // | | 100 | a | amounts Contents | + // | | | | nestedAssetData: | + // | | 100 + a | 32 | nestedAssetData Length | + // | | 132 + a | b | nestedAssetData Contents (offsets) | + // | | 132 + a + b | | nestedAssetData[0, ..., len] | + + // Load offset to `amounts` + // We must add 4 (function selector) + 32 (assetData len) + 4 (assetProxyId) to the assetData offset + let amountsOffset := calldataload(add(assetDataOffset, 40)) + + // Load offsets to `nestedAssetData` + let nestedAssetDataOffset := calldataload(add(assetDataOffset, 72)) + + // Load number of elements in `amounts` + // We must add 4 (function selector) + 128 (4 params * 32) + 32 (assetData len) + 4 (assetProxyId) + 32 (amounts len) to the amounts offset + let amountsContentsStart := add(amountsOffset, 200) + let amountsLen := calldataload(sub(amountsContentsStart, 32)) + + // Load number of elements in `nestedAssetData` + let nestedAssetDataContentsStart := add(nestedAssetDataOffset, 200) + let nestedAssetDataLen := calldataload(sub(nestedAssetDataContentsStart, 32)) + + // Revert if number of elements in `amounts` differs from number of elements in `nestedAssetData` + if iszero(eq(amountsLen, nestedAssetDataLen)) { + // Revert with `Error("LENGTH_MISMATCH")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000f4c454e4754485f4d49534d4154434800000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + // Copy `transferFrom` selector, offset to `assetData`, `from`, and `to` from calldata to memory + calldatacopy( + 0, // memory can safely be overwritten from beginning + 0, // start of calldata + 100 // length of selector (4) and 3 params (32 * 3) + ) + + // Overwrite existing offset to `assetData` with our own + mstore(4, 128) + + // Load `amount` + let amount := calldataload(100) + + // Calculate number of bytes in `amounts` contents + let amountsByteLen := mul(amountsLen, 32) + + // Loop through `amounts` and `nestedAssetData`, calling `transferFrom` for each respective element + for {let i := 0} lt(i, amountsByteLen) {i := add(i, 32)} { + + // Calculate the total amount + let amountsElement := calldataload(add(amountsContentsStart, i)) + let totalAmount := mul(amountsElement, amount) + + // Revert if multiplication resulted in an overflow + if iszero(eq(div(totalAmount, amount), amountsElement)) { + // Revert with `Error("UINT256_OVERFLOW")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + // Write `totalAmount` to memory + mstore(100, totalAmount) + + // Load offset to `nestedAssetData[i]` + let nestedAssetDataElementOffset := calldataload(add(nestedAssetDataContentsStart, i)) + + // Load length of `nestedAssetData[i]` + let nestedAssetDataElementContentsStart := add(nestedAssetDataElementOffset, 264) + let nestedAssetDataElementLen := calldataload(sub(nestedAssetDataElementContentsStart, 32)) + + // Revert if the `nestedAssetData` does not contain a 4 byte `assetProxyId` + if lt(nestedAssetDataElementLen, 4) { + // Revert with `Error("LENGTH_GREATER_THAN_3_REQUIRED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001e4c454e4754485f475245415445525f5448414e5f335f524551554952) + mstore(96, 0x4544000000000000000000000000000000000000000000000000000000000000) + revert(0, 100) + } + + // Load AssetProxy id + let assetProxyId := and(calldataload(nestedAssetDataElementContentsStart), 0xffffffff00000000000000000000000000000000000000000000000000000000) + + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + mstore(132, assetProxyId) + mstore(164, assetProxies_slot) + let assetProxy := sload(keccak256(132, 64)) + + // Revert if AssetProxy with given id does not exist + if iszero(assetProxy) { + // Revert with `Error("ASSET_PROXY_DOES_NOT_EXIST")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001a41535345545f50524f58595f444f45535f4e4f545f45584953540000) + mstore(96, 0) + revert(0, 100) + } + + // Copy `nestedAssetData[i]` from calldata to memory + calldatacopy( + 132, // memory slot after `amounts[i]` + nestedAssetDataElementOffset, // location of `nestedAssetData[i]` in calldata + add(nestedAssetDataElementLen, 32) // `nestedAssetData[i].length` plus 32 byte length + ) + + // call `assetProxy.transferFrom` + let success := call( + gas, // forward all gas + assetProxy, // call address of asset proxy + 0, // don't send any ETH + 0, // pointer to start of input + add(164, nestedAssetDataElementLen), // length of input + 100, // write output over memory that won't be reused + 512 // reserve 512 bytes for output + ) + + if iszero(success) { + revert(100, returndatasize()) + } + } + + // Return if no `transferFrom` calls reverted + return(0, 0) + } + + // Revert if undefined function is called + revert(0, 0) + } + } + + /// @dev Gets the proxy id associated with the proxy address. + /// @return Proxy id. + function getProxyId() + external + pure + returns (bytes4) + { + return PROXY_ID; + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol b/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol index 3e76e38dd0..21130a480e 100644 --- a/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol +++ b/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol @@ -18,6 +18,7 @@ // solhint-disable pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; // @dev Interface of the asset proxy's assetData. @@ -31,8 +32,14 @@ interface IAssetData { function ERC721Token( address tokenContract, - uint256 tokenId, - bytes receiverData + uint256 tokenId + ) + external + pure; + + function MultiAsset( + uint256[] amounts, + bytes[] nestedAssetData ) external pure; From 037e63ab49aba623c9878be3d39b8435fa3b987e Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 11 Nov 2018 20:08:24 -0800 Subject: [PATCH 028/230] Factor offsets into calldata locations --- .../protocol/AssetProxy/MultiAssetProxy.sol | 68 +++++++++++++++---- .../AssetProxy/interfaces/IAssetData.sol | 9 +-- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol index 9260ead8fd..531ee22645 100644 --- a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol +++ b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol @@ -49,7 +49,7 @@ contract MultiAssetProxy is // To lookup a value in a mapping, we load from the storage location keccak256(k, p), // where k is the key left padded to 32 bytes and p is the storage slot - mstore(0, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(0, caller) mstore(32, authorized_slot) // Revert if authorized[msg.sender] == false @@ -63,7 +63,7 @@ contract MultiAssetProxy is } // `transferFrom`. - // The function is marked `external`, so no abi decodeding is done for + // The function is marked `external`, so no abi decoding is done for // us. Instead, we expect the `calldata` memory to contain the // following: // @@ -107,20 +107,43 @@ contract MultiAssetProxy is // | | 132 + a | b | nestedAssetData Contents (offsets) | // | | 132 + a + b | | nestedAssetData[0, ..., len] | - // Load offset to `amounts` - // We must add 4 (function selector) + 32 (assetData len) + 4 (assetProxyId) to the assetData offset + // In order to find the offset to `amounts`, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) let amountsOffset := calldataload(add(assetDataOffset, 40)) - // Load offsets to `nestedAssetData` + // In order to find the offset to `nestedAssetData`, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + 32 (amounts offset) let nestedAssetDataOffset := calldataload(add(assetDataOffset, 72)) + // In order to find the start of the `amounts` contents, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + amountsOffset + // + 32 (amounts len) + let amountsContentsStart := add(assetDataOffset, add(amountsOffset, 72)) + // Load number of elements in `amounts` - // We must add 4 (function selector) + 128 (4 params * 32) + 32 (assetData len) + 4 (assetProxyId) + 32 (amounts len) to the amounts offset - let amountsContentsStart := add(amountsOffset, 200) let amountsLen := calldataload(sub(amountsContentsStart, 32)) + // In order to find the start of the `nestedAssetData` contents, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + nestedAssetDataOffset + // + 32 (nestedAssetData len) + let nestedAssetDataContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, 72)) + // Load number of elements in `nestedAssetData` - let nestedAssetDataContentsStart := add(nestedAssetDataOffset, 200) let nestedAssetDataLen := calldataload(sub(nestedAssetDataContentsStart, 32)) // Revert if number of elements in `amounts` differs from number of elements in `nestedAssetData` @@ -172,9 +195,20 @@ contract MultiAssetProxy is // Load offset to `nestedAssetData[i]` let nestedAssetDataElementOffset := calldataload(add(nestedAssetDataContentsStart, i)) + // In order to find the start of the `nestedAssetData[i]` contents, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + nestedAssetDataOffset + // + 32 (nestedAssetData len) + // + nestedAssetDataElementOffset + // + 32 (nestedAssetDataElement len) + let nestedAssetDataElementContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, add(nestedAssetDataElementOffset, 104))) + // Load length of `nestedAssetData[i]` - let nestedAssetDataElementContentsStart := add(nestedAssetDataElementOffset, 264) - let nestedAssetDataElementLen := calldataload(sub(nestedAssetDataElementContentsStart, 32)) + let nestedAssetDataElementLenStart := sub(nestedAssetDataElementContentsStart, 32) + let nestedAssetDataElementLen := calldataload(nestedAssetDataElementLenStart) // Revert if the `nestedAssetData` does not contain a 4 byte `assetProxyId` if lt(nestedAssetDataElementLen, 4) { @@ -208,7 +242,7 @@ contract MultiAssetProxy is // Copy `nestedAssetData[i]` from calldata to memory calldatacopy( 132, // memory slot after `amounts[i]` - nestedAssetDataElementOffset, // location of `nestedAssetData[i]` in calldata + nestedAssetDataElementLenStart, // location of `nestedAssetData[i]` in calldata add(nestedAssetDataElementLen, 32) // `nestedAssetData[i].length` plus 32 byte length ) @@ -219,12 +253,18 @@ contract MultiAssetProxy is 0, // don't send any ETH 0, // pointer to start of input add(164, nestedAssetDataElementLen), // length of input - 100, // write output over memory that won't be reused - 512 // reserve 512 bytes for output + 0, // write output over memory that won't be reused + 0 // reserve 512 bytes for output ) + // Revert with reason given by AssetProxy if `transferFrom` call failed if iszero(success) { - revert(100, returndatasize()) + returndatacopy( + 0, // copy to memory at 0 + 0, // copy from return data at 0 + returndatasize() // copy all return data + ) + revert(0, returndatasize()) } } diff --git a/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol b/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol index 21130a480e..e2da68919f 100644 --- a/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol +++ b/packages/contracts/contracts/protocol/AssetProxy/interfaces/IAssetData.sol @@ -27,21 +27,18 @@ pragma experimental ABIEncoderV2; interface IAssetData { function ERC20Token(address tokenContract) - external - pure; + external; function ERC721Token( address tokenContract, uint256 tokenId ) - external - pure; + external; function MultiAsset( uint256[] amounts, bytes[] nestedAssetData ) - external - pure; + external; } From d146e15ff3741dfd310b4e0b25166bb526b533f0 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 11 Nov 2018 20:08:58 -0800 Subject: [PATCH 029/230] Fix async loops in erc721Wrapper --- packages/contracts/test/utils/constants.ts | 2 +- packages/contracts/test/utils/erc721_wrapper.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts/test/utils/constants.ts b/packages/contracts/test/utils/constants.ts index cd21330e9d..d2c3ab512c 100644 --- a/packages/contracts/test/utils/constants.ts +++ b/packages/contracts/test/utils/constants.ts @@ -35,7 +35,7 @@ export const constants = { DUMMY_TOKEN_TOTAL_SUPPLY: new BigNumber(0), NULL_BYTES: '0x', NUM_DUMMY_ERC20_TO_DEPLOY: 3, - NUM_DUMMY_ERC721_TO_DEPLOY: 1, + NUM_DUMMY_ERC721_TO_DEPLOY: 2, NUM_ERC721_TOKENS_TO_MINT: 2, NULL_ADDRESS: '0x0000000000000000000000000000000000000000', UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), diff --git a/packages/contracts/test/utils/erc721_wrapper.ts b/packages/contracts/test/utils/erc721_wrapper.ts index 3ef4e701d2..9bd8fad013 100644 --- a/packages/contracts/test/utils/erc721_wrapper.ts +++ b/packages/contracts/test/utils/erc721_wrapper.ts @@ -29,7 +29,7 @@ export class ERC721Wrapper { this._contractOwnerAddress = contractOwnerAddress; } public async deployDummyTokensAsync(): Promise { - for (let i = 0; i < constants.NUM_DUMMY_ERC721_TO_DEPLOY; i++) { + for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) { this._dummyTokenContracts.push( await DummyERC721TokenContract.deployFrom0xArtifactAsync( artifacts.DummyERC721Token, @@ -61,7 +61,7 @@ export class ERC721Wrapper { this._initialTokenIdsByOwner = {}; for (const dummyTokenContract of this._dummyTokenContracts) { for (const tokenOwnerAddress of this._tokenOwnerAddresses) { - for (let i = 0; i < constants.NUM_ERC721_TOKENS_TO_MINT; i++) { + for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) { const tokenId = generatePseudoRandomSalt(); await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress); if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) { From a0bc97b589db46adcc4e2275aa61022c9e224a3f Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 11 Nov 2018 20:09:13 -0800 Subject: [PATCH 030/230] Add initial MultiAssetProxy tests --- packages/contracts/package.json | 6 +- packages/contracts/src/artifacts/index.ts | 2 + .../contracts/test/asset_proxy/proxies.ts | 669 +++++++++++++----- packages/contracts/tsconfig.json | 1 + 4 files changed, 486 insertions(+), 192 deletions(-) diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 25445c4f87..a6380a4672 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -19,7 +19,8 @@ "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", - "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "run_mocha": + "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", "compile": "sol-compiler --contracts-dir contracts", "clean": "shx rm -rf lib generated-artifacts generated-wrappers", "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers", @@ -32,7 +33,8 @@ "lint-contracts": "solhint contracts/**/**/**/**/*.sol" }, "config": { - "abis": "generated-artifacts/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|ERC20Proxy|ERC721Token|ERC721Proxy|Forwarder|Exchange|ExchangeWrapper|IAssetData|IAssetProxy|InvalidERC721Receiver|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|OrderValidator|ReentrantERC20Token|TestAssetProxyOwner|TestAssetProxyDispatcher|TestConstants|TestExchangeInternals|TestLibBytes|TestLibs|TestSignatureValidator|TestStaticCallReceiver|Validator|Wallet|Whitelist|WETH9|ZRXToken).json" + "abis": + "generated-artifacts/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|ERC20Proxy|ERC721Token|ERC721Proxy|Forwarder|Exchange|ExchangeWrapper|IAssetData|IAssetProxy|InvalidERC721Receiver|MixinAuthorizable|MultiAssetProxy|MultiSigWallet|MultiSigWalletWithTimeLock|OrderValidator|ReentrantERC20Token|TestAssetProxyOwner|TestAssetProxyDispatcher|TestConstants|TestExchangeInternals|TestLibBytes|TestLibs|TestSignatureValidator|TestStaticCallReceiver|Validator|Wallet|Whitelist|WETH9|ZRXToken).json" }, "repository": { "type": "git", diff --git a/packages/contracts/src/artifacts/index.ts b/packages/contracts/src/artifacts/index.ts index c30972a91b..97c1b62092 100644 --- a/packages/contracts/src/artifacts/index.ts +++ b/packages/contracts/src/artifacts/index.ts @@ -19,6 +19,7 @@ import * as InvalidERC721Receiver from '../../generated-artifacts/InvalidERC721R import * as IValidator from '../../generated-artifacts/IValidator.json'; import * as IWallet from '../../generated-artifacts/IWallet.json'; import * as MixinAuthorizable from '../../generated-artifacts/MixinAuthorizable.json'; +import * as MultiAssetProxy from '../../generated-artifacts/MultiAssetProxy.json'; import * as MultiSigWallet from '../../generated-artifacts/MultiSigWallet.json'; import * as MultiSigWalletWithTimeLock from '../../generated-artifacts/MultiSigWalletWithTimeLock.json'; import * as OrderValidator from '../../generated-artifacts/OrderValidator.json'; @@ -57,6 +58,7 @@ export const artifacts = { IWallet: IWallet as ContractArtifact, InvalidERC721Receiver: InvalidERC721Receiver as ContractArtifact, MixinAuthorizable: MixinAuthorizable as ContractArtifact, + MultiAssetProxy: MultiAssetProxy as ContractArtifact, MultiSigWallet: MultiSigWallet as ContractArtifact, MultiSigWalletWithTimeLock: MultiSigWalletWithTimeLock as ContractArtifact, OrderValidator: OrderValidator as ContractArtifact, diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index b8305993ee..94ea408f56 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -12,7 +12,9 @@ import { DummyMultipleReturnERC20TokenContract } from '../../generated-wrappers/ import { DummyNoReturnERC20TokenContract } from '../../generated-wrappers/dummy_no_return_erc20_token'; import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; +import { IAssetDataContract } from '../../generated-wrappers/i_asset_data'; import { IAssetProxyContract } from '../../generated-wrappers/i_asset_proxy'; +import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy'; import { artifacts } from '../../src/artifacts'; import { expectTransactionFailedAsync, expectTransactionFailedWithoutReasonAsync } from '../utils/assertions'; import { chaiSetup } from '../utils/chai_setup'; @@ -30,26 +32,35 @@ const assetProxyInterface = new IAssetProxyContract( constants.NULL_ADDRESS, provider, ); +const assetDataInterface = new IAssetDataContract( + artifacts.IAssetData.compilerOutput.abi, + constants.NULL_ADDRESS, + provider, +); // tslint:disable:no-unnecessary-type-assertion -describe('Asset Transfer Proxies', () => { +describe.only('Asset Transfer Proxies', () => { let owner: string; let notAuthorized: string; - let exchangeAddress: string; - let makerAddress: string; - let takerAddress: string; + let authorized: string; + let fromAddress: string; + let toAddress: string; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + let erc721TokenA: DummyERC721TokenContract; + let erc721TokenB: DummyERC721TokenContract; let erc721Receiver: DummyERC721ReceiverContract; let erc20Proxy: ERC20ProxyContract; let erc721Proxy: ERC721ProxyContract; let noReturnErc20Token: DummyNoReturnERC20TokenContract; let multipleReturnErc20Token: DummyMultipleReturnERC20TokenContract; + let multiAssetProxy: MultiAssetProxyContract; let erc20Wrapper: ERC20Wrapper; let erc721Wrapper: ERC721Wrapper; - let erc721MakerTokenId: BigNumber; + let erc721AFromTokenId: BigNumber; + let erc721BFromTokenId: BigNumber; before(async () => { await blockchainLifecycle.startAsync(); @@ -59,41 +70,73 @@ describe('Asset Transfer Proxies', () => { }); before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = _.slice( - accounts, - 0, - 5, - )); + const usedAddresses = ([owner, notAuthorized, authorized, fromAddress, toAddress] = _.slice(accounts, 0, 5)); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - const numDummyErc20ToDeploy = 1; - [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS); + // Deploy AssetProxies erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + artifacts.MultiAssetProxy, + provider, + txDefaults, + ); + + // Configure ERC20Proxy await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeAddress, { + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { from: owner, }), constants.AWAIT_TRANSACTION_MINED_MS, ); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerTokenId = erc721Balances[makerAddress][erc721Token.address][0]; + // Configure ERC721Proxy await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeAddress, { + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { from: owner, }), constants.AWAIT_TRANSACTION_MINED_MS, ); - erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync( - artifacts.DummyERC721Receiver, - provider, - txDefaults, + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure MultiAssetProxy + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Deploy and configure ERC20 tokens + const numDummyErc20ToDeploy = 2; + [erc20TokenA, erc20TokenB] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, ); noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync( artifacts.DummyNoReturnERC20Token, @@ -104,18 +147,6 @@ describe('Asset Transfer Proxies', () => { constants.DUMMY_TOKEN_DECIMALS, constants.DUMMY_TOKEN_TOTAL_SUPPLY, ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.setBalance.sendTransactionAsync(makerAddress, constants.INITIAL_ERC20_BALANCE), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.approve.sendTransactionAsync( - erc20Proxy.address, - constants.INITIAL_ERC20_ALLOWANCE, - { from: makerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync( artifacts.DummyMultipleReturnERC20Token, provider, @@ -125,9 +156,23 @@ describe('Asset Transfer Proxies', () => { constants.DUMMY_TOKEN_DECIMALS, constants.DUMMY_TOKEN_TOTAL_SUPPLY, ); + + await erc20Wrapper.setBalancesAndAllowancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await noReturnErc20Token.setBalance.sendTransactionAsync(fromAddress, constants.INITIAL_ERC20_BALANCE), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await noReturnErc20Token.approve.sendTransactionAsync( + erc20Proxy.address, + constants.INITIAL_ERC20_ALLOWANCE, + { from: fromAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); await web3Wrapper.awaitTransactionSuccessAsync( await multipleReturnErc20Token.setBalance.sendTransactionAsync( - makerAddress, + fromAddress, constants.INITIAL_ERC20_BALANCE, ), constants.AWAIT_TRANSACTION_MINED_MS, @@ -136,10 +181,23 @@ describe('Asset Transfer Proxies', () => { await multipleReturnErc20Token.approve.sendTransactionAsync( erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, - { from: makerAddress }, + { from: fromAddress }, ), constants.AWAIT_TRANSACTION_MINED_MS, ); + + // Deploy and configure ERC721 tokens and receiver + [erc721TokenA, erc721TokenB] = await erc721Wrapper.deployDummyTokensAsync(); + erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync( + artifacts.DummyERC721Receiver, + provider, + txDefaults, + ); + + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721AFromTokenId = erc721Balances[fromAddress][erc721TokenA.address][0]; + erc721BFromTokenId = erc721Balances[fromAddress][erc721TokenB.address][0]; }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -147,7 +205,8 @@ describe('Asset Transfer Proxies', () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('Transfer Proxy - ERC20', () => { + + describe('ERC20Proxy', () => { it('should revert if undefined function is called', async () => { const undefinedSelector = '0x01020304'; await expectTransactionFailedWithoutReasonAsync( @@ -159,141 +218,146 @@ describe('Asset Transfer Proxies', () => { }), ); }); + it('should have an id of 0xf47261b0', async () => { + const proxyId = await erc20Proxy.getProxyId.callAsync(); + const expectedProxyId = '0xf47261b0'; + expect(proxyId).to.equal(expectedProxyId); + }); describe('transferFrom', () => { it('should successfully transfer tokens', async () => { // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Perform a transfer from fromAddress to toAddress const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(amount), + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(amount), ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].add(amount), + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(amount), ); }); it('should successfully transfer tokens that do not return a value', async () => { // Construct ERC20 asset data const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address); - // Perform a transfer from makerAddress to takerAddress - const initialMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + // Perform a transfer from fromAddress to toAddress + const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful - const newMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const newTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - expect(newMakerBalance).to.be.bignumber.equal(initialMakerBalance.minus(amount)); - expect(newTakerBalance).to.be.bignumber.equal(initialTakerBalance.plus(amount)); + const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); + expect(newFromBalance).to.be.bignumber.equal(initialFromBalance.minus(amount)); + expect(newToBalance).to.be.bignumber.equal(initialToBalance.plus(amount)); }); it('should successfully transfer tokens and ignore extra assetData', async () => { // Construct ERC20 asset data const extraData = '0102030405060708'; - const encodedAssetData = `${assetDataUtils.encodeERC20AssetData(zrxToken.address)}${extraData}`; - // Perform a transfer from makerAddress to takerAddress + const encodedAssetData = `${assetDataUtils.encodeERC20AssetData(erc20TokenA.address)}${extraData}`; + // Perform a transfer from fromAddress to toAddress const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(amount), + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(amount), ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].add(amount), + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(amount), ); }); it('should do nothing if transferring 0 amount of a token', async () => { // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Perform a transfer from fromAddress to toAddress const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(0); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address], + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address], ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address], + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address], ); }); - it('should throw if allowances are too low', async () => { + it('should revert if allowances are too low', async () => { // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); // Create allowance less than transfer amount. Set allowance on proxy. const allowance = new BigNumber(0); const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, + await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, allowance, { + from: fromAddress, }), constants.AWAIT_TRANSACTION_MINED_MS, ); @@ -303,7 +367,7 @@ describe('Asset Transfer Proxies', () => { web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); @@ -311,7 +375,7 @@ describe('Asset Transfer Proxies', () => { expect(newBalances).to.deep.equal(erc20Balances); }); - it('should throw if allowances are too low and token does not return a value', async () => { + it('should revert if allowances are too low and token does not return a value', async () => { // Construct ERC20 asset data const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address); // Create allowance less than transfer amount. Set allowance on proxy. @@ -319,42 +383,42 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await noReturnErc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, + from: fromAddress, }), constants.AWAIT_TRANSACTION_MINED_MS, ); - const initialMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); // Perform a transfer; expect this to fail. await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); - const newMakerBalance = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const newTakerBalance = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - expect(newMakerBalance).to.be.bignumber.equal(initialMakerBalance); - expect(newTakerBalance).to.be.bignumber.equal(initialTakerBalance); + const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); + expect(newFromBalance).to.be.bignumber.equal(initialFromBalance); + expect(newToBalance).to.be.bignumber.equal(initialToBalance); }); - it('should throw if requesting address is not authorized', async () => { + it('should revert if caller is not authorized', async () => { // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); const erc20Balances = await erc20Wrapper.getBalancesAsync(); @@ -370,42 +434,36 @@ describe('Asset Transfer Proxies', () => { expect(newBalances).to.deep.equal(erc20Balances); }); - it('should throw if token returns more than 32 bytes', async () => { + it('should revert if token returns more than 32 bytes', async () => { // Construct ERC20 asset data const encodedAssetData = assetDataUtils.encodeERC20AssetData(multipleReturnErc20Token.address); const amount = new BigNumber(10); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); - const initialMakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialTakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(takerAddress); + const initialFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress); + const initialToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress); // Perform a transfer; expect this to fail. await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc20Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); - const newMakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(makerAddress); - const newTakerBalance = await multipleReturnErc20Token.balanceOf.callAsync(takerAddress); - expect(newMakerBalance).to.be.bignumber.equal(initialMakerBalance); - expect(newTakerBalance).to.be.bignumber.equal(initialTakerBalance); + const newFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress); + const newToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress); + expect(newFromBalance).to.be.bignumber.equal(initialFromBalance); + expect(newToBalance).to.be.bignumber.equal(initialToBalance); }); }); - - it('should have an id of 0xf47261b0', async () => { - const proxyId = await erc20Proxy.getProxyId.callAsync(); - const expectedProxyId = '0xf47261b0'; - expect(proxyId).to.equal(expectedProxyId); - }); }); - describe('Transfer Proxy - ERC721', () => { + describe('ERC721Proxy', () => { it('should revert if undefined function is called', async () => { const undefinedSelector = '0x01020304'; await expectTransactionFailedWithoutReasonAsync( @@ -417,76 +475,81 @@ describe('Asset Transfer Proxies', () => { }), ); }); + it('should have an id of 0x02571792', async () => { + const proxyId = await erc721Proxy.getProxyId.callAsync(); + const expectedProxyId = '0x02571792'; + expect(proxyId).to.equal(expectedProxyId); + }); describe('transferFrom', () => { it('should successfully transfer tokens', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(1); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress); }); it('should successfully transfer tokens and ignore extra assetData', async () => { // Construct ERC721 asset data const extraData = '0102030405060708'; const encodedAssetData = `${assetDataUtils.encodeERC721AssetData( - erc721Token.address, - erc721MakerTokenId, + erc721TokenA.address, + erc721AFromTokenId, )}${extraData}`; // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(1); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), constants.AWAIT_TRANSACTION_MINED_MS, ); // Verify transfer was successful - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress); }); it('should not call onERC721Received when transferring to a smart contract', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(1); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, + fromAddress, erc721Receiver.address, amount, ); @@ -495,79 +558,79 @@ describe('Asset Transfer Proxies', () => { await web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, gas: constants.MAX_TRANSFER_FROM_GAS, }), ); // Verify that no log was emitted by erc721 receiver expect(tx.logs.length).to.be.equal(1); // Verify transfer was successful - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.bignumber.equal(erc721Receiver.address); }); - it('should throw if transferring 0 amount of a token', async () => { + it('should revert if transferring 0 amount of a token', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(0); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.InvalidAmount, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); - it('should throw if transferring > 1 amount of a token', async () => { + it('should revert if transferring > 1 amount of a token', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(500); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.InvalidAmount, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); - it('should throw if allowances are too low', async () => { + it('should revert if allowances are too low', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Remove transfer approval for makerAddress. + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Remove transfer approval for fromAddress. await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721MakerTokenId, { - from: makerAddress, + await erc721TokenA.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721AFromTokenId, { + from: fromAddress, }), constants.AWAIT_TRANSACTION_MINED_MS, ); @@ -575,34 +638,34 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(1); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( web3Wrapper.sendTransactionAsync({ to: erc721Proxy.address, data, - from: exchangeAddress, + from: authorized, }), RevertReason.TransferFailed, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); - it('should throw if requesting address is not authorized', async () => { + it('should revert if caller is not authorized', async () => { // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); // Verify pre-condition - const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(ownerMakerAsset).to.be.equal(makerAddress); - // Perform a transfer from makerAddress to takerAddress + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress const amount = new BigNumber(1); const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( encodedAssetData, - makerAddress, - takerAddress, + fromAddress, + toAddress, amount, ); await expectTransactionFailedAsync( @@ -613,16 +676,242 @@ describe('Asset Transfer Proxies', () => { }), RevertReason.SenderNotAuthorized, ); - const newOwner = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); - expect(newOwner).to.be.equal(ownerMakerAsset); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); }); }); - - it('should have an id of 0x02571792', async () => { - const proxyId = await erc721Proxy.getProxyId.callAsync(); - const expectedProxyId = '0x02571792'; + }); + describe.only('MultiAssetProxy', () => { + it('should revert if undefined function is called', async () => { + const undefinedSelector = '0x01020304'; + await expectTransactionFailedWithoutReasonAsync( + web3Wrapper.sendTransactionAsync({ + from: owner, + to: multiAssetProxy.address, + value: constants.ZERO_AMOUNT, + data: undefinedSelector, + }), + ); + }); + it('should have an id of 0x94cfcdd7', async () => { + const proxyId = await multiAssetProxy.getProxyId.callAsync(); + const expectedProxyId = '0x94cfcdd7'; expect(proxyId).to.equal(expectedProxyId); }); + describe('transferFrom', () => { + it('should transfer a single ERC20 token', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const amounts = [erc20Amount]; + const nestedAssetData = [erc20AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalAmount = inputAmount.times(erc20Amount); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(totalAmount), + ); + }); + it('should successfully transfer multiple of the same ERC20 token', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const amounts = [erc20Amount1, erc20Amount2]; + const nestedAssetData = [erc20AssetData1, erc20AssetData2]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalAmount = inputAmount.times(erc20Amount1).plus(inputAmount.times(erc20Amount2)); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(totalAmount), + ); + }); + it('should successfully transfer multiple different ERC20 tokens', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); + const amounts = [erc20Amount1, erc20Amount2]; + const nestedAssetData = [erc20AssetData1, erc20AssetData2]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalErc20AAmount = inputAmount.times(erc20Amount1); + const totalErc20BAmount = inputAmount.times(erc20Amount2); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(totalErc20AAmount), + ); + expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), + ); + expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenB.address].add(totalErc20BAmount), + ); + }); + it('should transfer a single ERC721 token', async () => { + const inputAmount = new BigNumber(1); + const erc721Amount = new BigNumber(1); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc721Amount]; + const nestedAssetData = [erc721AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.equal(toAddress); + }); + it('should successfully transfer multiple of the same ERC721 token', async () => { + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1]; + const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const erc721AssetData2 = assetDataUtils.encodeERC721AssetData( + erc721TokenA.address, + erc721AFromTokenId2, + ); + const inputAmount = new BigNumber(1); + const erc721Amount = new BigNumber(1); + const amounts = [erc721Amount, erc721Amount]; + const nestedAssetData = [erc721AssetData1, erc721AssetData2]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset1).to.be.equal(fromAddress); + const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + expect(ownerFromAsset2).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + gas: constants.MAX_TRANSFER_FROM_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + expect(newOwnerFromAsset1).to.be.equal(toAddress); + expect(newOwnerFromAsset2).to.be.equal(toAddress); + }); + it('should successfully transfer multiple different ERC721 tokens', async () => { + const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const erc721AssetData2 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId); + const inputAmount = new BigNumber(1); + const erc721Amount = new BigNumber(1); + const amounts = [erc721Amount, erc721Amount]; + const nestedAssetData = [erc721AssetData1, erc721AssetData2]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset1).to.be.equal(fromAddress); + const ownerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + expect(ownerFromAsset2).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + gas: constants.MAX_TRANSFER_FROM_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + const newOwnerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + expect(newOwnerFromAsset1).to.be.equal(toAddress); + expect(newOwnerFromAsset2).to.be.equal(toAddress); + }); + it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => {}); + it('should successfully transfer tokens and ignore extra assetData', async () => {}); + it('should successfully transfer correct amounts when the `amount` > 1', async () => {}); + it('should revert if a single transfer fails', async () => {}); + it('should revert if an AssetProxy is not registered', async () => {}); + it('should revert if the length of `amounts` does not match the length of `nestedAssetData`', async () => {}); + it('should revert if amounts multiplication results in an overflow', async () => {}); + it('should revert if an element of `nestedAssetData` is < 4 bytes long', async () => {}); + it('should revert with the same reason as the called AssetProxy', async () => {}); + it('should revert if caller is not authorized', async () => {}); + }); }); }); // tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json index 8b29365cc1..e0f85079a5 100644 --- a/packages/contracts/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -26,6 +26,7 @@ "./generated-artifacts/IWallet.json", "./generated-artifacts/InvalidERC721Receiver.json", "./generated-artifacts/MixinAuthorizable.json", + "./generated-artifacts/MultiAssetProxy.json", "./generated-artifacts/MultiSigWallet.json", "./generated-artifacts/MultiSigWalletWithTimeLock.json", "./generated-artifacts/OrderValidator.json", From b773d5e592cc0ee57c902c0e2692554f54003234 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 12 Nov 2018 11:22:43 -0800 Subject: [PATCH 031/230] feat: Add and revert reasons --- packages/types/CHANGELOG.json | 9 +++++++++ packages/types/src/index.ts | 2 ++ 2 files changed, 11 insertions(+) diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index 0b32b60f04..53b24aff0b 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.4.0", + "changes": [ + { + "note": "Add `LengthMismatch` and `LengthGreaterThan3Required` revert reasons", + "pr": 1224 + } + ] + }, { "version": "1.3.0", "changes": [ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 1a86f45e6a..26d8f8e226 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -195,6 +195,7 @@ export enum RevertReason { FailedExecution = 'FAILED_EXECUTION', AssetProxyAlreadyExists = 'ASSET_PROXY_ALREADY_EXISTS', LengthGreaterThan0Required = 'LENGTH_GREATER_THAN_0_REQUIRED', + LengthGreaterThan3Required = 'LENGTH_GREATER_THAN_3_REQUIRED', LengthGreaterThan131Required = 'LENGTH_GREATER_THAN_131_REQUIRED', Length0Required = 'LENGTH_0_REQUIRED', Length65Required = 'LENGTH_65_REQUIRED', @@ -209,6 +210,7 @@ export enum RevertReason { MakerNotWhitelisted = 'MAKER_NOT_WHITELISTED', TakerNotWhitelisted = 'TAKER_NOT_WHITELISTED', AssetProxyDoesNotExist = 'ASSET_PROXY_DOES_NOT_EXIST', + LengthMismatch = 'LENGTH_MISMATCH', LibBytesGreaterThanZeroLengthRequired = 'GREATER_THAN_ZERO_LENGTH_REQUIRED', LibBytesGreaterOrEqualTo4LengthRequired = 'GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED', LibBytesGreaterOrEqualTo20LengthRequired = 'GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED', From 6f92f0a7b5cc9aa9ccd3a1729d6e26da54ebaddf Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 12 Nov 2018 11:23:34 -0800 Subject: [PATCH 032/230] Add more tests for MAP --- .../protocol/AssetProxy/MultiAssetProxy.sol | 2 +- .../contracts/test/asset_proxy/proxies.ts | 355 +++++++++++++++++- 2 files changed, 342 insertions(+), 15 deletions(-) diff --git a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol index 531ee22645..fc86ee942d 100644 --- a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol +++ b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol @@ -286,4 +286,4 @@ contract MultiAssetProxy is { return PROXY_ID; } -} \ No newline at end of file +} diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 94ea408f56..913c913201 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -39,7 +39,7 @@ const assetDataInterface = new IAssetDataContract( ); // tslint:disable:no-unnecessary-type-assertion -describe.only('Asset Transfer Proxies', () => { +describe('Asset Transfer Proxies', () => { let owner: string; let notAuthorized: string; let authorized: string; @@ -721,7 +721,6 @@ describe.only('Asset Transfer Proxies', () => { }), constants.AWAIT_TRANSACTION_MINED_MS, ); - // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); const totalAmount = inputAmount.times(erc20Amount); expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( @@ -755,7 +754,6 @@ describe.only('Asset Transfer Proxies', () => { }), constants.AWAIT_TRANSACTION_MINED_MS, ); - // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); const totalAmount = inputAmount.times(erc20Amount1).plus(inputAmount.times(erc20Amount2)); expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( @@ -789,7 +787,6 @@ describe.only('Asset Transfer Proxies', () => { }), constants.AWAIT_TRANSACTION_MINED_MS, ); - // Verify transfer was successful const newBalances = await erc20Wrapper.getBalancesAsync(); const totalErc20AAmount = inputAmount.times(erc20Amount1); const totalErc20BAmount = inputAmount.times(erc20Amount2); @@ -901,16 +898,346 @@ describe.only('Asset Transfer Proxies', () => { expect(newOwnerFromAsset1).to.be.equal(toAddress); expect(newOwnerFromAsset2).to.be.equal(toAddress); }); - it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => {}); - it('should successfully transfer tokens and ignore extra assetData', async () => {}); - it('should successfully transfer correct amounts when the `amount` > 1', async () => {}); - it('should revert if a single transfer fails', async () => {}); - it('should revert if an AssetProxy is not registered', async () => {}); - it('should revert if the length of `amounts` does not match the length of `nestedAssetData`', async () => {}); - it('should revert if amounts multiplication results in an overflow', async () => {}); - it('should revert if an element of `nestedAssetData` is < 4 bytes long', async () => {}); - it('should revert with the same reason as the called AssetProxy', async () => {}); - it('should revert if caller is not authorized', async () => {}); + it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721Amount = new BigNumber(1); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalAmount = inputAmount.times(erc20Amount); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(totalAmount), + ); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.equal(toAddress); + }); + it('should successfully transfer tokens and ignore extra assetData', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721Amount = new BigNumber(1); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const extraData = '0102030405060708'; + const assetData = `${assetDataInterface.MultiAsset.getABIEncodedTransactionData( + amounts, + nestedAssetData, + )}${extraData}`; + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalAmount = inputAmount.times(erc20Amount); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(totalAmount), + ); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.equal(toAddress); + }); + it('should successfully transfer correct amounts when the `amount` > 1', async () => { + const inputAmount = new BigNumber(100); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); + const amounts = [erc20Amount1, erc20Amount2]; + const nestedAssetData = [erc20AssetData1, erc20AssetData2]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalErc20AAmount = inputAmount.times(erc20Amount1); + const totalErc20BAmount = inputAmount.times(erc20Amount2); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(totalErc20AAmount), + ); + expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), + ); + expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenB.address].add(totalErc20BAmount), + ); + }); + it('should successfully transfer a large amount of tokens', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); + const erc721Amount = new BigNumber(1); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1]; + const erc721BFromTokenId2 = erc721Balances[fromAddress][erc721TokenB.address][1]; + const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const erc721AssetData2 = assetDataUtils.encodeERC721AssetData( + erc721TokenA.address, + erc721AFromTokenId2, + ); + const erc721AssetData3 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId); + const erc721AssetData4 = assetDataUtils.encodeERC721AssetData( + erc721TokenB.address, + erc721BFromTokenId2, + ); + const amounts = [erc721Amount, erc20Amount1, erc721Amount, erc20Amount2, erc721Amount, erc721Amount]; + const nestedAssetData = [ + erc721AssetData1, + erc20AssetData1, + erc721AssetData2, + erc20AssetData2, + erc721AssetData3, + erc721AssetData4, + ]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset1).to.be.equal(fromAddress); + const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + expect(ownerFromAsset2).to.be.equal(fromAddress); + const ownerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + expect(ownerFromAsset3).to.be.equal(fromAddress); + const ownerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2); + expect(ownerFromAsset4).to.be.equal(fromAddress); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + const newOwnerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + const newOwnerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2); + expect(newOwnerFromAsset1).to.be.equal(toAddress); + expect(newOwnerFromAsset2).to.be.equal(toAddress); + expect(newOwnerFromAsset3).to.be.equal(toAddress); + expect(newOwnerFromAsset4).to.be.equal(toAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalErc20AAmount = inputAmount.times(erc20Amount1); + const totalErc20BAmount = inputAmount.times(erc20Amount2); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].add(totalErc20AAmount), + ); + expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), + ); + expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenB.address].add(totalErc20BAmount), + ); + }); + it('should revert if a single transfer fails', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // 2 is an invalid erc721 amount + const erc721Amount = new BigNumber(2); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.InvalidAmount, + ); + }); + it('should revert if an AssetProxy is not registered', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721Amount = new BigNumber(1); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const invalidProxyId = '0x12345678'; + const invalidErc721AssetData = `${invalidProxyId}${erc721AssetData.slice(10)}`; + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, invalidErc721AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.AssetProxyDoesNotExist, + ); + }); + it('should revert if the length of `amounts` does not match the length of `nestedAssetData`', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc20Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.LengthMismatch, + ); + }); + it('should revert if amounts multiplication results in an overflow', async () => { + const inputAmount = new BigNumber(2).pow(128); + const erc20Amount = new BigNumber(2).pow(128); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const amounts = [erc20Amount]; + const nestedAssetData = [erc20AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.Uint256Overflow, + ); + }); + it('should revert if an element of `nestedAssetData` is < 4 bytes long', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721Amount = new BigNumber(1); + const erc721AssetData = '0x123456'; + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.LengthGreaterThan3Required, + ); + }); + it('should revert if caller is not authorized', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721Amount = new BigNumber(1); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: notAuthorized, + }), + RevertReason.SenderNotAuthorized, + ); + }); }); }); }); From 7b51eddc03762085050eba8dfcf3325360e85403 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 12 Nov 2018 11:28:34 -0800 Subject: [PATCH 033/230] Fix linting errors --- .../contracts/protocol/AssetProxy/MultiAssetProxy.sol | 5 ++++- packages/contracts/test/utils/erc721_wrapper.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol index fc86ee942d..6db42e44f3 100644 --- a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol +++ b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol @@ -221,7 +221,10 @@ contract MultiAssetProxy is } // Load AssetProxy id - let assetProxyId := and(calldataload(nestedAssetDataElementContentsStart), 0xffffffff00000000000000000000000000000000000000000000000000000000) + let assetProxyId := and( + calldataload(nestedAssetDataElementContentsStart), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) // To lookup a value in a mapping, we load from the storage location keccak256(k, p), // where k is the key left padded to 32 bytes and p is the storage slot diff --git a/packages/contracts/test/utils/erc721_wrapper.ts b/packages/contracts/test/utils/erc721_wrapper.ts index 9bd8fad013..e9da553d0a 100644 --- a/packages/contracts/test/utils/erc721_wrapper.ts +++ b/packages/contracts/test/utils/erc721_wrapper.ts @@ -29,6 +29,7 @@ export class ERC721Wrapper { this._contractOwnerAddress = contractOwnerAddress; } public async deployDummyTokensAsync(): Promise { + // tslint:disable-next-line:no-unused-variable for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) { this._dummyTokenContracts.push( await DummyERC721TokenContract.deployFrom0xArtifactAsync( @@ -61,6 +62,7 @@ export class ERC721Wrapper { this._initialTokenIdsByOwner = {}; for (const dummyTokenContract of this._dummyTokenContracts) { for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + // tslint:disable-next-line:no-unused-variable for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) { const tokenId = generatePseudoRandomSalt(); await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress); From 743c43f768e4701847cc4299783a09878f2402ee Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 12 Nov 2018 17:17:43 -0800 Subject: [PATCH 034/230] Add Exchange tests with MultiAsset orders --- .../contracts/test/asset_proxy/proxies.ts | 2 +- packages/contracts/test/exchange/core.ts | 393 ++++++++++++++++-- 2 files changed, 367 insertions(+), 28 deletions(-) diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 913c913201..face0b92b9 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -681,7 +681,7 @@ describe('Asset Transfer Proxies', () => { }); }); }); - describe.only('MultiAssetProxy', () => { + describe('MultiAssetProxy', () => { it('should revert if undefined function is called', async () => { const undefinedSelector = '0x01020304'; await expectTransactionFailedWithoutReasonAsync( diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index fc8dc5346a..9159b0d8f5 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -14,6 +14,8 @@ import { DummyNoReturnERC20TokenContract } from '../../generated-wrappers/dummy_ import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; import { ExchangeCancelEventArgs, ExchangeContract } from '../../generated-wrappers/exchange'; +import { IAssetDataContract } from '../../generated-wrappers/i_asset_data'; +import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy'; import { ReentrantERC20TokenContract } from '../../generated-wrappers/reentrant_erc20_token'; import { TestStaticCallReceiverContract } from '../../generated-wrappers/test_static_call_receiver'; import { artifacts } from '../../src/artifacts'; @@ -31,6 +33,11 @@ import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const assetDataInterface = new IAssetDataContract( + artifacts.IAssetData.compilerOutput.abi, + constants.NULL_ADDRESS, + provider, +); // tslint:disable:no-unnecessary-type-assertion describe('Exchange core', () => { let makerAddress: string; @@ -47,6 +54,7 @@ describe('Exchange core', () => { let exchange: ExchangeContract; let erc20Proxy: ERC20ProxyContract; let erc721Proxy: ERC721ProxyContract; + let multiAssetProxy: MultiAssetProxyContract; let maliciousWallet: TestStaticCallReceiverContract; let maliciousValidator: TestStaticCallReceiverContract; @@ -76,44 +84,26 @@ describe('Exchange core', () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + // Deploy AssetProxies, Exchange, tokens, and malicious contracts + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + artifacts.MultiAssetProxy, + provider, + txDefaults, + ); const numDummyErc20ToDeploy = 3; [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; - exchange = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, txDefaults, assetDataUtils.encodeERC20AssetData(zrxToken.address), ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( artifacts.TestStaticCallReceiver, provider, @@ -126,9 +116,72 @@ describe('Exchange core', () => { exchange.address, ); + // Configure ERC20Proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure ERC721Proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure MultiAssetProxy + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure Exchange + exchangeWrapper = new ExchangeWrapper(exchange, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner); + + // Configure ERC20 tokens + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + // Configure ERC721 tokens + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; + erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; + + // Configure order defaults defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; - const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, exchangeAddress: exchange.address, @@ -707,6 +760,292 @@ describe('Exchange core', () => { }); }); + describe('Testing exchange of multiple assets', () => { + it('should allow multiple assets to be exchanged for a single asset', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + makerAmounts, + makerNestedAssetData, + ); + const makerAssetAmount = new BigNumber(1); + const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const takerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.plus(takerAssetAmount)); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(takerAssetAmount)); + }); + it('should allow multiple assets to be exchanged for multiple assets', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + makerAmounts, + makerNestedAssetData, + ); + const makerAssetAmount = new BigNumber(1); + const takerAmounts = [new BigNumber(10), new BigNumber(1)]; + const takerAssetId = erc721TakerAssetIds[0]; + const takerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(zrxToken.address), + assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + ]; + const takerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + takerAmounts, + takerNestedAssetData, + ); + const takerAssetAmount = new BigNumber(1); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const finalOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.plus(takerAmounts[0].times(takerAssetAmount)), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.minus(takerAmounts[0].times(takerAssetAmount)), + ); + expect(finalOwnerTakerAsset).to.be.equal(makerAddress); + }); + it('should allow an order selling multiple assets to be partially filled', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + makerAmounts, + makerNestedAssetData, + ); + const makerAssetAmount = new BigNumber(30); + const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const takerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus( + makerAmounts[0].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus( + makerAmounts[1].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.plus( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus( + makerAmounts[0].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus( + makerAmounts[1].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.minus( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + }); + it('should allow an order buying multiple assets to be partially filled', async () => { + const takerAmounts = [new BigNumber(10), new BigNumber(20)]; + const takerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const takerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( + takerAmounts, + takerNestedAssetData, + ); + const takerAssetAmount = new BigNumber(30); + const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const makerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.plus( + takerAmounts[0].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.plus( + takerAmounts[1].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.minus( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.minus( + takerAmounts[0].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.minus( + takerAmounts[1].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.plus( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + }); + }); + describe('getOrderInfo', () => { beforeEach(async () => { signedOrder = await orderFactory.newSignedOrderAsync(); From 0b9e9eb0e4fb58ffbda84f0e77c3b0c83902d669 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 26 Nov 2018 17:52:16 -0800 Subject: [PATCH 035/230] Don't load assetProxy if currentProxyid is equal to the last seen proxyid --- .../protocol/AssetProxy/MultiAssetProxy.sol | 26 ++++++++++++------- .../contracts/test/asset_proxy/proxies.ts | 1 + 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol index 6db42e44f3..42231e73b0 100644 --- a/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol +++ b/packages/contracts/contracts/protocol/AssetProxy/MultiAssetProxy.sol @@ -18,13 +18,11 @@ pragma solidity 0.4.24; -import "../../utils/SafeMath/SafeMath.sol"; import "../Exchange/MixinAssetProxyDispatcher.sol"; import "./MixinAuthorizable.sol"; contract MultiAssetProxy is - SafeMath, MixinAssetProxyDispatcher, MixinAuthorizable { @@ -172,6 +170,10 @@ contract MultiAssetProxy is // Calculate number of bytes in `amounts` contents let amountsByteLen := mul(amountsLen, 32) + // Initialize `assetProxyId` and `assetProxy` to 0 + let assetProxyId := 0 + let assetProxy := 0 + // Loop through `amounts` and `nestedAssetData`, calling `transferFrom` for each respective element for {let i := 0} lt(i, amountsByteLen) {i := add(i, 32)} { @@ -221,16 +223,22 @@ contract MultiAssetProxy is } // Load AssetProxy id - let assetProxyId := and( + let currentAssetProxyId := and( calldataload(nestedAssetDataElementContentsStart), 0xffffffff00000000000000000000000000000000000000000000000000000000 ) - // To lookup a value in a mapping, we load from the storage location keccak256(k, p), - // where k is the key left padded to 32 bytes and p is the storage slot - mstore(132, assetProxyId) - mstore(164, assetProxies_slot) - let assetProxy := sload(keccak256(132, 64)) + // Only load `assetProxy` if `currentAssetProxyId` does not equal `assetProxyId` + // We do not need to check if `currentAssetProxyId` is 0 since `assetProxy` is also initialized to 0 + if iszero(eq(currentAssetProxyId, assetProxyId)) { + // Update `assetProxyId` + assetProxyId := currentAssetProxyId + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + mstore(132, assetProxyId) + mstore(164, assetProxies_slot) + assetProxy := sload(keccak256(132, 64)) + } // Revert if AssetProxy with given id does not exist if iszero(assetProxy) { @@ -257,7 +265,7 @@ contract MultiAssetProxy is 0, // pointer to start of input add(164, nestedAssetDataElementLen), // length of input 0, // write output over memory that won't be reused - 0 // reserve 512 bytes for output + 0 // don't copy output to memory ) // Revert with reason given by AssetProxy if `transferFrom` call failed diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index face0b92b9..8fa1e602a2 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -695,6 +695,7 @@ describe('Asset Transfer Proxies', () => { }); it('should have an id of 0x94cfcdd7', async () => { const proxyId = await multiAssetProxy.getProxyId.callAsync(); + // first 4 bytes of `keccak256('MultiAsset(uint256[],bytes[])')` const expectedProxyId = '0x94cfcdd7'; expect(proxyId).to.equal(expectedProxyId); }); From 708f4e9bb887431dec278546f2c01c124f8b61c2 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 26 Nov 2018 17:52:53 -0800 Subject: [PATCH 036/230] Update CHANGELOG --- packages/contracts/CHANGELOG.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/contracts/CHANGELOG.json b/packages/contracts/CHANGELOG.json index 00f94c83b3..7dfa069902 100644 --- a/packages/contracts/CHANGELOG.json +++ b/packages/contracts/CHANGELOG.json @@ -1,4 +1,14 @@ [ + { + "name": "MultiAssetProxy", + "version": "1.0.0", + "changes": [ + { + "note": "Add MultiAssetProxy implementation", + "pr": 1224 + } + ] + }, { "name": "OrderValidator", "version": "1.0.1", From ca894935f269a385f28e5d3a51720282ab403697 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 10:07:11 -0800 Subject: [PATCH 037/230] Take out asset name and data as we can use the selected event properties --- packages/instant/src/util/analytics.ts | 10 +--------- packages/instant/src/util/buy_quote_updater.ts | 8 +------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index dd6021453c..b85c6cee23 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -185,17 +185,9 @@ export const analytics = { ...buyQuoteEventProperties(buyQuote), fetchedVia, }), - trackQuoteError: ( - errorMessage: string, - assetName: string, - assetData: string, - assetAmount: BigNumber, - fetchedVia: QuoteFetchedVia, - ) => { + trackQuoteError: (errorMessage: string, assetAmount: BigNumber, fetchedVia: QuoteFetchedVia) => { trackingEventFnWithPayload(EventNames.QUOTE_ERROR)({ errorMessage, - assetName, - assetData, assetAmount: assetAmount.toString(), fetchedVia, }); diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 3c982ed1f4..d6c4bd71bf 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -37,13 +37,7 @@ export const buyQuoteUpdater = { } catch (error) { if (options.dispatchErrors) { dispatch(actions.setQuoteRequestStateFailure()); - analytics.trackQuoteError( - error.message ? error.message : 'other', - asset.metaData.name, - asset.assetData, - assetUnitAmount, - options.fetchedVia, - ); + analytics.trackQuoteError(error.message ? error.message : 'other', assetUnitAmount, options.fetchedVia); let errorMessage; if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); From 1b23c430fcb6e4e816b60f85343b5fe009ab4754 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 10:25:04 -0800 Subject: [PATCH 038/230] fix(instant): Half opacity instead of darkening on hover for clickable text --- packages/instant/src/components/ui/text.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index 8e573d7b94..e149e2d8e7 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -31,7 +31,7 @@ export const Text: React.StatelessComponent = ({ href, onClick, ...re return ; }; -const darkenOnHoverAmount = 0.3; +const opacityOnHoverAmount = 0.5; export const StyledText = styled.div < TextProps > @@ -56,8 +56,7 @@ export const StyledText = ${props => (props.textAlign ? `text-align: ${props.textAlign}` : '')}; ${props => (props.width ? `width: ${props.width}` : '')}; &:hover { - ${props => - props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''}; + ${props => (props.onClick ? `opacity: ${opacityOnHoverAmount};` : '')}; } } `; From 06b2f12b10c6c2030b326b3e817d497415862299 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 10:38:24 -0800 Subject: [PATCH 039/230] fix(instant): Fix uncontrolled input warning for token selector --- packages/instant/src/components/erc20_token_selector.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx index 0a3d4427a7..f7d5a4fe45 100644 --- a/packages/instant/src/components/erc20_token_selector.tsx +++ b/packages/instant/src/components/erc20_token_selector.tsx @@ -19,12 +19,12 @@ export interface ERC20TokenSelectorProps { } export interface ERC20TokenSelectorState { - searchQuery?: string; + searchQuery: string; } export class ERC20TokenSelector extends React.Component { public state: ERC20TokenSelectorState = { - searchQuery: undefined, + searchQuery: '', }; public render(): React.ReactNode { const { tokens, onTokenSelect } = this.props; @@ -62,10 +62,10 @@ export class ERC20TokenSelector extends React.Component }; private readonly _isTokenQueryMatch = (token: ERC20Asset): boolean => { const { searchQuery } = this.state; - if (_.isUndefined(searchQuery)) { + const searchQueryLowerCase = searchQuery.toLowerCase().trim(); + if (searchQueryLowerCase === '') { return true; } - const searchQueryLowerCase = searchQuery.toLowerCase(); const tokenName = token.metaData.name.toLowerCase(); const tokenSymbol = token.metaData.symbol.toLowerCase(); return _.startsWith(tokenSymbol, searchQueryLowerCase) || _.startsWith(tokenName, searchQueryLowerCase); From 3c000e70e3fe507b199871879dde54bcefe01e1e Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 10:48:38 -0800 Subject: [PATCH 040/230] Linting --- packages/instant/src/components/ui/text.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index e149e2d8e7..2824777588 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -1,4 +1,3 @@ -import { darken } from 'polished'; import * as React from 'react'; import { ColorOption, styled } from '../../style/theme'; From 5415d1589e922dc689eaa9ec64ab830f3fd46af4 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 10:58:41 -0800 Subject: [PATCH 041/230] feat: write version info to window --- packages/instant/src/index.umd.ts | 3 +++ packages/instant/src/util/version.ts | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 packages/instant/src/util/version.ts diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 3a8694d6a8..063874d360 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -6,6 +6,9 @@ import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_CLASS, INJECTED_DIV_ID import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index'; import { assert } from './util/assert'; import { util } from './util/util'; +import { versionUtil } from './util/version'; + +versionUtil.writeVersionInfoToWindow(); const isInstantRendered = (): boolean => !!document.getElementById(INJECTED_DIV_ID); diff --git a/packages/instant/src/util/version.ts b/packages/instant/src/util/version.ts new file mode 100644 index 0000000000..5264cd6591 --- /dev/null +++ b/packages/instant/src/util/version.ts @@ -0,0 +1,8 @@ +const anyWindow = window as any; + +export const versionUtil = { + writeVersionInfoToWindow: () => { + anyWindow.__zeroExInstantGitSha = process.env.GIT_SHA; + anyWindow.__zeroExInstantNpmVersion = process.env.NPM_PACKAGE_VERSION; + }, +}; From 8b05db35104bf6853bdec81b2a1f25b16914608f Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 11:07:32 -0800 Subject: [PATCH 042/230] feat: add deploy_live command --- packages/instant/.production.discharge.json | 13 +++++++++++++ packages/instant/README.md | 14 +++++++++++--- packages/instant/package.json | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 packages/instant/.production.discharge.json diff --git a/packages/instant/.production.discharge.json b/packages/instant/.production.discharge.json new file mode 100644 index 0000000000..447fa1756f --- /dev/null +++ b/packages/instant/.production.discharge.json @@ -0,0 +1,13 @@ +{ + "domain": "instant.0xproject.com", + "build_command": "yarn build", + "upload_directory": "umd", + "index_key": "instant.js", + "error_key": "404.html", + "trailing_slashes": true, + "cache": 3600, + "aws_profile": "default", + "aws_region": "us-east-1", + "cdn": true, + "dns_configured": true +} diff --git a/packages/instant/README.md b/packages/instant/README.md index fd94d37d15..0fb4dfe627 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -22,20 +22,28 @@ The package is available as a UMD module named `zeroExInstant`. You can deploy a work-in-progress version of 0x Instant at http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com for easy sharing. -To build and deploy the site run +To build and deploy the bundle run ``` yarn deploy_dogfood ``` -We also have a staging bucket that is to be updated less frequently can be used to share instant externally: http://0x-instant-staging.s3-website-us-east-1.amazonaws.com/ +We also have a staging bucket that is to be updated less frequently can be used to share a beta version of instant externally: http://0x-instant-staging.s3-website-us-east-1.amazonaws.com/ -To build and deploy to this bucket, run +To build and deploy to this bundle, run ``` yarn deploy_staging ``` +Finally, we have our live production bundle that is only meant to be updated with stable, polished releases. + +To build and deploy to this bundle, run + +``` +yarn deploy_live +``` + **NOTE: On deploying the site, it will say the site is available at a non-existent URL. Please ignore and use the (now updated) URL above.** ## Contributing diff --git a/packages/instant/package.json b/packages/instant/package.json index af23af3b9b..427d5984e5 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -20,6 +20,7 @@ "clean": "shx rm -rf lib coverage scripts", "deploy_dogfood": "discharge deploy -c .dogfood.discharge.json", "deploy_staging": "discharge deploy -c .staging.discharge.json", + "deploy_live": "discharge deploy -c .production.discharge.json", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, "config": { From 1375d694ac2026ff8fe553f2b7bc9daa4d5f6d7d Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 12:58:11 -0800 Subject: [PATCH 043/230] fix: instant error no longer makes instant move --- packages/instant/src/components/ui/container.tsx | 2 ++ packages/instant/src/containers/latest_error.tsx | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 4dafe13862..2143b05037 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -28,6 +28,7 @@ export interface ContainerProps { className?: string; backgroundColor?: ColorOption; hasBoxShadow?: boolean; + isHidden?: boolean; zIndex?: number; whiteSpace?: string; opacity?: number; @@ -70,6 +71,7 @@ export const Container = ${props => props.width && stylesForMedia('width', props.width)} ${props => props.height && stylesForMedia('height', props.height)} ${props => props.borderRadius && stylesForMedia('border-radius', props.borderRadius)} + ${props => (props.isHidden ? 'visibility: hidden;' : '')} background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; &:hover { diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index b7cfdb5044..0d4349124a 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { SlidingError } from '../components/sliding_error'; +import { Container } from '../components/ui/container'; import { Overlay } from '../components/ui/overlay'; import { Action } from '../redux/actions'; import { State } from '../redux/reducer'; @@ -23,7 +24,12 @@ export interface LatestErrorComponentProps { export const LatestErrorComponent: React.StatelessComponent = props => { if (!props.latestErrorMessage) { - return
; + // Render a hidden SlidingError such that instant does not move when a real error is rendered. + return ( + + + + ); } return ( From 6e3378d79f3edf46a24cacbe2ec920e0caaf5a93 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 13:09:19 -0800 Subject: [PATCH 044/230] fix: instant height change on loading state change --- packages/instant/src/components/payment_method.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx index ebcd62f35b..c23b432674 100644 --- a/packages/instant/src/components/payment_method.tsx +++ b/packages/instant/src/components/payment_method.tsx @@ -26,7 +26,7 @@ export interface PaymentMethodProps { export class PaymentMethod extends React.Component { public render(): React.ReactNode { return ( - + { const colors = { primaryColor, secondaryColor }; switch (account.state) { case AccountState.Loading: - // Just take up the same amount of space as the other states. - return ; + return null; case AccountState.Locked: return ( Date: Wed, 28 Nov 2018 13:16:09 -0800 Subject: [PATCH 045/230] fix: dont allow price labels to wrap --- packages/instant/src/components/instant_heading.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index ace577824b..808c6dc7f2 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -107,7 +107,14 @@ export class InstantHeading extends React.Component { private readonly _renderEthAmount = (): React.ReactNode => { return ( - + {format.ethBaseUnitAmount( this.props.totalEthBaseUnitAmount, 4, @@ -119,7 +126,7 @@ export class InstantHeading extends React.Component { private readonly _renderDollarAmount = (): React.ReactNode => { return ( - + {format.ethBaseUnitAmountInUsd( this.props.totalEthBaseUnitAmount, this.props.ethUsdPrice, From 85d1dba1ef5ba4d34de46cb7eb54bfdc7acae380 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 13:35:38 -0800 Subject: [PATCH 046/230] fix: notify of the used font size for re-renders of scaling input --- packages/instant/src/components/scaling_input.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index 129162a749..791692257f 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -98,6 +98,12 @@ export class ScalingInput extends React.Component Date: Tue, 27 Nov 2018 16:19:50 -0800 Subject: [PATCH 047/230] fix(instant): Progress bar background color should be 10% primary color --- .../src/components/timed_progress_bar.tsx | 17 +++++++++++++---- .../instant/src/components/ui/container.tsx | 13 ++++++++++++- packages/instant/src/style/theme.ts | 12 ++++++++++-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx index 8465b9cd0a..fb3927088f 100644 --- a/packages/instant/src/components/timed_progress_bar.tsx +++ b/packages/instant/src/components/timed_progress_bar.tsx @@ -1,8 +1,9 @@ import * as _ from 'lodash'; +import { transparentize } from 'polished'; import * as React from 'react'; import { PROGRESS_FINISH_ANIMATION_TIME_MS, PROGRESS_STALL_AT_WIDTH } from '../constants'; -import { ColorOption, css, keyframes, styled } from '../style/theme'; +import { ColorOption, css, keyframes, styled, ThemeConsumer } from '../style/theme'; import { Container } from './ui/container'; @@ -93,8 +94,16 @@ export interface ProgressBarProps extends ProgressProps {} export const ProgressBar: React.ComponentType> = React.forwardRef( (props, ref) => ( - - - + + {theme => ( + + + + )} + ), ); diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 4dafe13862..a015ab5bc6 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -27,6 +27,7 @@ export interface ContainerProps { borderBottom?: string; className?: string; backgroundColor?: ColorOption; + rawBackgroundColor?: string; hasBoxShadow?: boolean; zIndex?: number; whiteSpace?: string; @@ -38,6 +39,16 @@ export interface ContainerProps { flexGrow?: string | number; } +const getBackgroundColor = (theme: any, backgroundColor?: ColorOption, rawBackgroundColor?: string): string => { + if (backgroundColor) { + return theme[backgroundColor] as string; + } + if (rawBackgroundColor) { + return rawBackgroundColor; + } + return 'none'; +}; + export const Container = styled.div < ContainerProps > @@ -70,7 +81,7 @@ export const Container = ${props => props.width && stylesForMedia('width', props.width)} ${props => props.height && stylesForMedia('height', props.height)} ${props => props.borderRadius && stylesForMedia('border-radius', props.borderRadius)} - background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; + background-color: ${props => getBackgroundColor(props.theme, props.backgroundColor, props.rawBackgroundColor)}; border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; &:hover { ${props => diff --git a/packages/instant/src/style/theme.ts b/packages/instant/src/style/theme.ts index a0751286b8..71e4b70521 100644 --- a/packages/instant/src/style/theme.ts +++ b/packages/instant/src/style/theme.ts @@ -1,6 +1,14 @@ import * as styledComponents from 'styled-components'; -const { default: styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider } = styledComponents; +const { + default: styled, + css, + keyframes, + withTheme, + createGlobalStyle, + ThemeConsumer, + ThemeProvider, +} = styledComponents; export type Theme = { [key in ColorOption]: string }; @@ -45,4 +53,4 @@ export const generateOverlayBlack = (opacity = 0.6) => { return `rgba(0, 0, 0, ${opacity})`; }; -export { styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider }; +export { styled, css, keyframes, withTheme, createGlobalStyle, ThemeConsumer, ThemeProvider }; From f4cc14f43862c639d2cf9f6e5359e4822f1bcacf Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 14:11:06 -0800 Subject: [PATCH 048/230] QuoteFetchedVia -> QuoteFetchOrigin --- .../instant/src/components/zero_ex_instant_provider.tsx | 4 ++-- .../src/containers/selected_erc20_asset_amount_input.ts | 4 ++-- packages/instant/src/redux/async_data.ts | 4 ++-- packages/instant/src/types.ts | 2 +- packages/instant/src/util/analytics.ts | 6 +++--- packages/instant/src/util/buy_quote_updater.ts | 4 ++-- packages/instant/src/util/heartbeater_factory.ts | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 9f2c95e36e..7841f5bf75 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -11,7 +11,7 @@ import { asyncData } from '../redux/async_data'; import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; -import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource, QuoteFetchedVia } from '../types'; +import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource, QuoteFetchOrigin } from '../types'; import { analytics, disableAnalytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; @@ -117,7 +117,7 @@ export class ZeroExInstantProvider extends React.Component { const { buyOrderState, providerState, selectedAsset, selectedAssetUnitAmount, affiliateInfo } = state; const assetBuyer = providerState.assetBuyer; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index a8139c1857..ea2bcbc2fa 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -21,7 +21,7 @@ export enum OrderProcessState { Failure = 'FAILURE', } -export enum QuoteFetchedVia { +export enum QuoteFetchOrigin { Manual = 'Manual', Heartbeat = 'Heartbeat', } diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index b85c6cee23..c0ed8c6382 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -2,7 +2,7 @@ import { BuyQuote } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; -import { AffiliateInfo, Asset, Network, OrderSource, ProviderState, QuoteFetchedVia } from '../types'; +import { AffiliateInfo, Asset, Network, OrderSource, ProviderState, QuoteFetchOrigin } from '../types'; import { EventProperties, heapUtil } from './heap'; @@ -180,12 +180,12 @@ export const analytics = { trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_CHOSE)(payload), trackTokenSelectorSearched: (searchText: string) => trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_SEARCHED)({ searchText }), - trackQuoteFetched: (buyQuote: BuyQuote, fetchedVia: QuoteFetchedVia) => + trackQuoteFetched: (buyQuote: BuyQuote, fetchedVia: QuoteFetchOrigin) => trackingEventFnWithPayload(EventNames.QUOTE_FETCHED)({ ...buyQuoteEventProperties(buyQuote), fetchedVia, }), - trackQuoteError: (errorMessage: string, assetAmount: BigNumber, fetchedVia: QuoteFetchedVia) => { + trackQuoteError: (errorMessage: string, assetAmount: BigNumber, fetchedVia: QuoteFetchOrigin) => { trackingEventFnWithPayload(EventNames.QUOTE_ERROR)({ errorMessage, assetAmount: assetAmount.toString(), diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index d6c4bd71bf..14af57660f 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -6,7 +6,7 @@ import { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; import { Action, actions } from '../redux/actions'; -import { AffiliateInfo, ERC20Asset, QuoteFetchedVia } from '../types'; +import { AffiliateInfo, ERC20Asset, QuoteFetchOrigin } from '../types'; import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; @@ -20,7 +20,7 @@ export const buyQuoteUpdater = { options: { setPending: boolean; dispatchErrors: boolean; - fetchedVia: QuoteFetchedVia; + fetchedVia: QuoteFetchOrigin; affiliateInfo?: AffiliateInfo; }, ): Promise => { diff --git a/packages/instant/src/util/heartbeater_factory.ts b/packages/instant/src/util/heartbeater_factory.ts index bf9e4291f8..84aeb4dbce 100644 --- a/packages/instant/src/util/heartbeater_factory.ts +++ b/packages/instant/src/util/heartbeater_factory.ts @@ -1,6 +1,6 @@ import { asyncData } from '../redux/async_data'; import { Store } from '../redux/store'; -import { QuoteFetchedVia } from '../types'; +import { QuoteFetchOrigin } from '../types'; import { Heartbeater } from './heartbeater'; @@ -20,7 +20,7 @@ export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): H return new Heartbeater(async () => { await asyncData.fetchCurrentBuyQuoteAndDispatchToStore(store.getState(), store.dispatch, { updateSilently: true, - fetchedVia: QuoteFetchedVia.Heartbeat, + fetchedVia: QuoteFetchOrigin.Heartbeat, }); }, shouldPerformImmediatelyOnStart); }; From ec01893e9c987fcbd3fd7bcb4ec34498a6f516cc Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 14:11:53 -0800 Subject: [PATCH 049/230] fetchedVia -> fetchOrigin --- .../src/components/zero_ex_instant_provider.tsx | 2 +- .../containers/selected_erc20_asset_amount_input.ts | 2 +- packages/instant/src/redux/async_data.ts | 4 ++-- packages/instant/src/util/analytics.ts | 8 ++++---- packages/instant/src/util/buy_quote_updater.ts | 10 +++++++--- packages/instant/src/util/heartbeater_factory.ts | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 7841f5bf75..f7880fc12e 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -117,7 +117,7 @@ export class ZeroExInstantProvider extends React.Component { const { buyOrderState, providerState, selectedAsset, selectedAssetUnitAmount, affiliateInfo } = state; const assetBuyer = providerState.assetBuyer; @@ -102,7 +102,7 @@ export const asyncData = { { setPending: !options.updateSilently, dispatchErrors: !options.updateSilently, - fetchedVia: options.fetchedVia, + fetchOrigin: options.fetchOrigin, affiliateInfo, }, ); diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index c0ed8c6382..99e8736d39 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -180,16 +180,16 @@ export const analytics = { trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_CHOSE)(payload), trackTokenSelectorSearched: (searchText: string) => trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_SEARCHED)({ searchText }), - trackQuoteFetched: (buyQuote: BuyQuote, fetchedVia: QuoteFetchOrigin) => + trackQuoteFetched: (buyQuote: BuyQuote, fetchOrigin: QuoteFetchOrigin) => trackingEventFnWithPayload(EventNames.QUOTE_FETCHED)({ ...buyQuoteEventProperties(buyQuote), - fetchedVia, + fetchOrigin, }), - trackQuoteError: (errorMessage: string, assetAmount: BigNumber, fetchedVia: QuoteFetchOrigin) => { + trackQuoteError: (errorMessage: string, assetAmount: BigNumber, fetchOrigin: QuoteFetchOrigin) => { trackingEventFnWithPayload(EventNames.QUOTE_ERROR)({ errorMessage, assetAmount: assetAmount.toString(), - fetchedVia, + fetchOrigin, }); }, }; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 14af57660f..06474b69f9 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -20,7 +20,7 @@ export const buyQuoteUpdater = { options: { setPending: boolean; dispatchErrors: boolean; - fetchedVia: QuoteFetchOrigin; + fetchOrigin: QuoteFetchOrigin; affiliateInfo?: AffiliateInfo; }, ): Promise => { @@ -37,7 +37,11 @@ export const buyQuoteUpdater = { } catch (error) { if (options.dispatchErrors) { dispatch(actions.setQuoteRequestStateFailure()); - analytics.trackQuoteError(error.message ? error.message : 'other', assetUnitAmount, options.fetchedVia); + analytics.trackQuoteError( + error.message ? error.message : 'other', + assetUnitAmount, + options.fetchOrigin, + ); let errorMessage; if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); @@ -65,6 +69,6 @@ export const buyQuoteUpdater = { errorFlasher.clearError(dispatch); // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(newBuyQuote)); - analytics.trackQuoteFetched(newBuyQuote, options.fetchedVia); + analytics.trackQuoteFetched(newBuyQuote, options.fetchOrigin); }, }; diff --git a/packages/instant/src/util/heartbeater_factory.ts b/packages/instant/src/util/heartbeater_factory.ts index 84aeb4dbce..5f7ef55e5e 100644 --- a/packages/instant/src/util/heartbeater_factory.ts +++ b/packages/instant/src/util/heartbeater_factory.ts @@ -20,7 +20,7 @@ export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): H return new Heartbeater(async () => { await asyncData.fetchCurrentBuyQuoteAndDispatchToStore(store.getState(), store.dispatch, { updateSilently: true, - fetchedVia: QuoteFetchOrigin.Heartbeat, + fetchOrigin: QuoteFetchOrigin.Heartbeat, }); }, shouldPerformImmediatelyOnStart); }; From 208ee935c843cfff9f0559a4c4058af3908f6261 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 14:17:26 -0800 Subject: [PATCH 050/230] Move fetch origin out of options --- .../src/components/zero_ex_instant_provider.tsx | 3 +-- .../containers/selected_erc20_asset_amount_input.ts | 3 +-- packages/instant/src/redux/async_data.ts | 5 +++-- packages/instant/src/util/buy_quote_updater.ts | 10 +++------- packages/instant/src/util/heartbeater_factory.ts | 12 ++++++++---- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index f7880fc12e..a4a03bbf46 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -115,9 +115,8 @@ export class ZeroExInstantProvider extends React.Component { const { buyOrderState, providerState, selectedAsset, selectedAssetUnitAmount, affiliateInfo } = state; const assetBuyer = providerState.assetBuyer; @@ -99,10 +100,10 @@ export const asyncData = { dispatch, selectedAsset as ERC20Asset, selectedAssetUnitAmount, + fetchOrigin, { setPending: !options.updateSilently, dispatchErrors: !options.updateSilently, - fetchOrigin: options.fetchOrigin, affiliateInfo, }, ); diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 06474b69f9..5685a7d008 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -17,10 +17,10 @@ export const buyQuoteUpdater = { dispatch: Dispatch, asset: ERC20Asset, assetUnitAmount: BigNumber, + fetchOrigin: QuoteFetchOrigin, options: { setPending: boolean; dispatchErrors: boolean; - fetchOrigin: QuoteFetchOrigin; affiliateInfo?: AffiliateInfo; }, ): Promise => { @@ -37,11 +37,7 @@ export const buyQuoteUpdater = { } catch (error) { if (options.dispatchErrors) { dispatch(actions.setQuoteRequestStateFailure()); - analytics.trackQuoteError( - error.message ? error.message : 'other', - assetUnitAmount, - options.fetchOrigin, - ); + analytics.trackQuoteError(error.message ? error.message : 'other', assetUnitAmount, fetchOrigin); let errorMessage; if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); @@ -69,6 +65,6 @@ export const buyQuoteUpdater = { errorFlasher.clearError(dispatch); // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(newBuyQuote)); - analytics.trackQuoteFetched(newBuyQuote, options.fetchOrigin); + analytics.trackQuoteFetched(newBuyQuote, fetchOrigin); }, }; diff --git a/packages/instant/src/util/heartbeater_factory.ts b/packages/instant/src/util/heartbeater_factory.ts index 5f7ef55e5e..cf29bf3ea5 100644 --- a/packages/instant/src/util/heartbeater_factory.ts +++ b/packages/instant/src/util/heartbeater_factory.ts @@ -18,9 +18,13 @@ export const generateAccountHeartbeater = (options: HeartbeatFactoryOptions): He export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => { const { store, shouldPerformImmediatelyOnStart } = options; return new Heartbeater(async () => { - await asyncData.fetchCurrentBuyQuoteAndDispatchToStore(store.getState(), store.dispatch, { - updateSilently: true, - fetchOrigin: QuoteFetchOrigin.Heartbeat, - }); + await asyncData.fetchCurrentBuyQuoteAndDispatchToStore( + store.getState(), + store.dispatch, + QuoteFetchOrigin.Heartbeat, + { + updateSilently: true, + }, + ); }, shouldPerformImmediatelyOnStart); }; From b9c983b4d691e58894b8b47f8e5952fdfcc275f9 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 28 Nov 2018 14:24:02 -0800 Subject: [PATCH 051/230] Use base unit value --- packages/instant/src/util/buy_quote_updater.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 5685a7d008..c1899f8c1b 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -37,7 +37,7 @@ export const buyQuoteUpdater = { } catch (error) { if (options.dispatchErrors) { dispatch(actions.setQuoteRequestStateFailure()); - analytics.trackQuoteError(error.message ? error.message : 'other', assetUnitAmount, fetchOrigin); + analytics.trackQuoteError(error.message ? error.message : 'other', baseUnitValue, fetchOrigin); let errorMessage; if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); From 76259352229881287a94cb561f82498920de5443 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 16:36:52 -0800 Subject: [PATCH 052/230] feat: write version info directly to exported object --- packages/instant/README.md | 6 +++--- packages/instant/src/index.umd.ts | 7 ++++--- packages/instant/src/util/version.ts | 8 -------- 3 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 packages/instant/src/util/version.ts diff --git a/packages/instant/README.md b/packages/instant/README.md index 0fb4dfe627..a38e4f85cb 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -20,7 +20,7 @@ The package is available as a UMD module named `zeroExInstant`. ## Deploying -You can deploy a work-in-progress version of 0x Instant at http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com for easy sharing. +You can deploy a work-in-progress version of 0x Instant at http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com/instant.js for easy sharing. To build and deploy the bundle run @@ -28,7 +28,7 @@ To build and deploy the bundle run yarn deploy_dogfood ``` -We also have a staging bucket that is to be updated less frequently can be used to share a beta version of instant externally: http://0x-instant-staging.s3-website-us-east-1.amazonaws.com/ +We also have a staging bucket that is to be updated less frequently can be used to share a beta version of instant externally: http://0x-instant-staging.s3-website-us-east-1.amazonaws.com/instant.js To build and deploy to this bundle, run @@ -36,7 +36,7 @@ To build and deploy to this bundle, run yarn deploy_staging ``` -Finally, we have our live production bundle that is only meant to be updated with stable, polished releases. +Finally, we have our live production bundle that is only meant to be updated with stable, polished releases: https://instant.0xproject.com/instant.js To build and deploy to this bundle, run diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 063874d360..45369d9ee4 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -6,9 +6,6 @@ import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_CLASS, INJECTED_DIV_ID import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index'; import { assert } from './util/assert'; import { util } from './util/util'; -import { versionUtil } from './util/version'; - -versionUtil.writeVersionInfoToWindow(); const isInstantRendered = (): boolean => !!document.getElementById(INJECTED_DIV_ID); @@ -113,3 +110,7 @@ export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_Z }; window.onpopstate = onPopStateHandler; }; + +// Write version info to the exported object for debugging +export const GitSha = process.env.GIT_SHA; +export const NpmVersion = process.env.NPM_PACKAGE_VERSION; diff --git a/packages/instant/src/util/version.ts b/packages/instant/src/util/version.ts deleted file mode 100644 index 5264cd6591..0000000000 --- a/packages/instant/src/util/version.ts +++ /dev/null @@ -1,8 +0,0 @@ -const anyWindow = window as any; - -export const versionUtil = { - writeVersionInfoToWindow: () => { - anyWindow.__zeroExInstantGitSha = process.env.GIT_SHA; - anyWindow.__zeroExInstantNpmVersion = process.env.NPM_PACKAGE_VERSION; - }, -}; From 77d6594ecb78a85d51a8aef39744ae62e4085bbc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 1 Nov 2018 15:45:38 +0100 Subject: [PATCH 053/230] Plugging away at encoder --- packages/order-utils/test/abi_encoder.ts | 483 +++++++++++++++++++++++ 1 file changed, 483 insertions(+) create mode 100644 packages/order-utils/test/abi_encoder.ts diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts new file mode 100644 index 0000000000..46449662a7 --- /dev/null +++ b/packages/order-utils/test/abi_encoder.ts @@ -0,0 +1,483 @@ +import * as chai from 'chai'; +import 'mocha'; +import ethUtil = require('ethereumjs-util'); + +var _ = require('lodash'); + +import { assert } from '@0x/order-utils/src/assert'; + +import { chaiSetup } from '@0x/order-utils/test/utils/chai_setup'; +import { web3Wrapper } from '@0x/order-utils/test/utils/web3_wrapper'; + +import { MethodAbi, DataItem } from 'ethereum-types'; +import { throwError } from 'ethers/errors'; + +import { BigNumber } from '@0x/utils'; +import { MethodNotFound } from 'json-rpc-error'; +import { power } from 'js-combinatorics'; + +const simpleAbi = { + name: 'SimpleAbi', + inputs: [ + { + components: [ + { + name: 'greg', + type: 'uint256', + }, + { + name: 'gregStr', + type: 'string', + }, + ], + }, + ], +} as MethodAbi; +const fillOrderAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'makerAddress', + type: 'address', + }, + { + name: 'takerAddress', + type: 'address', + }, + { + name: 'feeRecipientAddress', + type: 'address', + }, + { + name: 'senderAddress', + type: 'address', + }, + { + name: 'makerAssetAmount', + type: 'uint256', + }, + { + name: 'takerAssetAmount', + type: 'uint256', + }, + { + name: 'makerFee', + type: 'uint256', + }, + { + name: 'takerFee', + type: 'uint256', + }, + { + name: 'expirationTimeSeconds', + type: 'uint256', + }, + { + name: 'salt', + type: 'uint256', + }, + { + name: 'makerAssetData', + type: 'bytes', + }, + { + name: 'takerAssetData', + type: 'bytes', + }, + ], + name: 'order', + type: 'tuple', + }, + { + name: 'takerAssetFillAmount', + type: 'uint256', + }, + { + name: 'salt', + type: 'uint256', + }, + { + name: 'orderSignature', + type: 'bytes', + }, + { + name: 'takerSignature', + type: 'bytes', + }, + ], + name: 'fillOrder', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +chaiSetup.configure(); +const expect = chai.expect; + +namespace AbiEncoder { + class Memory {} + + class Word {} + + abstract class DataType { + private dataItem: DataItem; + private hexValue: string; + + constructor(dataItem: DataItem) { + this.dataItem = dataItem; + this.hexValue = '0x'; + } + + protected assignHexValue(hexValue: string) { + this.hexValue = hexValue; + } + + public getHexValue(): string { + return this.hexValue; + } + + public abstract assignValue(value: any): void; + + // abstract match(type: string): Bool; + } + + class Calldata {} + + abstract class StaticDataType extends DataType { + constructor(dataItem: DataItem) { + super(dataItem); + } + } + + abstract class DynamicDataType extends DataType { + constructor(dataItem: DataItem) { + super(dataItem); + } + } + + class Address extends StaticDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + const hexValue = ethUtil.bufferToHex(new Buffer(value)); + this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return type === 'address'; + } + } + + class Bool extends StaticDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + //const hexValue = ethUtil.bufferToHex(new Buffer(value)); + //this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return type === 'bool'; + } + } + + class Int extends StaticDataType { + static matcher = RegExp( + '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + static DEFAULT_WIDTH = new BigNumber(1); + width: BigNumber = Byte.DEFAULT_WIDTH; + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = Byte.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 1) { + this.width = new BigNumber(matches[1], 10); + } + } + + public assignValue(value: string) { + //const hexValue = ethUtil.bufferToHex(new Buffer(value)); + //this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } + } + + class UInt extends StaticDataType { + static matcher = RegExp( + '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + static DEFAULT_WIDTH: number = 1; + width: number = UInt.DEFAULT_WIDTH; + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = Byte.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 1) { + this.width = parseInt(matches[1]); + } + } + + public getMaxValue() { + return new BigNumber(2).toPower(this.width - 1); + } + + public assignValue(value: BigNumber) { + if (value > this.getMaxValue()) { + throw 1; + } + + const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), 32); + const encodedValue = ethUtil.bufferToHex(valueBuf); + + this.assignHexValue(encodedValue); + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } + } + + class Byte extends StaticDataType { + static matcher = RegExp( + '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', + ); + + static DEFAULT_WIDTH = new BigNumber(1); + width: BigNumber = Byte.DEFAULT_WIDTH; + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = Byte.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 1) { + this.width = new BigNumber(matches[1], 10); + } + } + + public assignValue(value: string) { + //const hexValue = ethUtil.bufferToHex(new Buffer(value)); + //this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } + } + + class Tuple extends DynamicDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + //const hexValue = ethUtil.bufferToHex(new Buffer(value)); + //this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return type === 'tuple'; + } + } + + class Bytes extends StaticDataType { + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = SolArray.UNDEFINED_LENGTH; + + constructor(dataItem: DataItem) { + super(dataItem); + expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + //const hexValue = ethUtil.bufferToHex(new Buffer(value)); + //this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return type === 'bytes'; + } + } + + class SolArray extends DynamicDataType { + static matcher = RegExp('^.+\\[([0-9]d*)\\]$'); + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = SolArray.UNDEFINED_LENGTH; + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = SolArray.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 1) { + this.length = new BigNumber(matches[1], 10); + } + } + + public assignValue(value: string) { + //const hexValue = ethUtil.bufferToHex(new Buffer(value)); + //this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } + } + + class SolString extends DynamicDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(SolString.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), 32); + const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); + const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); + const encodedValue = ethUtil.bufferToHex(encodedValueBuf); + + this.assignHexValue(encodedValue); + } + + public static matchGrammar(type: string): boolean { + return type === 'string'; + } + } + + /* TODO + class Fixed extends StaticDataType {} + + class UFixed extends StaticDataType {}*/ + + class Pointer extends StaticDataType { + destDataType: DynamicDataType; + static metaDataItem = { name: '[ptr]', type: '[ptr]' } as DataItem; + + constructor(destDataType: DynamicDataType) { + super(Pointer.metaDataItem); + this.destDataType = destDataType; + } + + public assignValue(destDataType: DynamicDataType) { + this.destDataType = destDataType; + } + } + + class DataTypeFactory { + public static mapDataItemToDataType(dataItem: DataItem): DataType { + if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); + if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); + if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); + if (Int.matchGrammar(dataItem.type)) return new Int(dataItem); + if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem); + if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem); + if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem); + if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); + if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem); + if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem); + //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem); + //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem); + + throw new Error(`Unrecognized data type: '${dataItem.type}'`); + } + + public static create(dataItem: DataItem): DataType { + const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); + if (dataType instanceof StaticDataType) { + return dataType; + } else if (dataType instanceof DynamicDataType) { + const pointer = new Pointer(dataType); + return pointer; + } + + throw new Error(`Unrecognized instance type: '${dataType}'`); + } + } + + export class Method { + name: string; + params: DataType[]; + + constructor(abi: MethodAbi) { + // super(); + this.name = abi.name; + this.params = []; + + _.each(abi.inputs, function(this: Method, input: DataItem) { + this.params.push(DataTypeFactory.create(input)); + }); + } + + encode(args: any[]): string { + //const calldata = new Calldata(this.name, this.params.length); + let params = this.params; + _.each(params, function(args: any[], i: number, param: DataType) { + param.assignValue(args[i]); + console.log(param.getHexValue()); + //param.encodeToCalldata(calldata); + }); + + return ''; + + //return calldata.getRaw(); + } + + /* + encodeOptimized(args: any[]): string { + const calldata = new Memory(); + // Assign values + optimizableParams : StaticDataType = []; + _.each(this.params, function(args: any[], i: number, param: DataType) { + param.assignValue(args[i]); + if (param instanceof DynamicDataType) { + + } + }); + + // Find non-parameter leaves + + + return ''; + } */ + + /* + decode(rawCalldata: string): any[] { + const calldata = new Calldata(this.name, this.params.length); + calldata.assignRaw(rawCalldata); + let args: any[]; + let params = this.params; + _.each(params, function(args: any[], i: number, param: DataType) { + param.decodeFromCalldata(calldata); + args.push(param.getValue()); + }); + + return args; + }*/ + } +} + +describe.only('ABI Encoder', () => { + describe.only('Just a Greg, Eh', () => { + it.only('Yessir', async () => { + const method = new AbiEncoder.Method(simpleAbi); + method.encode([new BigNumber(5), 'five']); + expect(true).to.be.true(); + }); + }); +}); From 79126f3b4a83d9cf58e5a7935051820ae5e1a8e0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 11:13:56 -0800 Subject: [PATCH 054/230] renamed --- .../test/{abi_encoder.ts => abi_encoder_test.ts} | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) rename packages/order-utils/test/{abi_encoder.ts => abi_encoder_test.ts} (97%) diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder_test.ts similarity index 97% rename from packages/order-utils/test/abi_encoder.ts rename to packages/order-utils/test/abi_encoder_test.ts index 46449662a7..87e1e7fbac 100644 --- a/packages/order-utils/test/abi_encoder.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -4,17 +4,13 @@ import ethUtil = require('ethereumjs-util'); var _ = require('lodash'); -import { assert } from '@0x/order-utils/src/assert'; +// import { assert } from '@0x/order-utils/src/assert'; -import { chaiSetup } from '@0x/order-utils/test/utils/chai_setup'; -import { web3Wrapper } from '@0x/order-utils/test/utils/web3_wrapper'; +import { chaiSetup } from './utils/chai_setup'; import { MethodAbi, DataItem } from 'ethereum-types'; -import { throwError } from 'ethers/errors'; import { BigNumber } from '@0x/utils'; -import { MethodNotFound } from 'json-rpc-error'; -import { power } from 'js-combinatorics'; const simpleAbi = { name: 'SimpleAbi', From a1cff862c979d013f3f56af55c324fe38b588473 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 11:32:29 -0800 Subject: [PATCH 055/230] Fixed width parsing for UINT --- packages/order-utils/test/abi_encoder_test.ts | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 87e1e7fbac..f6f7fd93e7 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -13,22 +13,24 @@ import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; const simpleAbi = { - name: 'SimpleAbi', + constant: false, inputs: [ { - components: [ - { - name: 'greg', - type: 'uint256', - }, - { - name: 'gregStr', - type: 'string', - }, - ], + name: 'greg', + type: 'uint208', + }, + { + name: 'gregStr', + type: 'string', }, ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', } as MethodAbi; + const fillOrderAbi = { constant: false, inputs: [ @@ -198,7 +200,7 @@ namespace AbiEncoder { super(dataItem); const matches = Byte.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 1) { + if (matches !== null && matches.length === 2) { this.width = new BigNumber(matches[1], 10); } } @@ -218,16 +220,20 @@ namespace AbiEncoder { '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); - static DEFAULT_WIDTH: number = 1; + static DEFAULT_WIDTH: number = 256; width: number = UInt.DEFAULT_WIDTH; constructor(dataItem: DataItem) { super(dataItem); - const matches = Byte.matcher.exec(dataItem.type); + const matches = UInt.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 1) { + console.log(JSON.stringify(matches)); + if (matches !== null && matches.length === 2) { this.width = parseInt(matches[1]); + } else { + this.width = 256; } + console.log('Width = ' + this.width); } public getMaxValue() { @@ -262,7 +268,7 @@ namespace AbiEncoder { super(dataItem); const matches = Byte.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 1) { + if (matches !== null && matches.length === 2) { this.width = new BigNumber(matches[1], 10); } } @@ -377,6 +383,8 @@ namespace AbiEncoder { class DataTypeFactory { public static mapDataItemToDataType(dataItem: DataItem): DataType { + console.log(`Type: ${dataItem.type}`); + if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); @@ -415,7 +423,8 @@ namespace AbiEncoder { this.name = abi.name; this.params = []; - _.each(abi.inputs, function(this: Method, input: DataItem) { + _.each(abi.inputs, (input: DataItem) => { + console.log('--input--\n', input, '--end input--'); this.params.push(DataTypeFactory.create(input)); }); } From 331cca37e2bce35a1351f75a85b97ab60dfad196 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 13:17:15 -0800 Subject: [PATCH 056/230] works for assigning value to uint --- packages/order-utils/test/abi_encoder_test.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index f6f7fd93e7..6b32d59aaa 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -227,25 +227,29 @@ namespace AbiEncoder { super(dataItem); const matches = UInt.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); - console.log(JSON.stringify(matches)); if (matches !== null && matches.length === 2) { this.width = parseInt(matches[1]); } else { this.width = 256; } - console.log('Width = ' + this.width); } - public getMaxValue() { + public getMaxValue(): BigNumber { return new BigNumber(2).toPower(this.width - 1); } public assignValue(value: BigNumber) { - if (value > this.getMaxValue()) { - throw 1; + console.log(JSON.stringify(value)); + console.log(JSON.stringify(this.getMaxValue())); + if (value.greaterThan(this.getMaxValue())) { + throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; + } else if (value.lessThan(0)) { + throw `tried to assign value of ${value} to an unsigned integer.`; } - const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), 32); + const hexBase = 16; + const evmWordWidth = 32; + const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); const encodedValue = ethUtil.bufferToHex(valueBuf); this.assignHexValue(encodedValue); @@ -424,7 +428,6 @@ namespace AbiEncoder { this.params = []; _.each(abi.inputs, (input: DataItem) => { - console.log('--input--\n', input, '--end input--'); this.params.push(DataTypeFactory.create(input)); }); } @@ -432,7 +435,9 @@ namespace AbiEncoder { encode(args: any[]): string { //const calldata = new Calldata(this.name, this.params.length); let params = this.params; - _.each(params, function(args: any[], i: number, param: DataType) { + _.each(params, (param: DataType, i: number) => { + console.log('param:\n', param, '\n--end--\n'); + console.log('arg:\n', args[i], '\n--end\n'); param.assignValue(args[i]); console.log(param.getHexValue()); //param.encodeToCalldata(calldata); From bce62056d953abc424a5171adbaff1641f8f6626 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 13:28:59 -0800 Subject: [PATCH 057/230] cleaner name/type for pointer --- packages/order-utils/test/abi_encoder_test.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 6b32d59aaa..4b8ed48ccb 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -137,6 +137,10 @@ namespace AbiEncoder { return this.hexValue; } + public getDataItem(): DataItem { + return this.dataItem; + } + public abstract assignValue(value: any): void; // abstract match(type: string): Bool; @@ -373,15 +377,25 @@ namespace AbiEncoder { class Pointer extends StaticDataType { destDataType: DynamicDataType; - static metaDataItem = { name: '[ptr]', type: '[ptr]' } as DataItem; constructor(destDataType: DynamicDataType) { - super(Pointer.metaDataItem); + const destDataItem = destDataType.getDataItem(); + const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; + super(dataItem); this.destDataType = destDataType; } + /* public assignValue(destDataType: DynamicDataType) { this.destDataType = destDataType; + }*/ + + public assignValue(value: any) { + this.destDataType.assignValue(value); + } + + public getHexValue(): string { + return this.destDataType.getHexValue(); } } From d5dbd8cd686195e38c3c4a0857e3231e0a026287 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 13:47:44 -0800 Subject: [PATCH 058/230] tests for String --- packages/order-utils/test/abi_encoder_test.ts | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 4b8ed48ccb..0366ddfccd 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -120,7 +120,7 @@ namespace AbiEncoder { class Word {} - abstract class DataType { + export abstract class DataType { private dataItem: DataItem; private hexValue: string; @@ -148,19 +148,19 @@ namespace AbiEncoder { class Calldata {} - abstract class StaticDataType extends DataType { + export abstract class StaticDataType extends DataType { constructor(dataItem: DataItem) { super(dataItem); } } - abstract class DynamicDataType extends DataType { + export abstract class DynamicDataType extends DataType { constructor(dataItem: DataItem) { super(dataItem); } } - class Address extends StaticDataType { + export class Address extends StaticDataType { constructor(dataItem: DataItem) { super(dataItem); expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); @@ -176,7 +176,7 @@ namespace AbiEncoder { } } - class Bool extends StaticDataType { + export class Bool extends StaticDataType { constructor(dataItem: DataItem) { super(dataItem); expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); @@ -192,7 +192,7 @@ namespace AbiEncoder { } } - class Int extends StaticDataType { + export class Int extends StaticDataType { static matcher = RegExp( '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); @@ -219,7 +219,7 @@ namespace AbiEncoder { } } - class UInt extends StaticDataType { + export class UInt extends StaticDataType { static matcher = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); @@ -264,7 +264,7 @@ namespace AbiEncoder { } } - class Byte extends StaticDataType { + export class Byte extends StaticDataType { static matcher = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', ); @@ -291,7 +291,7 @@ namespace AbiEncoder { } } - class Tuple extends DynamicDataType { + export class Tuple extends DynamicDataType { constructor(dataItem: DataItem) { super(dataItem); expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); @@ -307,7 +307,7 @@ namespace AbiEncoder { } } - class Bytes extends StaticDataType { + export class Bytes extends StaticDataType { static UNDEFINED_LENGTH = new BigNumber(-1); length: BigNumber = SolArray.UNDEFINED_LENGTH; @@ -326,7 +326,7 @@ namespace AbiEncoder { } } - class SolArray extends DynamicDataType { + export class SolArray extends DynamicDataType { static matcher = RegExp('^.+\\[([0-9]d*)\\]$'); static UNDEFINED_LENGTH = new BigNumber(-1); length: BigNumber = SolArray.UNDEFINED_LENGTH; @@ -350,14 +350,16 @@ namespace AbiEncoder { } } - class SolString extends DynamicDataType { + export class SolString extends DynamicDataType { constructor(dataItem: DataItem) { super(dataItem); expect(SolString.matchGrammar(dataItem.type)).to.be.true(); } public assignValue(value: string) { - const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), 32); + const wordsForValue = Math.ceil(value.length / 32); + const paddedBytesForValue = wordsForValue * 32; + const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); const encodedValue = ethUtil.bufferToHex(encodedValueBuf); @@ -375,7 +377,7 @@ namespace AbiEncoder { class UFixed extends StaticDataType {}*/ - class Pointer extends StaticDataType { + export class Pointer extends StaticDataType { destDataType: DynamicDataType; constructor(destDataType: DynamicDataType) { @@ -399,7 +401,7 @@ namespace AbiEncoder { } } - class DataTypeFactory { + export class DataTypeFactory { public static mapDataItemToDataType(dataItem: DataItem): DataType { console.log(`Type: ${dataItem.type}`); @@ -497,11 +499,34 @@ namespace AbiEncoder { } describe.only('ABI Encoder', () => { - describe.only('Just a Greg, Eh', () => { - it.only('Yessir', async () => { + describe('Just a Greg, Eh', () => { + it('Yessir', async () => { const method = new AbiEncoder.Method(simpleAbi); method.encode([new BigNumber(5), 'five']); expect(true).to.be.true(); }); }); + + describe('String', () => { + const testStringDataItem = { name: 'testString', type: 'string' }; + it('Less than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + stringDataType.assignValue('five'); + const expectedAbiEncodedString = + '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + console.log(stringDataType.getHexValue()); + console.log(expectedAbiEncodedString); + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + + it('Greater than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + const testValue = 'a'.repeat(40); + stringDataType.assignValue(testValue); + const expectedAbiEncodedString = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + }); }); From 96bcc7e33281a3a7831e92dad65de5303ac39523 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 16:33:25 -0800 Subject: [PATCH 059/230] Going towards first calldata impl --- packages/order-utils/test/abi_encoder_test.ts | 265 +++++++++++++++++- 1 file changed, 250 insertions(+), 15 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 0366ddfccd..7e12dfee60 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -11,13 +11,14 @@ import { chaiSetup } from './utils/chai_setup'; import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; +import { assert } from '@0x/order-utils/src/assert'; const simpleAbi = { constant: false, inputs: [ { name: 'greg', - type: 'uint208', + type: 'uint256', }, { name: 'gregStr', @@ -116,17 +117,138 @@ chaiSetup.configure(); const expect = chai.expect; namespace AbiEncoder { - class Memory {} + class Word { + private value: string; - class Word {} + constructor(value?: string) { + if (value === undefined) { + this.value = ''; + } else { + this.value = value; + } + } + + public set(value: string) { + if (value.length !== 64) { + throw `Tried to create word that is not 32 bytes: ${value}`; + } + + this.value = value; + } + + public get(): string { + return this.value; + } + + public getAsHex(): string { + return `0x${this.value}`; + } + } + + enum CalldataSection { + NONE, + PARAMS, + DATA, + } + + class Memblock { + private dataType: DataType; + private location: { calldataSection: CalldataSection; offset: BigNumber }; + + constructor(dataType: DataType) { + this.dataType = dataType; + this.location = { + calldataSection: CalldataSection.NONE, + offset: new BigNumber(0), + }; + } + + public getSize(): BigNumber { + return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength); + } + + public assignLocation(calldataSection: CalldataSection, offset: BigNumber) { + this.location.calldataSection = calldataSection; + this.location.offset = offset; + } + + public get(): string { + return ethUtil.stripHexPrefix(this.dataType.getHexValue()); + } + } + + interface BindList { + [key: string]: Memblock; + } + + class Calldata { + private selector: string; + private params: Memblock[]; + private data: Memblock[]; + private dataOffset: BigNumber; + private currentDataOffset: BigNumber; + private currentParamOffset: BigNumber; + private bindList: BindList; + + constructor(selector: string, nParams: number) { + this.selector = selector; + console.log(this.selector); + this.params = []; + this.data = []; + const evmWordSize = 32; + this.dataOffset = new BigNumber(nParams).times(evmWordSize); + this.currentDataOffset = this.dataOffset; + this.currentParamOffset = new BigNumber(0); + this.bindList = {}; + } + + public bind(dataType: DataType, section: CalldataSection = CalldataSection.DATA) { + if (dataType.getId() in this.bindList) { + throw `Rebind`; + } + const memblock = new Memblock(dataType); + switch (section) { + case CalldataSection.PARAMS: + this.params.push(memblock); + memblock.assignLocation(section, this.currentParamOffset); + this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); + break; + + case CalldataSection.DATA: + this.data.push(memblock); + memblock.assignLocation(section, this.currentDataOffset); + this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); + break; + + default: + throw `Unrecognized calldata section: ${section}`; + } + + this.bindList[dataType.getId()] = memblock; + } + + public getHexValue(): string { + let hexValue = `0x${this.selector}`; + _.each(this.params, (memblock: Memblock) => { + hexValue += memblock.get(); + }); + _.each(this.data, (memblock: Memblock) => { + hexValue += memblock.get(); + }); + + return hexValue; + } + } export abstract class DataType { private dataItem: DataItem; private hexValue: string; + private memblock: Memblock | undefined; constructor(dataItem: DataItem) { this.dataItem = dataItem; this.hexValue = '0x'; + this.memblock = undefined; } protected assignHexValue(hexValue: string) { @@ -141,13 +263,25 @@ namespace AbiEncoder { return this.dataItem; } + public rbind(memblock: Memblock) { + this.memblock = memblock; + } + + public bind(calldata: Calldata) { + if (this.memblock !== undefined) return; // already binded + } + + public getId(): string { + return this.dataItem.name; + } + public abstract assignValue(value: any): void; + public abstract getSignature(): string; + public abstract encodeToCalldata(calldata: Calldata): void; // abstract match(type: string): Bool; } - class Calldata {} - export abstract class StaticDataType extends DataType { constructor(dataItem: DataItem) { super(dataItem); @@ -163,7 +297,7 @@ namespace AbiEncoder { export class Address extends StaticDataType { constructor(dataItem: DataItem) { super(dataItem); - expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + expect(Address.matchGrammar(dataItem.type)).to.be.true(); } public assignValue(value: string) { @@ -171,6 +305,14 @@ namespace AbiEncoder { this.assignHexValue(hexValue); } + public getSignature(): string { + throw 1; + } + + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public static matchGrammar(type: string): boolean { return type === 'address'; } @@ -187,6 +329,14 @@ namespace AbiEncoder { //this.assignHexValue(hexValue); } + public getSignature(): string { + throw 1; + } + + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public static matchGrammar(type: string): boolean { return type === 'bool'; } @@ -209,11 +359,19 @@ namespace AbiEncoder { } } + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public assignValue(value: string) { //const hexValue = ethUtil.bufferToHex(new Buffer(value)); //this.assignHexValue(hexValue); } + public getSignature(): string { + throw 1; + } + public static matchGrammar(type: string): boolean { return this.matcher.test(type); } @@ -259,6 +417,14 @@ namespace AbiEncoder { this.assignHexValue(encodedValue); } + public getSignature(): string { + return `uint${this.width}`; + } + + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public static matchGrammar(type: string): boolean { return this.matcher.test(type); } @@ -286,6 +452,14 @@ namespace AbiEncoder { //this.assignHexValue(hexValue); } + public getSignature(): string { + throw 1; + } + + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public static matchGrammar(type: string): boolean { return this.matcher.test(type); } @@ -302,6 +476,14 @@ namespace AbiEncoder { //this.assignHexValue(hexValue); } + public getSignature(): string { + throw 1; + } + + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public static matchGrammar(type: string): boolean { return type === 'tuple'; } @@ -321,6 +503,14 @@ namespace AbiEncoder { //this.assignHexValue(hexValue); } + public getSignature(): string { + throw 1; + } + + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public static matchGrammar(type: string): boolean { return type === 'bytes'; } @@ -345,9 +535,17 @@ namespace AbiEncoder { //this.assignHexValue(hexValue); } + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } + public static matchGrammar(type: string): boolean { return this.matcher.test(type); } + + public getSignature(): string { + throw 1; + } } export class SolString extends DynamicDataType { @@ -367,6 +565,12 @@ namespace AbiEncoder { this.assignHexValue(encodedValue); } + public getSignature(): string { + return 'string'; + } + + public encodeToCalldata(calldata: Calldata): void {} + public static matchGrammar(type: string): boolean { return type === 'string'; } @@ -399,6 +603,14 @@ namespace AbiEncoder { public getHexValue(): string { return this.destDataType.getHexValue(); } + + public getSignature(): string { + return this.destDataType.getSignature(); + } + + public encodeToCalldata(calldata: Calldata): void { + throw 2; + } } export class DataTypeFactory { @@ -437,6 +649,8 @@ namespace AbiEncoder { export class Method { name: string; params: DataType[]; + signature: string; + selector: string; constructor(abi: MethodAbi) { // super(); @@ -446,20 +660,40 @@ namespace AbiEncoder { _.each(abi.inputs, (input: DataItem) => { this.params.push(DataTypeFactory.create(input)); }); + + // Compute signature + this.signature = `${this.name}(`; + _.each(this.params, (param: DataType, i: number) => { + this.signature += param.getSignature(); + if (i < this.params.length - 1) { + this.signature += ','; + } + }); + this.signature += ')'; + + // Compute selector + this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4))); + + console.log(`--SIGNATURE--\n${this.signature}\n---------\n`); + console.log(`--SELECTOR--\n${this.selector}\n---------\n`); } encode(args: any[]): string { - //const calldata = new Calldata(this.name, this.params.length); - let params = this.params; + const calldata = new Calldata(this.selector, this.params.length); + + // Write params section + const params = this.params; _.each(params, (param: DataType, i: number) => { - console.log('param:\n', param, '\n--end--\n'); - console.log('arg:\n', args[i], '\n--end\n'); + // Assign value to param param.assignValue(args[i]); - console.log(param.getHexValue()); - //param.encodeToCalldata(calldata); + // Binds top-level parameter to the params section of calldata + calldata.bind(param, CalldataSection.PARAMS); + // Binds parameter's children to the data section of calldata, + // while retaining internal pointers + param.bind(calldata); }); - return ''; + return calldata.getHexValue(); //return calldata.getRaw(); } @@ -499,10 +733,11 @@ namespace AbiEncoder { } describe.only('ABI Encoder', () => { - describe('Just a Greg, Eh', () => { + describe.only('Just a Greg, Eh', () => { it('Yessir', async () => { const method = new AbiEncoder.Method(simpleAbi); - method.encode([new BigNumber(5), 'five']); + const calldata = method.encode([new BigNumber(5), 'five']); + console.log(calldata); expect(true).to.be.true(); }); }); From 7196046e2f38aba4709f61d1a88eb562d6c0a38a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 17:12:13 -0800 Subject: [PATCH 060/230] Prints full ABI encoding for subset of types, with off by factor of 2 for offsets --- packages/order-utils/test/abi_encoder_test.ts | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 7e12dfee60..e1d0e8a918 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -153,12 +153,13 @@ namespace AbiEncoder { class Memblock { private dataType: DataType; - private location: { calldataSection: CalldataSection; offset: BigNumber }; + private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber }; constructor(dataType: DataType) { this.dataType = dataType; this.location = { calldataSection: CalldataSection.NONE, + sectionOffset: new BigNumber(0), offset: new BigNumber(0), }; } @@ -167,14 +168,27 @@ namespace AbiEncoder { return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength); } - public assignLocation(calldataSection: CalldataSection, offset: BigNumber) { + public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) { this.location.calldataSection = calldataSection; + this.location.sectionOffset = sectionOffset; this.location.offset = offset; } public get(): string { return ethUtil.stripHexPrefix(this.dataType.getHexValue()); } + + public getOffset(): BigNumber { + return this.location.offset; + } + + public getAbsoluteOffset(): BigNumber { + return this.location.sectionOffset.plus(this.location.offset); + } + + public getSection(): CalldataSection { + return this.location.calldataSection; + } } interface BindList { @@ -204,19 +218,19 @@ namespace AbiEncoder { public bind(dataType: DataType, section: CalldataSection = CalldataSection.DATA) { if (dataType.getId() in this.bindList) { - throw `Rebind`; + throw `Rebind on ${dataType.getId()}`; } const memblock = new Memblock(dataType); switch (section) { case CalldataSection.PARAMS: this.params.push(memblock); - memblock.assignLocation(section, this.currentParamOffset); + memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); break; case CalldataSection.DATA: this.data.push(memblock); - memblock.assignLocation(section, this.currentDataOffset); + memblock.assignLocation(section, this.dataOffset, this.currentDataOffset); this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); break; @@ -225,6 +239,7 @@ namespace AbiEncoder { } this.bindList[dataType.getId()] = memblock; + dataType.rbind(memblock); } public getHexValue(): string { @@ -243,12 +258,14 @@ namespace AbiEncoder { export abstract class DataType { private dataItem: DataItem; private hexValue: string; - private memblock: Memblock | undefined; + protected memblock: Memblock | undefined; + protected children: DataType[]; constructor(dataItem: DataItem) { this.dataItem = dataItem; this.hexValue = '0x'; this.memblock = undefined; + this.children = []; } protected assignHexValue(hexValue: string) { @@ -268,13 +285,28 @@ namespace AbiEncoder { } public bind(calldata: Calldata) { - if (this.memblock !== undefined) return; // already binded + if (this.memblock === undefined) { + calldata.bind(this); + } + _.each(this.children, (child: DataType) => { + child.bind(calldata); + }); } public getId(): string { return this.dataItem.name; } + public getOffset(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getOffset(); + } + + public getAbsoluteOffset(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getAbsoluteOffset(); + } + public abstract assignValue(value: any): void; public abstract getSignature(): string; public abstract encodeToCalldata(calldata: Calldata): void; @@ -401,8 +433,6 @@ namespace AbiEncoder { } public assignValue(value: BigNumber) { - console.log(JSON.stringify(value)); - console.log(JSON.stringify(this.getMaxValue())); if (value.greaterThan(this.getMaxValue())) { throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; } else if (value.lessThan(0)) { @@ -589,6 +619,7 @@ namespace AbiEncoder { const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; super(dataItem); this.destDataType = destDataType; + this.children.push(destDataType); } /* @@ -601,7 +632,23 @@ namespace AbiEncoder { } public getHexValue(): string { - return this.destDataType.getHexValue(); + let offset = new BigNumber(0); + if (this.memblock !== undefined) { + switch (this.memblock.getSection()) { + case CalldataSection.PARAMS: + offset = this.destDataType.getAbsoluteOffset(); + break; + case CalldataSection.DATA: + offset = this.destDataType.getOffset(); + break; + } + } + + const hexBase = 16; + const evmWordWidth = 32; + const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth); + const encodedValue = ethUtil.bufferToHex(valueBuf); + return encodedValue; } public getSignature(): string { @@ -693,6 +740,8 @@ namespace AbiEncoder { param.bind(calldata); }); + console.log(calldata); + return calldata.getHexValue(); //return calldata.getRaw(); From b99252baaedd9bc37ef4e53d02e4085bed7e5142 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 17:18:40 -0800 Subject: [PATCH 061/230] ABI encodes some basic types correctly --- packages/order-utils/test/abi_encoder_test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index e1d0e8a918..1a33e4f781 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -211,7 +211,7 @@ namespace AbiEncoder { this.data = []; const evmWordSize = 32; this.dataOffset = new BigNumber(nParams).times(evmWordSize); - this.currentDataOffset = this.dataOffset; + this.currentDataOffset = new BigNumber(0); this.currentParamOffset = new BigNumber(0); this.bindList = {}; } @@ -243,7 +243,7 @@ namespace AbiEncoder { } public getHexValue(): string { - let hexValue = `0x${this.selector}`; + let hexValue = `${this.selector}`; _.each(this.params, (memblock: Memblock) => { hexValue += memblock.get(); }); @@ -634,6 +634,8 @@ namespace AbiEncoder { public getHexValue(): string { let offset = new BigNumber(0); if (this.memblock !== undefined) { + console.log('Abs Offset = ', JSON.stringify(this.destDataType.getAbsoluteOffset())); + console.log('Local Offset = ', JSON.stringify(this.destDataType.getOffset())); switch (this.memblock.getSection()) { case CalldataSection.PARAMS: offset = this.destDataType.getAbsoluteOffset(); From fc0e7b1132f8956c470a49b0e964d9ca1812e591 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 10:20:19 -0800 Subject: [PATCH 062/230] cleaner bind --- packages/order-utils/test/abi_encoder_test.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 1a33e4f781..292e615f35 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -216,7 +216,7 @@ namespace AbiEncoder { this.bindList = {}; } - public bind(dataType: DataType, section: CalldataSection = CalldataSection.DATA) { + public bind(dataType: DataType, section: CalldataSection) { if (dataType.getId() in this.bindList) { throw `Rebind on ${dataType.getId()}`; } @@ -284,12 +284,12 @@ namespace AbiEncoder { this.memblock = memblock; } - public bind(calldata: Calldata) { + public bind(calldata: Calldata, section: CalldataSection) { if (this.memblock === undefined) { - calldata.bind(this); + calldata.bind(this, section); } _.each(this.children, (child: DataType) => { - child.bind(calldata); + child.bind(calldata, CalldataSection.DATA); }); } @@ -729,17 +729,10 @@ namespace AbiEncoder { encode(args: any[]): string { const calldata = new Calldata(this.selector, this.params.length); - - // Write params section const params = this.params; _.each(params, (param: DataType, i: number) => { - // Assign value to param param.assignValue(args[i]); - // Binds top-level parameter to the params section of calldata - calldata.bind(param, CalldataSection.PARAMS); - // Binds parameter's children to the data section of calldata, - // while retaining internal pointers - param.bind(calldata); + param.bind(calldata, CalldataSection.PARAMS); }); console.log(calldata); From dfa9e435af7477bddb147a8af3700143b27b5dc6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 10:44:22 -0800 Subject: [PATCH 063/230] Implemented Address --- packages/order-utils/test/abi_encoder_test.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 292e615f35..7fb0910190 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -309,9 +309,6 @@ namespace AbiEncoder { public abstract assignValue(value: any): void; public abstract getSignature(): string; - public abstract encodeToCalldata(calldata: Calldata): void; - - // abstract match(type: string): Bool; } export abstract class StaticDataType extends DataType { @@ -333,16 +330,13 @@ namespace AbiEncoder { } public assignValue(value: string) { - const hexValue = ethUtil.bufferToHex(new Buffer(value)); + const evmWordWidth = 32; + const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth)); this.assignHexValue(hexValue); } public getSignature(): string { - throw 1; - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; + return `address`; } public static matchGrammar(type: string): boolean { @@ -634,8 +628,6 @@ namespace AbiEncoder { public getHexValue(): string { let offset = new BigNumber(0); if (this.memblock !== undefined) { - console.log('Abs Offset = ', JSON.stringify(this.destDataType.getAbsoluteOffset())); - console.log('Local Offset = ', JSON.stringify(this.destDataType.getOffset())); switch (this.memblock.getSection()) { case CalldataSection.PARAMS: offset = this.destDataType.getAbsoluteOffset(); @@ -786,6 +778,19 @@ describe.only('ABI Encoder', () => { }); }); + describe.only('Address', () => { + const testAddressDataItem = { name: 'testAddress', type: 'address' }; + it('Valid Address', async () => { + const addressDataType = new AbiEncoder.Address(testAddressDataItem); + addressDataType.assignValue('0xe41d2489571d322189246dafa5ebde1f4699f498'); + const expectedAbiEncodedAddress = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; + + console.log(addressDataType.getHexValue()); + console.log(expectedAbiEncodedAddress); + expect(addressDataType.getHexValue()).to.be.equal(expectedAbiEncodedAddress); + }); + }); + describe('String', () => { const testStringDataItem = { name: 'testString', type: 'string' }; it('Less than 32 bytes', async () => { From ef5ba0375acbff2f985be08a6bd19b0499628e33 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 10:49:53 -0800 Subject: [PATCH 064/230] Implemented bool type --- packages/order-utils/test/abi_encoder_test.ts | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 7fb0910190..76a6da00e8 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -347,20 +347,18 @@ namespace AbiEncoder { export class Bool extends StaticDataType { constructor(dataItem: DataItem) { super(dataItem); - expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + expect(Bool.matchGrammar(dataItem.type)).to.be.true(); } - public assignValue(value: string) { - //const hexValue = ethUtil.bufferToHex(new Buffer(value)); - //this.assignHexValue(hexValue); + public assignValue(value: boolean) { + const evmWordWidth = 32; + const encodedValue = value === true ? '0x1' : '0x0'; + const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth)); + this.assignHexValue(hexValue); } public getSignature(): string { - throw 1; - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; + return 'bool'; } public static matchGrammar(type: string): boolean { @@ -778,7 +776,7 @@ describe.only('ABI Encoder', () => { }); }); - describe.only('Address', () => { + describe('Address', () => { const testAddressDataItem = { name: 'testAddress', type: 'address' }; it('Valid Address', async () => { const addressDataType = new AbiEncoder.Address(testAddressDataItem); @@ -791,6 +789,23 @@ describe.only('ABI Encoder', () => { }); }); + describe.only('Bool', () => { + const testBoolDataItem = { name: 'testBool', type: 'bool' }; + it('True', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(true); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); + + it('False', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(false); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); + }); + describe('String', () => { const testStringDataItem = { name: 'testString', type: 'string' }; it('Less than 32 bytes', async () => { From f6cf3de1c4cabc0fcd6a4bb98948096c4924c2bd Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 11:31:49 -0800 Subject: [PATCH 065/230] Implemented int --- packages/order-utils/test/abi_encoder_test.ts | 105 +++++++++++++++--- 1 file changed, 87 insertions(+), 18 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 76a6da00e8..b557f605d9 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -371,29 +371,71 @@ namespace AbiEncoder { '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); - static DEFAULT_WIDTH = new BigNumber(1); - width: BigNumber = Byte.DEFAULT_WIDTH; + static DEFAULT_WIDTH: number = 256; + width: number = Int.DEFAULT_WIDTH; constructor(dataItem: DataItem) { super(dataItem); - const matches = Byte.matcher.exec(dataItem.type); + const matches = Int.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 2) { - this.width = new BigNumber(matches[1], 10); + if (matches !== null && matches.length === 2 && matches[1] !== undefined) { + this.width = parseInt(matches[1]); + } else { + this.width = 256; } } - public encodeToCalldata(calldata: Calldata): void { - throw 2; + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).sub(1); } - public assignValue(value: string) { - //const hexValue = ethUtil.bufferToHex(new Buffer(value)); - //this.assignHexValue(hexValue); + public getMinValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).times(-1); + } + + public assignValue(value: BigNumber) { + if (value.greaterThan(this.getMaxValue())) { + throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; + } else if (value.lessThan(this.getMinValue())) { + throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; + } + + const hexBase = 16; + const evmWordWidth = 32; + let valueBuf: Buffer; + if (value.greaterThanOrEqualTo(0)) { + valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); + } else { + // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. + // Step 1/3: Convert value to positive binary string + const binBase = 2; + const valueBin = value.times(-1).toString(binBase); + + // Step 2/3: Invert binary value + const bitsInEvmWord = 256; + let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length); + _.each(valueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, binBase); + + // Step 3/3: Add 1 to inverted value + // The result is the two's-complement represent of the input value. + const negativeValue = invertedValue.plus(1); + + // Convert the negated value to a hex string + valueBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), + evmWordWidth, + ); + } + + const encodedValue = ethUtil.bufferToHex(valueBuf); + this.assignHexValue(encodedValue); } public getSignature(): string { - throw 1; + return `uint${this.width}`; } public static matchGrammar(type: string): boolean { @@ -413,7 +455,7 @@ namespace AbiEncoder { super(dataItem); const matches = UInt.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 2) { + if (matches !== null && matches.length === 2 && matches[1] !== undefined) { this.width = parseInt(matches[1]); } else { this.width = 256; @@ -421,7 +463,7 @@ namespace AbiEncoder { } public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1); + return new BigNumber(2).toPower(this.width).sub(1); } public assignValue(value: BigNumber) { @@ -443,10 +485,6 @@ namespace AbiEncoder { return `uint${this.width}`; } - public encodeToCalldata(calldata: Calldata): void { - throw 2; - } - public static matchGrammar(type: string): boolean { return this.matcher.test(type); } @@ -789,7 +827,7 @@ describe.only('ABI Encoder', () => { }); }); - describe.only('Bool', () => { + describe('Bool', () => { const testBoolDataItem = { name: 'testBool', type: 'bool' }; it('True', async () => { const boolDataType = new AbiEncoder.Bool(testBoolDataItem); @@ -806,6 +844,37 @@ describe.only('ABI Encoder', () => { }); }); + describe.only('Integer', () => { + const testIntDataItem = { name: 'testInt', type: 'int' }; + it('Positive - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Positive', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-1)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-437829473)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + }); + describe('String', () => { const testStringDataItem = { name: 'testString', type: 'string' }; it('Less than 32 bytes', async () => { From e2b115a15c4745494043697130e7d1980cde05f4 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 11:37:07 -0800 Subject: [PATCH 066/230] refactored UInt/Int --- packages/order-utils/test/abi_encoder_test.ts | 75 ++++++++----------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index b557f605d9..f9e28015b7 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -366,17 +366,14 @@ namespace AbiEncoder { } } - export class Int extends StaticDataType { - static matcher = RegExp( - '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', - ); + abstract class Number extends StaticDataType { + static MAX_WIDTH: number = 256; + static DEFAULT_WIDTH: number = Number.MAX_WIDTH; + width: number = Number.DEFAULT_WIDTH; - static DEFAULT_WIDTH: number = 256; - width: number = Int.DEFAULT_WIDTH; - - constructor(dataItem: DataItem) { + constructor(dataItem: DataItem, matcher: RegExp) { super(dataItem); - const matches = Int.matcher.exec(dataItem.type); + const matches = matcher.exec(dataItem.type); expect(matches).to.be.not.null(); if (matches !== null && matches.length === 2 && matches[1] !== undefined) { this.width = parseInt(matches[1]); @@ -385,14 +382,6 @@ namespace AbiEncoder { } } - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).sub(1); - } - - public getMinValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).times(-1); - } - public assignValue(value: BigNumber) { if (value.greaterThan(this.getMaxValue())) { throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; @@ -434,8 +423,29 @@ namespace AbiEncoder { this.assignHexValue(encodedValue); } + public abstract getMaxValue(): BigNumber; + public abstract getMinValue(): BigNumber; + } + + export class Int extends Number { + static matcher = RegExp( + '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + constructor(dataItem: DataItem) { + super(dataItem, Int.matcher); + } + + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).sub(1); + } + + public getMinValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).times(-1); + } + public getSignature(): string { - return `uint${this.width}`; + return `int${this.width}`; } public static matchGrammar(type: string): boolean { @@ -443,42 +453,21 @@ namespace AbiEncoder { } } - export class UInt extends StaticDataType { + export class UInt extends Number { static matcher = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); - static DEFAULT_WIDTH: number = 256; - width: number = UInt.DEFAULT_WIDTH; - constructor(dataItem: DataItem) { - super(dataItem); - const matches = UInt.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 2 && matches[1] !== undefined) { - this.width = parseInt(matches[1]); - } else { - this.width = 256; - } + super(dataItem, UInt.matcher); } public getMaxValue(): BigNumber { return new BigNumber(2).toPower(this.width).sub(1); } - public assignValue(value: BigNumber) { - if (value.greaterThan(this.getMaxValue())) { - throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; - } else if (value.lessThan(0)) { - throw `tried to assign value of ${value} to an unsigned integer.`; - } - - const hexBase = 16; - const evmWordWidth = 32; - const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); - const encodedValue = ethUtil.bufferToHex(valueBuf); - - this.assignHexValue(encodedValue); + public getMinValue(): BigNumber { + return new BigNumber(0); } public getSignature(): string { From cef254fa8cacdcba37ec41dec574566b9249b84c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 11:39:48 -0800 Subject: [PATCH 067/230] tests for unsigned integer --- packages/order-utils/test/abi_encoder_test.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index f9e28015b7..fb149faa10 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -833,7 +833,7 @@ describe.only('ABI Encoder', () => { }); }); - describe.only('Integer', () => { + describe('Integer', () => { const testIntDataItem = { name: 'testInt', type: 'int' }; it('Positive - Base case', async () => { const intDataType = new AbiEncoder.Int(testIntDataItem); @@ -862,6 +862,34 @@ describe.only('ABI Encoder', () => { const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); }); + + // TODO: Add bounds tests + tests for different widths + }); + + describe('Unsigned Integer', () => { + const testIntDataItem = { name: 'testUInt', type: 'uint' }; + it('Lower Bound', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(0)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Base Case', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Random value', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedUInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + // TODO: Add bounds tests + tests for different widths }); describe('String', () => { From 91a08b9fdd6ec406c6a0e853ff314501347fac94 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 12:54:29 -0800 Subject: [PATCH 068/230] Static bytes tests --- packages/order-utils/test/abi_encoder_test.ts | 92 ++++++++++++++++--- 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index fb149faa10..6f723036b5 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -484,29 +484,43 @@ namespace AbiEncoder { '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', ); - static DEFAULT_WIDTH = new BigNumber(1); - width: BigNumber = Byte.DEFAULT_WIDTH; + static DEFAULT_WIDTH = 1; + width: number = Byte.DEFAULT_WIDTH; constructor(dataItem: DataItem) { super(dataItem); const matches = Byte.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 2) { - this.width = new BigNumber(matches[1], 10); + if (matches !== null && matches.length === 3 && matches[2] !== undefined) { + this.width = parseInt(matches[2]); + } else { + this.width = Byte.DEFAULT_WIDTH; } } - public assignValue(value: string) { - //const hexValue = ethUtil.bufferToHex(new Buffer(value)); - //this.assignHexValue(hexValue); + public assignValue(value: string | Buffer) { + // Convert value into a buffer and do bounds checking + const valueBuf = ethUtil.toBuffer(value); + if (valueBuf.byteLength > this.width) { + throw new Error( + `Tried to assign ${value} (${ + valueBuf.byteLength + } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, + ); + } else if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + // Store value as hex + const evmWordWidth = 32; + const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); + const hexValue = ethUtil.bufferToHex(paddedValue); + this.assignHexValue(hexValue); } public getSignature(): string { - throw 1; - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; + // Note that `byte` reduces to `bytes1` + return `bytes${this.width}`; } public static matchGrammar(type: string): boolean { @@ -892,6 +906,60 @@ describe.only('ABI Encoder', () => { // TODO: Add bounds tests + tests for different widths }); + describe.only('Static Bytes', () => { + it('Byte (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Byte (no padding)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect(byteDataType.assignValue('0x5')).to.throw(); + }); + + it('Bytes1', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes1' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x0001020304050607080911121314151617181920212223242526272829303132'); + const expectedAbiEncodedByte = '0x0001020304050607080911121314151617181920212223242526272829303132'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (unpadded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x1a18bf61'); + const expectedAbiEncodedByte = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Bytes32 - Too long', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect( + byteDataType.assignValue('0x000102030405060708091112131415161718192021222324252627282930313233'), + ).to.throw( + `Tried to assign 0x000102030405060708091112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32`, + ); + }); + }); + describe('String', () => { const testStringDataItem = { name: 'testString', type: 'string' }; it('Less than 32 bytes', async () => { From f35cf030308c0f18ad26669bfb3b269a5b96a39e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 13:14:46 -0800 Subject: [PATCH 069/230] implemented bytes (dynamic) --- packages/order-utils/test/abi_encoder_test.ts | 145 +++++++++++------- 1 file changed, 89 insertions(+), 56 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 6f723036b5..926f7bc81a 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -528,6 +528,69 @@ namespace AbiEncoder { } } + export class Bytes extends StaticDataType { + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = SolArray.UNDEFINED_LENGTH; + + constructor(dataItem: DataItem) { + super(dataItem); + expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string | Buffer) { + if (typeof value === 'string' && !value.startsWith('0x')) { + throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`); + } + const valueBuf = ethUtil.toBuffer(value); + if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + const wordsForValue = Math.ceil(valueBuf.byteLength / 32); + const paddedBytesForValue = wordsForValue * 32; + const paddedValueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); + const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); + const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); + const encodedValue = ethUtil.bufferToHex(encodedValueBuf); + + this.assignHexValue(encodedValue); + } + + public getSignature(): string { + return 'bytes'; + } + + public static matchGrammar(type: string): boolean { + return type === 'bytes'; + } + } + + export class SolString extends StaticDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(SolString.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + const wordsForValue = Math.ceil(value.length / 32); + const paddedBytesForValue = wordsForValue * 32; + const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); + const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); + const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); + const encodedValue = ethUtil.bufferToHex(encodedValueBuf); + + this.assignHexValue(encodedValue); + } + + public getSignature(): string { + return 'string'; + } + + public static matchGrammar(type: string): boolean { + return type === 'string'; + } + } + export class Tuple extends DynamicDataType { constructor(dataItem: DataItem) { super(dataItem); @@ -552,33 +615,6 @@ namespace AbiEncoder { } } - export class Bytes extends StaticDataType { - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = SolArray.UNDEFINED_LENGTH; - - constructor(dataItem: DataItem) { - super(dataItem); - expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - //const hexValue = ethUtil.bufferToHex(new Buffer(value)); - //this.assignHexValue(hexValue); - } - - public getSignature(): string { - throw 1; - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; - } - - public static matchGrammar(type: string): boolean { - return type === 'bytes'; - } - } - export class SolArray extends DynamicDataType { static matcher = RegExp('^.+\\[([0-9]d*)\\]$'); static UNDEFINED_LENGTH = new BigNumber(-1); @@ -611,34 +647,6 @@ namespace AbiEncoder { } } - export class SolString extends DynamicDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(SolString.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - const wordsForValue = Math.ceil(value.length / 32); - const paddedBytesForValue = wordsForValue * 32; - const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); - const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); - const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); - const encodedValue = ethUtil.bufferToHex(encodedValueBuf); - - this.assignHexValue(encodedValue); - } - - public getSignature(): string { - return 'string'; - } - - public encodeToCalldata(calldata: Calldata): void {} - - public static matchGrammar(type: string): boolean { - return type === 'string'; - } - } - /* TODO class Fixed extends StaticDataType {} @@ -906,7 +914,7 @@ describe.only('ABI Encoder', () => { // TODO: Add bounds tests + tests for different widths }); - describe.only('Static Bytes', () => { + describe('Static Bytes', () => { it('Byte (padded)', async () => { const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; const byteDataType = new AbiEncoder.Byte(testByteDataItem); @@ -960,6 +968,31 @@ describe.only('ABI Encoder', () => { }); }); + describe.only('Bytes (Dynamic)', () => { + const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; + it('Less than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + bytesDataType.assignValue('0x010203'); + const expectedAbiEncodedBytes = + '0x00000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000'; + + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + it('Greater than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + const testValue = '0x' + '61'.repeat(40); + bytesDataType.assignValue(testValue); + const expectedAbiEncodedBytes = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + // @TODO: Add test for throw on half-byte + // @TODO: Test with no 0x prefix + // @TODO: Test with Buffer as input + }); + describe('String', () => { const testStringDataItem = { name: 'testString', type: 'string' }; it('Less than 32 bytes', async () => { From 5053c19599e96b9886e6b36173350162b92405a3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 13:44:27 -0800 Subject: [PATCH 070/230] constructor for an Array + a test -- appears to work --- packages/order-utils/test/abi_encoder_test.ts | 99 ++++++++++++------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 926f7bc81a..bed75b9358 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -528,7 +528,7 @@ namespace AbiEncoder { } } - export class Bytes extends StaticDataType { + export class Bytes extends DynamicDataType { static UNDEFINED_LENGTH = new BigNumber(-1); length: BigNumber = SolArray.UNDEFINED_LENGTH; @@ -565,7 +565,7 @@ namespace AbiEncoder { } } - export class SolString extends StaticDataType { + export class SolString extends DynamicDataType { constructor(dataItem: DataItem) { super(dataItem); expect(SolString.matchGrammar(dataItem.type)).to.be.true(); @@ -591,6 +591,59 @@ namespace AbiEncoder { } } + export class SolArray extends DynamicDataType { + static matcher = RegExp('^(.+)\\[([0-9]d*)\\]$'); + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = SolArray.UNDEFINED_LENGTH; + type: string = '[undefined]'; + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = SolArray.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches === null || matches.length !== 3) { + throw new Error(`Could not parse array: ${dataItem.type}`); + } else if (matches[1] === undefined) { + throw new Error(`Could not parse array type: ${dataItem.type}`); + } else if (matches[2] === undefined) { + // Parse out array type and length + this.type = matches[1]; + this.length = new BigNumber(SolArray.UNDEFINED_LENGTH); + return; + } + + // Parse out array type and length + this.type = matches[1]; + this.length = new BigNumber(matches[2], 10); + if (this.length.lessThan(0)) { + throw new Error(`Bad array length: ${JSON.stringify(this.length)}`); + } + + // Construct children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const childDataItem = { + type: this.type, + name: `${this.getDataItem().name}[${idx.toString(10)}]`, + } as DataItem; + const child = DataTypeFactory.create(childDataItem); + this.children.push(child); + } + } + + public assignValue(value: any[]) { + //const hexValue = ethUtil.bufferToHex(new Buffer(value)); + //this.assignHexValue(hexValue); + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } + + public getSignature(): string { + return `${this.type}[${this.length}]`; + } + } + export class Tuple extends DynamicDataType { constructor(dataItem: DataItem) { super(dataItem); @@ -615,38 +668,6 @@ namespace AbiEncoder { } } - export class SolArray extends DynamicDataType { - static matcher = RegExp('^.+\\[([0-9]d*)\\]$'); - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = SolArray.UNDEFINED_LENGTH; - - constructor(dataItem: DataItem) { - super(dataItem); - const matches = SolArray.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 1) { - this.length = new BigNumber(matches[1], 10); - } - } - - public assignValue(value: string) { - //const hexValue = ethUtil.bufferToHex(new Buffer(value)); - //this.assignHexValue(hexValue); - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - - public getSignature(): string { - throw 1; - } - } - /* TODO class Fixed extends StaticDataType {} @@ -816,7 +837,15 @@ namespace AbiEncoder { } describe.only('ABI Encoder', () => { - describe.only('Just a Greg, Eh', () => { + describe.only('Array', () => { + it('sample', async () => { + const testDataItem = { name: 'testArray', type: 'string[2]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + }); + }); + + describe('Just a Greg, Eh', () => { it('Yessir', async () => { const method = new AbiEncoder.Method(simpleAbi); const calldata = method.encode([new BigNumber(5), 'five']); From 7de7fe782398eded47598046699fdc0915597e33 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 14:18:05 -0800 Subject: [PATCH 071/230] Array of basic types works --- packages/order-utils/test/abi_encoder_test.ts | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index bed75b9358..a455e6a00a 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -612,14 +612,16 @@ namespace AbiEncoder { return; } - // Parse out array type and length + // Parse out array type/length and construct children this.type = matches[1]; this.length = new BigNumber(matches[2], 10); if (this.length.lessThan(0)) { throw new Error(`Bad array length: ${JSON.stringify(this.length)}`); } + this.constructChildren(); + } - // Construct children + private constructChildren() { for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { const childDataItem = { type: this.type, @@ -631,8 +633,43 @@ namespace AbiEncoder { } public assignValue(value: any[]) { - //const hexValue = ethUtil.bufferToHex(new Buffer(value)); - //this.assignHexValue(hexValue); + // Sanity check length + const valueLength = new BigNumber(value.length); + if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + throw new Error( + `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( + valueLength, + )}`, + ); + } + + // Assign length if not already set + if (this.length === SolArray.UNDEFINED_LENGTH) { + this.length = valueLength; + this.constructChildren(); + } + + // Assign values to children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + this.children[idxNumber].assignValue(value[idxNumber]); + } + } + + public getHexValue(): string { + const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); + const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); + let valueBuf = lengthBuf; + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + const childValueHex = this.children[idxNumber].getHexValue(); + const childValueBuf = ethUtil.toBuffer(childValueHex); + valueBuf = Buffer.concat([valueBuf, childValueBuf]); + } + + // Convert value buffer to hex + const valueHex = ethUtil.bufferToHex(valueBuf); + return valueHex; } public static matchGrammar(type: string): boolean { @@ -839,9 +876,15 @@ namespace AbiEncoder { describe.only('ABI Encoder', () => { describe.only('Array', () => { it('sample', async () => { - const testDataItem = { name: 'testArray', type: 'string[2]' }; + const testDataItem = { name: 'testArray', type: 'int[2]' }; const dataType = new AbiEncoder.SolArray(testDataItem); console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue([new BigNumber(5), new BigNumber(6)]); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); }); }); From 39fa26b2f3b4c34c7ff4aa87d6b99ff1af8a4c36 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 14:23:50 -0800 Subject: [PATCH 072/230] arrays with dynamic size --- packages/order-utils/test/abi_encoder_test.ts | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index a455e6a00a..0d7f68780b 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -592,7 +592,7 @@ namespace AbiEncoder { } export class SolArray extends DynamicDataType { - static matcher = RegExp('^(.+)\\[([0-9]d*)\\]$'); + static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); static UNDEFINED_LENGTH = new BigNumber(-1); length: BigNumber = SolArray.UNDEFINED_LENGTH; type: string = '[undefined]'; @@ -601,14 +601,19 @@ namespace AbiEncoder { super(dataItem); const matches = SolArray.matcher.exec(dataItem.type); expect(matches).to.be.not.null(); + console.log(JSON.stringify(matches)); if (matches === null || matches.length !== 3) { throw new Error(`Could not parse array: ${dataItem.type}`); } else if (matches[1] === undefined) { throw new Error(`Could not parse array type: ${dataItem.type}`); } else if (matches[2] === undefined) { - // Parse out array type and length + throw new Error(`Could not parse array length: ${dataItem.type}`); + } + + // Check if length is undefined + if (matches[2] === '') { this.type = matches[1]; - this.length = new BigNumber(SolArray.UNDEFINED_LENGTH); + this.length = SolArray.UNDEFINED_LENGTH; return; } @@ -886,6 +891,18 @@ describe.only('ABI Encoder', () => { console.log('*'.repeat(60)); console.log(hexValue); }); + + it.only('sample undefined size', async () => { + const testDataItem = { name: 'testArray', type: 'int[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue([new BigNumber(5), new BigNumber(6)]); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + }); }); describe('Just a Greg, Eh', () => { From 9f35096fa98a17be0e6ed98dcc4c363a5c4e96f7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 14:37:05 -0800 Subject: [PATCH 073/230] corrected selector for Array --- packages/order-utils/test/abi_encoder_test.ts | 58 +++++++++++++++++-- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 0d7f68780b..8e24c8299c 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -32,6 +32,21 @@ const simpleAbi = { type: 'function', } as MethodAbi; +const stringAbi = { + constant: false, + inputs: [ + { + name: 'greg', + type: 'string[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + const fillOrderAbi = { constant: false, inputs: [ @@ -145,7 +160,7 @@ namespace AbiEncoder { } } - enum CalldataSection { + export enum CalldataSection { NONE, PARAMS, DATA, @@ -195,7 +210,7 @@ namespace AbiEncoder { [key: string]: Memblock; } - class Calldata { + export class Calldata { private selector: string; private params: Memblock[]; private data: Memblock[]; @@ -530,7 +545,7 @@ namespace AbiEncoder { export class Bytes extends DynamicDataType { static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = SolArray.UNDEFINED_LENGTH; + length: BigNumber = Bytes.UNDEFINED_LENGTH; constructor(dataItem: DataItem) { super(dataItem); @@ -682,6 +697,9 @@ namespace AbiEncoder { } public getSignature(): string { + if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { + return `${this.type}[]`; + } return `${this.type}[${this.length}]`; } } @@ -879,7 +897,7 @@ namespace AbiEncoder { } describe.only('ABI Encoder', () => { - describe.only('Array', () => { + describe('Array', () => { it('sample', async () => { const testDataItem = { name: 'testArray', type: 'int[2]' }; const dataType = new AbiEncoder.SolArray(testDataItem); @@ -892,7 +910,7 @@ describe.only('ABI Encoder', () => { console.log(hexValue); }); - it.only('sample undefined size', async () => { + it('sample undefined size', async () => { const testDataItem = { name: 'testArray', type: 'int[]' }; const dataType = new AbiEncoder.SolArray(testDataItem); console.log(JSON.stringify(dataType, null, 4)); @@ -903,15 +921,43 @@ describe.only('ABI Encoder', () => { console.log('*'.repeat(60)); console.log(hexValue); }); + + it('sample dynamic types', async () => { + const testDataItem = { name: 'testArray', type: 'string[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue(['five', 'six', 'seven']); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + const calldata = new AbiEncoder.Calldata('0x01020304', 1); + dataType.bind(calldata, AbiEncoder.CalldataSection.PARAMS); + console.log('*'.repeat(60)); + console.log(calldata.getHexValue()); + }); }); - describe('Just a Greg, Eh', () => { + describe.only('Just a Greg, Eh', () => { it('Yessir', async () => { const method = new AbiEncoder.Method(simpleAbi); const calldata = method.encode([new BigNumber(5), 'five']); console.log(calldata); expect(true).to.be.true(); }); + + it.only('Yessir', async () => { + const method = new AbiEncoder.Method(stringAbi); + const calldata = method.encode([['five', 'six', 'seven']]); + console.log(method.signature); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); }); describe('Address', () => { From 2b4febe337ec188ef0f105f817c124d9a583d669 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 15:22:09 -0800 Subject: [PATCH 074/230] switched from depth-first to breadth-first param binding --- packages/order-utils/test/abi_encoder_test.ts | 87 ++++++++++++------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 8e24c8299c..cd721202ec 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -240,12 +240,20 @@ namespace AbiEncoder { case CalldataSection.PARAMS: this.params.push(memblock); memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); + + console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); break; case CalldataSection.DATA: this.data.push(memblock); memblock.assignLocation(section, this.dataOffset, this.currentDataOffset); + + console.log( + `Binding ${dataType.getDataItem().name} to DATA at ${memblock + .getAbsoluteOffset() + .toString(16)}`, + ); this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); break; @@ -300,12 +308,7 @@ namespace AbiEncoder { } public bind(calldata: Calldata, section: CalldataSection) { - if (this.memblock === undefined) { - calldata.bind(this, section); - } - _.each(this.children, (child: DataType) => { - child.bind(calldata, CalldataSection.DATA); - }); + calldata.bind(this, section); } public getId(): string { @@ -322,6 +325,10 @@ namespace AbiEncoder { return this.memblock.getAbsoluteOffset(); } + public getChildren(): DataType[] { + return this.children; + } + public abstract assignValue(value: any): void; public abstract getSignature(): string; } @@ -680,12 +687,12 @@ namespace AbiEncoder { const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); let valueBuf = lengthBuf; - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + /*for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { const idxNumber = idx.toNumber(); const childValueHex = this.children[idxNumber].getHexValue(); const childValueBuf = ethUtil.toBuffer(childValueHex); valueBuf = Buffer.concat([valueBuf, childValueBuf]); - } + }*/ // Convert value buffer to hex const valueHex = ethUtil.bufferToHex(valueBuf); @@ -815,6 +822,16 @@ namespace AbiEncoder { } } + class Queue { + private store: T[] = []; + push(val: T) { + this.store.push(val); + } + pop(): T | undefined { + return this.store.shift(); + } + } + export class Method { name: string; params: DataType[]; @@ -850,11 +867,23 @@ namespace AbiEncoder { encode(args: any[]): string { const calldata = new Calldata(this.selector, this.params.length); const params = this.params; + const paramQueue = new Queue(); _.each(params, (param: DataType, i: number) => { param.assignValue(args[i]); param.bind(calldata, CalldataSection.PARAMS); + _.each(param.getChildren(), (child: DataType) => { + paramQueue.push(child); + }); }); + let param: DataType | undefined = undefined; + while ((param = paramQueue.pop()) !== undefined) { + param.bind(calldata, CalldataSection.DATA); + _.each(param.getChildren(), (child: DataType) => { + paramQueue.push(child); + }); + } + console.log(calldata); return calldata.getHexValue(); @@ -897,6 +926,27 @@ namespace AbiEncoder { } describe.only('ABI Encoder', () => { + describe.only('Just a Greg, Eh', () => { + it('Yessir', async () => { + const method = new AbiEncoder.Method(simpleAbi); + const calldata = method.encode([new BigNumber(5), 'five']); + console.log(calldata); + expect(true).to.be.true(); + }); + + it.only('Yessir', async () => { + const method = new AbiEncoder.Method(stringAbi); + const calldata = method.encode([['five', 'six', 'seven']]); + console.log(method.signature); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + }); + describe('Array', () => { it('sample', async () => { const testDataItem = { name: 'testArray', type: 'int[2]' }; @@ -939,27 +989,6 @@ describe.only('ABI Encoder', () => { }); }); - describe.only('Just a Greg, Eh', () => { - it('Yessir', async () => { - const method = new AbiEncoder.Method(simpleAbi); - const calldata = method.encode([new BigNumber(5), 'five']); - console.log(calldata); - expect(true).to.be.true(); - }); - - it.only('Yessir', async () => { - const method = new AbiEncoder.Method(stringAbi); - const calldata = method.encode([['five', 'six', 'seven']]); - console.log(method.signature); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = - '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - }); - }); - describe('Address', () => { const testAddressDataItem = { name: 'testAddress', type: 'address' }; it('Valid Address', async () => { From 02f37fa2d9788b81c9f22abb04d12078bce1719e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 16:20:07 -0800 Subject: [PATCH 075/230] Works for encoding arrays --- packages/order-utils/test/abi_encoder_test.ts | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index cd721202ec..23af071b77 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -325,6 +325,11 @@ namespace AbiEncoder { return this.memblock.getAbsoluteOffset(); } + public getSize(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getSize(); + } + public getChildren(): DataType[] { return this.children; } @@ -654,7 +659,7 @@ namespace AbiEncoder { type: this.type, name: `${this.getDataItem().name}[${idx.toString(10)}]`, } as DataItem; - const child = DataTypeFactory.create(childDataItem); + const child = DataTypeFactory.create(childDataItem, this); this.children.push(child); } } @@ -742,12 +747,14 @@ namespace AbiEncoder { export class Pointer extends StaticDataType { destDataType: DynamicDataType; + parentDataType: DataType; - constructor(destDataType: DynamicDataType) { + constructor(destDataType: DynamicDataType, parentDataType: DataType) { const destDataItem = destDataType.getDataItem(); const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; super(dataItem); this.destDataType = destDataType; + this.parentDataType = parentDataType; this.children.push(destDataType); } @@ -761,18 +768,16 @@ namespace AbiEncoder { } public getHexValue(): string { - let offset = new BigNumber(0); - if (this.memblock !== undefined) { - switch (this.memblock.getSection()) { - case CalldataSection.PARAMS: - offset = this.destDataType.getAbsoluteOffset(); - break; - case CalldataSection.DATA: - offset = this.destDataType.getOffset(); - break; - } - } + console.log( + '*'.repeat(40), + this.destDataType.getAbsoluteOffset().toString(16), + '^'.repeat(150), + this.parentDataType.getAbsoluteOffset().toString(16), + ); + let offset = this.destDataType + .getAbsoluteOffset() + .minus(this.parentDataType.getAbsoluteOffset().plus(this.parentDataType.getSize())); const hexBase = 16; const evmWordWidth = 32; const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth); @@ -809,12 +814,12 @@ namespace AbiEncoder { throw new Error(`Unrecognized data type: '${dataItem.type}'`); } - public static create(dataItem: DataItem): DataType { + public static create(dataItem: DataItem, parentDataType: DataType): DataType { const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); if (dataType instanceof StaticDataType) { return dataType; } else if (dataType instanceof DynamicDataType) { - const pointer = new Pointer(dataType); + const pointer = new Pointer(dataType, parentDataType); return pointer; } @@ -832,19 +837,19 @@ namespace AbiEncoder { } } - export class Method { + export class Method extends DataType { name: string; params: DataType[]; - signature: string; + private signature: string; selector: string; constructor(abi: MethodAbi) { - // super(); + super({ type: 'method', name: abi.name }); this.name = abi.name; this.params = []; _.each(abi.inputs, (input: DataItem) => { - this.params.push(DataTypeFactory.create(input)); + this.params.push(DataTypeFactory.create(input, this)); }); // Compute signature @@ -864,7 +869,11 @@ namespace AbiEncoder { console.log(`--SELECTOR--\n${this.selector}\n---------\n`); } - encode(args: any[]): string { + public getSignature(): string { + return this.signature; + } + + public assignValue(args: any[]) { const calldata = new Calldata(this.selector, this.params.length); const params = this.params; const paramQueue = new Queue(); @@ -886,11 +895,16 @@ namespace AbiEncoder { console.log(calldata); - return calldata.getHexValue(); + this.assignHexValue(calldata.getHexValue()); //return calldata.getRaw(); } + public encode(args: any[]): string { + this.assignValue(args); + return this.getHexValue(); + } + /* encodeOptimized(args: any[]): string { const calldata = new Memory(); @@ -937,7 +951,7 @@ describe.only('ABI Encoder', () => { it.only('Yessir', async () => { const method = new AbiEncoder.Method(stringAbi); const calldata = method.encode([['five', 'six', 'seven']]); - console.log(method.signature); + console.log(method.getSignature()); console.log(method.selector); console.log(calldata); From af27dd6fe48d4216fad470b5e4ce7b39ef0b0888 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 17:03:09 -0800 Subject: [PATCH 076/230] tests for tuple --- packages/order-utils/test/abi_encoder_test.ts | 163 +++++++++++++++++- 1 file changed, 154 insertions(+), 9 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 23af071b77..499dfe5ee1 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -47,6 +47,31 @@ const stringAbi = { type: 'function', } as MethodAbi; +const tupleAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + ], + name: 'order', + type: 'tuple', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + const fillOrderAbi = { constant: false, inputs: [ @@ -717,22 +742,90 @@ namespace AbiEncoder { } export class Tuple extends DynamicDataType { + private length: BigNumber; + private childMap: { [key: string]: number }; + constructor(dataItem: DataItem) { super(dataItem); expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + this.length = new BigNumber(0); + this.childMap = {}; + if (dataItem.components !== undefined) { + this.constructChildren(dataItem.components); + this.length = new BigNumber(dataItem.components.length); + } } - public assignValue(value: string) { - //const hexValue = ethUtil.bufferToHex(new Buffer(value)); - //this.assignHexValue(hexValue); + private constructChildren(dataItems: DataItem[]) { + _.each(dataItems, (dataItem: DataItem) => { + const childDataItem = { + type: dataItem.type, + name: `${this.getDataItem().name}.${dataItem.name}`, + } as DataItem; + const child = DataTypeFactory.create(childDataItem, this); + this.childMap[dataItem.name] = this.children.length; + this.children.push(child); + }); + } + + private assignValueFromArray(value: any[]) { + // Sanity check length + const valueLength = new BigNumber(value.length); + if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + throw new Error( + `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( + valueLength, + )}`, + ); + } + + // Assign values to children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + this.children[idxNumber].assignValue(value[idxNumber]); + } + } + + private assignValueFromObject(obj: object) { + let childMap = _.cloneDeep(this.childMap); + _.forOwn(obj, (value: any, key: string) => { + if (key in childMap === false) { + throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); + } + this.children[this.childMap[key]].assignValue(value); + delete childMap[key]; + }); + + if (Object.keys(childMap).length !== 0) { + throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); + } + } + + public assignValue(value: any[] | object) { + if (value instanceof Array) { + this.assignValueFromArray(value); + } else if (typeof value === 'object') { + this.assignValueFromObject(value); + } else { + throw new Error(`Unexpected type for ${value}`); + } + } + + public getHexValue(): string { + return '0x'; } public getSignature(): string { - throw 1; - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; + // Compute signature + let signature = `(`; + _.each(this.children, (child: DataType, i: number) => { + signature += child.getSignature(); + if (i < this.children.length - 1) { + signature += ','; + } + }); + signature += ')'; + return signature; } public static matchGrammar(type: string): boolean { @@ -948,7 +1041,7 @@ describe.only('ABI Encoder', () => { expect(true).to.be.true(); }); - it.only('Yessir', async () => { + it('Array ABI', async () => { const method = new AbiEncoder.Method(stringAbi); const calldata = method.encode([['five', 'six', 'seven']]); console.log(method.getSignature()); @@ -959,6 +1052,58 @@ describe.only('ABI Encoder', () => { '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); }); + + it('Object ABI (Array input)', async () => { + const method = new AbiEncoder.Method(tupleAbi); + const calldata = method.encode([[new BigNumber(5), 'five']]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Object ABI (Object input)', async () => { + const method = new AbiEncoder.Method(tupleAbi); + const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.skip('Object ABI (Object input - Missing Key)', async () => { + const method = new AbiEncoder.Method(tupleAbi); + const calldata = method.encode([{ someUint: new BigNumber(5) }]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + // @TODO: Figure out how to catch throw + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.skip('Object ABI (Object input - Too Many Keys)', async () => { + const method = new AbiEncoder.Method(tupleAbi); + const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five', unwantedKey: 14 }]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + // @TODO: Figure out how to catch throw + expect(calldata).to.be.equal(expectedCalldata); + }); }); describe('Array', () => { From 993c5f4b4a084a210fa158e643027e63fb9443f8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 7 Nov 2018 20:38:55 -0800 Subject: [PATCH 077/230] Another passing simple Abi 2 --- packages/order-utils/test/abi_encoder_test.ts | 344 +++++++++++++++++- 1 file changed, 336 insertions(+), 8 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 499dfe5ee1..95b6630eec 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -72,6 +72,165 @@ const tupleAbi = { type: 'function', } as MethodAbi; +const crazyAbi = { + constant: false, + inputs: [ + /*{ + name: 'someUInt256', + type: 'uint256', + }, + { + name: 'someInt256', + type: 'int256', + }, + { + name: 'someInt32', + type: 'int32', + },*/ + { + name: 'someByte', + type: 'byte', + }, + { + name: 'someBytes32', + type: 'bytes32', + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someString', + type: 'string', + }, + /*{ + name: 'someAddress', + type: 'address', + }, + { + name: 'someBool', + type: 'bool', + },*/ + + /* + + { + name: 'someStaticArray', + type: 'uint8[3]', + }, + { + name: 'someStaticArrayWithDynamicMembers', + type: 'string[2]', + }, + { + name: 'someDynamicArrayWithDynamicMembers', + type: 'bytes[]', + }, + { + name: 'some2DArray', + type: 'string[][]', + }, + { + name: 'someTuple', + type: 'tuple', + components: [ + { + name: 'someUint32', + type: 'uint32', + }, + { + name: 'someStr', + type: 'string', + }, + ], + }, + { + name: 'someTupleWithDynamicTypes', + type: 'tuple', + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + //{ + // name: 'someStrArray', + // type: 'string[]', + /// }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someAddress', + type: 'address', + }, + ], + } /*, + { + name: 'someArrayOfTuplesWithDynamicTypes', + type: 'tuple[]', + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + { + name: 'someStrArray', + type: 'string[]', + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someAddress', + type: 'address', + }, + ], + },*/ + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +const simpleAbi2 = { + constant: false, + inputs: [ + { + name: 'someByte', + type: 'byte', + }, + { + name: 'someBytes32', + type: 'bytes32', + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someString', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + const fillOrderAbi = { constant: false, inputs: [ @@ -734,10 +893,24 @@ namespace AbiEncoder { } public getSignature(): string { - if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { - return `${this.type}[]`; + let type = this.type; + if (this.type === 'tuple') { + let tupleDataItem = { + type: 'tuple', + name: 'N/A', + } as DataItem; + const tupleComponents = this.getDataItem().components; + if (tupleComponents !== undefined) { + tupleDataItem.components = tupleComponents; + } + const tuple = new Tuple(tupleDataItem); + type = tuple.getSignature(); } - return `${this.type}[${this.length}]`; + + if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { + return `${type}[]`; + } + return `${type}[${this.length}]`; } } @@ -753,6 +926,8 @@ namespace AbiEncoder { if (dataItem.components !== undefined) { this.constructChildren(dataItem.components); this.length = new BigNumber(dataItem.components.length); + } else { + throw new Error('Components undefined'); } } @@ -773,9 +948,9 @@ namespace AbiEncoder { const valueLength = new BigNumber(value.length); if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { throw new Error( - `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( - valueLength, - )}`, + `Expected array of ${JSON.stringify( + this.length, + )} elements, but got array of length ${JSON.stringify(valueLength)}`, ); } @@ -971,7 +1146,12 @@ namespace AbiEncoder { const params = this.params; const paramQueue = new Queue(); _.each(params, (param: DataType, i: number) => { - param.assignValue(args[i]); + try { + param.assignValue(args[i]); + } catch (e) { + console.log('Failed to assign to ', param.getDataItem().name); + throw e; + } param.bind(calldata, CalldataSection.PARAMS); _.each(param.getChildren(), (child: DataType) => { paramQueue.push(child); @@ -1034,6 +1214,154 @@ namespace AbiEncoder { describe.only('ABI Encoder', () => { describe.only('Just a Greg, Eh', () => { + it.skip('Crazy ABI', async () => { + const method = new AbiEncoder.Method(crazyAbi); + console.log(method.getSignature()); + + /* + (uint256 b,int256 c,int32 d, + bytes1 e,bytes32 f, + bytes g,string h,address i,bool j,uint8[3] k,string[2] l,bytes[] m, string[][] n, O o,P p, P[]) + + struct P { + uint256 a; + string b; + string[] c; + bytes d; + address e; + }*/ + + const args = [ + /*new BigNumber(437829473), // b + new BigNumber(-437829473), // c + new BigNumber(-14), // d*/ + '0xaf', // e (bytes1) + '0x0001020304050607080911121314151617181920212223242526272829303132', // f (bytes32) + '0x616161616161616161616161616161616161616161616161616161616161616161616161616161611114f324567838475647382938475677448899338457668899002020202020', // g + 'My first name is Greg and my last name is Hysen, what do ya know!', // h + ]; + const a2 = [ + /*'0xe41d2489571d322189246dafa5ebde1f4699f498', // i + true, // j*/ + [new BigNumber(127), new BigNumber(14), new BigNumber(54)], // k + [ + 'the little piping piper piped a piping pipper papper', + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + ], // l + [ + '0x38745637834987324827439287423897238947239847', + '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398', + '0x283473298473248923749238742398742398472894729843278942374982374892374892743982', + ], // m + [ + [ + 'some string', + 'some another string', + 'there are just too many stringsup in', + 'here', + 'yall ghonna make me lose my mind', + ], + [ + 'the little piping piper piped a piping pipper papper', + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + ], + [], + ], // n + [ + new BigNumber(4037824789), + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + ], // o + [ + new BigNumber('239048320948320948230', 10), + 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', + /*[ + [ + '23432423342', + 'skdjfhdsjkfdhsfkjsdhfjkdshfdsjkfhsdjkfhsdjkfhdsjkfhdsjfhsdfjdshjkfsdhf', + 'sdfsdfdfdffsdf', + ], + [], + [], + ['23ehsdjkfhsiufhwfuefhesfhauhesufheuifhsefushfsufehfeuif'], + ],*/ + '0xf74848484848484848484848484848484848483847576879809433994458585848932091', + '0xe41d2489571d322189246dafa5ebde1f4699f498', + ], // p + /*[ + [ + new BigNumber('23904848320948230', 10), + 'akdhjasshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', + [ + [ + '234324342', + 'skdjfhdsjkfdhsfkjsjkfhsdjkfhsdjkfhdsjkfhdsjfhsdfjdshjkfsdhf', + 'sdffdfdffsdf', + ], + [], + [], + ['23ehsdjkfhsiufhwfuefsufheuifhsefushfsufehfeuif'], + ], + '0xf7484848484848484848484848484876879809433994458585848932091', + '0xe41d2489571d322189246dafa6ebde1f4699f498', + ], + [ + new BigNumber('23904832094832030', 10), + 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdkdhsajkdhsadjk', + [ + [ + '2343342', + 'skdjfhdsjkfdhsfkjsdhfjkdshfdsjkfhsdjkfhsdjkfhdssjfhsdfjdshjkfsdhf', + 'sdfsdfdfdffsf', + ], + [], + [], + ['jkfhsiufhwfuefhesfhauhesufhefeuif'], + ], + '0xf7484848484848484848484848484848484848384757687980943091', + '0xe41d2489571d322189246dafa5ebde1f469af498', + ], + [], + [], + ],*/ + ]; + + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + + /*const expectedCalldata = + '0x89771c30af00000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000009e00000000000000000000000000000000000000000000000000000000000000ae00000000000000000000000000000000000000000000000000000000000000047616161616161616161616161616161616161616161616161616161616161616161616161616161611114f3245678384756473829384756774488993384576688990020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414d79206669727374206e616d65206973204772656720616e64206d79206c617374206e616d6520697320487973656e2c207768617420646f207961206b6e6f772100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; + */ + + //expect(calldata).to.be.equal(expectedCalldata); + + /*const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata);*/ + }); + + it.only('Simple ABI 2', async () => { + const method = new AbiEncoder.Method(crazyAbi); + + const args = [ + '0xaf', // e (bytes1) + '0x0001020304050607080911121314151617181920212223242526272829303132', // f (bytes32) + '0x616161616161616161616161616161616161616161616161616161616161616161616161616161611114f324567838475647382938475677448899338457668899002020202020', // g + 'My first name is Greg and my last name is Hysen, what do ya know!', // h + ]; + + const calldata = method.encode(args); + const expectedCalldata = + '0x7ac2bd96af000000000000000000000000000000000000000000000000000000000000000001020304050607080911121314151617181920212223242526272829303132000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000047616161616161616161616161616161616161616161616161616161616161616161616161616161611114f3245678384756473829384756774488993384576688990020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414d79206669727374206e616d65206973204772656720616e64206d79206c617374206e616d6520697320487973656e2c207768617420646f207961206b6e6f772100000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + it('Yessir', async () => { const method = new AbiEncoder.Method(simpleAbi); const calldata = method.encode([new BigNumber(5), 'five']); @@ -1291,7 +1619,7 @@ describe.only('ABI Encoder', () => { }); }); - describe.only('Bytes (Dynamic)', () => { + describe('Bytes (Dynamic)', () => { const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; it('Less than 32 bytes', async () => { const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); From 1600820deab0636d37a9a50025576a75ae8feeec Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 06:37:25 -0800 Subject: [PATCH 078/230] static array abi tests --- packages/order-utils/test/abi_encoder_test.ts | 131 ++++++++++++------ 1 file changed, 90 insertions(+), 41 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 95b6630eec..b27dc47935 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -72,6 +72,21 @@ const tupleAbi = { type: 'function', } as MethodAbi; +const staticArrayAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'uint8[3]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + const crazyAbi = { constant: false, inputs: [ @@ -86,7 +101,7 @@ const crazyAbi = { { name: 'someInt32', type: 'int32', - },*/ + }, { name: 'someByte', type: 'byte', @@ -102,7 +117,7 @@ const crazyAbi = { { name: 'someString', type: 'string', - }, + },*/ /*{ name: 'someAddress', type: 'address', @@ -112,12 +127,10 @@ const crazyAbi = { type: 'bool', },*/ - /* - { name: 'someStaticArray', type: 'uint8[3]', - }, + } /* { name: 'someStaticArrayWithDynamicMembers', type: 'string[2]', @@ -195,7 +208,7 @@ const crazyAbi = { type: 'address', }, ], - },*/ + },*/, ], name: 'simpleFunction', outputs: [], @@ -520,6 +533,7 @@ namespace AbiEncoder { public abstract assignValue(value: any): void; public abstract getSignature(): string; + public abstract isStatic(): boolean; } export abstract class StaticDataType extends DataType { @@ -550,6 +564,10 @@ namespace AbiEncoder { return `address`; } + public isStatic(): boolean { + return true; + } + public static matchGrammar(type: string): boolean { return type === 'address'; } @@ -572,6 +590,10 @@ namespace AbiEncoder { return 'bool'; } + public isStatic(): boolean { + return true; + } + public static matchGrammar(type: string): boolean { return type === 'bool'; } @@ -634,6 +656,10 @@ namespace AbiEncoder { this.assignHexValue(encodedValue); } + public isStatic(): boolean { + return true; + } + public abstract getMaxValue(): BigNumber; public abstract getMinValue(): BigNumber; } @@ -715,7 +741,7 @@ namespace AbiEncoder { if (valueBuf.byteLength > this.width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { @@ -734,6 +760,10 @@ namespace AbiEncoder { return `bytes${this.width}`; } + public isStatic(): boolean { + return true; + } + public static matchGrammar(type: string): boolean { return this.matcher.test(type); } @@ -771,6 +801,10 @@ namespace AbiEncoder { return 'bytes'; } + public isStatic(): boolean { + return false; + } + public static matchGrammar(type: string): boolean { return type === 'bytes'; } @@ -797,6 +831,10 @@ namespace AbiEncoder { return 'string'; } + public isStatic(): boolean { + return false; + } + public static matchGrammar(type: string): boolean { return type === 'string'; } @@ -807,6 +845,7 @@ namespace AbiEncoder { static UNDEFINED_LENGTH = new BigNumber(-1); length: BigNumber = SolArray.UNDEFINED_LENGTH; type: string = '[undefined]'; + isLengthDefined: boolean; constructor(dataItem: DataItem) { super(dataItem); @@ -825,10 +864,12 @@ namespace AbiEncoder { if (matches[2] === '') { this.type = matches[1]; this.length = SolArray.UNDEFINED_LENGTH; + this.isLengthDefined = false; return; } // Parse out array type/length and construct children + this.isLengthDefined = true; this.type = matches[1]; this.length = new BigNumber(matches[2], 10); if (this.length.lessThan(0)) { @@ -873,6 +914,10 @@ namespace AbiEncoder { } public getHexValue(): string { + if (this.isLengthDefined) { + return '0x'; + } + const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); let valueBuf = lengthBuf; @@ -888,6 +933,10 @@ namespace AbiEncoder { return valueHex; } + public isStatic(): boolean { + return this.isLengthDefined; + } + public static matchGrammar(type: string): boolean { return this.matcher.test(type); } @@ -1003,6 +1052,10 @@ namespace AbiEncoder { return signature; } + public isStatic(): boolean { + return false; // @TODO: True in every case or only when dynamic data? + } + public static matchGrammar(type: string): boolean { return type === 'tuple'; } @@ -1057,6 +1110,10 @@ namespace AbiEncoder { return this.destDataType.getSignature(); } + public isStatic(): boolean { + return true; + } + public encodeToCalldata(calldata: Calldata): void { throw 2; } @@ -1084,9 +1141,9 @@ namespace AbiEncoder { public static create(dataItem: DataItem, parentDataType: DataType): DataType { const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); - if (dataType instanceof StaticDataType) { + if (dataType.isStatic()) { return dataType; - } else if (dataType instanceof DynamicDataType) { + } else { const pointer = new Pointer(dataType, parentDataType); return pointer; } @@ -1178,6 +1235,10 @@ namespace AbiEncoder { return this.getHexValue(); } + public isStatic(): boolean { + return true; + } + /* encodeOptimized(args: any[]): string { const calldata = new Memory(); @@ -1214,36 +1275,13 @@ namespace AbiEncoder { describe.only('ABI Encoder', () => { describe.only('Just a Greg, Eh', () => { - it.skip('Crazy ABI', async () => { - const method = new AbiEncoder.Method(crazyAbi); + it.only('Crazy ABI', async () => { + const method = new AbiEncoder.Method(staticArrayAbi); console.log(method.getSignature()); - /* - (uint256 b,int256 c,int32 d, - bytes1 e,bytes32 f, - bytes g,string h,address i,bool j,uint8[3] k,string[2] l,bytes[] m, string[][] n, O o,P p, P[]) + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - struct P { - uint256 a; - string b; - string[] c; - bytes d; - address e; - }*/ - - const args = [ - /*new BigNumber(437829473), // b - new BigNumber(-437829473), // c - new BigNumber(-14), // d*/ - '0xaf', // e (bytes1) - '0x0001020304050607080911121314151617181920212223242526272829303132', // f (bytes32) - '0x616161616161616161616161616161616161616161616161616161616161616161616161616161611114f324567838475647382938475677448899338457668899002020202020', // g - 'My first name is Greg and my last name is Hysen, what do ya know!', // h - ]; const a2 = [ - /*'0xe41d2489571d322189246dafa5ebde1f4699f498', // i - true, // j*/ - [new BigNumber(127), new BigNumber(14), new BigNumber(54)], // k [ 'the little piping piper piped a piping pipper papper', 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', @@ -1330,11 +1368,10 @@ describe.only('ABI Encoder', () => { console.log('*'.repeat(40)); console.log(JSON.stringify(args)); - /*const expectedCalldata = - '0x89771c30af00000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000009e00000000000000000000000000000000000000000000000000000000000000ae00000000000000000000000000000000000000000000000000000000000000047616161616161616161616161616161616161616161616161616161616161616161616161616161611114f3245678384756473829384756774488993384576688990020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414d79206669727374206e616d65206973204772656720616e64206d79206c617374206e616d6520697320487973656e2c207768617420646f207961206b6e6f772100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; - */ + const expectedCalldata = + '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; - //expect(calldata).to.be.equal(expectedCalldata); + expect(calldata).to.be.equal(expectedCalldata); /*const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); console.log(method.getSignature()); @@ -1346,8 +1383,20 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata);*/ }); - it.only('Simple ABI 2', async () => { - const method = new AbiEncoder.Method(crazyAbi); + it.only('Static Array ABI', async () => { + const method = new AbiEncoder.Method(staticArrayAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + const expectedCalldata = + '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Simple ABI 2', async () => { + const method = new AbiEncoder.Method(simpleAbi2); const args = [ '0xaf', // e (bytes1) From a13099bde3aa6a47516127ae7c7d9f78488a3911 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 07:27:14 -0800 Subject: [PATCH 079/230] hack for static arrays --- packages/order-utils/test/abi_encoder_test.ts | 134 ++++++++++-------- 1 file changed, 78 insertions(+), 56 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index b27dc47935..b8f1d39ad5 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -130,7 +130,7 @@ const crazyAbi = { { name: 'someStaticArray', type: 'uint8[3]', - } /* + }, { name: 'someStaticArrayWithDynamicMembers', type: 'string[2]', @@ -170,9 +170,9 @@ const crazyAbi = { type: 'string', }, //{ - // name: 'someStrArray', - // type: 'string[]', - /// }, + // name: 'someStrArray', + // type: 'string[]', + /// }, { name: 'someBytes', type: 'bytes', @@ -889,6 +889,15 @@ namespace AbiEncoder { } } + // @TODO: HACKY -- shouldn't really have children for a + public getChildren(): DataType[] { + if (this.isStatic()) { + return []; + } else { + return this.children; + } + } + public assignValue(value: any[]) { // Sanity check length const valueLength = new BigNumber(value.length); @@ -913,26 +922,40 @@ namespace AbiEncoder { } } - public getHexValue(): string { - if (this.isLengthDefined) { - return '0x'; - } - + private getHexValueDynamicArray(): string { const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); let valueBuf = lengthBuf; - /*for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + + const valueHex = ethUtil.bufferToHex(valueBuf); + return valueHex; + } + + private getHexValueStaticArray(): string { + let valueBuf = new Buffer(""); + + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { const idxNumber = idx.toNumber(); const childValueHex = this.children[idxNumber].getHexValue(); const childValueBuf = ethUtil.toBuffer(childValueHex); valueBuf = Buffer.concat([valueBuf, childValueBuf]); - }*/ + + console.log(JSON.stringify(idx)); + } // Convert value buffer to hex const valueHex = ethUtil.bufferToHex(valueBuf); return valueHex; } + public getHexValue(): string { + if (this.isLengthDefined) { + return this.getHexValueStaticArray(); + } else { + return this.getHexValueDynamicArray(); + } + } + public isStatic(): boolean { return this.isLengthDefined; } @@ -1275,56 +1298,54 @@ namespace AbiEncoder { describe.only('ABI Encoder', () => { describe.only('Just a Greg, Eh', () => { - it.only('Crazy ABI', async () => { - const method = new AbiEncoder.Method(staticArrayAbi); + it('Crazy ABI', async () => { + const method = new AbiEncoder.Method(crazyAbi); console.log(method.getSignature()); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - - const a2 = [ + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)], + [ + 'the little piping piper piped a piping pipper papper', + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + ], // l + [ + '0x38745637834987324827439287423897238947239847', + '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398', + '0x283473298473248923749238742398742398472894729843278942374982374892374892743982', + ], // m + [ + [ + 'some string', + 'some another string', + 'there are just too many stringsup in', + 'here', + 'yall ghonna make me lose my mind', + ], [ 'the little piping piper piped a piping pipper papper', 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', - ], // l - [ - '0x38745637834987324827439287423897238947239847', - '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398', - '0x283473298473248923749238742398742398472894729843278942374982374892374892743982', - ], // m - [ + ], + [], + ], // n + [ + new BigNumber(4037824789), + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + ], // o + [ + new BigNumber('239048320948320948230', 10), + 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', + /*[ [ - 'some string', - 'some another string', - 'there are just too many stringsup in', - 'here', - 'yall ghonna make me lose my mind', - ], - [ - 'the little piping piper piped a piping pipper papper', - 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + '23432423342', + 'skdjfhdsjkfdhsfkjsdhfjkdshfdsjkfhsdjkfhsdjkfhdsjkfhdsjfhsdfjdshjkfsdhf', + 'sdfsdfdfdffsdf', ], [], - ], // n - [ - new BigNumber(4037824789), - 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', - ], // o - [ - new BigNumber('239048320948320948230', 10), - 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', - /*[ - [ - '23432423342', - 'skdjfhdsjkfdhsfkjsdhfjkdshfdsjkfhsdjkfhsdjkfhdsjkfhdsjfhsdfjdshjkfsdhf', - 'sdfsdfdfdffsdf', - ], - [], - [], - ['23ehsdjkfhsiufhwfuefhesfhauhesufheuifhsefushfsufehfeuif'], - ],*/ - '0xf74848484848484848484848484848484848483847576879809433994458585848932091', - '0xe41d2489571d322189246dafa5ebde1f4699f498', - ], // p + [], + ['23ehsdjkfhsiufhwfuefhesfhauhesufheuifhsefushfsufehfeuif'], + ],*/ + '0xf74848484848484848484848484848484848483847576879809433994458585848932091', + '0xe41d2489571d322189246dafa5ebde1f4699f498', + ], // p /*[ [ new BigNumber('23904848320948230', 10), @@ -1367,11 +1388,12 @@ describe.only('ABI Encoder', () => { console.log(calldata); console.log('*'.repeat(40)); console.log(JSON.stringify(args)); + console.log(method.getSignature()); + /* const expectedCalldata = - '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; - - expect(calldata).to.be.equal(expectedCalldata); + '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata);*/ /*const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); console.log(method.getSignature()); From 637ab1076a4071505ea3d4797767826070a65e16 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 10:31:30 -0800 Subject: [PATCH 080/230] ABI Encoding for all combinations of arrays --- packages/order-utils/test/abi_encoder.ts | 1098 ++++++++++++++ packages/order-utils/test/abi_encoder_test.ts | 1336 +---------------- packages/order-utils/test/abi_samples.ts | 358 +++++ 3 files changed, 1501 insertions(+), 1291 deletions(-) create mode 100644 packages/order-utils/test/abi_encoder.ts create mode 100644 packages/order-utils/test/abi_samples.ts diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts new file mode 100644 index 0000000000..55b63b7011 --- /dev/null +++ b/packages/order-utils/test/abi_encoder.ts @@ -0,0 +1,1098 @@ + +import * as chai from 'chai'; +import 'mocha'; +import ethUtil = require('ethereumjs-util'); + +var _ = require('lodash'); + +import { chaiSetup } from './utils/chai_setup'; + +import { MethodAbi, DataItem } from 'ethereum-types'; + +import { BigNumber } from '@0x/utils'; +import { bigNumberify } from 'ethers/utils'; + +chaiSetup.configure(); +const expect = chai.expect; + + +class Word { + private value: string; + + constructor(value?: string) { + if (value === undefined) { + this.value = ''; + } else { + this.value = value; + } + } + + public set(value: string) { + if (value.length !== 64) { + throw `Tried to create word that is not 32 bytes: ${value}`; + } + + this.value = value; + } + + public get(): string { + return this.value; + } + + public getAsHex(): string { + return `0x${this.value}`; + } +} + +export enum CalldataSection { + NONE, + PARAMS, + DATA, +} + +class Memblock { + private dataType: DataType; + private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber }; + + constructor(dataType: DataType) { + this.dataType = dataType; + this.location = { + calldataSection: CalldataSection.NONE, + sectionOffset: new BigNumber(0), + offset: new BigNumber(0), + }; + } + + public getSize(): BigNumber { + return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength); + } + + public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) { + this.location.calldataSection = calldataSection; + this.location.sectionOffset = sectionOffset; + this.location.offset = offset; + } + + public get(): string { + return ethUtil.stripHexPrefix(this.dataType.getHexValue()); + } + + public getOffset(): BigNumber { + return this.location.offset; + } + + public getAbsoluteOffset(): BigNumber { + return this.location.sectionOffset.plus(this.location.offset); + } + + public getSection(): CalldataSection { + return this.location.calldataSection; + } +} + +interface BindList { + [key: string]: Memblock; +} + +export class Calldata { + private selector: string; + private params: Memblock[]; + private data: Memblock[]; + private dataOffset: BigNumber; + private currentDataOffset: BigNumber; + private currentParamOffset: BigNumber; + private bindList: BindList; + + constructor(selector: string, nParams: number) { + this.selector = selector; + this.params = []; + this.data = []; + const evmWordSize = 32; + this.dataOffset = new BigNumber(nParams).times(evmWordSize); + this.currentDataOffset = new BigNumber(0); + this.currentParamOffset = new BigNumber(0); + this.bindList = {}; + } + + public bind(dataType: DataType, section: CalldataSection) { + if (dataType.getId() in this.bindList) { + throw `Rebind on ${dataType.getId()}`; + } + const memblock = new Memblock(dataType); + switch (section) { + case CalldataSection.PARAMS: + this.params.push(memblock); + memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); + + console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); + this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); + break; + + case CalldataSection.DATA: + this.data.push(memblock); + memblock.assignLocation(section, this.dataOffset, this.currentDataOffset); + + console.log( + `Binding ${dataType.getDataItem().name} to DATA at ${memblock + .getAbsoluteOffset() + .toString(16)}`, + ); + this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); + break; + + default: + throw `Unrecognized calldata section: ${section}`; + } + + this.bindList[dataType.getId()] = memblock; + dataType.rbind(memblock); + } + + public getHexValue(): string { + let hexValue = `${this.selector}`; + _.each(this.params, (memblock: Memblock) => { + hexValue += memblock.get(); + }); + _.each(this.data, (memblock: Memblock) => { + hexValue += memblock.get(); + }); + + return hexValue; + } +} + +export abstract class DataType { + private dataItem: DataItem; + private hexValue: string; + protected memblock: Memblock | undefined; + protected children: DataType[]; + + constructor(dataItem: DataItem) { + this.dataItem = dataItem; + this.hexValue = '0x'; + this.memblock = undefined; + this.children = []; + } + + protected assignHexValue(hexValue: string) { + this.hexValue = hexValue; + } + + public getHexValue(): string { + return this.hexValue; + } + + public getDataItem(): DataItem { + return this.dataItem; + } + + public rbind(memblock: Memblock) { + this.memblock = memblock; + } + + public bind(calldata: Calldata, section: CalldataSection) { + calldata.bind(this, section); + _.each(this.getChildren(), (child: DataType) => { + child.bind(calldata, CalldataSection.DATA); + }); + } + + public getId(): string { + return this.dataItem.name; + } + + public getOffset(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getOffset(); + } + + public getAbsoluteOffset(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getAbsoluteOffset(); + } + /* + public getSize(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getSize(); + } + */ + + public getChildren(): DataType[] { + return this.children; + } + + public getSize(): BigNumber { + return this.getHeaderSize().plus(this.getBodySize()); + } + + public abstract assignValue(value: any): void; + public abstract getSignature(): string; + public abstract isStatic(): boolean; + public abstract getHeaderSize(): BigNumber; + public abstract getBodySize(): BigNumber; +} + +export abstract class StaticDataType extends DataType { + constructor(dataItem: DataItem) { + super(dataItem); + } +} + +export abstract class DynamicDataType extends DataType { + constructor(dataItem: DataItem) { + super(dataItem); + } +} + +export class Address extends StaticDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(Address.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + const evmWordWidth = 32; + const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth)); + this.assignHexValue(hexValue); + } + + public getSignature(): string { + return `address`; + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + public static matchGrammar(type: string): boolean { + return type === 'address'; + } +} + +export class Bool extends StaticDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(Bool.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: boolean) { + const evmWordWidth = 32; + const encodedValue = value === true ? '0x1' : '0x0'; + const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth)); + this.assignHexValue(hexValue); + } + + public getSignature(): string { + return 'bool'; + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + public static matchGrammar(type: string): boolean { + return type === 'bool'; + } +} + +abstract class Number extends StaticDataType { + static MAX_WIDTH: number = 256; + static DEFAULT_WIDTH: number = Number.MAX_WIDTH; + width: number = Number.DEFAULT_WIDTH; + + constructor(dataItem: DataItem, matcher: RegExp) { + super(dataItem); + const matches = matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 2 && matches[1] !== undefined) { + this.width = parseInt(matches[1]); + } else { + this.width = 256; + } + } + + public assignValue(value: BigNumber) { + if (value.greaterThan(this.getMaxValue())) { + throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; + } else if (value.lessThan(this.getMinValue())) { + throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; + } + + const hexBase = 16; + const evmWordWidth = 32; + let valueBuf: Buffer; + if (value.greaterThanOrEqualTo(0)) { + valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); + } else { + // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. + // Step 1/3: Convert value to positive binary string + const binBase = 2; + const valueBin = value.times(-1).toString(binBase); + + // Step 2/3: Invert binary value + const bitsInEvmWord = 256; + let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length); + _.each(valueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, binBase); + + // Step 3/3: Add 1 to inverted value + // The result is the two's-complement represent of the input value. + const negativeValue = invertedValue.plus(1); + + // Convert the negated value to a hex string + valueBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), + evmWordWidth, + ); + } + + const encodedValue = ethUtil.bufferToHex(valueBuf); + this.assignHexValue(encodedValue); + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + public abstract getMaxValue(): BigNumber; + public abstract getMinValue(): BigNumber; +} + +export class Int extends Number { + static matcher = RegExp( + '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + constructor(dataItem: DataItem) { + super(dataItem, Int.matcher); + } + + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).sub(1); + } + + public getMinValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).times(-1); + } + + public getSignature(): string { + return `int${this.width}`; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class UInt extends Number { + static matcher = RegExp( + '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + constructor(dataItem: DataItem) { + super(dataItem, UInt.matcher); + } + + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this.width).sub(1); + } + + public getMinValue(): BigNumber { + return new BigNumber(0); + } + + public getSignature(): string { + return `uint${this.width}`; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class Byte extends StaticDataType { + static matcher = RegExp( + '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', + ); + + static DEFAULT_WIDTH = 1; + width: number = Byte.DEFAULT_WIDTH; + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = Byte.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 3 && matches[2] !== undefined) { + this.width = parseInt(matches[2]); + } else { + this.width = Byte.DEFAULT_WIDTH; + } + } + + public assignValue(value: string | Buffer) { + // Convert value into a buffer and do bounds checking + const valueBuf = ethUtil.toBuffer(value); + if (valueBuf.byteLength > this.width) { + throw new Error( + `Tried to assign ${value} (${ + valueBuf.byteLength + } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, + ); + } else if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + // Store value as hex + const evmWordWidth = 32; + const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); + const hexValue = ethUtil.bufferToHex(paddedValue); + this.assignHexValue(hexValue); + } + + public getSignature(): string { + // Note that `byte` reduces to `bytes1` + return `bytes${this.width}`; + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class Bytes extends DynamicDataType { + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = Bytes.UNDEFINED_LENGTH; + + constructor(dataItem: DataItem) { + super(dataItem); + expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string | Buffer) { + if (typeof value === 'string' && !value.startsWith('0x')) { + throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`); + } + const valueBuf = ethUtil.toBuffer(value); + if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + const wordsForValue = Math.ceil(valueBuf.byteLength / 32); + const paddedBytesForValue = wordsForValue * 32; + const paddedValueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); + const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); + const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); + const encodedValue = ethUtil.bufferToHex(encodedValueBuf); + + this.assignHexValue(encodedValue); + } + + public getSignature(): string { + return 'bytes'; + } + + public isStatic(): boolean { + return false; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(this.getHexValue().length); + } + + public static matchGrammar(type: string): boolean { + return type === 'bytes'; + } +} + +export class SolString extends DynamicDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(SolString.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + const wordsForValue = Math.ceil(value.length / 32); + const paddedBytesForValue = wordsForValue * 32; + const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); + const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); + const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); + const encodedValue = ethUtil.bufferToHex(encodedValueBuf); + + this.assignHexValue(encodedValue); + } + + public getSignature(): string { + return 'string'; + } + + public isStatic(): boolean { + return false; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(this.getHexValue().length); + } + + public static matchGrammar(type: string): boolean { + return type === 'string'; + } +} + +export class SolArray extends DynamicDataType { + static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = SolArray.UNDEFINED_LENGTH; + type: string = '[undefined]'; + isLengthDefined: boolean; + isStaticArray: boolean; // An array is dynamic if it's lenghth is undefined or if its children are dynamic. + private elements: DataType[]; + + /* + --- Layout 1: Fixed Length Array with Static Types --- + Elem1, Elem2, ..., ElemN + + --- Layout 2: Fixed Length Array with Dynamic Types --- + PtrToArray, ..., Elem1, Elem2, ..., ElemN + + --- Layout 3: Dynamic Length Array with Static Types --- + PtrToArray, ..., ArrayLength, Elem1, Elem2, ..., ElemN + + --- Layout 4: Dynamic Length Array with Dynamic Types --- + PtrToArray, ..., ArrayLength, PtrToElem1, PtrToElem2, ..., PtrToElemN, ..., Elem1, Elem2, ..., ElemN + */ + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = SolArray.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + console.log(JSON.stringify(matches)); + if (matches === null || matches.length !== 3) { + throw new Error(`Could not parse array: ${dataItem.type}`); + } else if (matches[1] === undefined) { + throw new Error(`Could not parse array type: ${dataItem.type}`); + } else if (matches[2] === undefined) { + throw new Error(`Could not parse array length: ${dataItem.type}`); + } + + this.elements = []; + + // Check if length is undefined + if (matches[2] === '') { + this.type = matches[1]; + this.length = SolArray.UNDEFINED_LENGTH; + this.isLengthDefined = false; + this.isStaticArray = false; + return; + } + + // Parse out array type/length and construct children + this.isLengthDefined = true; + this.type = matches[1]; + this.length = new BigNumber(matches[2], 10); + if (this.length.lessThan(1)) { + throw new Error(`Bad array length: ${JSON.stringify(this.length)}`); + } + this.constructChildren(); + + // Check if we're static or not + this.isStaticArray = !(this.elements[0] instanceof Pointer); //this.elements[0].isStatic(); + //throw new Error(`Am I static? ${this.isStaticArray}`); + } + + private constructChildren() { + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const childDataItem = { + type: this.type, + name: `${this.getDataItem().name}[${idx.toString(10)}]`, + } as DataItem; + const child = DataTypeFactory.create(childDataItem, this); + this.elements.push(child); + if (child instanceof Pointer) { + const pointsTo = child.getChildren()[0]; + console.log(JSON.stringify(pointsTo)); + this.children.push(pointsTo); // DataType pointing to + } + } + } + + // @TODO: HACKY -- shouldn't really have children for a + /* + public getChildren(): DataType[] { + if (this.isStatic()) { + return []; + } else { + return this.children; + } + }*/ + + public assignValue(value: any[]) { + // Sanity check length + const valueLength = new BigNumber(value.length); + if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + throw new Error( + `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( + valueLength, + )}`, + ); + } + + // Assign length if not already set + if (this.length === SolArray.UNDEFINED_LENGTH) { + this.length = valueLength; + this.constructChildren(); + } + + // Assign values to children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + this.elements[idxNumber].assignValue(value[idxNumber]); + } + } + + public getHexValue(): string { + let valueBuf = new Buffer(""); + + if (this.isLengthDefined === false) { + // Must include the array length + const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); + const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); + valueBuf = lengthBuf; + } + + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + const childValueHex = this.elements[idxNumber].getHexValue(); + const childValueBuf = ethUtil.toBuffer(childValueHex); + valueBuf = Buffer.concat([valueBuf, childValueBuf]); + } + + // Convert value buffer to hex + const valueHex = ethUtil.bufferToHex(valueBuf); + return valueHex; + } + + public isStatic(): boolean { + return this.isStaticArray; + } + + public getHeaderSize(): BigNumber { + let size = new BigNumber(0); + if (!this.isLengthDefined) { + size = new BigNumber(32); // stores length of bytes + } + return size; + } + + public getBodySize(): BigNumber { + const evmWordWidth = new BigNumber(32); + const body = this.length.times(evmWordWidth); + return body; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } + + public getSignature(): string { + let type = this.type; + if (this.type === 'tuple') { + let tupleDataItem = { + type: 'tuple', + name: 'N/A', + } as DataItem; + const tupleComponents = this.getDataItem().components; + if (tupleComponents !== undefined) { + tupleDataItem.components = tupleComponents; + } + const tuple = new Tuple(tupleDataItem); + type = tuple.getSignature(); + } + + if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { + return `${type}[]`; + } + return `${type}[${this.length}]`; + } +} + +export class Tuple extends DynamicDataType { + private length: BigNumber; + private childMap: { [key: string]: number }; + + constructor(dataItem: DataItem) { + super(dataItem); + expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + this.length = new BigNumber(0); + this.childMap = {}; + if (dataItem.components !== undefined) { + this.constructChildren(dataItem.components); + this.length = new BigNumber(dataItem.components.length); + } else { + throw new Error('Components undefined'); + } + } + + private constructChildren(dataItems: DataItem[]) { + _.each(dataItems, (dataItem: DataItem) => { + const childDataItem = { + type: dataItem.type, + name: `${this.getDataItem().name}.${dataItem.name}`, + } as DataItem; + const child = DataTypeFactory.create(childDataItem, this); + this.childMap[dataItem.name] = this.children.length; + this.children.push(child); + }); + } + + private assignValueFromArray(value: any[]) { + // Sanity check length + const valueLength = new BigNumber(value.length); + if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + throw new Error( + `Expected array of ${JSON.stringify( + this.length, + )} elements, but got array of length ${JSON.stringify(valueLength)}`, + ); + } + + // Assign values to children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + this.children[idxNumber].assignValue(value[idxNumber]); + } + } + + private assignValueFromObject(obj: object) { + let childMap = _.cloneDeep(this.childMap); + _.forOwn(obj, (value: any, key: string) => { + if (key in childMap === false) { + throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); + } + this.children[this.childMap[key]].assignValue(value); + delete childMap[key]; + }); + + if (Object.keys(childMap).length !== 0) { + throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); + } + } + + public assignValue(value: any[] | object) { + if (value instanceof Array) { + this.assignValueFromArray(value); + } else if (typeof value === 'object') { + this.assignValueFromObject(value); + } else { + throw new Error(`Unexpected type for ${value}`); + } + } + + public getHexValue(): string { + return '0x'; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + const evmWordWidth = new BigNumber(32); + const size = this.length.times(evmWordWidth); + return size; + } + + public getSignature(): string { + // Compute signature + let signature = `(`; + _.each(this.children, (child: DataType, i: number) => { + signature += child.getSignature(); + if (i < this.children.length - 1) { + signature += ','; + } + }); + signature += ')'; + return signature; + } + + public isStatic(): boolean { + return false; // @TODO: True in every case or only when dynamic data? + } + + public static matchGrammar(type: string): boolean { + return type === 'tuple'; + } +} + +/* TODO +class Fixed extends StaticDataType {} + +class UFixed extends StaticDataType {}*/ + +export class Pointer extends StaticDataType { + destDataType: DynamicDataType; + parentDataType: DataType; + + constructor(destDataType: DynamicDataType, parentDataType: DataType) { + const destDataItem = destDataType.getDataItem(); + const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; + super(dataItem); + this.destDataType = destDataType; + this.parentDataType = parentDataType; + this.children.push(destDataType); + } + + /* + public assignValue(destDataType: DynamicDataType) { + this.destDataType = destDataType; + }*/ + + public assignValue(value: any) { + this.destDataType.assignValue(value); + } + + public getHexValue(): string { + console.log( + '*'.repeat(40), + this.destDataType.getAbsoluteOffset().toString(16), + '^'.repeat(150), + this.parentDataType.getAbsoluteOffset().toString(16), + ); + + let offset = this.destDataType + .getAbsoluteOffset() + .minus(this.parentDataType.getAbsoluteOffset()) + .minus(this.parentDataType.getHeaderSize()); + + console.log("OFFSET == ", JSON.stringify(offset), " or in hex -- 0x", offset.toString(16)); + + const hexBase = 16; + const evmWordWidth = 32; + const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth); + const encodedValue = ethUtil.bufferToHex(valueBuf); + return encodedValue; + } + + public getSignature(): string { + return this.destDataType.getSignature(); + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + +} + +export class DataTypeFactory { + public static mapDataItemToDataType(dataItem: DataItem): DataType { + console.log(`Type: ${dataItem.type}`); + + if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); + if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); + if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); + if (Int.matchGrammar(dataItem.type)) return new Int(dataItem); + if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem); + if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem); + if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem); + if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); + if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem); + if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem); + //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem); + //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem); + + throw new Error(`Unrecognized data type: '${dataItem.type}'`); + } + + public static create(dataItem: DataItem, parentDataType: DataType): DataType { + const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); + if (dataType.isStatic()) { + return dataType; + } else { + const pointer = new Pointer(dataType, parentDataType); + return pointer; + } + + throw new Error(`Unrecognized instance type: '${dataType}'`); + } +} + +class Queue { + private store: T[] = []; + push(val: T) { + this.store.push(val); + } + pop(): T | undefined { + return this.store.shift(); + } +} + +export class Method extends DataType { + name: string; + params: DataType[]; + private signature: string; + selector: string; + + constructor(abi: MethodAbi) { + super({ type: 'method', name: abi.name }); + this.name = abi.name; + this.params = []; + + _.each(abi.inputs, (input: DataItem) => { + this.params.push(DataTypeFactory.create(input, this)); + }); + + // Compute signature + this.signature = `${this.name}(`; + _.each(this.params, (param: DataType, i: number) => { + this.signature += param.getSignature(); + if (i < this.params.length - 1) { + this.signature += ','; + } + }); + this.signature += ')'; + + // Compute selector + this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4))); + + console.log(`--SIGNATURE--\n${this.signature}\n---------\n`); + console.log(`--SELECTOR--\n${this.selector}\n---------\n`); + } + + public getSignature(): string { + return this.signature; + } + + public assignValue(args: any[]) { + _.each(this.params, (param: DataType, i: number) => { + // Assign value to parameter + try { + param.assignValue(args[i]); + } catch (e) { + console.log('Failed to assign to ', param.getDataItem().name); + throw e; + } + + if (param instanceof Pointer) { + this.children.push(param.getChildren()[0]); + } + }); + } + + public getHexValue(): string { + let value = ""; + _.each(this.params, (param: DataType) => { + value += param.getHexValue(); + }); + + return value; + } + + public encode(args: any[]): string { + this.assignValue(args); + const calldata = new Calldata(this.selector, this.params.length); + this.bind(calldata, CalldataSection.PARAMS); + + return calldata.getHexValue(); + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + // Exclude selector + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + const nParams = new BigNumber(this.params.length); + const evmWordWidth = new BigNumber(32); + const size = nParams.times(evmWordWidth); + return size; + } + + /* + encodeOptimized(args: any[]): string { + const calldata = new Memory(); + // Assign values + optimizableParams : StaticDataType = []; + _.each(this.params, function(args: any[], i: number, param: DataType) { + param.assignValue(args[i]); + if (param instanceof DynamicDataType) { + + } + }); + + // Find non-parameter leaves + + + return ''; + } */ + + /* + decode(rawCalldata: string): any[] { + const calldata = new Calldata(this.name, this.params.length); + calldata.assignRaw(rawCalldata); + let args: any[]; + let params = this.params; + _.each(params, function(args: any[], i: number, param: DataType) { + param.decodeFromCalldata(calldata); + args.push(param.getValue()); + }); + + return args; + }*/ +} \ No newline at end of file diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index b8f1d39ad5..3155372264 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -12,1294 +12,18 @@ import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; import { assert } from '@0x/order-utils/src/assert'; - -const simpleAbi = { - constant: false, - inputs: [ - { - name: 'greg', - type: 'uint256', - }, - { - name: 'gregStr', - type: 'string', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const stringAbi = { - constant: false, - inputs: [ - { - name: 'greg', - type: 'string[]', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const tupleAbi = { - constant: false, - inputs: [ - { - components: [ - { - name: 'someUint', - type: 'uint256', - }, - { - name: 'someStr', - type: 'string', - }, - ], - name: 'order', - type: 'tuple', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const staticArrayAbi = { - constant: false, - inputs: [ - { - name: 'someStaticArray', - type: 'uint8[3]', - } - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const crazyAbi = { - constant: false, - inputs: [ - /*{ - name: 'someUInt256', - type: 'uint256', - }, - { - name: 'someInt256', - type: 'int256', - }, - { - name: 'someInt32', - type: 'int32', - }, - { - name: 'someByte', - type: 'byte', - }, - { - name: 'someBytes32', - type: 'bytes32', - }, - { - name: 'someBytes', - type: 'bytes', - }, - { - name: 'someString', - type: 'string', - },*/ - /*{ - name: 'someAddress', - type: 'address', - }, - { - name: 'someBool', - type: 'bool', - },*/ - - { - name: 'someStaticArray', - type: 'uint8[3]', - }, - { - name: 'someStaticArrayWithDynamicMembers', - type: 'string[2]', - }, - { - name: 'someDynamicArrayWithDynamicMembers', - type: 'bytes[]', - }, - { - name: 'some2DArray', - type: 'string[][]', - }, - { - name: 'someTuple', - type: 'tuple', - components: [ - { - name: 'someUint32', - type: 'uint32', - }, - { - name: 'someStr', - type: 'string', - }, - ], - }, - { - name: 'someTupleWithDynamicTypes', - type: 'tuple', - components: [ - { - name: 'someUint', - type: 'uint256', - }, - { - name: 'someStr', - type: 'string', - }, - //{ - // name: 'someStrArray', - // type: 'string[]', - /// }, - { - name: 'someBytes', - type: 'bytes', - }, - { - name: 'someAddress', - type: 'address', - }, - ], - } /*, - { - name: 'someArrayOfTuplesWithDynamicTypes', - type: 'tuple[]', - components: [ - { - name: 'someUint', - type: 'uint256', - }, - { - name: 'someStr', - type: 'string', - }, - { - name: 'someStrArray', - type: 'string[]', - }, - { - name: 'someBytes', - type: 'bytes', - }, - { - name: 'someAddress', - type: 'address', - }, - ], - },*/, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const simpleAbi2 = { - constant: false, - inputs: [ - { - name: 'someByte', - type: 'byte', - }, - { - name: 'someBytes32', - type: 'bytes32', - }, - { - name: 'someBytes', - type: 'bytes', - }, - { - name: 'someString', - type: 'string', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const fillOrderAbi = { - constant: false, - inputs: [ - { - components: [ - { - name: 'makerAddress', - type: 'address', - }, - { - name: 'takerAddress', - type: 'address', - }, - { - name: 'feeRecipientAddress', - type: 'address', - }, - { - name: 'senderAddress', - type: 'address', - }, - { - name: 'makerAssetAmount', - type: 'uint256', - }, - { - name: 'takerAssetAmount', - type: 'uint256', - }, - { - name: 'makerFee', - type: 'uint256', - }, - { - name: 'takerFee', - type: 'uint256', - }, - { - name: 'expirationTimeSeconds', - type: 'uint256', - }, - { - name: 'salt', - type: 'uint256', - }, - { - name: 'makerAssetData', - type: 'bytes', - }, - { - name: 'takerAssetData', - type: 'bytes', - }, - ], - name: 'order', - type: 'tuple', - }, - { - name: 'takerAssetFillAmount', - type: 'uint256', - }, - { - name: 'salt', - type: 'uint256', - }, - { - name: 'orderSignature', - type: 'bytes', - }, - { - name: 'takerSignature', - type: 'bytes', - }, - ], - name: 'fillOrder', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; +import * as AbiEncoder from './abi_encoder'; +import * as AbiSamples from './abi_samples'; chaiSetup.configure(); const expect = chai.expect; -namespace AbiEncoder { - class Word { - private value: string; - - constructor(value?: string) { - if (value === undefined) { - this.value = ''; - } else { - this.value = value; - } - } - - public set(value: string) { - if (value.length !== 64) { - throw `Tried to create word that is not 32 bytes: ${value}`; - } - - this.value = value; - } - - public get(): string { - return this.value; - } - - public getAsHex(): string { - return `0x${this.value}`; - } - } - - export enum CalldataSection { - NONE, - PARAMS, - DATA, - } - - class Memblock { - private dataType: DataType; - private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber }; - - constructor(dataType: DataType) { - this.dataType = dataType; - this.location = { - calldataSection: CalldataSection.NONE, - sectionOffset: new BigNumber(0), - offset: new BigNumber(0), - }; - } - - public getSize(): BigNumber { - return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength); - } - - public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) { - this.location.calldataSection = calldataSection; - this.location.sectionOffset = sectionOffset; - this.location.offset = offset; - } - - public get(): string { - return ethUtil.stripHexPrefix(this.dataType.getHexValue()); - } - - public getOffset(): BigNumber { - return this.location.offset; - } - - public getAbsoluteOffset(): BigNumber { - return this.location.sectionOffset.plus(this.location.offset); - } - - public getSection(): CalldataSection { - return this.location.calldataSection; - } - } - - interface BindList { - [key: string]: Memblock; - } - - export class Calldata { - private selector: string; - private params: Memblock[]; - private data: Memblock[]; - private dataOffset: BigNumber; - private currentDataOffset: BigNumber; - private currentParamOffset: BigNumber; - private bindList: BindList; - - constructor(selector: string, nParams: number) { - this.selector = selector; - console.log(this.selector); - this.params = []; - this.data = []; - const evmWordSize = 32; - this.dataOffset = new BigNumber(nParams).times(evmWordSize); - this.currentDataOffset = new BigNumber(0); - this.currentParamOffset = new BigNumber(0); - this.bindList = {}; - } - - public bind(dataType: DataType, section: CalldataSection) { - if (dataType.getId() in this.bindList) { - throw `Rebind on ${dataType.getId()}`; - } - const memblock = new Memblock(dataType); - switch (section) { - case CalldataSection.PARAMS: - this.params.push(memblock); - memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); - - console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); - this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); - break; - - case CalldataSection.DATA: - this.data.push(memblock); - memblock.assignLocation(section, this.dataOffset, this.currentDataOffset); - - console.log( - `Binding ${dataType.getDataItem().name} to DATA at ${memblock - .getAbsoluteOffset() - .toString(16)}`, - ); - this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); - break; - - default: - throw `Unrecognized calldata section: ${section}`; - } - - this.bindList[dataType.getId()] = memblock; - dataType.rbind(memblock); - } - - public getHexValue(): string { - let hexValue = `${this.selector}`; - _.each(this.params, (memblock: Memblock) => { - hexValue += memblock.get(); - }); - _.each(this.data, (memblock: Memblock) => { - hexValue += memblock.get(); - }); - - return hexValue; - } - } - - export abstract class DataType { - private dataItem: DataItem; - private hexValue: string; - protected memblock: Memblock | undefined; - protected children: DataType[]; - - constructor(dataItem: DataItem) { - this.dataItem = dataItem; - this.hexValue = '0x'; - this.memblock = undefined; - this.children = []; - } - - protected assignHexValue(hexValue: string) { - this.hexValue = hexValue; - } - - public getHexValue(): string { - return this.hexValue; - } - - public getDataItem(): DataItem { - return this.dataItem; - } - - public rbind(memblock: Memblock) { - this.memblock = memblock; - } - - public bind(calldata: Calldata, section: CalldataSection) { - calldata.bind(this, section); - } - - public getId(): string { - return this.dataItem.name; - } - - public getOffset(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getOffset(); - } - - public getAbsoluteOffset(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getAbsoluteOffset(); - } - - public getSize(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getSize(); - } - - public getChildren(): DataType[] { - return this.children; - } - - public abstract assignValue(value: any): void; - public abstract getSignature(): string; - public abstract isStatic(): boolean; - } - - export abstract class StaticDataType extends DataType { - constructor(dataItem: DataItem) { - super(dataItem); - } - } - - export abstract class DynamicDataType extends DataType { - constructor(dataItem: DataItem) { - super(dataItem); - } - } - - export class Address extends StaticDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(Address.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - const evmWordWidth = 32; - const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth)); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - return `address`; - } - - public isStatic(): boolean { - return true; - } - - public static matchGrammar(type: string): boolean { - return type === 'address'; - } - } - - export class Bool extends StaticDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(Bool.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: boolean) { - const evmWordWidth = 32; - const encodedValue = value === true ? '0x1' : '0x0'; - const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth)); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - return 'bool'; - } - - public isStatic(): boolean { - return true; - } - - public static matchGrammar(type: string): boolean { - return type === 'bool'; - } - } - - abstract class Number extends StaticDataType { - static MAX_WIDTH: number = 256; - static DEFAULT_WIDTH: number = Number.MAX_WIDTH; - width: number = Number.DEFAULT_WIDTH; - - constructor(dataItem: DataItem, matcher: RegExp) { - super(dataItem); - const matches = matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 2 && matches[1] !== undefined) { - this.width = parseInt(matches[1]); - } else { - this.width = 256; - } - } - - public assignValue(value: BigNumber) { - if (value.greaterThan(this.getMaxValue())) { - throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; - } else if (value.lessThan(this.getMinValue())) { - throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; - } - - const hexBase = 16; - const evmWordWidth = 32; - let valueBuf: Buffer; - if (value.greaterThanOrEqualTo(0)) { - valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); - } else { - // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. - // Step 1/3: Convert value to positive binary string - const binBase = 2; - const valueBin = value.times(-1).toString(binBase); - - // Step 2/3: Invert binary value - const bitsInEvmWord = 256; - let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length); - _.each(valueBin, (bit: string) => { - invertedValueBin += bit === '1' ? '0' : '1'; - }); - const invertedValue = new BigNumber(invertedValueBin, binBase); - - // Step 3/3: Add 1 to inverted value - // The result is the two's-complement represent of the input value. - const negativeValue = invertedValue.plus(1); - - // Convert the negated value to a hex string - valueBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), - evmWordWidth, - ); - } - - const encodedValue = ethUtil.bufferToHex(valueBuf); - this.assignHexValue(encodedValue); - } - - public isStatic(): boolean { - return true; - } - - public abstract getMaxValue(): BigNumber; - public abstract getMinValue(): BigNumber; - } - - export class Int extends Number { - static matcher = RegExp( - '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', - ); - - constructor(dataItem: DataItem) { - super(dataItem, Int.matcher); - } - - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).sub(1); - } - - public getMinValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).times(-1); - } - - public getSignature(): string { - return `int${this.width}`; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - } - - export class UInt extends Number { - static matcher = RegExp( - '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', - ); - - constructor(dataItem: DataItem) { - super(dataItem, UInt.matcher); - } - - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width).sub(1); - } - - public getMinValue(): BigNumber { - return new BigNumber(0); - } - - public getSignature(): string { - return `uint${this.width}`; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - } - - export class Byte extends StaticDataType { - static matcher = RegExp( - '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', - ); - - static DEFAULT_WIDTH = 1; - width: number = Byte.DEFAULT_WIDTH; - - constructor(dataItem: DataItem) { - super(dataItem); - const matches = Byte.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 3 && matches[2] !== undefined) { - this.width = parseInt(matches[2]); - } else { - this.width = Byte.DEFAULT_WIDTH; - } - } - - public assignValue(value: string | Buffer) { - // Convert value into a buffer and do bounds checking - const valueBuf = ethUtil.toBuffer(value); - if (valueBuf.byteLength > this.width) { - throw new Error( - `Tried to assign ${value} (${ - valueBuf.byteLength - } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, - ); - } else if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - - // Store value as hex - const evmWordWidth = 32; - const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); - const hexValue = ethUtil.bufferToHex(paddedValue); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - // Note that `byte` reduces to `bytes1` - return `bytes${this.width}`; - } - - public isStatic(): boolean { - return true; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - } - - export class Bytes extends DynamicDataType { - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = Bytes.UNDEFINED_LENGTH; - - constructor(dataItem: DataItem) { - super(dataItem); - expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string | Buffer) { - if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`); - } - const valueBuf = ethUtil.toBuffer(value); - if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - - const wordsForValue = Math.ceil(valueBuf.byteLength / 32); - const paddedBytesForValue = wordsForValue * 32; - const paddedValueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); - const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); - const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); - const encodedValue = ethUtil.bufferToHex(encodedValueBuf); - - this.assignHexValue(encodedValue); - } - - public getSignature(): string { - return 'bytes'; - } - - public isStatic(): boolean { - return false; - } - - public static matchGrammar(type: string): boolean { - return type === 'bytes'; - } - } - - export class SolString extends DynamicDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(SolString.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - const wordsForValue = Math.ceil(value.length / 32); - const paddedBytesForValue = wordsForValue * 32; - const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); - const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); - const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); - const encodedValue = ethUtil.bufferToHex(encodedValueBuf); - - this.assignHexValue(encodedValue); - } - - public getSignature(): string { - return 'string'; - } - - public isStatic(): boolean { - return false; - } - - public static matchGrammar(type: string): boolean { - return type === 'string'; - } - } - - export class SolArray extends DynamicDataType { - static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = SolArray.UNDEFINED_LENGTH; - type: string = '[undefined]'; - isLengthDefined: boolean; - - constructor(dataItem: DataItem) { - super(dataItem); - const matches = SolArray.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - console.log(JSON.stringify(matches)); - if (matches === null || matches.length !== 3) { - throw new Error(`Could not parse array: ${dataItem.type}`); - } else if (matches[1] === undefined) { - throw new Error(`Could not parse array type: ${dataItem.type}`); - } else if (matches[2] === undefined) { - throw new Error(`Could not parse array length: ${dataItem.type}`); - } - - // Check if length is undefined - if (matches[2] === '') { - this.type = matches[1]; - this.length = SolArray.UNDEFINED_LENGTH; - this.isLengthDefined = false; - return; - } - - // Parse out array type/length and construct children - this.isLengthDefined = true; - this.type = matches[1]; - this.length = new BigNumber(matches[2], 10); - if (this.length.lessThan(0)) { - throw new Error(`Bad array length: ${JSON.stringify(this.length)}`); - } - this.constructChildren(); - } - - private constructChildren() { - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const childDataItem = { - type: this.type, - name: `${this.getDataItem().name}[${idx.toString(10)}]`, - } as DataItem; - const child = DataTypeFactory.create(childDataItem, this); - this.children.push(child); - } - } - - // @TODO: HACKY -- shouldn't really have children for a - public getChildren(): DataType[] { - if (this.isStatic()) { - return []; - } else { - return this.children; - } - } - - public assignValue(value: any[]) { - // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { - throw new Error( - `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( - valueLength, - )}`, - ); - } - - // Assign length if not already set - if (this.length === SolArray.UNDEFINED_LENGTH) { - this.length = valueLength; - this.constructChildren(); - } - - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.children[idxNumber].assignValue(value[idxNumber]); - } - } - - private getHexValueDynamicArray(): string { - const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); - const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); - let valueBuf = lengthBuf; - - const valueHex = ethUtil.bufferToHex(valueBuf); - return valueHex; - } - - private getHexValueStaticArray(): string { - let valueBuf = new Buffer(""); - - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - const childValueHex = this.children[idxNumber].getHexValue(); - const childValueBuf = ethUtil.toBuffer(childValueHex); - valueBuf = Buffer.concat([valueBuf, childValueBuf]); - - console.log(JSON.stringify(idx)); - } - - // Convert value buffer to hex - const valueHex = ethUtil.bufferToHex(valueBuf); - return valueHex; - } - - public getHexValue(): string { - if (this.isLengthDefined) { - return this.getHexValueStaticArray(); - } else { - return this.getHexValueDynamicArray(); - } - } - - public isStatic(): boolean { - return this.isLengthDefined; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - - public getSignature(): string { - let type = this.type; - if (this.type === 'tuple') { - let tupleDataItem = { - type: 'tuple', - name: 'N/A', - } as DataItem; - const tupleComponents = this.getDataItem().components; - if (tupleComponents !== undefined) { - tupleDataItem.components = tupleComponents; - } - const tuple = new Tuple(tupleDataItem); - type = tuple.getSignature(); - } - - if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { - return `${type}[]`; - } - return `${type}[${this.length}]`; - } - } - - export class Tuple extends DynamicDataType { - private length: BigNumber; - private childMap: { [key: string]: number }; - - constructor(dataItem: DataItem) { - super(dataItem); - expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); - this.length = new BigNumber(0); - this.childMap = {}; - if (dataItem.components !== undefined) { - this.constructChildren(dataItem.components); - this.length = new BigNumber(dataItem.components.length); - } else { - throw new Error('Components undefined'); - } - } - - private constructChildren(dataItems: DataItem[]) { - _.each(dataItems, (dataItem: DataItem) => { - const childDataItem = { - type: dataItem.type, - name: `${this.getDataItem().name}.${dataItem.name}`, - } as DataItem; - const child = DataTypeFactory.create(childDataItem, this); - this.childMap[dataItem.name] = this.children.length; - this.children.push(child); - }); - } - - private assignValueFromArray(value: any[]) { - // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { - throw new Error( - `Expected array of ${JSON.stringify( - this.length, - )} elements, but got array of length ${JSON.stringify(valueLength)}`, - ); - } - - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.children[idxNumber].assignValue(value[idxNumber]); - } - } - - private assignValueFromObject(obj: object) { - let childMap = _.cloneDeep(this.childMap); - _.forOwn(obj, (value: any, key: string) => { - if (key in childMap === false) { - throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); - } - this.children[this.childMap[key]].assignValue(value); - delete childMap[key]; - }); - - if (Object.keys(childMap).length !== 0) { - throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); - } - } - - public assignValue(value: any[] | object) { - if (value instanceof Array) { - this.assignValueFromArray(value); - } else if (typeof value === 'object') { - this.assignValueFromObject(value); - } else { - throw new Error(`Unexpected type for ${value}`); - } - } - - public getHexValue(): string { - return '0x'; - } - - public getSignature(): string { - // Compute signature - let signature = `(`; - _.each(this.children, (child: DataType, i: number) => { - signature += child.getSignature(); - if (i < this.children.length - 1) { - signature += ','; - } - }); - signature += ')'; - return signature; - } - - public isStatic(): boolean { - return false; // @TODO: True in every case or only when dynamic data? - } - - public static matchGrammar(type: string): boolean { - return type === 'tuple'; - } - } - - /* TODO - class Fixed extends StaticDataType {} - - class UFixed extends StaticDataType {}*/ - - export class Pointer extends StaticDataType { - destDataType: DynamicDataType; - parentDataType: DataType; - - constructor(destDataType: DynamicDataType, parentDataType: DataType) { - const destDataItem = destDataType.getDataItem(); - const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; - super(dataItem); - this.destDataType = destDataType; - this.parentDataType = parentDataType; - this.children.push(destDataType); - } - - /* - public assignValue(destDataType: DynamicDataType) { - this.destDataType = destDataType; - }*/ - - public assignValue(value: any) { - this.destDataType.assignValue(value); - } - - public getHexValue(): string { - console.log( - '*'.repeat(40), - this.destDataType.getAbsoluteOffset().toString(16), - '^'.repeat(150), - this.parentDataType.getAbsoluteOffset().toString(16), - ); - - let offset = this.destDataType - .getAbsoluteOffset() - .minus(this.parentDataType.getAbsoluteOffset().plus(this.parentDataType.getSize())); - const hexBase = 16; - const evmWordWidth = 32; - const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth); - const encodedValue = ethUtil.bufferToHex(valueBuf); - return encodedValue; - } - - public getSignature(): string { - return this.destDataType.getSignature(); - } - - public isStatic(): boolean { - return true; - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; - } - } - - export class DataTypeFactory { - public static mapDataItemToDataType(dataItem: DataItem): DataType { - console.log(`Type: ${dataItem.type}`); - - if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); - if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); - if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); - if (Int.matchGrammar(dataItem.type)) return new Int(dataItem); - if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem); - if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem); - if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem); - if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); - if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem); - if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem); - //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem); - //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem); - - throw new Error(`Unrecognized data type: '${dataItem.type}'`); - } - - public static create(dataItem: DataItem, parentDataType: DataType): DataType { - const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); - if (dataType.isStatic()) { - return dataType; - } else { - const pointer = new Pointer(dataType, parentDataType); - return pointer; - } - - throw new Error(`Unrecognized instance type: '${dataType}'`); - } - } - - class Queue { - private store: T[] = []; - push(val: T) { - this.store.push(val); - } - pop(): T | undefined { - return this.store.shift(); - } - } - - export class Method extends DataType { - name: string; - params: DataType[]; - private signature: string; - selector: string; - - constructor(abi: MethodAbi) { - super({ type: 'method', name: abi.name }); - this.name = abi.name; - this.params = []; - - _.each(abi.inputs, (input: DataItem) => { - this.params.push(DataTypeFactory.create(input, this)); - }); - - // Compute signature - this.signature = `${this.name}(`; - _.each(this.params, (param: DataType, i: number) => { - this.signature += param.getSignature(); - if (i < this.params.length - 1) { - this.signature += ','; - } - }); - this.signature += ')'; - - // Compute selector - this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4))); - - console.log(`--SIGNATURE--\n${this.signature}\n---------\n`); - console.log(`--SELECTOR--\n${this.selector}\n---------\n`); - } - - public getSignature(): string { - return this.signature; - } - - public assignValue(args: any[]) { - const calldata = new Calldata(this.selector, this.params.length); - const params = this.params; - const paramQueue = new Queue(); - _.each(params, (param: DataType, i: number) => { - try { - param.assignValue(args[i]); - } catch (e) { - console.log('Failed to assign to ', param.getDataItem().name); - throw e; - } - param.bind(calldata, CalldataSection.PARAMS); - _.each(param.getChildren(), (child: DataType) => { - paramQueue.push(child); - }); - }); - - let param: DataType | undefined = undefined; - while ((param = paramQueue.pop()) !== undefined) { - param.bind(calldata, CalldataSection.DATA); - _.each(param.getChildren(), (child: DataType) => { - paramQueue.push(child); - }); - } - - console.log(calldata); - - this.assignHexValue(calldata.getHexValue()); - - //return calldata.getRaw(); - } - - public encode(args: any[]): string { - this.assignValue(args); - return this.getHexValue(); - } - - public isStatic(): boolean { - return true; - } - - /* - encodeOptimized(args: any[]): string { - const calldata = new Memory(); - // Assign values - optimizableParams : StaticDataType = []; - _.each(this.params, function(args: any[], i: number, param: DataType) { - param.assignValue(args[i]); - if (param instanceof DynamicDataType) { - - } - }); - - // Find non-parameter leaves - - - return ''; - } */ - - /* - decode(rawCalldata: string): any[] { - const calldata = new Calldata(this.name, this.params.length); - calldata.assignRaw(rawCalldata); - let args: any[]; - let params = this.params; - _.each(params, function(args: any[], i: number, param: DataType) { - param.decodeFromCalldata(calldata); - args.push(param.getValue()); - }); - - return args; - }*/ - } -} - describe.only('ABI Encoder', () => { describe.only('Just a Greg, Eh', () => { + + it('Crazy ABI', async () => { - const method = new AbiEncoder.Method(crazyAbi); + const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)], @@ -1405,20 +129,50 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata);*/ }); - it.only('Static Array ABI', async () => { - const method = new AbiEncoder.Method(staticArrayAbi); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + it.only('Fixed Lenfgth Array / Dynamic Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [["Brave", "New", "World"]]; const calldata = method.encode(args); console.log(calldata); console.log('*'.repeat(40)); console.log(JSON.stringify(args)); + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.only('Unfixed Length Array / Dynamic Members ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); + const args = [["Brave", "New", "World"]]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.only('Unfixed Length Array / Static Members ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + const expectedCalldata = '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + + it.only('Fixed Length Array / Static Members ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); const expectedCalldata = '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; expect(calldata).to.be.equal(expectedCalldata); }); + it('Simple ABI 2', async () => { - const method = new AbiEncoder.Method(simpleAbi2); + const method = new AbiEncoder.Method(AbiSamples.simpleAbi2); const args = [ '0xaf', // e (bytes1) @@ -1434,14 +188,14 @@ describe.only('ABI Encoder', () => { }); it('Yessir', async () => { - const method = new AbiEncoder.Method(simpleAbi); + const method = new AbiEncoder.Method(AbiSamples.simpleAbi); const calldata = method.encode([new BigNumber(5), 'five']); console.log(calldata); expect(true).to.be.true(); }); it('Array ABI', async () => { - const method = new AbiEncoder.Method(stringAbi); + const method = new AbiEncoder.Method(AbiSamples.stringAbi); const calldata = method.encode([['five', 'six', 'seven']]); console.log(method.getSignature()); console.log(method.selector); @@ -1453,7 +207,7 @@ describe.only('ABI Encoder', () => { }); it('Object ABI (Array input)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([[new BigNumber(5), 'five']]); console.log(method.getSignature()); console.log(method.selector); @@ -1465,7 +219,7 @@ describe.only('ABI Encoder', () => { }); it('Object ABI (Object input)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); console.log(method.getSignature()); console.log(method.selector); @@ -1477,7 +231,7 @@ describe.only('ABI Encoder', () => { }); it.skip('Object ABI (Object input - Missing Key)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5) }]); console.log(method.getSignature()); console.log(method.selector); @@ -1491,7 +245,7 @@ describe.only('ABI Encoder', () => { }); it.skip('Object ABI (Object input - Too Many Keys)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five', unwantedKey: 14 }]); console.log(method.getSignature()); console.log(method.selector); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts new file mode 100644 index 0000000000..2e71115041 --- /dev/null +++ b/packages/order-utils/test/abi_samples.ts @@ -0,0 +1,358 @@ +import { MethodAbi } from 'ethereum-types'; + +export const simpleAbi = { + constant: false, + inputs: [ + { + name: 'greg', + type: 'uint256', + }, + { + name: 'gregStr', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const stringAbi = { + constant: false, + inputs: [ + { + name: 'greg', + type: 'string[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const tupleAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + ], + name: 'order', + type: 'tuple', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const staticArrayAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'uint8[3]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const staticArrayDynamicMembersAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'string[3]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const dynamicArrayDynamicMembersAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'string[]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const dynamicArrayStaticMembersAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'uint8[]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const crazyAbi = { + constant: false, + inputs: [ + /*{ + name: 'someUInt256', + type: 'uint256', + }, + { + name: 'someInt256', + type: 'int256', + }, + { + name: 'someInt32', + type: 'int32', + }, + { + name: 'someByte', + type: 'byte', + }, + { + name: 'someBytes32', + type: 'bytes32', + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someString', + type: 'string', + },*/ + /*{ + name: 'someAddress', + type: 'address', + }, + { + name: 'someBool', + type: 'bool', + },*/ + + { + name: 'someStaticArray', + type: 'uint8[3]', + }, + { + name: 'someStaticArrayWithDynamicMembers', + type: 'string[2]', + }, + { + name: 'someDynamicArrayWithDynamicMembers', + type: 'bytes[]', + }, + { + name: 'some2DArray', + type: 'string[][]', + }, + { + name: 'someTuple', + type: 'tuple', + components: [ + { + name: 'someUint32', + type: 'uint32', + }, + { + name: 'someStr', + type: 'string', + }, + ], + }, + { + name: 'someTupleWithDynamicTypes', + type: 'tuple', + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + //{ + // name: 'someStrArray', + // type: 'string[]', + /// }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someAddress', + type: 'address', + }, + ], + } /*, + { + name: 'someArrayOfTuplesWithDynamicTypes', + type: 'tuple[]', + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + { + name: 'someStrArray', + type: 'string[]', + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someAddress', + type: 'address', + }, + ], + },*/, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const simpleAbi2 = { + constant: false, + inputs: [ + { + name: 'someByte', + type: 'byte', + }, + { + name: 'someBytes32', + type: 'bytes32', + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someString', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const fillOrderAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'makerAddress', + type: 'address', + }, + { + name: 'takerAddress', + type: 'address', + }, + { + name: 'feeRecipientAddress', + type: 'address', + }, + { + name: 'senderAddress', + type: 'address', + }, + { + name: 'makerAssetAmount', + type: 'uint256', + }, + { + name: 'takerAssetAmount', + type: 'uint256', + }, + { + name: 'makerFee', + type: 'uint256', + }, + { + name: 'takerFee', + type: 'uint256', + }, + { + name: 'expirationTimeSeconds', + type: 'uint256', + }, + { + name: 'salt', + type: 'uint256', + }, + { + name: 'makerAssetData', + type: 'bytes', + }, + { + name: 'takerAssetData', + type: 'bytes', + }, + ], + name: 'order', + type: 'tuple', + }, + { + name: 'takerAssetFillAmount', + type: 'uint256', + }, + { + name: 'salt', + type: 'uint256', + }, + { + name: 'orderSignature', + type: 'bytes', + }, + { + name: 'takerSignature', + type: 'bytes', + }, + ], + name: 'fillOrder', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; From e95aa617b6eb0e4b6bace7fbcf66de2658e314aa Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 10:49:19 -0800 Subject: [PATCH 081/230] All existing ABI tests passing --- packages/order-utils/test/abi_encoder.ts | 18 +++++++++++++----- packages/order-utils/test/abi_encoder_test.ts | 13 ++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts index 55b63b7011..77cb890569 100644 --- a/packages/order-utils/test/abi_encoder.ts +++ b/packages/order-utils/test/abi_encoder.ts @@ -74,6 +74,7 @@ class Memblock { } public get(): string { + console.log(`Unstripped = '${this.dataType.getHexValue()}' and Stripped = '${ethUtil.stripHexPrefix(this.dataType.getHexValue())}'`); return ethUtil.stripHexPrefix(this.dataType.getHexValue()); } @@ -472,6 +473,7 @@ export class Byte extends StaticDataType { const evmWordWidth = 32; const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); const hexValue = ethUtil.bufferToHex(paddedValue); + this.assignHexValue(hexValue); } @@ -538,7 +540,9 @@ export class Bytes extends DynamicDataType { } public getBodySize(): BigNumber { - return new BigNumber(this.getHexValue().length); + const valueBuf = ethUtil.toBuffer(this.getHexValue()); + const size = new BigNumber(valueBuf.byteLength); + return size; } public static matchGrammar(type: string): boolean { @@ -576,7 +580,9 @@ export class SolString extends DynamicDataType { } public getBodySize(): BigNumber { - return new BigNumber(this.getHexValue().length); + const valueBuf = ethUtil.toBuffer(this.getHexValue()); + const size = new BigNumber(valueBuf.byteLength); + return size; } public static matchGrammar(type: string): boolean { @@ -1032,12 +1038,14 @@ export class Method extends DataType { } public getHexValue(): string { - let value = ""; + let paramBufs: Buffer[] = []; _.each(this.params, (param: DataType) => { - value += param.getHexValue(); + paramBufs.push(ethUtil.toBuffer(param.getHexValue())); }); - return value; + const value = Buffer.concat(paramBufs); + const hexValue = ethUtil.bufferToHex(value); + return hexValue; } public encode(args: any[]): string { diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 3155372264..91a32bfbfd 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -22,7 +22,7 @@ describe.only('ABI Encoder', () => { describe.only('Just a Greg, Eh', () => { - it('Crazy ABI', async () => { + it.skip('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); @@ -114,6 +114,9 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); console.log(method.getSignature()); + const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + /* const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; @@ -129,7 +132,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata);*/ }); - it.only('Fixed Lenfgth Array / Dynamic Members', async () => { + it('Fixed Lenfgth Array / Dynamic Members', async () => { const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); const args = [["Brave", "New", "World"]]; const calldata = method.encode(args); @@ -141,7 +144,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.only('Unfixed Length Array / Dynamic Members ABI', async () => { + it('Unfixed Length Array / Dynamic Members ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); const args = [["Brave", "New", "World"]]; const calldata = method.encode(args); @@ -152,7 +155,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.only('Unfixed Length Array / Static Members ABI', async () => { + it('Unfixed Length Array / Static Members ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; const calldata = method.encode(args); @@ -161,7 +164,7 @@ describe.only('ABI Encoder', () => { }); - it.only('Fixed Length Array / Static Members ABI', async () => { + it('Fixed Length Array / Static Members ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; const calldata = method.encode(args); From 3bc45395cc0e1c5c483e7319967b6308122123bf Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 11:14:44 -0800 Subject: [PATCH 082/230] static vs dynamic tuple differentatiion --- packages/order-utils/test/abi_encoder.ts | 30 ++++++++++---- packages/order-utils/test/abi_encoder_test.ts | 34 +++++++++++----- packages/order-utils/test/abi_samples.ts | 39 +++++++++++++++++-- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts index 77cb890569..853aac6276 100644 --- a/packages/order-utils/test/abi_encoder.ts +++ b/packages/order-utils/test/abi_encoder.ts @@ -770,12 +770,14 @@ export class SolArray extends DynamicDataType { export class Tuple extends DynamicDataType { private length: BigNumber; private childMap: { [key: string]: number }; + private members: DataType[]; constructor(dataItem: DataItem) { super(dataItem); expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); this.length = new BigNumber(0); this.childMap = {}; + this.members = []; if (dataItem.components !== undefined) { this.constructChildren(dataItem.components); this.length = new BigNumber(dataItem.components.length); @@ -792,7 +794,11 @@ export class Tuple extends DynamicDataType { } as DataItem; const child = DataTypeFactory.create(childDataItem, this); this.childMap[dataItem.name] = this.children.length; - this.children.push(child); + + if (child instanceof Pointer) { + this.children.push(child.getChildren()[0]); + } + this.members.push(child); }); } @@ -810,7 +816,7 @@ export class Tuple extends DynamicDataType { // Assign values to children for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { const idxNumber = idx.toNumber(); - this.children[idxNumber].assignValue(value[idxNumber]); + this.members[idxNumber].assignValue(value[idxNumber]); } } @@ -820,7 +826,7 @@ export class Tuple extends DynamicDataType { if (key in childMap === false) { throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); } - this.children[this.childMap[key]].assignValue(value); + this.members[this.childMap[key]].assignValue(value); delete childMap[key]; }); @@ -840,7 +846,14 @@ export class Tuple extends DynamicDataType { } public getHexValue(): string { - return '0x'; + let paramBufs: Buffer[] = []; + _.each(this.members, (member: DataType) => { + paramBufs.push(ethUtil.toBuffer(member.getHexValue())); + }); + + const value = Buffer.concat(paramBufs); + const hexValue = ethUtil.bufferToHex(value); + return hexValue; } public getHeaderSize(): BigNumber { @@ -856,9 +869,9 @@ export class Tuple extends DynamicDataType { public getSignature(): string { // Compute signature let signature = `(`; - _.each(this.children, (child: DataType, i: number) => { - signature += child.getSignature(); - if (i < this.children.length - 1) { + _.each(this.members, (member: DataType, i: number) => { + signature += member.getSignature(); + if (i < this.members.length - 1) { signature += ','; } }); @@ -867,7 +880,8 @@ export class Tuple extends DynamicDataType { } public isStatic(): boolean { - return false; // @TODO: True in every case or only when dynamic data? + const isStaticTuple = this.children.length === 0; + return isStaticTuple; // @TODO: True in every case or only when dynamic data? } public static matchGrammar(type: string): boolean { diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 91a32bfbfd..070bfe7f6f 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -36,7 +36,7 @@ describe.only('ABI Encoder', () => { '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398', '0x283473298473248923749238742398742398472894729843278942374982374892374892743982', ], // m - [ + /*[ [ 'some string', 'some another string', @@ -49,7 +49,7 @@ describe.only('ABI Encoder', () => { 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', ], [], - ], // n + ],*/ // n [ new BigNumber(4037824789), 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', @@ -114,7 +114,9 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); console.log(method.getSignature()); - const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; + const expectedCalldata = '0x76c14e63000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e728347239823742398472398472984724892749874897428472894723948749874984787432942374349234732984723984237489237489237489234723894728947894748937428947289473894274982374329874238947238947328947238943724982374982374289347239800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000272834732984732489237492387423987423984728947298432789423749823748923748927439820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; + + //const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); /* @@ -209,8 +211,21 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it('Object ABI (Array input)', async () => { - const method = new AbiEncoder.Method(AbiSamples.tupleAbi); + it.only('Static Tuple', async () => { + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); + const calldata = method.encode([[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Dynamic Tuple (Array input)', async () => { + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); const calldata = method.encode([[new BigNumber(5), 'five']]); console.log(method.getSignature()); console.log(method.selector); @@ -221,8 +236,9 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it('Object ABI (Object input)', async () => { - const method = new AbiEncoder.Method(AbiSamples.tupleAbi); + it('Dynamic Tuple (Object input)', async () => { + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); console.log(method.getSignature()); console.log(method.selector); @@ -234,7 +250,7 @@ describe.only('ABI Encoder', () => { }); it.skip('Object ABI (Object input - Missing Key)', async () => { - const method = new AbiEncoder.Method(AbiSamples.tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5) }]); console.log(method.getSignature()); console.log(method.selector); @@ -248,7 +264,7 @@ describe.only('ABI Encoder', () => { }); it.skip('Object ABI (Object input - Too Many Keys)', async () => { - const method = new AbiEncoder.Method(AbiSamples.tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five', unwantedKey: 14 }]); console.log(method.getSignature()); console.log(method.selector); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index 2e71115041..fb5cfbeb7d 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -34,7 +34,7 @@ export const stringAbi = { type: 'function', } as MethodAbi; -export const tupleAbi = { +export const dynamicTupleAbi = { constant: false, inputs: [ { @@ -59,6 +59,39 @@ export const tupleAbi = { type: 'function', } as MethodAbi; +export const staticTupleAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint1', + type: 'uint256', + }, + { + name: 'someUint2', + type: 'uint256', + }, + { + name: 'someUint3', + type: 'uint256', + }, + { + name: 'someBool', + type: 'bool', + }, + ], + name: 'order', + type: 'tuple', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const staticArrayAbi = { constant: false, inputs: [ @@ -171,10 +204,10 @@ export const crazyAbi = { name: 'someDynamicArrayWithDynamicMembers', type: 'bytes[]', }, - { + /* { name: 'some2DArray', type: 'string[][]', - }, + }, */ { name: 'someTuple', type: 'tuple', From 687e6ccdd37da7c35a3fafac43f0fdff03351c0c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 11:33:51 -0800 Subject: [PATCH 083/230] Got the crazy ABI working --- packages/order-utils/test/abi_encoder.ts | 44 ++++++++++++++++--- packages/order-utils/test/abi_encoder_test.ts | 4 +- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts index 853aac6276..ae573b39f4 100644 --- a/packages/order-utils/test/abi_encoder.ts +++ b/packages/order-utils/test/abi_encoder.ts @@ -89,6 +89,10 @@ class Memblock { public getSection(): CalldataSection { return this.location.calldataSection; } + + public getName(): string { + return this.dataType.getDataItem().name; + } } interface BindList { @@ -120,13 +124,18 @@ export class Calldata { throw `Rebind on ${dataType.getId()}`; } const memblock = new Memblock(dataType); + + this.params.push(memblock); + memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); + + console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); + this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); + console.log("CURRENT PARAM OFFSET -------- 0x", this.currentParamOffset.toString(16)); + + /* switch (section) { case CalldataSection.PARAMS: - this.params.push(memblock); - memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); - - console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); - this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); + ; break; case CalldataSection.DATA: @@ -143,7 +152,7 @@ export class Calldata { default: throw `Unrecognized calldata section: ${section}`; - } + }*/ this.bindList[dataType.getId()] = memblock; dataType.rbind(memblock); @@ -160,6 +169,26 @@ export class Calldata { return hexValue; } + + public printAnnotated() { + let hexValue = `${this.selector}`; + console.log(hexValue); + let offset = new BigNumber(0); + _.each(this.params, (memblock: Memblock) => { + const offsetStr = `0x${offset.toString(16)}`; + const hexValue = memblock.get(); + const annotation = memblock.getName(); + + console.log(`${offsetStr} ${hexValue} ${annotation}`); + }); + _.each(this.data, (memblock: Memblock) => { + const offsetStr = `0x${offset.toString(16)}`; + const hexValue = memblock.get(); + const annotation = memblock.getName(); + + console.log(`${offsetStr} ${hexValue} ${annotation}`); + }); + } } export abstract class DataType { @@ -793,7 +822,7 @@ export class Tuple extends DynamicDataType { name: `${this.getDataItem().name}.${dataItem.name}`, } as DataItem; const child = DataTypeFactory.create(childDataItem, this); - this.childMap[dataItem.name] = this.children.length; + this.childMap[dataItem.name] = this.members.length; if (child instanceof Pointer) { this.children.push(child.getChildren()[0]); @@ -1065,6 +1094,7 @@ export class Method extends DataType { public encode(args: any[]): string { this.assignValue(args); const calldata = new Calldata(this.selector, this.params.length); + calldata.printAnnotated(); this.bind(calldata, CalldataSection.PARAMS); return calldata.getHexValue(); diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 070bfe7f6f..553fac43e1 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -22,7 +22,7 @@ describe.only('ABI Encoder', () => { describe.only('Just a Greg, Eh', () => { - it.skip('Crazy ABI', async () => { + it('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); @@ -211,7 +211,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.only('Static Tuple', async () => { + it('Static Tuple', async () => { // This is dynamic because it has dynamic members const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); const calldata = method.encode([[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]); From e59669c94937ec694d9e41c1a163a8a6f770fd32 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 12:47:30 -0800 Subject: [PATCH 084/230] multidimensional arrays with static type --- packages/order-utils/test/abi_encoder.ts | 1 - packages/order-utils/test/abi_encoder_test.ts | 40 +++++++++ packages/order-utils/test/abi_samples.ts | 87 +++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts index ae573b39f4..f63dc804a0 100644 --- a/packages/order-utils/test/abi_encoder.ts +++ b/packages/order-utils/test/abi_encoder.ts @@ -690,7 +690,6 @@ export class SolArray extends DynamicDataType { this.elements.push(child); if (child instanceof Pointer) { const pointsTo = child.getChildren()[0]; - console.log(JSON.stringify(pointsTo)); this.children.push(pointsTo); // DataType pointing to } } diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 553fac43e1..1155806244 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -134,6 +134,46 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata);*/ }); + it.only('Multidimensional Arrays / Static Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); + + // Eight 3-dimensional arrays of uint8[2][2][2] + let value = 0; + const args = []; + for (let i = 0; i < 8; ++i) { + args.push( + [ + [ + [new BigNumber(++value), new BigNumber(++value)], + [new BigNumber(++value), new BigNumber(++value)], + ], + [ + [new BigNumber(++value), new BigNumber(++value)], + [new BigNumber(++value), new BigNumber(++value)], + ] + ] + ); + } + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Fixed Lenfgth Array / Dynamic Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [["Brave", "New", "World"]]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + it('Fixed Lenfgth Array / Dynamic Members', async () => { const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); const args = [["Brave", "New", "World"]]; diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index fb5cfbeb7d..cbbc5f8f36 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -34,6 +34,93 @@ export const stringAbi = { type: 'function', } as MethodAbi; +export const multiDimensionalArraysStaticTypeAbi = { + constant: false, + inputs: [ + { + name: 'a', + type: 'uint8[][][]', + }, + { + name: 'b', + type: 'uint8[][][2]', + }, + { + name: 'c', + type: 'uint8[][2][]', + }, + { + name: 'd', + type: 'uint8[2][][]', + }, + { + name: 'e', + type: 'uint8[][2][2]', + }, + { + name: 'f', + type: 'uint8[2][2][]', + }, + { + name: 'g', + type: 'uint8[2][][2]', + }, + { + name: 'h', + type: 'uint8[2][2][2]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const multiDimensionalArraysDynamicTypeAbi = { + constant: false, + inputs: [ + { + name: 'a', + type: 'string[][][2]', + }, + { + name: 'a', + type: 'string[][1][]', + }, + { + name: 'a', + type: 'string[1][1][2]', + }, + { + name: 'a', + type: 'string[][][]', + }, + { + name: 'a', + type: 'uint[][][]', + }, + { + name: 'b', + type: 'uint8[][2][]', + }, + { + name: 'c', + type: 'uint8[1][2][]', + }, + + { + name: 'c', + type: 'uint8[1][2][2]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const dynamicTupleAbi = { constant: false, inputs: [ From 180d1ca63a45ca96df2c9f811075411ca495f693 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 13:09:03 -0800 Subject: [PATCH 085/230] multidimensional arrays with dynamic objects --- packages/order-utils/test/abi_encoder_test.ts | 30 +++++++++++++++++++ packages/order-utils/test/abi_samples.ts | 25 +++------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 1155806244..4ba9bb611f 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -160,6 +160,36 @@ describe.only('ABI Encoder', () => { console.log(method.getSignature()); console.log(JSON.stringify(args)); const expectedCalldata = '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; expect(calldata).to.be.equal(expectedCalldata); + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.only('Multidimensional Arrays / Dynamic Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi); + + // Eight 3-dimensional arrays of string[2][2][2] + let value = 0; + const args = []; + for (let i = 0; i < 4; ++i) { + args.push( + [ + [ + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + ], + [ + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + ] + ] + ); + } + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); }); it('Fixed Lenfgth Array / Dynamic Members', async () => { diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index cbbc5f8f36..0882b389ad 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -80,38 +80,21 @@ export const multiDimensionalArraysStaticTypeAbi = { export const multiDimensionalArraysDynamicTypeAbi = { constant: false, inputs: [ - { - name: 'a', - type: 'string[][][2]', - }, - { - name: 'a', - type: 'string[][1][]', - }, - { - name: 'a', - type: 'string[1][1][2]', - }, { name: 'a', type: 'string[][][]', }, - { - name: 'a', - type: 'uint[][][]', - }, { name: 'b', - type: 'uint8[][2][]', + type: 'string[][][2]', }, { name: 'c', - type: 'uint8[1][2][]', + type: 'string[][2][]', }, - { - name: 'c', - type: 'uint8[1][2][2]', + name: 'h', + type: 'string[2][2][2]', }, ], name: 'simpleFunction', From 8f61f6d0f9532e95fd32cbab8dd0344b42de2da7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 13:41:58 -0800 Subject: [PATCH 086/230] Arrays of tuples --- packages/order-utils/test/abi_encoder.ts | 7 + packages/order-utils/test/abi_encoder_test.ts | 76 ++++++++- packages/order-utils/test/abi_samples.ts | 150 ++++++++++++++++++ 3 files changed, 231 insertions(+), 2 deletions(-) diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts index f63dc804a0..83ecc2f9a4 100644 --- a/packages/order-utils/test/abi_encoder.ts +++ b/packages/order-utils/test/abi_encoder.ts @@ -686,6 +686,10 @@ export class SolArray extends DynamicDataType { type: this.type, name: `${this.getDataItem().name}[${idx.toString(10)}]`, } as DataItem; + const components = this.getDataItem().components; + if (components !== undefined) { + childDataItem.components = components; + } const child = DataTypeFactory.create(childDataItem, this); this.elements.push(child); if (child instanceof Pointer) { @@ -706,6 +710,9 @@ export class SolArray extends DynamicDataType { }*/ public assignValue(value: any[]) { + console.log('GREG'.repeat(15), JSON.stringify(value)); + + // Sanity check length const valueLength = new BigNumber(value.length); if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 4ba9bb611f..cea33112db 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -134,7 +134,79 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata);*/ }); - it.only('Multidimensional Arrays / Static Members', async () => { + it('Array of Static Tuples (Array has defined length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Array of Static Tuples (Array has dynamic length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Array of Dynamic Tuples (Array has defined length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), (new BigNumber(++value)).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Array of Dynamic Tuples (Array has dynamic length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), (new BigNumber(++value)).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it('Multidimensional Arrays / Static Members', async () => { const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); // Eight 3-dimensional arrays of uint8[2][2][2] @@ -163,7 +235,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.only('Multidimensional Arrays / Dynamic Members', async () => { + it('Multidimensional Arrays / Dynamic Members', async () => { const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi); // Eight 3-dimensional arrays of string[2][2][2] diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index 0882b389ad..7939cbaebf 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -129,6 +129,156 @@ export const dynamicTupleAbi = { type: 'function', } as MethodAbi; +export const arrayOfStaticTuplesWithDefinedLengthAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someUint2', + type: 'uint256', + }, + ], + name: 'order', + type: 'tuple[8]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const arrayOfStaticTuplesWithDynamicLengthAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someUint2', + type: 'uint256', + }, + ], + name: 'order', + type: 'tuple[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const arrayOfDynamicTuplesWithDefinedLengthAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someString', + type: 'string', + }, + ], + name: 'order', + type: 'tuple[8]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const arrayOfDynamicTuplesWithUndefinedLengthAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someString', + type: 'string', + }, + ], + name: 'order', + type: 'tuple[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const arrayOfDynamicTuplesAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someString', + type: 'string', + }, + ], + name: 'order', + type: 'tuple[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const multidimensionalArrayOfDynamicTuplesAbi = { + constant: false, + inputs: [ + { + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someString', + type: 'string', + }, + ], + name: 'order', + type: 'tuple[][2][]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const staticTupleAbi = { constant: false, inputs: [ From 8b91727364c205cd2bd44abdabd62c1044dd13d4 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 13:57:36 -0800 Subject: [PATCH 087/230] types with default widths --- packages/order-utils/test/abi_encoder.ts | 22 +++++------- packages/order-utils/test/abi_encoder_test.ts | 12 +++++++ packages/order-utils/test/abi_samples.ts | 35 +++++++++++++++++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts index 83ecc2f9a4..bcd427deeb 100644 --- a/packages/order-utils/test/abi_encoder.ts +++ b/packages/order-utils/test/abi_encoder.ts @@ -781,20 +781,16 @@ export class SolArray extends DynamicDataType { } public getSignature(): string { - let type = this.type; - if (this.type === 'tuple') { - let tupleDataItem = { - type: 'tuple', - name: 'N/A', - } as DataItem; - const tupleComponents = this.getDataItem().components; - if (tupleComponents !== undefined) { - tupleDataItem.components = tupleComponents; - } - const tuple = new Tuple(tupleDataItem); - type = tuple.getSignature(); + let dataItem = { + type: this.type, + name: 'N/A', + } as DataItem; + const components = this.getDataItem().components; + if (components !== undefined) { + dataItem.components = components; } - + const elementDataType = DataTypeFactory.mapDataItemToDataType(dataItem); + const type = elementDataType.getSignature(); if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { return `${type}[]`; } diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index cea33112db..ad43e70a33 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -134,6 +134,18 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata);*/ }); + it.only('Types with default widths', async () => { + const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); + const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + it('Array of Static Tuples (Array has defined length)', async () => { const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index 7939cbaebf..9214087246 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -34,6 +34,41 @@ export const stringAbi = { type: 'function', } as MethodAbi; +export const typesWithDefaultWidthsAbi = { + constant: false, + inputs: [ + { + name: 'someUint', + type: 'uint', + }, + { + name: 'someInt', + type: 'int', + }, + { + name: 'someByte', + type: 'byte', + }, + { + name: 'someUint', + type: 'uint[]', + }, + { + name: 'someInt', + type: 'int[]', + }, + { + name: 'someByte', + type: 'byte[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const multiDimensionalArraysStaticTypeAbi = { constant: false, inputs: [ From 1ca23b35ab067ed59a5d19543ceec646272e77a3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 14:23:41 -0800 Subject: [PATCH 088/230] complex type tesst --- packages/order-utils/test/abi_encoder_test.ts | 140 ++++++++---------- packages/order-utils/test/abi_samples.ts | 40 +++-- 2 files changed, 83 insertions(+), 97 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index ad43e70a33..c8a0642ead 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -19,24 +19,23 @@ chaiSetup.configure(); const expect = chai.expect; describe.only('ABI Encoder', () => { - describe.only('Just a Greg, Eh', () => { - + describe.only('ABI Tests at Method Level', () => { it('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)], - [ + const someStaticArray = [new BigNumber(127), new BigNumber(14), new BigNumber(54)]; + const someStaticArrayWithDynamicMembers = [ 'the little piping piper piped a piping pipper papper', 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', - ], // l - [ + ]; + const someDynamicArrayWithDynamicMembers = [ '0x38745637834987324827439287423897238947239847', '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398', '0x283473298473248923749238742398742398472894729843278942374982374892374892743982', - ], // m - /*[ + ]; + const some2DArray = [ [ 'some string', 'some another string', @@ -49,64 +48,32 @@ describe.only('ABI Encoder', () => { 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', ], [], - ],*/ // n - [ - new BigNumber(4037824789), - 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', - ], // o - [ - new BigNumber('239048320948320948230', 10), - 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', - /*[ - [ - '23432423342', - 'skdjfhdsjkfdhsfkjsdhfjkdshfdsjkfhsdjkfhsdjkfhdsjkfhdsjfhsdfjdshjkfsdhf', - 'sdfsdfdfdffsdf', - ], - [], - [], - ['23ehsdjkfhsiufhwfuefhesfhauhesufheuifhsefushfsufehfeuif'], - ],*/ - '0xf74848484848484848484848484848484848483847576879809433994458585848932091', - '0xe41d2489571d322189246dafa5ebde1f4699f498', - ], // p - /*[ - [ - new BigNumber('23904848320948230', 10), - 'akdhjasshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', - [ - [ - '234324342', - 'skdjfhdsjkfdhsfkjsjkfhsdjkfhsdjkfhdsjkfhdsjfhsdfjdshjkfsdhf', - 'sdffdfdffsdf', - ], - [], - [], - ['23ehsdjkfhsiufhwfuefsufheuifhsefushfsufehfeuif'], - ], - '0xf7484848484848484848484848484876879809433994458585848932091', - '0xe41d2489571d322189246dafa6ebde1f4699f498', - ], - [ - new BigNumber('23904832094832030', 10), - 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdkdhsajkdhsadjk', - [ - [ - '2343342', - 'skdjfhdsjkfdhsfkjsdhfjkdshfdsjkfhsdjkfhsdjkfhdssjfhsdfjdshjkfsdhf', - 'sdfsdfdfdffsf', - ], - [], - [], - ['jkfhsiufhwfuefhesfhauhesufhefeuif'], - ], - '0xf7484848484848484848484848484848484848384757687980943091', - '0xe41d2489571d322189246dafa5ebde1f469af498', - ], - [], - [], - ],*/ ]; + const someTuple = { + someUint32: new BigNumber(4037824789), + someStr: 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.' + }; + const someTupleWithDynamicTypes = { + someUint: new BigNumber(4024789), + someStr: 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', + someBytes: '0x29384723894723843743289742389472398473289472348927489274894738427428947389facdea', + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', + }; + const someTupleWithDynamicTypes2 = { + someUint: new BigNumber(9024789), + someStr: 'ksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjkakdhjasjkdhasjkldshdjahdkjsahdaj', + someBytes: '0x29384723894398473289472348927489272384374328974238947274894738427428947389facde1', + someAddress: '0x746dafa5ebde1f4699f4981d3221892e41d24895', + }; + const someTupleWithDynamicTypes3 = { + someUint: new BigNumber(1024789), + someStr: 'sdhsajkdhsajkdhadjkashdjakdhjasjkdhasjkldshdjahdkjsahdajkksadhajkdhsajkdhsadjk', + someBytes: '0x38947238437432829384729742389472398473289472348927489274894738427428947389facdef', + someAddress: '0x89571d322189e415ebde1f4699f498d24246dafa', + }; + const someArrayOfTuplesWithDynamicTypes = [someTupleWithDynamicTypes2, someTupleWithDynamicTypes3]; + + const args = [someStaticArray, someStaticArrayWithDynamicMembers, someDynamicArrayWithDynamicMembers, some2DArray, someTuple, someTupleWithDynamicTypes, someArrayOfTuplesWithDynamicTypes]; const calldata = method.encode(args); console.log(calldata); @@ -114,27 +81,36 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); console.log(method.getSignature()); - const expectedCalldata = '0x76c14e63000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e728347239823742398472398472984724892749874897428472894723948749874984787432942374349234732984723984237489237489237489234723894728947894748937428947289473894274982374329874238947238947328947238943724982374982374289347239800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000272834732984732489237492387423987423984728947298432789423749823748923748927439820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; - + const expectedCalldata = '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; //const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); - - /* - const expectedCalldata = - '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata);*/ - - /*const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); - console.log(method.getSignature()); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = - '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata);*/ }); - it.only('Types with default widths', async () => { + it('Crazy ABI #1', async () => { + const method = new AbiEncoder.Method(AbiSamples.crazyAbi1); + const args = [ + new BigNumber(256745454), + new BigNumber(-256745454), + new BigNumber(434244), + '0x43', + '0x0001020304050607080911121314151617181920212223242526272829303132', + '0x0001020304050607080911121314151617181920212223242526272829303132080911121314151617181920212223242526272829303132', + 'Little peter piper piped a piping pepper pot', + '0xe41d2489571d322189246dafa5ebde1f4699f498', + true + ]; + + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + + it('Types with default widths', async () => { const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; const calldata = method.encode(args); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index 9214087246..82427986ad 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -407,10 +407,10 @@ export const dynamicArrayStaticMembersAbi = { type: 'function', } as MethodAbi; -export const crazyAbi = { +export const crazyAbi1 = { constant: false, inputs: [ - /*{ + { name: 'someUInt256', type: 'uint256', }, @@ -437,16 +437,26 @@ export const crazyAbi = { { name: 'someString', type: 'string', - },*/ - /*{ + }, + { name: 'someAddress', type: 'address', }, { name: 'someBool', type: 'bool', - },*/ + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; +export const crazyAbi = { + constant: false, + inputs: [ { name: 'someStaticArray', type: 'uint8[3]', @@ -459,10 +469,10 @@ export const crazyAbi = { name: 'someDynamicArrayWithDynamicMembers', type: 'bytes[]', }, - /* { + { name: 'some2DArray', type: 'string[][]', - }, */ + }, { name: 'someTuple', type: 'tuple', @@ -489,10 +499,10 @@ export const crazyAbi = { name: 'someStr', type: 'string', }, - //{ - // name: 'someStrArray', - // type: 'string[]', - /// }, + /*{ + name: 'someStrArray', + type: 'string[]', + },*/ { name: 'someBytes', type: 'bytes', @@ -502,7 +512,7 @@ export const crazyAbi = { type: 'address', }, ], - } /*, + }, { name: 'someArrayOfTuplesWithDynamicTypes', type: 'tuple[]', @@ -515,10 +525,10 @@ export const crazyAbi = { name: 'someStr', type: 'string', }, - { + /*{ name: 'someStrArray', type: 'string[]', - }, + },*/ { name: 'someBytes', type: 'bytes', @@ -528,7 +538,7 @@ export const crazyAbi = { type: 'address', }, ], - },*/, + } ], name: 'simpleFunction', outputs: [], From 78498b7019e02a9b280cc1ffe9fafbf30bff24ec Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 16:21:31 -0800 Subject: [PATCH 089/230] Initial port -- wont compile yet --- packages/order-utils/test/abi/abi_encoder.ts | 1 + packages/order-utils/test/abi/calldata.ts | 177 +++++++ packages/order-utils/test/abi/config.ts | 4 + packages/order-utils/test/abi/data_type.ts | 278 +++++++++++ .../order-utils/test/abi/evm_data_types.ts | 439 ++++++++++++++++++ 5 files changed, 899 insertions(+) create mode 100644 packages/order-utils/test/abi/abi_encoder.ts create mode 100644 packages/order-utils/test/abi/calldata.ts create mode 100644 packages/order-utils/test/abi/config.ts create mode 100644 packages/order-utils/test/abi/data_type.ts create mode 100644 packages/order-utils/test/abi/evm_data_types.ts diff --git a/packages/order-utils/test/abi/abi_encoder.ts b/packages/order-utils/test/abi/abi_encoder.ts new file mode 100644 index 0000000000..12755209aa --- /dev/null +++ b/packages/order-utils/test/abi/abi_encoder.ts @@ -0,0 +1 @@ +export * from './calldata_block'; \ No newline at end of file diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts new file mode 100644 index 0000000000..dc0fcfb2b8 --- /dev/null +++ b/packages/order-utils/test/abi/calldata.ts @@ -0,0 +1,177 @@ +import ethUtil = require('ethereumjs-util'); +var _ = require('lodash'); + +export abstract class CalldataBlock { + private name: string; + private signature: string; + private offsetInBytes: number; + private headerSizeInBytes: number; + private bodySizeInBytes: number; + private relocatable: boolean; + + constructor(name: string, signature: string, offsetInBytes: number, headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { + this.name = name; + this.signature = signature; + this.offsetInBytes = offsetInBytes; + this.headerSizeInBytes = headerSizeInBytes; + this.bodySizeInBytes = bodySizeInBytes; + this.relocatable = relocatable; + } + + public getName(): string { + return this.name; + } + + public getSignature(): string { + return this.signature; + } + + public isRelocatable(): boolean { + return this.relocatable; + } + + public getHeaderSizeInBytes(): number { + return this.headerSizeInBytes; + } + + public getBodySizeInBytes(): number { + return this.bodySizeInBytes; + } + + public getSizeInBytes(): number { + return this.headerSizeInBytes + this.bodySizeInBytes; + } + + public getOffsetInBytes(): number { + return this.offsetInBytes; + } + + public abstract toHexString(): string; +} + +export class PayloadCalldataBlock extends CalldataBlock { + private payload: Buffer; + + constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, payload: Buffer) { + const headerSizeInBytes = 0; + const bodySizeInBytes = payload.byteLength; + super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); + this.payload = payload; + } + + public toHexString(): string { + const payloadHex = ethUtil.bufferToHex(this.payload); + return payloadHex; + } +} + +export class DependentCalldataBlock extends CalldataBlock { + public static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; + private parent: CalldataBlock; + private dependency: CalldataBlock; + + constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, parent: CalldataBlock, dependency: CalldataBlock) { + const headerSizeInBytes = 0; + const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES; + super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); + this.parent = parent; + this.dependency = dependency; + } + + public toHexString(): string { + const dependencyOffset = this.dependency.getOffsetInBytes(); + const parentOffset = this.parent.getOffsetInBytes(); + const parentHeaderSize = this.parent.getHeaderSizeInBytes(); + const pointer = dependencyOffset - parentOffset + parentHeaderSize; + const pointerBuf = new Buffer(`0x${pointer.toString(16)}`); + const evmWordWidthInBytes = 32; + const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); + const pointerHex = ethUtil.bufferToHex(pointerBufPadded); + return pointerHex; + } +} + +export class MemberCalldataBlock extends CalldataBlock { + private static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; + private header: Buffer | undefined; + private members: CalldataBlock[]; + + constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, members: CalldataBlock[], header?: Buffer) { + const headerSizeInBytes = (header === undefined) ? 0 : header.byteLength; + let bodySizeInBytes = 0; + _.each(members, (member: Memblock) => { + bodySizeInBytes += member.getSizeInBytes(); + }); + + super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); + this.members = members; + this.header = header; + } + + public toHexString(): string { + let valueBuffers: Buffer[] = []; + if (this.header !== undefined) valueBuffers.push(this.header); + _.each(this.members, (member: CalldataBlock) => { + const memberHexString = member.toHexString(); + const memberBuf = ethUtil.toBuffer(memberHexString); + valueBuffers.push(memberBuf); + }); + const combinedValueBufs = Buffer.concat(valueBuffers); + const combinedValuesAsHex = ethUtil.bufferToHex(combinedValueBufs); + return combinedValuesAsHex; + } +} + +export class Calldata { + private selector: string; + private sizeInBytes: number; + private blocks: CalldataBlock[]; + + constructor() { + this.selector = '0x'; + this.sizeInBytes = 0; + this.blocks = []; + } + + public toHexString(): string { + let calldataString = `${this.selector}`; + _.each(this.blocks, (block: CalldataBlock) => { + const blockAsHexString = block.toHexString(); + const blockAsHexWithoutPrefix = ethUtil.stripHexPrefix(blockAsHexString); + calldataString += blockAsHexWithoutPrefix; + }); + return calldataString; + } + + public getSelectorHex(): string { + return this.selector; + } + + public getSizeInBytes(): number { + return this.sizeInBytes; + } + + public toAnnotatedString(): string { + return ""; + } + + public pushBlock(block: CalldataBlock) { + this.blocks.push(block); + this.sizeInBytes += block.getSizeInBytes(); + } + + public setSelector(selector: string) { + // Ensure we have a 0x prefix + if (selector.startsWith('0x')) { + this.selector = selector; + } else { + this.selector = `$0x${selector}`; + } + + // The selector must be 10 characters: '0x' followed by 4 bytes (two hex chars per byte) + if (this.selector.length !== 10) { + throw new Error(`Invalid selector '${this.selector}'`); + } + this.sizeInBytes += 8; + } +} \ No newline at end of file diff --git a/packages/order-utils/test/abi/config.ts b/packages/order-utils/test/abi/config.ts new file mode 100644 index 0000000000..015cee59ad --- /dev/null +++ b/packages/order-utils/test/abi/config.ts @@ -0,0 +1,4 @@ +import { DataTypeFactory } from './data_type'; +import { EvmDataTypeFactoryImpl } from './evm_data_types'; + +DataTypeFactory.setImpl(new EvmDataTypeFactoryImpl()); \ No newline at end of file diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts new file mode 100644 index 0000000000..f746210854 --- /dev/null +++ b/packages/order-utils/test/abi/data_type.ts @@ -0,0 +1,278 @@ +import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock } from "./calldata"; +import { MethodAbi, DataItem } from 'ethereum-types'; +import { BigNumber } from '@0x/utils'; +import ethUtil = require('ethereumjs-util'); +var _ = require('lodash'); + + + +export abstract class DataType { + private dataItem: DataItem; + + constructor(dataItem: DataItem) { + this.dataItem = dataItem; + } + + public getDataItem(): DataItem { + return this.dataItem; + } + + protected abstract createCalldataBlock(): CalldataBlock; + public abstract encode(value: any, calldata: Calldata): void; + public abstract getSignature(): string; + public abstract isStatic(): boolean; +} + +export abstract class PayloadDataType extends DataType { + protected hasConstantSize: boolean; + + public constructor(dataItem: DataItem, hasConstantSize: boolean) { + super(dataItem); + this.hasConstantSize = hasConstantSize; + } + + protected generateCalldataBlock(payload: Buffer, calldata: Calldata): void { + const name = this.getDataItem().name; + const signature = this.getSignature(); + const offsetInBytes = calldata.getSizeInBytes(); + const relocatable = false; + const block = new PayloadCalldataBlock(name, signature, offsetInBytes, relocatable, payload); + calldata.pushBlock(block); + } + + public isStatic(): boolean { + // If a payload has a constant size then it's static + return this.hasConstantSize; + } +} + +export abstract class DependentDataType extends DataType { + protected dependency: DataType; + protected parent: DataType; + + public constructor(dataItem: DataItem, dependency: DataType, parent: DataType) { + super(dataItem); + this.dependency = dependency; + this.parent = parent; + } + + protected generateCalldataBlock(offsetInBytes: number, calldata: Calldata): CalldataBlock { + const name = this.getDataItem().name; + const signature = this.getSignature(); + const relocatable = false; + const parentBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); + const dependencyBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); + const block = new DependentCalldataBlock(name, signature, offsetInBytes, relocatable, dependencyBlock, parentBlock); + calldata.pushBlock(block); + } + + public encode(value: any, calldata: Calldata = new Calldata()): void { + const offsetInBytes = calldata.reserveSpace(DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES); + this.dependency.encode(value, calldata); + this.generateCalldataBlock(offsetInBytes, calldata); + } + + public isStatic(): boolean { + return true; + } +} + +export interface MemberMap { + [key: string]: number; +} + +export abstract class MemberDataType extends DataType { + private memberMap: MemberMap; + private members: DataType[]; + private isArray: boolean; + protected arrayLength: number | undefined; + + + public constructor(dataItem: DataItem, isArray: boolean = false, arrayLength?: number) { + super(dataItem); + + this.memberMap = {}; + this.members = []; + this.isArray = isArray; + this.arrayLength = arrayLength; + if (isArray && arrayLength !== undefined) { + [this.members, this.memberMap] = MemberDataType.createMembersWithLength(arrayLength); + } else if (!isArray) { + [this.members, this.memberMap] = MemberDataType.createMembersWithKeys(dataItem); + } + } + + private static createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { + // Sanity check + if (dataItem.components === undefined) { + throw new Error(`Expected components`); + } + + let members: DataType[] = []; + let memberMap: MemberMap = {}; + _.each(dataItem.components, (dataItem: DataItem) => { + const childDataItem = { + type: dataItem.type, + name: `${dataItem.name}.${dataItem.name}`, + } as DataItem; + const child = DataTypeFactory.create(childDataItem, this); + members.push(child); + memberMap[dataItem.name] = members.length; + }); + + return [members, memberMap]; + } + + private static createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { + let members: DataType[] = []; + let memberMap: MemberMap = {}; + const range = _.range(length); + _.each(range, (idx: number) => { + const childDataItem = { + type: this.type, + name: `${dataItem.name}[${idx.toString(10)}]`, + } as DataItem; + const components = dataItem.components; + if (components !== undefined) { + childDataItem.components = components; + } + const child = DataTypeFactory.create(childDataItem, this); + members.push(child); + memberMap[idx.toString(10)] = members.length; + }); + + return [members, memberMap]; + } + + protected encodeFromArray(value: any[], calldata: Calldata) { + // Sanity check length + const valueLength = new BigNumber(value.length); + if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + throw new Error( + `Expected array of ${JSON.stringify( + this.length, + )} elements, but got array of length ${JSON.stringify(valueLength)}`, + ); + } + + // Assign values to children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + this.members[idxNumber].assignValue(value[idxNumber]); + } + } + + protected encodeFromObject(obj: object, calldata: Calldata) { + let childMap = _.cloneDeep(this.memberMap); + _.forOwn(obj, (value: any, key: string) => { + if (key in childMap === false) { + throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); + } + this.members[this.childMap[key]].assignValue(value); + delete childMap[key]; + }); + + if (Object.keys(childMap).length !== 0) { + throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); + } + } + + public encode(value: any[] | object, calldata = new Calldata()) { + if (value instanceof Array) { + this.encodeFromArray(value, calldata); + } else if (typeof value === 'object') { + this.encodeFromObject(value, encodeFromObject); + } else { + throw new Error(`Unexpected type for ${value}`); + } + } + + protected generateCalldataBlock(offsetInBytes: number, calldata: Calldata): CalldataBlock { + const name = this.getDataItem().name; + const signature = this.getSignature(); + const relocatable = false; + const parentBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); + const dependencyBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); + const block = new DependentCalldataBlock(name, signature, offsetInBytes, relocatable, dependencyBlock, parentBlock); + calldata.pushBlock(block); + } + + protected computeSignatureOfMembers(): string { + // Compute signature of members + let signature = `(`; + _.each(this.members, (member: DataType, i: number) => { + signature += member.getSignature(); + if (i < this.members.length - 1) { + signature += ','; + } + }); + signature += ')'; + return signature; + } + + public isStatic(): boolean { + /* For Tuple: + const isStaticTuple = this.children.length === 0; + return isStaticTuple; // @TODO: True in every case or only when dynamic data? + + For Array: + if isLengthDefined = false then this is false + + Otherwise if the first element is a Pointer then false + */ + + if (this.isArray && this.arrayLength === undefined) { + return true; + } + + // Search for dependent members + const dependentMember = _.find(this.members, (member: DataType) => { + return (member instanceof DependentDataType); + }); + const isStatic = (dependentMember === undefined); // static if we couldn't find a dependent member + return isStatic; + } +} + +export interface DataTypeFactoryImpl { + create: (dataItem: DataItem, parentDataType: DataType) => DataType; + mapDataItemToDataType: (dataItem: DataItem) => DataType; +} + +export class DataTypeFactory { + private static instance: DataTypeFactory; + private provider: DataTypeFactoryImpl | undefined; + + private constructor() { } + + private static getInstance(): DataTypeFactory { + if (!DataTypeFactory.instance) { + DataTypeFactory.instance = new DataTypeFactory(); + } + return DataTypeFactory.instance; + } + + public static setImpl(provider: DataTypeFactoryImpl) { + const instance = DataTypeFactory.getInstance(); + if (instance.provider !== undefined) { + throw new Error(`Tried to set implementation more than once`); + } + DataTypeFactory.getInstance().provider = provider; + } + + public static create(dataItem: DataItem, parentDataType: DataType): DataType { + const instance = DataTypeFactory.getInstance(); + if (instance.provider === undefined) { + throw new Error(`Tried to create before implementation is set`); + } + return instance.provider.create(dataItem, parentDataType); + } + + public static mapDataItemToDataType(dataItem: DataItem): DataType { + const instance = DataTypeFactory.getInstance(); + if (instance.provider === undefined) { + throw new Error(`Tried to create before implementation is set`); + } + return instance.provider.mapDataItemToDataType(dataItem); + } +} \ No newline at end of file diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts new file mode 100644 index 0000000000..4e42a992a3 --- /dev/null +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -0,0 +1,439 @@ +import { DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; + +import { MethodAbi, DataItem } from 'ethereum-types'; + +import ethUtil = require('ethereumjs-util'); + +import { BigNumber } from '@0x/utils'; + +export interface DataTypeStaticInterface { + matchGrammar: (type: string) => boolean; + encodeValue: (value: any) => Buffer; +} + +export class Address extends PayloadDataType { + private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + + constructor(dataItem: DataItem) { + super(dataItem, Address.SIZE_KNOWN_AT_COMPILE_TIME); + if (!Address.matchGrammar(dataItem.type)) { + throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`); + } + } + + public getSignature(): string { + return 'address'; + } + + public static matchGrammar(type: string): boolean { + return type === 'address'; + } + + public static encodeValue(value: boolean): Buffer { + const evmWordWidth = 32; + const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth); + return encodedValueBuf; + } +} + +export class Bool extends PayloadDataType { + private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + + constructor(dataItem: DataItem) { + super(dataItem, Bool.SIZE_KNOWN_AT_COMPILE_TIME); + if (!Bool.matchGrammar(dataItem.type)) { + throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`); + } + } + + public getSignature(): string { + return 'bool'; + } + + public static matchGrammar(type: string): boolean { + return type === 'bool'; + } + + public static encodeValue(value: boolean): Buffer { + const evmWordWidth = 32; + const encodedValue = value === true ? '0x1' : '0x0'; + const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth); + return encodedValueBuf; + } +} + +abstract class Number extends PayloadDataType { + private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + static MAX_WIDTH: number = 256; + static DEFAULT_WIDTH: number = Number.MAX_WIDTH; + width: number = Number.DEFAULT_WIDTH; + + constructor(dataItem: DataItem, matcher: RegExp) { + super(dataItem, Number.SIZE_KNOWN_AT_COMPILE_TIME); + const matches = matcher.exec(dataItem.type); + if (matches === null) { + throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`); + } + if (matches !== null && matches.length === 2 && matches[1] !== undefined) { + this.width = parseInt(matches[1]); + } else { + this.width = 256; + } + } + + public static encodeValue(value: BigNumber): Buffer { + if (value.greaterThan(this.getMaxValue())) { + throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; + } else if (value.lessThan(this.getMinValue())) { + throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; + } + + const hexBase = 16; + const evmWordWidth = 32; + let valueBuf: Buffer; + if (value.greaterThanOrEqualTo(0)) { + valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); + } else { + // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. + // Step 1/3: Convert value to positive binary string + const binBase = 2; + const valueBin = value.times(-1).toString(binBase); + + // Step 2/3: Invert binary value + const bitsInEvmWord = 256; + let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length); + _.each(valueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, binBase); + + // Step 3/3: Add 1 to inverted value + // The result is the two's-complement represent of the input value. + const negativeValue = invertedValue.plus(1); + + // Convert the negated value to a hex string + valueBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), + evmWordWidth, + ); + } + + return valueBuf; + } + + public abstract getMaxValue(): BigNumber; + public abstract getMinValue(): BigNumber; +} + +export class Int extends Number { + static matcher = RegExp( + '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + constructor(dataItem: DataItem) { + super(dataItem, Int.matcher); + } + + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).sub(1); + } + + public getMinValue(): BigNumber { + return new BigNumber(2).toPower(this.width - 1).times(-1); + } + + public getSignature(): string { + return `int${this.width}`; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class UInt extends Number { + static matcher = RegExp( + '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + constructor(dataItem: DataItem) { + super(dataItem, UInt.matcher); + } + + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this.width).sub(1); + } + + public getMinValue(): BigNumber { + return new BigNumber(0); + } + + public getSignature(): string { + return `uint${this.width}`; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class Byte extends PayloadDataType { + private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + static matcher = RegExp( + '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', + ); + + static DEFAULT_WIDTH = 1; + width: number = Byte.DEFAULT_WIDTH; + + constructor(dataItem: DataItem) { + super(dataItem, Byte.SIZE_KNOWN_AT_COMPILE_TIME); + const matches = Byte.matcher.exec(dataItem.type); + if (!Byte.matchGrammar(dataItem.type)) { + throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`); + } + if (matches !== null && matches.length === 3 && matches[2] !== undefined) { + this.width = parseInt(matches[2]); + } else { + this.width = Byte.DEFAULT_WIDTH; + } + } + + public getSignature(): string { + // Note that `byte` reduces to `bytes1` + return `bytes${this.width}`; + } + + public static encodeValue(value: string | Buffer): Buffer { + // Convert value into a buffer and do bounds checking + const valueBuf = ethUtil.toBuffer(value); + if (valueBuf.byteLength > this.width) { + throw new Error( + `Tried to assign ${value} (${ + valueBuf.byteLength + } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, + ); + } else if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + // Store value as hex + const evmWordWidth = 32; + const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); + return paddedValue; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class Bytes extends PayloadDataType { + private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = Bytes.UNDEFINED_LENGTH; + + constructor(dataItem: DataItem) { + super(dataItem, Bytes.SIZE_KNOWN_AT_COMPILE_TIME); + if (!Bytes.matchGrammar(dataItem.type)) { + throw new Error(`Tried to instantiate Bytes with bad input: ${dataItem}`); + } + } + + public encodeValue(value: string | Buffer): Buffer { + if (typeof value === 'string' && !value.startsWith('0x')) { + throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`); + } + const valueBuf = ethUtil.toBuffer(value); + if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + const wordsForValue = Math.ceil(valueBuf.byteLength / 32); + const paddedBytesForValue = wordsForValue * 32; + const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedBytesForValue); + const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); + const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); + return encodedValueBuf; + } + + public getSignature(): string { + return 'bytes'; + } + + public static matchGrammar(type: string): boolean { + return type === 'bytes'; + } +} + +export class SolString extends PayloadDataType { + private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; + constructor(dataItem: DataItem) { + super(dataItem, SolString.SIZE_KNOWN_AT_COMPILE_TIME); + if (!SolString.matchGrammar(dataItem.type)) { + throw new Error(`Tried to instantiate String with bad input: ${dataItem}`); + } + } + + public static encodeValue(value: string): Buffer { + const wordsForValue = Math.ceil(value.length / 32); + const paddedBytesForValue = wordsForValue * 32; + const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); + const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); + const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); + return encodedValueBuf; + } + + public getSignature(): string { + return 'string'; + } + + public static matchGrammar(type: string): boolean { + return type === 'string'; + } +} + +export class Pointer extends DependentDataType { + + constructor(destDataType: DataType, parentDataType: DataType) { + const destDataItem = destDataType.getDataItem(); + const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; + super(dataItem, destDataType, parentDataType); + } + + public getSignature(): string { + return this.dependency.getSignature(); + } +} + +export class Tuple extends MemberDataType { + private tupleSignature: string; + + constructor(dataItem: DataItem) { + super(dataItem); + if (!Tuple.matchGrammar(dataItem.type)) { + throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); + } + this.tupleSignature = this.computeSignatureOfMembers(); + } + + public getSignature(): string { + return this.tupleSignature; + } + + public static matchGrammar(type: string): boolean { + return type === 'tuple'; + } +} + +export class SolArray extends MemberDataType { + static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); + private arraySignature: string; + private elementType: string; + + constructor(dataItem: DataItem) { + // Sanity check + const matches = SolArray.matcher.exec(dataItem.type); + if (matches === null || matches.length !== 3) { + throw new Error(`Could not parse array: ${dataItem.type}`); + } else if (matches[1] === undefined) { + throw new Error(`Could not parse array type: ${dataItem.type}`); + } else if (matches[2] === undefined) { + throw new Error(`Could not parse array length: ${dataItem.type}`); + } + + const isArray = true; + const arrayLength = (matches[2] === '') ? undefined : parseInt(matches[2], 10); + super(dataItem, isArray, arrayLength); + this.elementType = matches[1]; + this.arraySignature = this.computeSignature(); + } + + private computeSignature(): string { + let dataItem = { + type: this.elementType, + name: 'N/A', + } as DataItem; + const components = this.getDataItem().components; + if (components !== undefined) { + dataItem.components = components; + } + const elementDataType = DataTypeFactory.mapDataItemToDataType(dataItem); + const type = elementDataType.getSignature(); + if (this.arrayLength === undefined) { + return `${type}[]`; + } else { + return `${type}[${this.arrayLength}]`; + } + } + + public getSignature(): string { + return this.arraySignature; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class Method extends MemberDataType { + private methodSignature: string; + private methodSelector: string; + + constructor(abi: MethodAbi) { + super({ type: 'method', name: abi.name }); + this.methodSignature = this.computeSignature(); + this.methodSelector = this.computeSelector(); + } + + private computeSignature(): string { + const memberSignature = this.computeSignatureOfMembers(); + const methodSignature = `${this.getDataItem().name}${memberSignature}`; + return methodSignature; + } + + private computeSelector(): string { + const signature = this.computeSignature(); + const selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(signature).slice(0, 4))); + return selector; + } + + public getSignature(): string { + return this.methodSignature; + } + + public getSelector(): string { + return this.methodSelector; + } +} + +export class EvmDataTypeFactoryImpl implements DataTypeFactoryImpl { + + public mapDataItemToDataType(dataItem: DataItem): DataType { + console.log(`Type: ${dataItem.type}`); + + if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); + if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); + if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); + if (Int.matchGrammar(dataItem.type)) return new Int(dataItem); + if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem); + if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem); + if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem); + if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem); + if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem); + //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem); + //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem); + + throw new Error(`Unrecognized data type: '${dataItem.type}'`); + } + + public create(dataItem: DataItem, parentDataType: DataType): DataType { + const dataType = this.mapDataItemToDataType(dataItem); + if (dataType.isStatic()) { + return dataType; + } + + const pointer = new Pointer(dataType, parentDataType); + return pointer; + } +} \ No newline at end of file From 2802aed79cac4e73e64e1f33e328bcd05a4daf8b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 18:10:58 -0800 Subject: [PATCH 090/230] refactored implementation done but not tested --- packages/order-utils/test/abi/calldata.ts | 140 +++++++++++++----- packages/order-utils/test/abi/data_type.ts | 98 ++++++------ .../order-utils/test/abi/evm_data_types.ts | 17 ++- 3 files changed, 167 insertions(+), 88 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index dc0fcfb2b8..725bdce629 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -9,15 +9,23 @@ export abstract class CalldataBlock { private bodySizeInBytes: number; private relocatable: boolean; - constructor(name: string, signature: string, offsetInBytes: number, headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { this.name = name; this.signature = signature; - this.offsetInBytes = offsetInBytes; + this.offsetInBytes = 0; this.headerSizeInBytes = headerSizeInBytes; this.bodySizeInBytes = bodySizeInBytes; this.relocatable = relocatable; } + protected setHeaderSize(headerSizeInBytes: number) { + this.headerSizeInBytes = headerSizeInBytes; + } + + protected setBodySize(bodySizeInBytes: number) { + this.bodySizeInBytes = bodySizeInBytes; + } + public getName(): string { return this.name; } @@ -46,22 +54,25 @@ export abstract class CalldataBlock { return this.offsetInBytes; } - public abstract toHexString(): string; + public setOffset(offsetInBytes: number) { + this.offsetInBytes = offsetInBytes; + } + + public abstract toBuffer(): Buffer; } export class PayloadCalldataBlock extends CalldataBlock { private payload: Buffer; - constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, payload: Buffer) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, payload: Buffer) { const headerSizeInBytes = 0; const bodySizeInBytes = payload.byteLength; - super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); + super(name, signature, /*offsetInBytes,*/ headerSizeInBytes, bodySizeInBytes, relocatable); this.payload = payload; } - public toHexString(): string { - const payloadHex = ethUtil.bufferToHex(this.payload); - return payloadHex; + public toBuffer(): Buffer { + return this.payload; } } @@ -70,15 +81,15 @@ export class DependentCalldataBlock extends CalldataBlock { private parent: CalldataBlock; private dependency: CalldataBlock; - constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, parent: CalldataBlock, dependency: CalldataBlock) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, parent: CalldataBlock, dependency: CalldataBlock) { const headerSizeInBytes = 0; const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES; - super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); + super(name, signature, /*offsetInBytes,*/ headerSizeInBytes, bodySizeInBytes, relocatable); this.parent = parent; this.dependency = dependency; } - public toHexString(): string { + public toBuffer(): Buffer { const dependencyOffset = this.dependency.getOffsetInBytes(); const parentOffset = this.parent.getOffsetInBytes(); const parentHeaderSize = this.parent.getHeaderSizeInBytes(); @@ -86,8 +97,11 @@ export class DependentCalldataBlock extends CalldataBlock { const pointerBuf = new Buffer(`0x${pointer.toString(16)}`); const evmWordWidthInBytes = 32; const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); - const pointerHex = ethUtil.bufferToHex(pointerBufPadded); - return pointerHex; + return pointerBufPadded; + } + + public getDependency(): CalldataBlock { + return this.dependency; } } @@ -96,51 +110,97 @@ export class MemberCalldataBlock extends CalldataBlock { private header: Buffer | undefined; private members: CalldataBlock[]; - constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, members: CalldataBlock[], header?: Buffer) { - const headerSizeInBytes = (header === undefined) ? 0 : header.byteLength; + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean) { + super(name, signature, /*offsetInBytes,*/ 0, 0, relocatable); + this.members = []; + this.header = undefined; + } + + public setMembers(members: CalldataBlock[]) { let bodySizeInBytes = 0; - _.each(members, (member: Memblock) => { + _.each(members, (member: CalldataBlock) => { bodySizeInBytes += member.getSizeInBytes(); }); - - super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); this.members = members; + this.setBodySize(bodySizeInBytes); + } + + public setHeader(header: Buffer) { + this.setHeaderSize(header.byteLength); this.header = header; } - public toHexString(): string { - let valueBuffers: Buffer[] = []; - if (this.header !== undefined) valueBuffers.push(this.header); - _.each(this.members, (member: CalldataBlock) => { - const memberHexString = member.toHexString(); - const memberBuf = ethUtil.toBuffer(memberHexString); - valueBuffers.push(memberBuf); - }); - const combinedValueBufs = Buffer.concat(valueBuffers); - const combinedValuesAsHex = ethUtil.bufferToHex(combinedValueBufs); - return combinedValuesAsHex; + public toBuffer(): Buffer { + if (this.header !== undefined) return this.header; + return new Buffer(''); + } + + public getMembers(): CalldataBlock[] { + return this.members; + } +} + +class Queue { + private store: T[] = []; + push(val: T) { + this.store.push(val); + } + pop(): T | undefined { + return this.store.shift(); } } export class Calldata { private selector: string; private sizeInBytes: number; - private blocks: CalldataBlock[]; + private root: CalldataBlock | undefined; constructor() { this.selector = '0x'; this.sizeInBytes = 0; - this.blocks = []; + this.root = undefined; } public toHexString(): string { - let calldataString = `${this.selector}`; - _.each(this.blocks, (block: CalldataBlock) => { - const blockAsHexString = block.toHexString(); - const blockAsHexWithoutPrefix = ethUtil.stripHexPrefix(blockAsHexString); - calldataString += blockAsHexWithoutPrefix; - }); - return calldataString; + let selectorBuffer = ethUtil.toBuffer(this.selector); + if (this.root === undefined) { + throw new Error('expected root'); + } + + const blockQueue = new Queue(); + blockQueue.push(this.root); + + // Assign locations in breadth-first manner + let block: CalldataBlock | undefined; + let offset = 0; + while ((block = blockQueue.pop()) !== undefined) { + block.setOffset(offset); + if (block instanceof DependentCalldataBlock) { + blockQueue.push(block.getDependency()); + } else if (block instanceof MemberCalldataBlock) { + _.each(block.getMembers(), (member: CalldataBlock) => { + blockQueue.push(member); + }); + } + } + + // Fetch values using same technique + const valueBufs: Buffer[] = [selectorBuffer]; + while ((block = blockQueue.pop()) !== undefined) { + valueBufs.push(block.toBuffer()); + + if (block instanceof DependentCalldataBlock) { + blockQueue.push(block.getDependency()); + } else if (block instanceof MemberCalldataBlock) { + _.each(block.getMembers(), (member: CalldataBlock) => { + blockQueue.push(member); + }); + } + } + + const combinedBuffers = Buffer.concat(valueBufs); + const hexValue = ethUtil.bufferToHex(combinedBuffers); + return hexValue; } public getSelectorHex(): string { @@ -155,8 +215,8 @@ export class Calldata { return ""; } - public pushBlock(block: CalldataBlock) { - this.blocks.push(block); + public setRoot(block: CalldataBlock) { + this.root = block; this.sizeInBytes += block.getSizeInBytes(); } diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index f746210854..1a4610f7c8 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -1,4 +1,4 @@ -import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock } from "./calldata"; +import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata"; import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; import ethUtil = require('ethereumjs-util'); @@ -17,7 +17,7 @@ export abstract class DataType { return this.dataItem; } - protected abstract createCalldataBlock(): CalldataBlock; + public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; public abstract encode(value: any, calldata: Calldata): void; public abstract getSignature(): string; public abstract isStatic(): boolean; @@ -31,19 +31,27 @@ export abstract class PayloadDataType extends DataType { this.hasConstantSize = hasConstantSize; } - protected generateCalldataBlock(payload: Buffer, calldata: Calldata): void { + public generateCalldataBlocks(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { + const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); - const offsetInBytes = calldata.getSizeInBytes(); + // const offsetInBytes = calldata.getSizeInBytes(); const relocatable = false; - const block = new PayloadCalldataBlock(name, signature, offsetInBytes, relocatable, payload); - calldata.pushBlock(block); + const block = new PayloadCalldataBlock(name, signature, /*offsetInBytes,*/ relocatable, encodedValue); + return block; + } + + public encode(value: any, calldata: Calldata): void { + const block = this.generateCalldataBlock(value); + calldata.setRoot(block); } public isStatic(): boolean { // If a payload has a constant size then it's static return this.hasConstantSize; } + + public abstract encodeValue(value: any): Buffer; } export abstract class DependentDataType extends DataType { @@ -56,20 +64,21 @@ export abstract class DependentDataType extends DataType { this.parent = parent; } - protected generateCalldataBlock(offsetInBytes: number, calldata: Calldata): CalldataBlock { + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): DependentCalldataBlock { + if (parentBlock === undefined) { + throw new Error(`DependentDataType requires a parent block to generate its block`); + } + const dependencyBlock = this.dependency.generateCalldataBlock(value); const name = this.getDataItem().name; const signature = this.getSignature(); const relocatable = false; - const parentBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const dependencyBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const block = new DependentCalldataBlock(name, signature, offsetInBytes, relocatable, dependencyBlock, parentBlock); - calldata.pushBlock(block); + const block = new DependentCalldataBlock(name, signature, /*offsetInBytes,*/ relocatable, dependencyBlock, parentBlock); + return block; } public encode(value: any, calldata: Calldata = new Calldata()): void { - const offsetInBytes = calldata.reserveSpace(DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES); - this.dependency.encode(value, calldata); - this.generateCalldataBlock(offsetInBytes, calldata); + const block = this.generateCalldataBlock(value); + calldata.setRoot(block); } public isStatic(): boolean { @@ -96,7 +105,7 @@ export abstract class MemberDataType extends DataType { this.isArray = isArray; this.arrayLength = arrayLength; if (isArray && arrayLength !== undefined) { - [this.members, this.memberMap] = MemberDataType.createMembersWithLength(arrayLength); + [this.members, this.memberMap] = MemberDataType.createMembersWithLength(dataItem, arrayLength); } else if (!isArray) { [this.members, this.memberMap] = MemberDataType.createMembersWithKeys(dataItem); } @@ -129,7 +138,7 @@ export abstract class MemberDataType extends DataType { const range = _.range(length); _.each(range, (idx: number) => { const childDataItem = { - type: this.type, + type: dataItem.type, name: `${dataItem.name}[${idx.toString(10)}]`, } as DataItem; const components = dataItem.components; @@ -144,57 +153,60 @@ export abstract class MemberDataType extends DataType { return [members, memberMap]; } - protected encodeFromArray(value: any[], calldata: Calldata) { + protected generateCalldataBlockFromArray(value: any[]): MemberCalldataBlock { // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + if (this.arrayLength !== undefined && value.length !== this.arrayLength) { throw new Error( `Expected array of ${JSON.stringify( - this.length, - )} elements, but got array of length ${JSON.stringify(valueLength)}`, + this.arrayLength, + )} elements, but got array of length ${JSON.stringify(value.length)}`, ); } - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.members[idxNumber].assignValue(value[idxNumber]); + let members = this.members; + if (this.arrayLength === undefined) { + [members,] = MemberDataType.createMembersWithLength(this.getDataItem(), value.length); } + + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + const memberBlocks: CalldataBlock[] = []; + _.each(members, (member: DataType) => { + const block = member.generateCalldataBlock(value, methodBlock); + memberBlocks.push(block); + }); + methodBlock.setMembers(memberBlocks); + return methodBlock; } - protected encodeFromObject(obj: object, calldata: Calldata) { + protected generateCalldataBlockFromObject(obj: object): MemberCalldataBlock { + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + const memberBlocks: CalldataBlock[] = []; let childMap = _.cloneDeep(this.memberMap); _.forOwn(obj, (value: any, key: string) => { if (key in childMap === false) { throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); } - this.members[this.childMap[key]].assignValue(value); + const block = this.members[this.memberMap[key]].generateCalldataBlock(value, methodBlock); + memberBlocks.push(block); delete childMap[key]; }); if (Object.keys(childMap).length !== 0) { throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); } + + methodBlock.setMembers(memberBlocks); + return methodBlock; } - public encode(value: any[] | object, calldata = new Calldata()) { - if (value instanceof Array) { - this.encodeFromArray(value, calldata); - } else if (typeof value === 'object') { - this.encodeFromObject(value, encodeFromObject); - } else { - throw new Error(`Unexpected type for ${value}`); - } + public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { + const block = (value instanceof Array) ? this.generateCalldataBlockFromArray(value) : this.generateCalldataBlockFromObject(value, calldata); + return block; } - protected generateCalldataBlock(offsetInBytes: number, calldata: Calldata): CalldataBlock { - const name = this.getDataItem().name; - const signature = this.getSignature(); - const relocatable = false; - const parentBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const dependencyBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const block = new DependentCalldataBlock(name, signature, offsetInBytes, relocatable, dependencyBlock, parentBlock); - calldata.pushBlock(block); + public encode(value: any, calldata: Calldata = new Calldata()): void { + const block = this.generateCalldataBlock(value); + calldata.setRoot(block); } protected computeSignatureOfMembers(): string { diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index 4e42a992a3..bdbaf5b3c7 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -4,6 +4,8 @@ import { MethodAbi, DataItem } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); +import { Calldata } from './calldata'; + import { BigNumber } from '@0x/utils'; export interface DataTypeStaticInterface { @@ -29,7 +31,7 @@ export class Address extends PayloadDataType { return type === 'address'; } - public static encodeValue(value: boolean): Buffer { + public encodeValue(value: boolean): Buffer { const evmWordWidth = 32; const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth); return encodedValueBuf; @@ -54,7 +56,7 @@ export class Bool extends PayloadDataType { return type === 'bool'; } - public static encodeValue(value: boolean): Buffer { + public encodeValue(value: boolean): Buffer { const evmWordWidth = 32; const encodedValue = value === true ? '0x1' : '0x0'; const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth); @@ -81,7 +83,7 @@ abstract class Number extends PayloadDataType { } } - public static encodeValue(value: BigNumber): Buffer { + public encodeValue(value: BigNumber): Buffer { if (value.greaterThan(this.getMaxValue())) { throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; } else if (value.lessThan(this.getMinValue())) { @@ -204,7 +206,7 @@ export class Byte extends PayloadDataType { return `bytes${this.width}`; } - public static encodeValue(value: string | Buffer): Buffer { + public encodeValue(value: string | Buffer): Buffer { // Convert value into a buffer and do bounds checking const valueBuf = ethUtil.toBuffer(value); if (valueBuf.byteLength > this.width) { @@ -275,7 +277,7 @@ export class SolString extends PayloadDataType { } } - public static encodeValue(value: string): Buffer { + public encodeValue(value: string): Buffer { const wordsForValue = Math.ceil(value.length / 32); const paddedBytesForValue = wordsForValue * 32; const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); @@ -398,6 +400,11 @@ export class Method extends MemberDataType { return selector; } + public encode(value: any[] | object, calldata = new Calldata()) { + calldata.setSelector(this.methodSelector); + super.encode(value, calldata); + } + public getSignature(): string { return this.methodSignature; } From 1f7fbcf52ccc037c4c4bb49e629eecc83e5309e5 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 18:14:31 -0800 Subject: [PATCH 091/230] Should build? TBD! --- packages/order-utils/test/abi/abi_encoder.ts | 5 ++++- packages/order-utils/test/abi/evm_data_types.ts | 3 ++- packages/order-utils/test/abi_encoder_test.ts | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/order-utils/test/abi/abi_encoder.ts b/packages/order-utils/test/abi/abi_encoder.ts index 12755209aa..ddcfb1fd12 100644 --- a/packages/order-utils/test/abi/abi_encoder.ts +++ b/packages/order-utils/test/abi/abi_encoder.ts @@ -1 +1,4 @@ -export * from './calldata_block'; \ No newline at end of file +export * from './config'; +export * from './calldata'; +export * from './data_type'; +export * from './evm_data_types'; \ No newline at end of file diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index bdbaf5b3c7..15fe4c9e3e 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -400,9 +400,10 @@ export class Method extends MemberDataType { return selector; } - public encode(value: any[] | object, calldata = new Calldata()) { + public encode(value: any[] | object, calldata = new Calldata()): string { calldata.setSelector(this.methodSelector); super.encode(value, calldata); + return calldata.toHexString(); } public getSignature(): string { diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index c8a0642ead..f46a1812cc 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -12,7 +12,10 @@ import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; import { assert } from '@0x/order-utils/src/assert'; -import * as AbiEncoder from './abi_encoder'; +//import * as AbiEncoder from './abi_encoder'; + +import * as AbiEncoder from './abi/abi_encoder'; + import * as AbiSamples from './abi_samples'; chaiSetup.configure(); From 0835cf0ea2bb3c2c18d2a5d44ec914e2945af1b0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 18:18:52 -0800 Subject: [PATCH 092/230] Refactor Builds! --- packages/order-utils/test/abi/data_type.ts | 14 +- .../order-utils/test/abi/evm_data_types.ts | 8 +- packages/order-utils/test/abi_encoder.ts | 1152 ----------------- packages/order-utils/test/abi_encoder_test.ts | 452 +++---- 4 files changed, 240 insertions(+), 1386 deletions(-) delete mode 100644 packages/order-utils/test/abi_encoder.ts diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 1a4610f7c8..201eb89f94 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -31,7 +31,7 @@ export abstract class PayloadDataType extends DataType { this.hasConstantSize = hasConstantSize; } - public generateCalldataBlocks(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); @@ -105,13 +105,13 @@ export abstract class MemberDataType extends DataType { this.isArray = isArray; this.arrayLength = arrayLength; if (isArray && arrayLength !== undefined) { - [this.members, this.memberMap] = MemberDataType.createMembersWithLength(dataItem, arrayLength); + [this.members, this.memberMap] = this.createMembersWithLength(dataItem, arrayLength); } else if (!isArray) { - [this.members, this.memberMap] = MemberDataType.createMembersWithKeys(dataItem); + [this.members, this.memberMap] = this.createMembersWithKeys(dataItem); } } - private static createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { + private createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { // Sanity check if (dataItem.components === undefined) { throw new Error(`Expected components`); @@ -132,7 +132,7 @@ export abstract class MemberDataType extends DataType { return [members, memberMap]; } - private static createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { + private createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { let members: DataType[] = []; let memberMap: MemberMap = {}; const range = _.range(length); @@ -165,7 +165,7 @@ export abstract class MemberDataType extends DataType { let members = this.members; if (this.arrayLength === undefined) { - [members,] = MemberDataType.createMembersWithLength(this.getDataItem(), value.length); + [members,] = this.createMembersWithLength(this.getDataItem(), value.length); } const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); @@ -200,7 +200,7 @@ export abstract class MemberDataType extends DataType { } public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { - const block = (value instanceof Array) ? this.generateCalldataBlockFromArray(value) : this.generateCalldataBlockFromObject(value, calldata); + const block = (value instanceof Array) ? this.generateCalldataBlockFromArray(value) : this.generateCalldataBlockFromObject(value); return block; } diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index 15fe4c9e3e..4dffedb8d6 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -8,6 +8,8 @@ import { Calldata } from './calldata'; import { BigNumber } from '@0x/utils'; +var _ = require('lodash'); + export interface DataTypeStaticInterface { matchGrammar: (type: string) => boolean; encodeValue: (value: any) => Buffer; @@ -382,10 +384,14 @@ export class Method extends MemberDataType { private methodSignature: string; private methodSelector: string; + // TMP + public selector: string; + constructor(abi: MethodAbi) { super({ type: 'method', name: abi.name }); this.methodSignature = this.computeSignature(); - this.methodSelector = this.computeSelector(); + this.selector = this.methodSelector = this.computeSelector(); + } private computeSignature(): string { diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts deleted file mode 100644 index bcd427deeb..0000000000 --- a/packages/order-utils/test/abi_encoder.ts +++ /dev/null @@ -1,1152 +0,0 @@ - -import * as chai from 'chai'; -import 'mocha'; -import ethUtil = require('ethereumjs-util'); - -var _ = require('lodash'); - -import { chaiSetup } from './utils/chai_setup'; - -import { MethodAbi, DataItem } from 'ethereum-types'; - -import { BigNumber } from '@0x/utils'; -import { bigNumberify } from 'ethers/utils'; - -chaiSetup.configure(); -const expect = chai.expect; - - -class Word { - private value: string; - - constructor(value?: string) { - if (value === undefined) { - this.value = ''; - } else { - this.value = value; - } - } - - public set(value: string) { - if (value.length !== 64) { - throw `Tried to create word that is not 32 bytes: ${value}`; - } - - this.value = value; - } - - public get(): string { - return this.value; - } - - public getAsHex(): string { - return `0x${this.value}`; - } -} - -export enum CalldataSection { - NONE, - PARAMS, - DATA, -} - -class Memblock { - private dataType: DataType; - private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber }; - - constructor(dataType: DataType) { - this.dataType = dataType; - this.location = { - calldataSection: CalldataSection.NONE, - sectionOffset: new BigNumber(0), - offset: new BigNumber(0), - }; - } - - public getSize(): BigNumber { - return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength); - } - - public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) { - this.location.calldataSection = calldataSection; - this.location.sectionOffset = sectionOffset; - this.location.offset = offset; - } - - public get(): string { - console.log(`Unstripped = '${this.dataType.getHexValue()}' and Stripped = '${ethUtil.stripHexPrefix(this.dataType.getHexValue())}'`); - return ethUtil.stripHexPrefix(this.dataType.getHexValue()); - } - - public getOffset(): BigNumber { - return this.location.offset; - } - - public getAbsoluteOffset(): BigNumber { - return this.location.sectionOffset.plus(this.location.offset); - } - - public getSection(): CalldataSection { - return this.location.calldataSection; - } - - public getName(): string { - return this.dataType.getDataItem().name; - } -} - -interface BindList { - [key: string]: Memblock; -} - -export class Calldata { - private selector: string; - private params: Memblock[]; - private data: Memblock[]; - private dataOffset: BigNumber; - private currentDataOffset: BigNumber; - private currentParamOffset: BigNumber; - private bindList: BindList; - - constructor(selector: string, nParams: number) { - this.selector = selector; - this.params = []; - this.data = []; - const evmWordSize = 32; - this.dataOffset = new BigNumber(nParams).times(evmWordSize); - this.currentDataOffset = new BigNumber(0); - this.currentParamOffset = new BigNumber(0); - this.bindList = {}; - } - - public bind(dataType: DataType, section: CalldataSection) { - if (dataType.getId() in this.bindList) { - throw `Rebind on ${dataType.getId()}`; - } - const memblock = new Memblock(dataType); - - this.params.push(memblock); - memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); - - console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); - this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); - console.log("CURRENT PARAM OFFSET -------- 0x", this.currentParamOffset.toString(16)); - - /* - switch (section) { - case CalldataSection.PARAMS: - ; - break; - - case CalldataSection.DATA: - this.data.push(memblock); - memblock.assignLocation(section, this.dataOffset, this.currentDataOffset); - - console.log( - `Binding ${dataType.getDataItem().name} to DATA at ${memblock - .getAbsoluteOffset() - .toString(16)}`, - ); - this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); - break; - - default: - throw `Unrecognized calldata section: ${section}`; - }*/ - - this.bindList[dataType.getId()] = memblock; - dataType.rbind(memblock); - } - - public getHexValue(): string { - let hexValue = `${this.selector}`; - _.each(this.params, (memblock: Memblock) => { - hexValue += memblock.get(); - }); - _.each(this.data, (memblock: Memblock) => { - hexValue += memblock.get(); - }); - - return hexValue; - } - - public printAnnotated() { - let hexValue = `${this.selector}`; - console.log(hexValue); - let offset = new BigNumber(0); - _.each(this.params, (memblock: Memblock) => { - const offsetStr = `0x${offset.toString(16)}`; - const hexValue = memblock.get(); - const annotation = memblock.getName(); - - console.log(`${offsetStr} ${hexValue} ${annotation}`); - }); - _.each(this.data, (memblock: Memblock) => { - const offsetStr = `0x${offset.toString(16)}`; - const hexValue = memblock.get(); - const annotation = memblock.getName(); - - console.log(`${offsetStr} ${hexValue} ${annotation}`); - }); - } -} - -export abstract class DataType { - private dataItem: DataItem; - private hexValue: string; - protected memblock: Memblock | undefined; - protected children: DataType[]; - - constructor(dataItem: DataItem) { - this.dataItem = dataItem; - this.hexValue = '0x'; - this.memblock = undefined; - this.children = []; - } - - protected assignHexValue(hexValue: string) { - this.hexValue = hexValue; - } - - public getHexValue(): string { - return this.hexValue; - } - - public getDataItem(): DataItem { - return this.dataItem; - } - - public rbind(memblock: Memblock) { - this.memblock = memblock; - } - - public bind(calldata: Calldata, section: CalldataSection) { - calldata.bind(this, section); - _.each(this.getChildren(), (child: DataType) => { - child.bind(calldata, CalldataSection.DATA); - }); - } - - public getId(): string { - return this.dataItem.name; - } - - public getOffset(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getOffset(); - } - - public getAbsoluteOffset(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getAbsoluteOffset(); - } - /* - public getSize(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getSize(); - } - */ - - public getChildren(): DataType[] { - return this.children; - } - - public getSize(): BigNumber { - return this.getHeaderSize().plus(this.getBodySize()); - } - - public abstract assignValue(value: any): void; - public abstract getSignature(): string; - public abstract isStatic(): boolean; - public abstract getHeaderSize(): BigNumber; - public abstract getBodySize(): BigNumber; -} - -export abstract class StaticDataType extends DataType { - constructor(dataItem: DataItem) { - super(dataItem); - } -} - -export abstract class DynamicDataType extends DataType { - constructor(dataItem: DataItem) { - super(dataItem); - } -} - -export class Address extends StaticDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(Address.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - const evmWordWidth = 32; - const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth)); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - return `address`; - } - - public isStatic(): boolean { - return true; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - return new BigNumber(32); - } - - public static matchGrammar(type: string): boolean { - return type === 'address'; - } -} - -export class Bool extends StaticDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(Bool.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: boolean) { - const evmWordWidth = 32; - const encodedValue = value === true ? '0x1' : '0x0'; - const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth)); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - return 'bool'; - } - - public isStatic(): boolean { - return true; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - return new BigNumber(32); - } - - public static matchGrammar(type: string): boolean { - return type === 'bool'; - } -} - -abstract class Number extends StaticDataType { - static MAX_WIDTH: number = 256; - static DEFAULT_WIDTH: number = Number.MAX_WIDTH; - width: number = Number.DEFAULT_WIDTH; - - constructor(dataItem: DataItem, matcher: RegExp) { - super(dataItem); - const matches = matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 2 && matches[1] !== undefined) { - this.width = parseInt(matches[1]); - } else { - this.width = 256; - } - } - - public assignValue(value: BigNumber) { - if (value.greaterThan(this.getMaxValue())) { - throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; - } else if (value.lessThan(this.getMinValue())) { - throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; - } - - const hexBase = 16; - const evmWordWidth = 32; - let valueBuf: Buffer; - if (value.greaterThanOrEqualTo(0)) { - valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); - } else { - // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. - // Step 1/3: Convert value to positive binary string - const binBase = 2; - const valueBin = value.times(-1).toString(binBase); - - // Step 2/3: Invert binary value - const bitsInEvmWord = 256; - let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length); - _.each(valueBin, (bit: string) => { - invertedValueBin += bit === '1' ? '0' : '1'; - }); - const invertedValue = new BigNumber(invertedValueBin, binBase); - - // Step 3/3: Add 1 to inverted value - // The result is the two's-complement represent of the input value. - const negativeValue = invertedValue.plus(1); - - // Convert the negated value to a hex string - valueBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), - evmWordWidth, - ); - } - - const encodedValue = ethUtil.bufferToHex(valueBuf); - this.assignHexValue(encodedValue); - } - - public isStatic(): boolean { - return true; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - return new BigNumber(32); - } - - public abstract getMaxValue(): BigNumber; - public abstract getMinValue(): BigNumber; -} - -export class Int extends Number { - static matcher = RegExp( - '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', - ); - - constructor(dataItem: DataItem) { - super(dataItem, Int.matcher); - } - - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).sub(1); - } - - public getMinValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).times(-1); - } - - public getSignature(): string { - return `int${this.width}`; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } -} - -export class UInt extends Number { - static matcher = RegExp( - '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', - ); - - constructor(dataItem: DataItem) { - super(dataItem, UInt.matcher); - } - - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width).sub(1); - } - - public getMinValue(): BigNumber { - return new BigNumber(0); - } - - public getSignature(): string { - return `uint${this.width}`; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } -} - -export class Byte extends StaticDataType { - static matcher = RegExp( - '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', - ); - - static DEFAULT_WIDTH = 1; - width: number = Byte.DEFAULT_WIDTH; - - constructor(dataItem: DataItem) { - super(dataItem); - const matches = Byte.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 3 && matches[2] !== undefined) { - this.width = parseInt(matches[2]); - } else { - this.width = Byte.DEFAULT_WIDTH; - } - } - - public assignValue(value: string | Buffer) { - // Convert value into a buffer and do bounds checking - const valueBuf = ethUtil.toBuffer(value); - if (valueBuf.byteLength > this.width) { - throw new Error( - `Tried to assign ${value} (${ - valueBuf.byteLength - } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, - ); - } else if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - - // Store value as hex - const evmWordWidth = 32; - const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); - const hexValue = ethUtil.bufferToHex(paddedValue); - - this.assignHexValue(hexValue); - } - - public getSignature(): string { - // Note that `byte` reduces to `bytes1` - return `bytes${this.width}`; - } - - public isStatic(): boolean { - return true; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - return new BigNumber(32); - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } -} - -export class Bytes extends DynamicDataType { - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = Bytes.UNDEFINED_LENGTH; - - constructor(dataItem: DataItem) { - super(dataItem); - expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string | Buffer) { - if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`); - } - const valueBuf = ethUtil.toBuffer(value); - if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - - const wordsForValue = Math.ceil(valueBuf.byteLength / 32); - const paddedBytesForValue = wordsForValue * 32; - const paddedValueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); - const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); - const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); - const encodedValue = ethUtil.bufferToHex(encodedValueBuf); - - this.assignHexValue(encodedValue); - } - - public getSignature(): string { - return 'bytes'; - } - - public isStatic(): boolean { - return false; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - const valueBuf = ethUtil.toBuffer(this.getHexValue()); - const size = new BigNumber(valueBuf.byteLength); - return size; - } - - public static matchGrammar(type: string): boolean { - return type === 'bytes'; - } -} - -export class SolString extends DynamicDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(SolString.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - const wordsForValue = Math.ceil(value.length / 32); - const paddedBytesForValue = wordsForValue * 32; - const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); - const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); - const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); - const encodedValue = ethUtil.bufferToHex(encodedValueBuf); - - this.assignHexValue(encodedValue); - } - - public getSignature(): string { - return 'string'; - } - - public isStatic(): boolean { - return false; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - const valueBuf = ethUtil.toBuffer(this.getHexValue()); - const size = new BigNumber(valueBuf.byteLength); - return size; - } - - public static matchGrammar(type: string): boolean { - return type === 'string'; - } -} - -export class SolArray extends DynamicDataType { - static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = SolArray.UNDEFINED_LENGTH; - type: string = '[undefined]'; - isLengthDefined: boolean; - isStaticArray: boolean; // An array is dynamic if it's lenghth is undefined or if its children are dynamic. - private elements: DataType[]; - - /* - --- Layout 1: Fixed Length Array with Static Types --- - Elem1, Elem2, ..., ElemN - - --- Layout 2: Fixed Length Array with Dynamic Types --- - PtrToArray, ..., Elem1, Elem2, ..., ElemN - - --- Layout 3: Dynamic Length Array with Static Types --- - PtrToArray, ..., ArrayLength, Elem1, Elem2, ..., ElemN - - --- Layout 4: Dynamic Length Array with Dynamic Types --- - PtrToArray, ..., ArrayLength, PtrToElem1, PtrToElem2, ..., PtrToElemN, ..., Elem1, Elem2, ..., ElemN - */ - - constructor(dataItem: DataItem) { - super(dataItem); - const matches = SolArray.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - console.log(JSON.stringify(matches)); - if (matches === null || matches.length !== 3) { - throw new Error(`Could not parse array: ${dataItem.type}`); - } else if (matches[1] === undefined) { - throw new Error(`Could not parse array type: ${dataItem.type}`); - } else if (matches[2] === undefined) { - throw new Error(`Could not parse array length: ${dataItem.type}`); - } - - this.elements = []; - - // Check if length is undefined - if (matches[2] === '') { - this.type = matches[1]; - this.length = SolArray.UNDEFINED_LENGTH; - this.isLengthDefined = false; - this.isStaticArray = false; - return; - } - - // Parse out array type/length and construct children - this.isLengthDefined = true; - this.type = matches[1]; - this.length = new BigNumber(matches[2], 10); - if (this.length.lessThan(1)) { - throw new Error(`Bad array length: ${JSON.stringify(this.length)}`); - } - this.constructChildren(); - - // Check if we're static or not - this.isStaticArray = !(this.elements[0] instanceof Pointer); //this.elements[0].isStatic(); - //throw new Error(`Am I static? ${this.isStaticArray}`); - } - - private constructChildren() { - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const childDataItem = { - type: this.type, - name: `${this.getDataItem().name}[${idx.toString(10)}]`, - } as DataItem; - const components = this.getDataItem().components; - if (components !== undefined) { - childDataItem.components = components; - } - const child = DataTypeFactory.create(childDataItem, this); - this.elements.push(child); - if (child instanceof Pointer) { - const pointsTo = child.getChildren()[0]; - this.children.push(pointsTo); // DataType pointing to - } - } - } - - // @TODO: HACKY -- shouldn't really have children for a - /* - public getChildren(): DataType[] { - if (this.isStatic()) { - return []; - } else { - return this.children; - } - }*/ - - public assignValue(value: any[]) { - console.log('GREG'.repeat(15), JSON.stringify(value)); - - - // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { - throw new Error( - `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( - valueLength, - )}`, - ); - } - - // Assign length if not already set - if (this.length === SolArray.UNDEFINED_LENGTH) { - this.length = valueLength; - this.constructChildren(); - } - - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.elements[idxNumber].assignValue(value[idxNumber]); - } - } - - public getHexValue(): string { - let valueBuf = new Buffer(""); - - if (this.isLengthDefined === false) { - // Must include the array length - const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); - const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); - valueBuf = lengthBuf; - } - - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - const childValueHex = this.elements[idxNumber].getHexValue(); - const childValueBuf = ethUtil.toBuffer(childValueHex); - valueBuf = Buffer.concat([valueBuf, childValueBuf]); - } - - // Convert value buffer to hex - const valueHex = ethUtil.bufferToHex(valueBuf); - return valueHex; - } - - public isStatic(): boolean { - return this.isStaticArray; - } - - public getHeaderSize(): BigNumber { - let size = new BigNumber(0); - if (!this.isLengthDefined) { - size = new BigNumber(32); // stores length of bytes - } - return size; - } - - public getBodySize(): BigNumber { - const evmWordWidth = new BigNumber(32); - const body = this.length.times(evmWordWidth); - return body; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - - public getSignature(): string { - let dataItem = { - type: this.type, - name: 'N/A', - } as DataItem; - const components = this.getDataItem().components; - if (components !== undefined) { - dataItem.components = components; - } - const elementDataType = DataTypeFactory.mapDataItemToDataType(dataItem); - const type = elementDataType.getSignature(); - if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { - return `${type}[]`; - } - return `${type}[${this.length}]`; - } -} - -export class Tuple extends DynamicDataType { - private length: BigNumber; - private childMap: { [key: string]: number }; - private members: DataType[]; - - constructor(dataItem: DataItem) { - super(dataItem); - expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); - this.length = new BigNumber(0); - this.childMap = {}; - this.members = []; - if (dataItem.components !== undefined) { - this.constructChildren(dataItem.components); - this.length = new BigNumber(dataItem.components.length); - } else { - throw new Error('Components undefined'); - } - } - - private constructChildren(dataItems: DataItem[]) { - _.each(dataItems, (dataItem: DataItem) => { - const childDataItem = { - type: dataItem.type, - name: `${this.getDataItem().name}.${dataItem.name}`, - } as DataItem; - const child = DataTypeFactory.create(childDataItem, this); - this.childMap[dataItem.name] = this.members.length; - - if (child instanceof Pointer) { - this.children.push(child.getChildren()[0]); - } - this.members.push(child); - }); - } - - private assignValueFromArray(value: any[]) { - // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { - throw new Error( - `Expected array of ${JSON.stringify( - this.length, - )} elements, but got array of length ${JSON.stringify(valueLength)}`, - ); - } - - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.members[idxNumber].assignValue(value[idxNumber]); - } - } - - private assignValueFromObject(obj: object) { - let childMap = _.cloneDeep(this.childMap); - _.forOwn(obj, (value: any, key: string) => { - if (key in childMap === false) { - throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); - } - this.members[this.childMap[key]].assignValue(value); - delete childMap[key]; - }); - - if (Object.keys(childMap).length !== 0) { - throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); - } - } - - public assignValue(value: any[] | object) { - if (value instanceof Array) { - this.assignValueFromArray(value); - } else if (typeof value === 'object') { - this.assignValueFromObject(value); - } else { - throw new Error(`Unexpected type for ${value}`); - } - } - - public getHexValue(): string { - let paramBufs: Buffer[] = []; - _.each(this.members, (member: DataType) => { - paramBufs.push(ethUtil.toBuffer(member.getHexValue())); - }); - - const value = Buffer.concat(paramBufs); - const hexValue = ethUtil.bufferToHex(value); - return hexValue; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - const evmWordWidth = new BigNumber(32); - const size = this.length.times(evmWordWidth); - return size; - } - - public getSignature(): string { - // Compute signature - let signature = `(`; - _.each(this.members, (member: DataType, i: number) => { - signature += member.getSignature(); - if (i < this.members.length - 1) { - signature += ','; - } - }); - signature += ')'; - return signature; - } - - public isStatic(): boolean { - const isStaticTuple = this.children.length === 0; - return isStaticTuple; // @TODO: True in every case or only when dynamic data? - } - - public static matchGrammar(type: string): boolean { - return type === 'tuple'; - } -} - -/* TODO -class Fixed extends StaticDataType {} - -class UFixed extends StaticDataType {}*/ - -export class Pointer extends StaticDataType { - destDataType: DynamicDataType; - parentDataType: DataType; - - constructor(destDataType: DynamicDataType, parentDataType: DataType) { - const destDataItem = destDataType.getDataItem(); - const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; - super(dataItem); - this.destDataType = destDataType; - this.parentDataType = parentDataType; - this.children.push(destDataType); - } - - /* - public assignValue(destDataType: DynamicDataType) { - this.destDataType = destDataType; - }*/ - - public assignValue(value: any) { - this.destDataType.assignValue(value); - } - - public getHexValue(): string { - console.log( - '*'.repeat(40), - this.destDataType.getAbsoluteOffset().toString(16), - '^'.repeat(150), - this.parentDataType.getAbsoluteOffset().toString(16), - ); - - let offset = this.destDataType - .getAbsoluteOffset() - .minus(this.parentDataType.getAbsoluteOffset()) - .minus(this.parentDataType.getHeaderSize()); - - console.log("OFFSET == ", JSON.stringify(offset), " or in hex -- 0x", offset.toString(16)); - - const hexBase = 16; - const evmWordWidth = 32; - const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth); - const encodedValue = ethUtil.bufferToHex(valueBuf); - return encodedValue; - } - - public getSignature(): string { - return this.destDataType.getSignature(); - } - - public isStatic(): boolean { - return true; - } - - public getHeaderSize(): BigNumber { - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - return new BigNumber(32); - } - -} - -export class DataTypeFactory { - public static mapDataItemToDataType(dataItem: DataItem): DataType { - console.log(`Type: ${dataItem.type}`); - - if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); - if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); - if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); - if (Int.matchGrammar(dataItem.type)) return new Int(dataItem); - if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem); - if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem); - if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem); - if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); - if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem); - if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem); - //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem); - //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem); - - throw new Error(`Unrecognized data type: '${dataItem.type}'`); - } - - public static create(dataItem: DataItem, parentDataType: DataType): DataType { - const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); - if (dataType.isStatic()) { - return dataType; - } else { - const pointer = new Pointer(dataType, parentDataType); - return pointer; - } - - throw new Error(`Unrecognized instance type: '${dataType}'`); - } -} - -class Queue { - private store: T[] = []; - push(val: T) { - this.store.push(val); - } - pop(): T | undefined { - return this.store.shift(); - } -} - -export class Method extends DataType { - name: string; - params: DataType[]; - private signature: string; - selector: string; - - constructor(abi: MethodAbi) { - super({ type: 'method', name: abi.name }); - this.name = abi.name; - this.params = []; - - _.each(abi.inputs, (input: DataItem) => { - this.params.push(DataTypeFactory.create(input, this)); - }); - - // Compute signature - this.signature = `${this.name}(`; - _.each(this.params, (param: DataType, i: number) => { - this.signature += param.getSignature(); - if (i < this.params.length - 1) { - this.signature += ','; - } - }); - this.signature += ')'; - - // Compute selector - this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4))); - - console.log(`--SIGNATURE--\n${this.signature}\n---------\n`); - console.log(`--SELECTOR--\n${this.selector}\n---------\n`); - } - - public getSignature(): string { - return this.signature; - } - - public assignValue(args: any[]) { - _.each(this.params, (param: DataType, i: number) => { - // Assign value to parameter - try { - param.assignValue(args[i]); - } catch (e) { - console.log('Failed to assign to ', param.getDataItem().name); - throw e; - } - - if (param instanceof Pointer) { - this.children.push(param.getChildren()[0]); - } - }); - } - - public getHexValue(): string { - let paramBufs: Buffer[] = []; - _.each(this.params, (param: DataType) => { - paramBufs.push(ethUtil.toBuffer(param.getHexValue())); - }); - - const value = Buffer.concat(paramBufs); - const hexValue = ethUtil.bufferToHex(value); - return hexValue; - } - - public encode(args: any[]): string { - this.assignValue(args); - const calldata = new Calldata(this.selector, this.params.length); - calldata.printAnnotated(); - this.bind(calldata, CalldataSection.PARAMS); - - return calldata.getHexValue(); - } - - public isStatic(): boolean { - return true; - } - - public getHeaderSize(): BigNumber { - // Exclude selector - return new BigNumber(0); - } - - public getBodySize(): BigNumber { - const nParams = new BigNumber(this.params.length); - const evmWordWidth = new BigNumber(32); - const size = nParams.times(evmWordWidth); - return size; - } - - /* - encodeOptimized(args: any[]): string { - const calldata = new Memory(); - // Assign values - optimizableParams : StaticDataType = []; - _.each(this.params, function(args: any[], i: number, param: DataType) { - param.assignValue(args[i]); - if (param instanceof DynamicDataType) { - - } - }); - - // Find non-parameter leaves - - - return ''; - } */ - - /* - decode(rawCalldata: string): any[] { - const calldata = new Calldata(this.name, this.params.length); - calldata.assignRaw(rawCalldata); - let args: any[]; - let params = this.params; - _.each(params, function(args: any[], i: number, param: DataType) { - param.decodeFromCalldata(calldata); - args.push(param.getValue()); - }); - - return args; - }*/ -} \ No newline at end of file diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index f46a1812cc..d75c9dbf19 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -410,237 +410,237 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); }); - - describe('Array', () => { - it('sample', async () => { - const testDataItem = { name: 'testArray', type: 'int[2]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue([new BigNumber(5), new BigNumber(6)]); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); + /* + describe('Array', () => { + it('sample', async () => { + const testDataItem = { name: 'testArray', type: 'int[2]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue([new BigNumber(5), new BigNumber(6)]); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + }); + + it('sample undefined size', async () => { + const testDataItem = { name: 'testArray', type: 'int[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue([new BigNumber(5), new BigNumber(6)]); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + }); + + it('sample dynamic types', async () => { + const testDataItem = { name: 'testArray', type: 'string[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue(['five', 'six', 'seven']); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + const calldata = new AbiEncoder.Calldata('0x01020304', 1); + dataType.bind(calldata, AbiEncoder.CalldataSection.PARAMS); + console.log('*'.repeat(60)); + console.log(calldata.getHexValue()); + }); }); - - it('sample undefined size', async () => { - const testDataItem = { name: 'testArray', type: 'int[]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue([new BigNumber(5), new BigNumber(6)]); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); + + describe('Address', () => { + const testAddressDataItem = { name: 'testAddress', type: 'address' }; + it('Valid Address', async () => { + const addressDataType = new AbiEncoder.Address(testAddressDataItem); + addressDataType.assignValue('0xe41d2489571d322189246dafa5ebde1f4699f498'); + const expectedAbiEncodedAddress = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; + + console.log(addressDataType.getHexValue()); + console.log(expectedAbiEncodedAddress); + expect(addressDataType.getHexValue()).to.be.equal(expectedAbiEncodedAddress); + }); }); - - it('sample dynamic types', async () => { - const testDataItem = { name: 'testArray', type: 'string[]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue(['five', 'six', 'seven']); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); - const calldata = new AbiEncoder.Calldata('0x01020304', 1); - dataType.bind(calldata, AbiEncoder.CalldataSection.PARAMS); - console.log('*'.repeat(60)); - console.log(calldata.getHexValue()); + + describe('Bool', () => { + const testBoolDataItem = { name: 'testBool', type: 'bool' }; + it('True', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(true); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); + + it('False', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(false); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); }); - }); - - describe('Address', () => { - const testAddressDataItem = { name: 'testAddress', type: 'address' }; - it('Valid Address', async () => { - const addressDataType = new AbiEncoder.Address(testAddressDataItem); - addressDataType.assignValue('0xe41d2489571d322189246dafa5ebde1f4699f498'); - const expectedAbiEncodedAddress = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; - - console.log(addressDataType.getHexValue()); - console.log(expectedAbiEncodedAddress); - expect(addressDataType.getHexValue()).to.be.equal(expectedAbiEncodedAddress); + + describe('Integer', () => { + const testIntDataItem = { name: 'testInt', type: 'int' }; + it('Positive - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Positive', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-1)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-437829473)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + // TODO: Add bounds tests + tests for different widths }); - }); - - describe('Bool', () => { - const testBoolDataItem = { name: 'testBool', type: 'bool' }; - it('True', async () => { - const boolDataType = new AbiEncoder.Bool(testBoolDataItem); - boolDataType.assignValue(true); - const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + + describe('Unsigned Integer', () => { + const testIntDataItem = { name: 'testUInt', type: 'uint' }; + it('Lower Bound', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(0)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Base Case', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Random value', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedUInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + // TODO: Add bounds tests + tests for different widths }); - - it('False', async () => { - const boolDataType = new AbiEncoder.Bool(testBoolDataItem); - boolDataType.assignValue(false); - const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + + describe('Static Bytes', () => { + it('Byte (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Byte (no padding)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect(byteDataType.assignValue('0x5')).to.throw(); + }); + + it('Bytes1', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes1' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x0001020304050607080911121314151617181920212223242526272829303132'); + const expectedAbiEncodedByte = '0x0001020304050607080911121314151617181920212223242526272829303132'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (unpadded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x1a18bf61'); + const expectedAbiEncodedByte = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Bytes32 - Too long', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect( + byteDataType.assignValue('0x000102030405060708091112131415161718192021222324252627282930313233'), + ).to.throw( + `Tried to assign 0x000102030405060708091112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32`, + ); + }); }); - }); - - describe('Integer', () => { - const testIntDataItem = { name: 'testInt', type: 'int' }; - it('Positive - Base case', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(1)); - const expectedAbiEncodedInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + + describe('Bytes (Dynamic)', () => { + const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; + it('Less than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + bytesDataType.assignValue('0x010203'); + const expectedAbiEncodedBytes = + '0x00000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000'; + + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + it('Greater than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + const testValue = '0x' + '61'.repeat(40); + bytesDataType.assignValue(testValue); + const expectedAbiEncodedBytes = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + // @TODO: Add test for throw on half-byte + // @TODO: Test with no 0x prefix + // @TODO: Test with Buffer as input }); - - it('Positive', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(437829473)); - const expectedAbiEncodedInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); - }); - - it('Negative - Base case', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(-1)); - const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); - }); - - it('Negative', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(-437829473)); - const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); - }); - - // TODO: Add bounds tests + tests for different widths - }); - - describe('Unsigned Integer', () => { - const testIntDataItem = { name: 'testUInt', type: 'uint' }; - it('Lower Bound', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(0)); - const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); - }); - - it('Base Case', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(1)); - const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); - }); - - it('Random value', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(437829473)); - const expectedAbiEncodedUInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); - }); - - // TODO: Add bounds tests + tests for different widths - }); - - describe('Static Bytes', () => { - it('Byte (padded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x05'); - const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it.skip('Byte (no padding)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - - // @TODO: This does not catch the Error - expect(byteDataType.assignValue('0x5')).to.throw(); - }); - - it('Bytes1', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes1' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x05'); - const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it('Bytes32 (padded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x0001020304050607080911121314151617181920212223242526272829303132'); - const expectedAbiEncodedByte = '0x0001020304050607080911121314151617181920212223242526272829303132'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it('Bytes32 (unpadded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x1a18bf61'); - const expectedAbiEncodedByte = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it.skip('Bytes32 - Too long', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - - // @TODO: This does not catch the Error - expect( - byteDataType.assignValue('0x000102030405060708091112131415161718192021222324252627282930313233'), - ).to.throw( - `Tried to assign 0x000102030405060708091112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32`, - ); - }); - }); - - describe('Bytes (Dynamic)', () => { - const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; - it('Less than 32 bytes', async () => { - const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); - bytesDataType.assignValue('0x010203'); - const expectedAbiEncodedBytes = - '0x00000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000'; - - expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); - }); - - it('Greater than 32 bytes', async () => { - const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); - const testValue = '0x' + '61'.repeat(40); - bytesDataType.assignValue(testValue); - const expectedAbiEncodedBytes = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); - }); - - // @TODO: Add test for throw on half-byte - // @TODO: Test with no 0x prefix - // @TODO: Test with Buffer as input - }); - - describe('String', () => { - const testStringDataItem = { name: 'testString', type: 'string' }; - it('Less than 32 bytes', async () => { - const stringDataType = new AbiEncoder.SolString(testStringDataItem); - stringDataType.assignValue('five'); - const expectedAbiEncodedString = - '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - - console.log(stringDataType.getHexValue()); - console.log(expectedAbiEncodedString); - expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); - }); - - it('Greater than 32 bytes', async () => { - const stringDataType = new AbiEncoder.SolString(testStringDataItem); - const testValue = 'a'.repeat(40); - stringDataType.assignValue(testValue); - const expectedAbiEncodedString = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); - }); - }); + + describe('String', () => { + const testStringDataItem = { name: 'testString', type: 'string' }; + it('Less than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + stringDataType.assignValue('five'); + const expectedAbiEncodedString = + '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + console.log(stringDataType.getHexValue()); + console.log(expectedAbiEncodedString); + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + + it('Greater than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + const testValue = 'a'.repeat(40); + stringDataType.assignValue(testValue); + const expectedAbiEncodedString = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + });*/ }); From 41e01e98064b129f588d72ed25267f4865c58f5c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 18:59:27 -0800 Subject: [PATCH 093/230] making progress on basic example --- packages/order-utils/test/abi/data_type.ts | 15 +++++++++------ packages/order-utils/test/abi/evm_data_types.ts | 7 ++++--- packages/order-utils/test/abi_encoder_test.ts | 10 +++++++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 201eb89f94..c1e3525087 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -95,15 +95,17 @@ export abstract class MemberDataType extends DataType { private members: DataType[]; private isArray: boolean; protected arrayLength: number | undefined; + protected arrayElementType: string | undefined; - public constructor(dataItem: DataItem, isArray: boolean = false, arrayLength?: number) { + public constructor(dataItem: DataItem, isArray: boolean = false, arrayLength?: number, arrayElementType?: string) { super(dataItem); this.memberMap = {}; this.members = []; this.isArray = isArray; this.arrayLength = arrayLength; + this.arrayElementType = arrayElementType; if (isArray && arrayLength !== undefined) { [this.members, this.memberMap] = this.createMembersWithLength(dataItem, arrayLength); } else if (!isArray) { @@ -119,10 +121,10 @@ export abstract class MemberDataType extends DataType { let members: DataType[] = []; let memberMap: MemberMap = {}; - _.each(dataItem.components, (dataItem: DataItem) => { + _.each(dataItem.components, (memberItem: DataItem) => { const childDataItem = { - type: dataItem.type, - name: `${dataItem.name}.${dataItem.name}`, + type: memberItem.type, + name: `${dataItem.name}.${memberItem.name}`, } as DataItem; const child = DataTypeFactory.create(childDataItem, this); members.push(child); @@ -133,12 +135,13 @@ export abstract class MemberDataType extends DataType { } private createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { + console.log(dataItem); let members: DataType[] = []; let memberMap: MemberMap = {}; const range = _.range(length); _.each(range, (idx: number) => { const childDataItem = { - type: dataItem.type, + type: this.arrayElementType, name: `${dataItem.name}[${idx.toString(10)}]`, } as DataItem; const components = dataItem.components; @@ -164,7 +167,7 @@ export abstract class MemberDataType extends DataType { } let members = this.members; - if (this.arrayLength === undefined) { + if (this.isArray && this.arrayLength === undefined) { [members,] = this.createMembersWithLength(this.getDataItem(), value.length); } diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index 4dffedb8d6..1bd4f0d51d 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -347,9 +347,10 @@ export class SolArray extends MemberDataType { } const isArray = true; + const arrayElementType = matches[1]; const arrayLength = (matches[2] === '') ? undefined : parseInt(matches[2], 10); - super(dataItem, isArray, arrayLength); - this.elementType = matches[1]; + super(dataItem, isArray, arrayLength, arrayElementType); + this.elementType = arrayElementType; this.arraySignature = this.computeSignature(); } @@ -388,7 +389,7 @@ export class Method extends MemberDataType { public selector: string; constructor(abi: MethodAbi) { - super({ type: 'method', name: abi.name }); + super({ type: 'method', name: abi.name, components: abi.inputs }); this.methodSignature = this.computeSignature(); this.selector = this.methodSelector = this.computeSelector(); diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index d75c9dbf19..6655af1ffd 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -18,13 +18,15 @@ import * as AbiEncoder from './abi/abi_encoder'; import * as AbiSamples from './abi_samples'; +AbiEncoder.DataTypeFactory.setImpl(new AbiEncoder.EvmDataTypeFactoryImpl()); + chaiSetup.configure(); const expect = chai.expect; describe.only('ABI Encoder', () => { describe.only('ABI Tests at Method Level', () => { - it('Crazy ABI', async () => { + it.skip('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); @@ -89,8 +91,9 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it('Crazy ABI #1', async () => { + it.skip('Crazy ABI #1', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi1); + const args = [ new BigNumber(256745454), new BigNumber(-256745454), @@ -115,6 +118,7 @@ describe.only('ABI Encoder', () => { it('Types with default widths', async () => { const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); + console.log(method); const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; const calldata = method.encode(args); console.log(calldata); @@ -325,7 +329,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it('Yessir', async () => { + it.only('Yessir', async () => { const method = new AbiEncoder.Method(AbiSamples.simpleAbi); const calldata = method.encode([new BigNumber(5), 'five']); console.log(calldata); From 3027e6bc0db9d0722f0dcf09f84505ad8440e65b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 20:18:23 -0800 Subject: [PATCH 094/230] resolved issue with value.greaterThan --- packages/order-utils/test/abi/data_type.ts | 5 +++-- packages/order-utils/test/abi/evm_data_types.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index c1e3525087..be7ae6154e 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -32,6 +32,7 @@ export abstract class PayloadDataType extends DataType { } public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { + //console.log(); const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); @@ -173,8 +174,8 @@ export abstract class MemberDataType extends DataType { const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); const memberBlocks: CalldataBlock[] = []; - _.each(members, (member: DataType) => { - const block = member.generateCalldataBlock(value, methodBlock); + _.each(members, (member: DataType, idx: number) => { + const block = member.generateCalldataBlock(value[idx], methodBlock); memberBlocks.push(block); }); methodBlock.setMembers(memberBlocks); diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index 1bd4f0d51d..fcced646a4 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -86,6 +86,7 @@ abstract class Number extends PayloadDataType { } public encodeValue(value: BigNumber): Buffer { + console.log(value); if (value.greaterThan(this.getMaxValue())) { throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; } else if (value.lessThan(this.getMinValue())) { From 58177060a49917d0de88e6a58559b13211127efe Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 20:22:48 -0800 Subject: [PATCH 095/230] works for simple case --- packages/order-utils/test/abi/calldata.ts | 3 ++- packages/order-utils/test/abi_encoder_test.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 725bdce629..aaad332f8c 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -81,7 +81,7 @@ export class DependentCalldataBlock extends CalldataBlock { private parent: CalldataBlock; private dependency: CalldataBlock; - constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, parent: CalldataBlock, dependency: CalldataBlock) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, dependency: CalldataBlock, parent: CalldataBlock) { const headerSizeInBytes = 0; const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES; super(name, signature, /*offsetInBytes,*/ headerSizeInBytes, bodySizeInBytes, relocatable); @@ -174,6 +174,7 @@ export class Calldata { let block: CalldataBlock | undefined; let offset = 0; while ((block = blockQueue.pop()) !== undefined) { + console.log(block.getName()); block.setOffset(offset); if (block instanceof DependentCalldataBlock) { blockQueue.push(block.getDependency()); diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 6655af1ffd..0455169a31 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -116,7 +116,7 @@ describe.only('ABI Encoder', () => { }); - it('Types with default widths', async () => { + it.skip('Types with default widths', async () => { const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); console.log(method); const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; @@ -329,7 +329,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.only('Yessir', async () => { + it('Yessir', async () => { const method = new AbiEncoder.Method(AbiSamples.simpleAbi); const calldata = method.encode([new BigNumber(5), 'five']); console.log(calldata); From addfe85f088fc1aa31638acdd73bae560afceb27 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 21:48:06 -0800 Subject: [PATCH 096/230] passing tests slowly but surely --- packages/order-utils/test/abi/calldata.ts | 16 +++++++++++++--- packages/order-utils/test/abi/data_type.ts | 7 ++++++- packages/order-utils/test/abi_encoder_test.ts | 5 +++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index aaad332f8c..74f0c0924b 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -91,10 +91,15 @@ export class DependentCalldataBlock extends CalldataBlock { public toBuffer(): Buffer { const dependencyOffset = this.dependency.getOffsetInBytes(); + console.log("Dependency Offset - ", dependencyOffset); const parentOffset = this.parent.getOffsetInBytes(); + console.log("Parent Offset - ", parentOffset); const parentHeaderSize = this.parent.getHeaderSizeInBytes(); - const pointer = dependencyOffset - parentOffset + parentHeaderSize; - const pointerBuf = new Buffer(`0x${pointer.toString(16)}`); + console.log("Parent Header size - ", parentHeaderSize); + const pointer: number = (dependencyOffset - parentOffset) + parentHeaderSize; + console.log("DAT PTR = ", pointer); + const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(16)}`); + console.log("Chye - ", pointerBuf); const evmWordWidthInBytes = 32; const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); return pointerBufPadded; @@ -122,7 +127,7 @@ export class MemberCalldataBlock extends CalldataBlock { bodySizeInBytes += member.getSizeInBytes(); }); this.members = members; - this.setBodySize(bodySizeInBytes); + this.setBodySize(0); } public setHeader(header: Buffer) { @@ -176,7 +181,9 @@ export class Calldata { while ((block = blockQueue.pop()) !== undefined) { console.log(block.getName()); block.setOffset(offset); + offset += block.getSizeInBytes(); if (block instanceof DependentCalldataBlock) { + console.log(block.getDependency()); blockQueue.push(block.getDependency()); } else if (block instanceof MemberCalldataBlock) { _.each(block.getMembers(), (member: CalldataBlock) => { @@ -185,8 +192,11 @@ export class Calldata { } } + console.log(this.root); + // Fetch values using same technique const valueBufs: Buffer[] = [selectorBuffer]; + blockQueue.push(this.root); while ((block = blockQueue.pop()) !== undefined) { valueBufs.push(block.toBuffer()); diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index be7ae6154e..5311ffb819 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -167,12 +167,17 @@ export abstract class MemberDataType extends DataType { ); } + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + let members = this.members; if (this.isArray && this.arrayLength === undefined) { [members,] = this.createMembersWithLength(this.getDataItem(), value.length); + + const lenBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.length.toString(16)}`), 32); + methodBlock.setHeader(lenBuf); } - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + const memberBlocks: CalldataBlock[] = []; _.each(members, (member: DataType, idx: number) => { const block = member.generateCalldataBlock(value[idx], methodBlock); diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 0455169a31..e600542b89 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -116,7 +116,7 @@ describe.only('ABI Encoder', () => { }); - it.skip('Types with default widths', async () => { + it('Types with default widths', async () => { const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); console.log(method); const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; @@ -303,7 +303,7 @@ describe.only('ABI Encoder', () => { }); - it('Fixed Length Array / Static Members ABI', async () => { + it.only('Fixed Length Array / Static Members ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; const calldata = method.encode(args); @@ -338,6 +338,7 @@ describe.only('ABI Encoder', () => { it('Array ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.stringAbi); + console.log(method); const calldata = method.encode([['five', 'six', 'seven']]); console.log(method.getSignature()); console.log(method.selector); From 20f1526c7d0e1edb4a1de8e8a26811ae81b609dc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 21:59:37 -0800 Subject: [PATCH 097/230] arrays working --- packages/order-utils/test/abi/calldata.ts | 2 +- packages/order-utils/test/abi/data_type.ts | 4 +++- packages/order-utils/test/abi_encoder_test.ts | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 74f0c0924b..c1630c34a3 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -96,7 +96,7 @@ export class DependentCalldataBlock extends CalldataBlock { console.log("Parent Offset - ", parentOffset); const parentHeaderSize = this.parent.getHeaderSizeInBytes(); console.log("Parent Header size - ", parentHeaderSize); - const pointer: number = (dependencyOffset - parentOffset) + parentHeaderSize; + const pointer: number = dependencyOffset - (parentOffset + parentHeaderSize); console.log("DAT PTR = ", pointer); const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(16)}`); console.log("Chye - ", pointerBuf); diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 5311ffb819..72d270bf16 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -102,6 +102,8 @@ export abstract class MemberDataType extends DataType { public constructor(dataItem: DataItem, isArray: boolean = false, arrayLength?: number, arrayElementType?: string) { super(dataItem); + console.log('*'.repeat(40), arrayLength); + this.memberMap = {}; this.members = []; this.isArray = isArray; @@ -243,7 +245,7 @@ export abstract class MemberDataType extends DataType { */ if (this.isArray && this.arrayLength === undefined) { - return true; + return false; } // Search for dependent members diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index e600542b89..4c571434e6 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -259,7 +259,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it('Fixed Lenfgth Array / Dynamic Members', async () => { + it.only('Fixed Lenfgth Array / Dynamic Members', async () => { const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); const args = [["Brave", "New", "World"]]; const calldata = method.encode(args); @@ -303,7 +303,7 @@ describe.only('ABI Encoder', () => { }); - it.only('Fixed Length Array / Static Members ABI', async () => { + it('Fixed Length Array / Static Members ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; const calldata = method.encode(args); From 59206c387e702492d22e975acf806ebe7de36bfd Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 22:24:24 -0800 Subject: [PATCH 098/230] Works for types with default widths --- packages/order-utils/test/abi/calldata.ts | 24 +++++++++++-------- packages/order-utils/test/abi/data_type.ts | 9 ++----- packages/order-utils/test/abi_encoder_test.ts | 6 ++--- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index c1630c34a3..dc67a731e2 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -91,15 +91,10 @@ export class DependentCalldataBlock extends CalldataBlock { public toBuffer(): Buffer { const dependencyOffset = this.dependency.getOffsetInBytes(); - console.log("Dependency Offset - ", dependencyOffset); const parentOffset = this.parent.getOffsetInBytes(); - console.log("Parent Offset - ", parentOffset); const parentHeaderSize = this.parent.getHeaderSizeInBytes(); - console.log("Parent Header size - ", parentHeaderSize); const pointer: number = dependencyOffset - (parentOffset + parentHeaderSize); - console.log("DAT PTR = ", pointer); const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(16)}`); - console.log("Chye - ", pointerBuf); const evmWordWidthInBytes = 32; const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); return pointerBufPadded; @@ -114,11 +109,13 @@ export class MemberCalldataBlock extends CalldataBlock { private static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; private header: Buffer | undefined; private members: CalldataBlock[]; + private contiguous: boolean; - constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, contiguous: boolean) { super(name, signature, /*offsetInBytes,*/ 0, 0, relocatable); this.members = []; this.header = undefined; + this.contiguous = contiguous; } public setMembers(members: CalldataBlock[]) { @@ -130,6 +127,10 @@ export class MemberCalldataBlock extends CalldataBlock { this.setBodySize(0); } + public isContiguous(): boolean { + return true; + } + public setHeader(header: Buffer) { this.setHeaderSize(header.byteLength); this.header = header; @@ -150,6 +151,9 @@ class Queue { push(val: T) { this.store.push(val); } + pushFront(val: T) { + this.store.unshift(val); + } pop(): T | undefined { return this.store.shift(); } @@ -186,8 +190,8 @@ export class Calldata { console.log(block.getDependency()); blockQueue.push(block.getDependency()); } else if (block instanceof MemberCalldataBlock) { - _.each(block.getMembers(), (member: CalldataBlock) => { - blockQueue.push(member); + _.eachRight(block.getMembers(), (member: CalldataBlock) => { + blockQueue.pushFront(member); }); } } @@ -203,8 +207,8 @@ export class Calldata { if (block instanceof DependentCalldataBlock) { blockQueue.push(block.getDependency()); } else if (block instanceof MemberCalldataBlock) { - _.each(block.getMembers(), (member: CalldataBlock) => { - blockQueue.push(member); + _.eachRight(block.getMembers(), (member: CalldataBlock) => { + blockQueue.pushFront(member); }); } } diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 72d270bf16..0783b9c562 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -32,7 +32,6 @@ export abstract class PayloadDataType extends DataType { } public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { - //console.log(); const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); @@ -101,9 +100,6 @@ export abstract class MemberDataType extends DataType { public constructor(dataItem: DataItem, isArray: boolean = false, arrayLength?: number, arrayElementType?: string) { super(dataItem); - - console.log('*'.repeat(40), arrayLength); - this.memberMap = {}; this.members = []; this.isArray = isArray; @@ -138,7 +134,6 @@ export abstract class MemberDataType extends DataType { } private createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { - console.log(dataItem); let members: DataType[] = []; let memberMap: MemberMap = {}; const range = _.range(length); @@ -169,7 +164,7 @@ export abstract class MemberDataType extends DataType { ); } - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), this.isStatic(), false); let members = this.members; if (this.isArray && this.arrayLength === undefined) { @@ -190,7 +185,7 @@ export abstract class MemberDataType extends DataType { } protected generateCalldataBlockFromObject(obj: object): MemberCalldataBlock { - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), this.isStatic(), false); const memberBlocks: CalldataBlock[] = []; let childMap = _.cloneDeep(this.memberMap); _.forOwn(obj, (value: any, key: string) => { diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 4c571434e6..404f160fdd 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -116,7 +116,7 @@ describe.only('ABI Encoder', () => { }); - it('Types with default widths', async () => { + it.only('Types with default widths', async () => { const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); console.log(method); const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; @@ -201,7 +201,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it('Multidimensional Arrays / Static Members', async () => { + it.only('Multidimensional Arrays / Static Members', async () => { const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); // Eight 3-dimensional arrays of uint8[2][2][2] @@ -259,7 +259,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.only('Fixed Lenfgth Array / Dynamic Members', async () => { + it('Fixed Lenfgth Array / Dynamic Members', async () => { const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); const args = [["Brave", "New", "World"]]; const calldata = method.encode(args); From bb7dd2373893366dcab0fe1c4cffe0c1e31af837 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 23:00:20 -0800 Subject: [PATCH 099/230] passing 18 tests --- packages/order-utils/test/abi/calldata.ts | 54 +++++++++++++++++-- packages/order-utils/test/abi/data_type.ts | 13 +++-- .../order-utils/test/abi/evm_data_types.ts | 1 + packages/order-utils/test/abi_encoder_test.ts | 4 +- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index dc67a731e2..37dd30e572 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -1,4 +1,5 @@ import ethUtil = require('ethereumjs-util'); +import CommunicationChatBubbleOutline from 'material-ui/SvgIcon'; var _ = require('lodash'); export abstract class CalldataBlock { @@ -157,12 +158,18 @@ class Queue { pop(): T | undefined { return this.store.shift(); } + merge(q: Queue) { + this.store = this.store.concat(q.store); + } + mergeFront(q: Queue) { + this.store = q.store.concat(this.store); + } } export class Calldata { private selector: string; private sizeInBytes: number; - private root: CalldataBlock | undefined; + private root: MemberCalldataBlock | undefined; constructor() { this.selector = '0x'; @@ -170,12 +177,53 @@ export class Calldata { this.root = undefined; } + private createQueue(memberBlock: MemberCalldataBlock): Queue { + const blockQueue = new Queue(); + _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => { + if (member instanceof MemberCalldataBlock) { + blockQueue.mergeFront(this.createQueue(member)); + } else { + blockQueue.pushFront(member); + } + }); + + // Children + _.each(memberBlock.getMembers(), (member: CalldataBlock) => { + if (member instanceof DependentCalldataBlock) { + const dependency = member.getDependency(); + if (dependency instanceof MemberCalldataBlock) { + blockQueue.merge(this.createQueue(dependency)); + } else { + blockQueue.push(dependency); + } + } + }); + + blockQueue.pushFront(memberBlock); + return blockQueue; + } + public toHexString(): string { let selectorBuffer = ethUtil.toBuffer(this.selector); if (this.root === undefined) { throw new Error('expected root'); } + const offsetQueue = this.createQueue(this.root); + let block: CalldataBlock | undefined; + let offset = 0; + while ((block = offsetQueue.pop()) !== undefined) { + block.setOffset(offset); + offset += block.getSizeInBytes(); + } + + const valueQueue = this.createQueue(this.root); + const valueBufs: Buffer[] = [selectorBuffer]; + while ((block = valueQueue.pop()) !== undefined) { + valueBufs.push(block.toBuffer()); + } + + /* const blockQueue = new Queue(); blockQueue.push(this.root); @@ -211,7 +259,7 @@ export class Calldata { blockQueue.pushFront(member); }); } - } + }*/ const combinedBuffers = Buffer.concat(valueBufs); const hexValue = ethUtil.bufferToHex(combinedBuffers); @@ -230,7 +278,7 @@ export class Calldata { return ""; } - public setRoot(block: CalldataBlock) { + public setRoot(block: MemberCalldataBlock) { this.root = block; this.sizeInBytes += block.getSizeInBytes(); } diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 0783b9c562..7f110c4fc2 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -43,7 +43,7 @@ export abstract class PayloadDataType extends DataType { public encode(value: any, calldata: Calldata): void { const block = this.generateCalldataBlock(value); - calldata.setRoot(block); + // calldata.setRoot(block); } public isStatic(): boolean { @@ -78,7 +78,7 @@ export abstract class DependentDataType extends DataType { public encode(value: any, calldata: Calldata = new Calldata()): void { const block = this.generateCalldataBlock(value); - calldata.setRoot(block); + //calldata.setRoot(block); } public isStatic(): boolean { @@ -125,15 +125,20 @@ export abstract class MemberDataType extends DataType { type: memberItem.type, name: `${dataItem.name}.${memberItem.name}`, } as DataItem; + const components = memberItem.components; + if (components !== undefined) { + childDataItem.components = components; + } const child = DataTypeFactory.create(childDataItem, this); + memberMap[memberItem.name] = members.length; members.push(child); - memberMap[dataItem.name] = members.length; }); return [members, memberMap]; } private createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { + console.log('!'.repeat(30), dataItem); let members: DataType[] = []; let memberMap: MemberMap = {}; const range = _.range(length); @@ -147,8 +152,8 @@ export abstract class MemberDataType extends DataType { childDataItem.components = components; } const child = DataTypeFactory.create(childDataItem, this); - members.push(child); memberMap[idx.toString(10)] = members.length; + members.push(child); }); return [members, memberMap]; diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index fcced646a4..dfa541e801 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -315,6 +315,7 @@ export class Tuple extends MemberDataType { private tupleSignature: string; constructor(dataItem: DataItem) { + console.log(dataItem); super(dataItem); if (!Tuple.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 404f160fdd..9845079233 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -116,7 +116,7 @@ describe.only('ABI Encoder', () => { }); - it.only('Types with default widths', async () => { + it('Types with default widths', async () => { const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); console.log(method); const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; @@ -201,7 +201,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.only('Multidimensional Arrays / Static Members', async () => { + it('Multidimensional Arrays / Static Members', async () => { const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); // Eight 3-dimensional arrays of uint8[2][2][2] From 707a93e9f95e2adac992dc0e7903b7b1646c78c5 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 23:00:58 -0800 Subject: [PATCH 100/230] Refactor passing all regression tests --- packages/order-utils/test/abi_encoder_test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 9845079233..fa0fa23c14 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -26,7 +26,7 @@ const expect = chai.expect; describe.only('ABI Encoder', () => { describe.only('ABI Tests at Method Level', () => { - it.skip('Crazy ABI', async () => { + it('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); @@ -91,7 +91,7 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); - it.skip('Crazy ABI #1', async () => { + it('Crazy ABI #1', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi1); const args = [ From a9e3d489d60df1be4805f1c84f136eb127fef949 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 12 Nov 2018 12:49:03 -0800 Subject: [PATCH 101/230] Tests for nested tuples -- couldnt test because not supported by reference ABI encoder --- packages/order-utils/test/abi/data_type.ts | 2 +- packages/order-utils/test/abi_encoder_test.ts | 74 +++++++++++++++ packages/order-utils/test/abi_samples.ts | 89 +++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 7f110c4fc2..532407f52e 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -195,7 +195,7 @@ export abstract class MemberDataType extends DataType { let childMap = _.cloneDeep(this.memberMap); _.forOwn(obj, (value: any, key: string) => { if (key in childMap === false) { - throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); + throw new Error(`Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`); } const block = this.members[this.memberMap[key]].generateCalldataBlock(value, methodBlock); memberBlocks.push(block); diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index fa0fa23c14..0db5f4281d 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -387,6 +387,80 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); + it.skip('Nested Tuples', async () => { + // Couldn't get nested tuples to work with Remix + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.nestedTuples); + const firstTuple = { + someUint32: new BigNumber(30472), + nestedTuple: { + someUint: new BigNumber('48384725243211555532'), + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498' + } + }; + const secondTuple = { + someUint: new BigNumber(2984237422), + someStr: 'This string will exceed 256 bits, so it will spill into the next word of memory.', + nestedTuple: { + someUint32: new BigNumber(23), + secondNestedTuple: { + someUint: new BigNumber(234324), + someStr: 'Im also a short string -- oops I just got loooooooooooooooooonger!', + someBytes: '0x23847287fff3472984723498ff23487324987aaa237438911873429472ba', + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498' + } + }, + someBytes: '0x2834y3947289423u489aaaff4783924739847489', + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699afaf', + }; + const thirdTuple = { + 'someUint': new BigNumber(37422), + 'someStr': 'This into the next word of memory. string will exceed 256 bits, so it will spill.', + 'nestedTuple': { + someUint32: new BigNumber(23999222), + 'secondNestedTuple': { + 'someUint': new BigNumber(324), + 'someStr': 'Im also a short st', + 'someBytes': '0x723498ff2348732498723847287fff3472984aaa237438911873429472ba', + 'someAddress': '0x46dafa5ebde1f4699f498e41d2489571d3221892' + } + }, + 'someBytes': '0x947289423u489aaaff472834y383924739847489', + 'someAddress': '0x46dafa5ebde1f46e41d2489571d322189299afaf', + }; + const fourthTuple = { + 'someUint': new BigNumber(222283488822), + 'someStr': 'exceed 256 bits, so it will spill into the. This string will next word of memory.', + 'nestedTuple': { + someUint32: new BigNumber(2300), + 'secondNestedTuple': { + 'someUint': new BigNumber(343224), + 'someStr': 'The alphabet backwards is arguably easier to say if thats the way you learned the first time.', + 'someBytes': '0x87324987aaa23743891187323847287fff3472984723498ff234429472ba', + 'someAddress': '0x71d322189246dafa5ebe41d24895de1f4699f498' + } + }, + 'someBytes': '0x2783924739847488343947289423u489aaaff490', + 'someAddress': '0xebde1d322189246dafa1f4699afafe41d2489575', + }; + const args = [ + [firstTuple], + [secondTuple, thirdTuple, fourthTuple] + ]; + + console.log('*'.repeat(250), method, '*'.repeat(250)); + + + const calldata = method.encode(args); + console.log(method.getSignature()); + console.log(method.selector); + console.log(JSON.stringify(args)); + + console.log(calldata); + const expectedCalldata = '0x'; + expect(calldata).to.be.equal(expectedCalldata); + }); + it.skip('Object ABI (Object input - Missing Key)', async () => { const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5) }]); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index 82427986ad..3f7b1a9278 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -547,6 +547,95 @@ export const crazyAbi = { type: 'function', } as MethodAbi; +export const nestedTuples = { + constant: false, + inputs: [ + { + name: 'firstTuple', + type: 'tuple[1]', + components: [ + { + name: 'someUint32', + type: 'uint32', + }, + { + name: 'nestedTuple', + type: 'tuple', + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someAddress', + type: 'address', + }, + ], + }, + ], + }, + { + name: 'secondTuple', + type: 'tuple[]', + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + { + name: 'nestedTuple', + type: 'tuple', + components: [ + { + name: 'someUint32', + type: 'uint32', + }, + { + name: 'secondNestedTuple', + type: 'tuple', + components: [ + { + name: 'someUint', + type: 'uint256', + }, + { + name: 'someStr', + type: 'string', + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someAddress', + type: 'address', + }, + ], + }, + ], + }, + { + name: 'someBytes', + type: 'bytes', + }, + { + name: 'someAddress', + type: 'address', + }, + ], + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const simpleAbi2 = { constant: false, inputs: [ From 13d456fda1da19c8227acb2bf23106bcbdaf6f12 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 12 Nov 2018 13:06:45 -0800 Subject: [PATCH 102/230] removed old code --- packages/order-utils/test/abi/calldata.ts | 38 ----------------------- 1 file changed, 38 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 37dd30e572..94f6a5571f 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -223,44 +223,6 @@ export class Calldata { valueBufs.push(block.toBuffer()); } - /* - const blockQueue = new Queue(); - blockQueue.push(this.root); - - // Assign locations in breadth-first manner - let block: CalldataBlock | undefined; - let offset = 0; - while ((block = blockQueue.pop()) !== undefined) { - console.log(block.getName()); - block.setOffset(offset); - offset += block.getSizeInBytes(); - if (block instanceof DependentCalldataBlock) { - console.log(block.getDependency()); - blockQueue.push(block.getDependency()); - } else if (block instanceof MemberCalldataBlock) { - _.eachRight(block.getMembers(), (member: CalldataBlock) => { - blockQueue.pushFront(member); - }); - } - } - - console.log(this.root); - - // Fetch values using same technique - const valueBufs: Buffer[] = [selectorBuffer]; - blockQueue.push(this.root); - while ((block = blockQueue.pop()) !== undefined) { - valueBufs.push(block.toBuffer()); - - if (block instanceof DependentCalldataBlock) { - blockQueue.push(block.getDependency()); - } else if (block instanceof MemberCalldataBlock) { - _.eachRight(block.getMembers(), (member: CalldataBlock) => { - blockQueue.pushFront(member); - }); - } - }*/ - const combinedBuffers = Buffer.concat(valueBufs); const hexValue = ethUtil.bufferToHex(combinedBuffers); return hexValue; From 9b1f56c9687d3fffb2f59fa98d619ead90c1b910 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 12 Nov 2018 17:10:32 -0800 Subject: [PATCH 103/230] Decoding works for some basic typs --- packages/order-utils/test/abi/calldata.ts | 102 ++++++++++++++++- packages/order-utils/test/abi/data_type.ts | 43 +++++++- .../order-utils/test/abi/evm_data_types.ts | 103 +++++++++++++++++- packages/order-utils/test/abi_encoder_test.ts | 42 +++++-- 4 files changed, 275 insertions(+), 15 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 94f6a5571f..0445f68ecc 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -164,6 +164,12 @@ class Queue { mergeFront(q: Queue) { this.store = q.store.concat(this.store); } + getStore(): T[] { + return this.store; + } + peek(): T | undefined { + return this.store.length >= 0 ? this.store[0] : undefined; + } } export class Calldata { @@ -203,7 +209,29 @@ export class Calldata { return blockQueue; } - public toHexString(): string { + /* + + // Basic optimize method that prunes duplicate branches of the tree + // Notes: + // 1. Pruning is at the calldata block level, so it is independent of type + // 2. + private optimize(blocks: CalldataBlock[]) { + // Build hash table of blocks + const blockLookupTable: { [key: string]: string } = {}; + _.each(blocks, (block: CalldataBlock) => { + if (blocks instanceof DependentCalldataBlock === false) { + + return; + } + + const leavesHash = block.hashLeaves(); + if (leavesHash in blockLookupTable) { + + } + }) + }*/ + + public toHexString(optimize: boolean = false): string { let selectorBuffer = ethUtil.toBuffer(this.selector); if (this.root === undefined) { throw new Error('expected root'); @@ -223,6 +251,8 @@ export class Calldata { valueBufs.push(block.toBuffer()); } + // if (optimize) this.optimize(valueQueue.getStore()); + const combinedBuffers = Buffer.concat(valueBufs); const hexValue = ethUtil.bufferToHex(combinedBuffers); return hexValue; @@ -259,4 +289,74 @@ export class Calldata { } this.sizeInBytes += 8; } +} + +export class RawCalldata { + private value: Buffer; + private offset: number; // tracks current offset into raw calldata; used for parsing + private selector: string; + private scopes: Queue; + + constructor(value: string | Buffer) { + if (typeof value === 'string' && !value.startsWith('0x')) { + throw new Error(`Expected raw calldata to start with '0x'`); + } + const valueBuf = ethUtil.toBuffer(value); + this.selector = ethUtil.bufferToHex(valueBuf.slice(0, 4)); + this.value = valueBuf.slice(4); // disregard selector + this.offset = 0; + this.scopes = new Queue(); + this.scopes.push(0); + } + + public popBytes(lengthInBytes: number): Buffer { + const value = this.value.slice(this.offset, this.offset + lengthInBytes); + this.setOffset(this.offset + lengthInBytes); + return value; + } + + public popWord(): Buffer { + const wordInBytes = 32; + return this.popBytes(wordInBytes); + } + + public popWords(length: number): Buffer { + const wordInBytes = 32; + return this.popBytes(length * wordInBytes); + } + + public readBytes(from: number, to: number): Buffer { + const value = this.value.slice(from, to); + return value; + } + + public setOffset(offsetInBytes: number) { + this.offset = offsetInBytes; + console.log('0'.repeat(100), this.offset); + } + + public startScope() { + this.scopes.pushFront(this.offset); + } + + public endScope() { + this.scopes.pop(); + } + + public getOffset(): number { + return this.offset; + } + + public toAbsoluteOffset(relativeOffset: number) { + const scopeOffset = this.scopes.peek(); + if (scopeOffset === undefined) { + throw new Error(`Tried to access undefined scope.`); + } + const absoluteOffset = relativeOffset + scopeOffset; + return absoluteOffset; + } + + public getSelector(): string { + return this.selector; + } } \ No newline at end of file diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 532407f52e..af170f7e67 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -1,4 +1,4 @@ -import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata"; +import { RawCalldata, Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata"; import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; import ethUtil = require('ethereumjs-util'); @@ -18,6 +18,7 @@ export abstract class DataType { } public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; + public abstract generateValue(calldata: RawCalldata): any; public abstract encode(value: any, calldata: Calldata): void; public abstract getSignature(): string; public abstract isStatic(): boolean; @@ -46,12 +47,18 @@ export abstract class PayloadDataType extends DataType { // calldata.setRoot(block); } + public generateValue(calldata: RawCalldata): any { + const value = this.decodeValue(calldata); + return value; + } + public isStatic(): boolean { // If a payload has a constant size then it's static return this.hasConstantSize; } public abstract encodeValue(value: any): Buffer; + public abstract decodeValue(calldata: RawCalldata): any; } export abstract class DependentDataType extends DataType { @@ -81,6 +88,17 @@ export abstract class DependentDataType extends DataType { //calldata.setRoot(block); } + public generateValue(calldata: RawCalldata): any { + const destinationOffsetBuf = calldata.popWord(); + const currentOffset = calldata.getOffset(); + const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), 16); + const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); + calldata.setOffset(destinationOffsetAbsolute); + const value = this.dependency.generateValue(calldata); + calldata.setOffset(currentOffset); + return value; + } + public isStatic(): boolean { return true; } @@ -179,7 +197,6 @@ export abstract class MemberDataType extends DataType { methodBlock.setHeader(lenBuf); } - const memberBlocks: CalldataBlock[] = []; _.each(members, (member: DataType, idx: number) => { const block = member.generateCalldataBlock(value[idx], methodBlock); @@ -220,6 +237,28 @@ export abstract class MemberDataType extends DataType { calldata.setRoot(block); } + public generateValue(calldata: RawCalldata): any[] { + let members = this.members; + if (this.isArray && this.arrayLength === undefined) { + const arrayLengthBuf = calldata.popWord(); + const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); + const hexBase = 16; + const arrayLength = new BigNumber(arrayLengthHex, hexBase); + + [members,] = this.createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); + } + + calldata.startScope(); + const decodedValue: any[] = []; + _.each(members, (member: DataType, idx: number) => { + let memberValue = member.generateValue(calldata); + decodedValue.push(memberValue); + }); + calldata.endScope(); + + return decodedValue; + } + protected computeSignatureOfMembers(): string { // Compute signature of members let signature = `(`; diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index dfa541e801..a05c29b281 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -4,7 +4,7 @@ import { MethodAbi, DataItem } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); -import { Calldata } from './calldata'; +import { Calldata, RawCalldata } from './calldata'; import { BigNumber } from '@0x/utils'; @@ -13,6 +13,7 @@ var _ = require('lodash'); export interface DataTypeStaticInterface { matchGrammar: (type: string) => boolean; encodeValue: (value: any) => Buffer; + // decodeValue: (value: Buffer) => [any, Buffer]; } export class Address extends PayloadDataType { @@ -38,6 +39,13 @@ export class Address extends PayloadDataType { const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth); return encodedValueBuf; } + + public decodeValue(calldata: RawCalldata): string { + const paddedValueBuf = calldata.popWord(); + const valueBuf = paddedValueBuf.slice(12); + const value = ethUtil.bufferToHex(valueBuf); + return value; + } } export class Bool extends PayloadDataType { @@ -64,6 +72,17 @@ export class Bool extends PayloadDataType { const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth); return encodedValueBuf; } + + public decodeValue(calldata: RawCalldata): boolean { + const valueBuf = calldata.popWord(); + const valueHex = ethUtil.bufferToHex(valueBuf); + const valueNumber = new BigNumber(valueHex, 16); + let value: boolean = (valueNumber.equals(0)) ? false : true; + if (!(valueNumber.equals(0) || valueNumber.equals(1))) { + throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`); + } + return value; + } } abstract class Number extends PayloadDataType { @@ -126,6 +145,37 @@ abstract class Number extends PayloadDataType { return valueBuf; } + public decodeValue(calldata: RawCalldata): BigNumber { + const decodedValueBuf = calldata.popWord(); + const decodedValueHex = ethUtil.bufferToHex(decodedValueBuf); + let decodedValue = new BigNumber(decodedValueHex, 16); + if (this instanceof Int) { + // Check if we're negative + const binBase = 2; + const decodedValueBin = decodedValue.toString(binBase); + if (decodedValueBin[0] === '1') { + // Negative + // Step 1/3: Invert binary value + const bitsInEvmWord = 256; + let invertedValueBin = '1'.repeat(bitsInEvmWord - decodedValueBin.length); + _.each(decodedValueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, binBase); + + // Step 2/3: Add 1 to inverted value + // The result is the two's-complement represent of the input value. + const positiveDecodedValue = invertedValue.plus(binBase); + + // Step 3/3: Invert positive value + const negativeDecodedValue = positiveDecodedValue.times(-1); + decodedValue = negativeDecodedValue; + } + } + + return decodedValue; + } + public abstract getMaxValue(): BigNumber; public abstract getMinValue(): BigNumber; } @@ -228,6 +278,13 @@ export class Byte extends PayloadDataType { return paddedValue; } + public decodeValue(calldata: RawCalldata): string { + const paddedValueBuf = calldata.popWord(); + const valueBuf = paddedValueBuf.slice(0, this.width); + const value = ethUtil.bufferToHex(valueBuf); + return value; + } + public static matchGrammar(type: string): boolean { return this.matcher.test(type); } @@ -262,6 +319,17 @@ export class Bytes extends PayloadDataType { return encodedValueBuf; } + public decodeValue(calldata: RawCalldata): string { + const lengthBuf = calldata.popWord(); + const lengthHex = ethUtil.bufferToHex(lengthBuf); + const length = parseInt(lengthHex, 16); + const wordsForValue = Math.ceil(length / 32); + const paddedValueBuf = calldata.popWords(wordsForValue); + const valueBuf = paddedValueBuf.slice(0, length); + const decodedValue = ethUtil.bufferToHex(valueBuf); + return decodedValue; + } + public getSignature(): string { return 'bytes'; } @@ -289,6 +357,18 @@ export class SolString extends PayloadDataType { return encodedValueBuf; } + public decodeValue(calldata: RawCalldata): string { + const lengthBuf = calldata.popWord(); + const lengthHex = ethUtil.bufferToHex(lengthBuf); + const length = parseInt(lengthHex, 16); + const wordsForValue = Math.ceil(length / 32); + const paddedValueBuf = calldata.popWords(wordsForValue); + const valueBuf = paddedValueBuf.slice(0, length); + console.log('LENGTH UPINYA === ', length); + const value = valueBuf.toString('ascii'); + return value; + } + public getSignature(): string { return 'string'; } @@ -415,6 +495,27 @@ export class Method extends MemberDataType { return calldata.toHexString(); } + /* + protected decodeValue(value: Buffer): any[] { + const selectorBuf = value.slice(4); + const selectorHex = ethUtil.bufferToHex(selectorBuf); + if (this.selector !== selectorHex) { + throw new Error(`Tried to decode calldata with mismatched selector. Expected '${this.selector}', got '${selectorHex}'`); + } + const remainingValue = value.slice(9); + const decodedValue = super.decodeValue(remainingValue); + return decodedValue; + }*/ + + public decode(calldata: string): any[] { + const calldata_ = new RawCalldata(calldata); + if (this.selector !== calldata_.getSelector()) { + throw new Error(`Tried to decode calldata with mismatched selector. Expected '${this.selector}', got '${calldata_.getSelector()}'`); + } + const value = super.generateValue(calldata_); + return value; + } + public getSignature(): string { return this.methodSignature; } diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 0db5f4281d..e8ebd7b987 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -310,6 +310,12 @@ describe.only('ABI Encoder', () => { const expectedCalldata = '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); @@ -327,38 +333,52 @@ describe.only('ABI Encoder', () => { const expectedCalldata = '0x7ac2bd96af000000000000000000000000000000000000000000000000000000000000000001020304050607080911121314151617181920212223242526272829303132000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000047616161616161616161616161616161616161616161616161616161616161616161616161616161611114f3245678384756473829384756774488993384576688990020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414d79206669727374206e616d65206973204772656720616e64206d79206c617374206e616d6520697320487973656e2c207768617420646f207961206b6e6f772100000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); - it('Yessir', async () => { - const method = new AbiEncoder.Method(AbiSamples.simpleAbi); - const calldata = method.encode([new BigNumber(5), 'five']); - console.log(calldata); - expect(true).to.be.true(); - }); - - it('Array ABI', async () => { + it.only('Array ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.stringAbi); console.log(method); - const calldata = method.encode([['five', 'six', 'seven']]); + const args = [['five', 'six', 'seven']]; + const calldata = method.encode(args); console.log(method.getSignature()); console.log(method.selector); + /* console.log(calldata); const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); + expect(calldata).to.be.equal(expectedCalldata);*/ + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Static Tuple', async () => { // This is dynamic because it has dynamic members const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); - const calldata = method.encode([[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]); + const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]; + const calldata = method.encode(args); console.log(method.getSignature()); console.log(method.selector); console.log(calldata); const expectedCalldata = '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Dynamic Tuple (Array input)', async () => { From c5d252ba4a79a0f16e053a071b0173a7bd666328 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 12 Nov 2018 17:14:25 -0800 Subject: [PATCH 104/230] decoding works for most cases --- packages/order-utils/test/abi_encoder_test.ts | 94 ++++++++++++++++++- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index e8ebd7b987..631255e683 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -89,9 +89,15 @@ describe.only('ABI Encoder', () => { const expectedCalldata = '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; //const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + /*const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson);*/ }); - it('Crazy ABI #1', async () => { + it.only('Crazy ABI #1', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi1); const args = [ @@ -113,6 +119,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); @@ -127,6 +139,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Array of Static Tuples (Array has defined length)', async () => { @@ -145,6 +163,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Array of Static Tuples (Array has dynamic length)', async () => { @@ -163,6 +187,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Array of Dynamic Tuples (Array has defined length)', async () => { @@ -181,6 +211,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Array of Dynamic Tuples (Array has dynamic length)', async () => { @@ -199,6 +235,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Multidimensional Arrays / Static Members', async () => { @@ -228,6 +270,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; expect(calldata).to.be.equal(expectedCalldata); expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Multidimensional Arrays / Dynamic Members', async () => { @@ -257,6 +305,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Fixed Lenfgth Array / Dynamic Members', async () => { @@ -269,6 +323,12 @@ describe.only('ABI Encoder', () => { const expectedCalldata = '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Fixed Lenfgth Array / Dynamic Members', async () => { @@ -281,6 +341,12 @@ describe.only('ABI Encoder', () => { const expectedCalldata = '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Unfixed Length Array / Dynamic Members ABI', async () => { @@ -292,6 +358,12 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(args)); const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Unfixed Length Array / Static Members ABI', async () => { @@ -300,6 +372,12 @@ describe.only('ABI Encoder', () => { const calldata = method.encode(args); const expectedCalldata = '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); @@ -341,7 +419,7 @@ describe.only('ABI Encoder', () => { expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); - it.only('Array ABI', async () => { + it('Array ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.stringAbi); console.log(method); const args = [['five', 'six', 'seven']]; @@ -349,11 +427,10 @@ describe.only('ABI Encoder', () => { console.log(method.getSignature()); console.log(method.selector); - /* console.log(calldata); const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata);*/ + expect(calldata).to.be.equal(expectedCalldata); // Test decoding const expectedDecodedValueJson = JSON.stringify(args); @@ -384,7 +461,8 @@ describe.only('ABI Encoder', () => { it('Dynamic Tuple (Array input)', async () => { // This is dynamic because it has dynamic members const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); - const calldata = method.encode([[new BigNumber(5), 'five']]); + const args = [[new BigNumber(5), 'five']]; + const calldata = method.encode(args); console.log(method.getSignature()); console.log(method.selector); @@ -392,6 +470,12 @@ describe.only('ABI Encoder', () => { const expectedCalldata = '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); it('Dynamic Tuple (Object input)', async () => { From 2a6b358717e3134aef3ff86cc5343b3b37edee7e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 12 Nov 2018 17:28:46 -0800 Subject: [PATCH 105/230] Decoder works for uints with width less than 256 --- .../order-utils/test/abi/evm_data_types.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index a05c29b281..abb4fba14d 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -146,34 +146,34 @@ abstract class Number extends PayloadDataType { } public decodeValue(calldata: RawCalldata): BigNumber { - const decodedValueBuf = calldata.popWord(); - const decodedValueHex = ethUtil.bufferToHex(decodedValueBuf); - let decodedValue = new BigNumber(decodedValueHex, 16); + const paddedValueBuf = calldata.popWord(); + const paddedValueHex = ethUtil.bufferToHex(paddedValueBuf); + let value = new BigNumber(paddedValueHex, 16); if (this instanceof Int) { // Check if we're negative const binBase = 2; - const decodedValueBin = decodedValue.toString(binBase); - if (decodedValueBin[0] === '1') { + const paddedValueBin = value.toString(binBase); + const valueBin = paddedValueBin.slice(paddedValueBin.length - this.width); + if (valueBin[0].startsWith('1')) { // Negative // Step 1/3: Invert binary value - const bitsInEvmWord = 256; - let invertedValueBin = '1'.repeat(bitsInEvmWord - decodedValueBin.length); - _.each(decodedValueBin, (bit: string) => { + let invertedValueBin = ''; + _.each(valueBin, (bit: string) => { invertedValueBin += bit === '1' ? '0' : '1'; }); const invertedValue = new BigNumber(invertedValueBin, binBase); // Step 2/3: Add 1 to inverted value // The result is the two's-complement represent of the input value. - const positiveDecodedValue = invertedValue.plus(binBase); + const positiveValue = invertedValue.plus(1); // Step 3/3: Invert positive value - const negativeDecodedValue = positiveDecodedValue.times(-1); - decodedValue = negativeDecodedValue; + const negativeValue = positiveValue.times(-1); + value = negativeValue; } } - return decodedValue; + return value; } public abstract getMaxValue(): BigNumber; From 52474a0b8edb2f49f850069c1a79dcd9b5d77e9d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 12 Nov 2018 17:49:52 -0800 Subject: [PATCH 106/230] decoding with objects as structs works --- packages/order-utils/test/abi/data_type.ts | 37 ++++++++++++------- .../order-utils/test/abi/evm_data_types.ts | 21 +++-------- packages/order-utils/test/abi_encoder_test.ts | 21 ++++++++--- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index af170f7e67..725d314f36 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -4,7 +4,9 @@ import { BigNumber } from '@0x/utils'; import ethUtil = require('ethereumjs-util'); var _ = require('lodash'); - +export interface GenerateValueRules { + structsAsObjects: boolean; +} export abstract class DataType { private dataItem: DataItem; @@ -18,7 +20,7 @@ export abstract class DataType { } public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; - public abstract generateValue(calldata: RawCalldata): any; + public abstract generateValue(calldata: RawCalldata, rules: GenerateValueRules): any; public abstract encode(value: any, calldata: Calldata): void; public abstract getSignature(): string; public abstract isStatic(): boolean; @@ -47,7 +49,7 @@ export abstract class PayloadDataType extends DataType { // calldata.setRoot(block); } - public generateValue(calldata: RawCalldata): any { + public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any { const value = this.decodeValue(calldata); return value; } @@ -88,13 +90,13 @@ export abstract class DependentDataType extends DataType { //calldata.setRoot(block); } - public generateValue(calldata: RawCalldata): any { + public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any { const destinationOffsetBuf = calldata.popWord(); const currentOffset = calldata.getOffset(); const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), 16); const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); calldata.setOffset(destinationOffsetAbsolute); - const value = this.dependency.generateValue(calldata); + const value = this.dependency.generateValue(calldata, rules); calldata.setOffset(currentOffset); return value; } @@ -237,7 +239,7 @@ export abstract class MemberDataType extends DataType { calldata.setRoot(block); } - public generateValue(calldata: RawCalldata): any[] { + public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any[] | object { let members = this.members; if (this.isArray && this.arrayLength === undefined) { const arrayLengthBuf = calldata.popWord(); @@ -249,14 +251,23 @@ export abstract class MemberDataType extends DataType { } calldata.startScope(); - const decodedValue: any[] = []; - _.each(members, (member: DataType, idx: number) => { - let memberValue = member.generateValue(calldata); - decodedValue.push(memberValue); - }); + let value: any[] | object; + if (rules.structsAsObjects && !this.isArray) { + value = {}; + _.each(this.memberMap, (idx: number, key: string) => { + const member = this.members[idx]; + let memberValue = member.generateValue(calldata, rules); + (value as { [key: string]: any })[key] = memberValue; + }); + } else { + value = []; + _.each(members, (member: DataType, idx: number) => { + let memberValue = member.generateValue(calldata, rules); + (value as any[]).push(memberValue); + }); + } calldata.endScope(); - - return decodedValue; + return value; } protected computeSignatureOfMembers(): string { diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index abb4fba14d..7b366a985e 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -1,4 +1,4 @@ -import { DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; +import { GenerateValueRules, DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; import { MethodAbi, DataItem } from 'ethereum-types'; @@ -495,24 +495,15 @@ export class Method extends MemberDataType { return calldata.toHexString(); } - /* - protected decodeValue(value: Buffer): any[] { - const selectorBuf = value.slice(4); - const selectorHex = ethUtil.bufferToHex(selectorBuf); - if (this.selector !== selectorHex) { - throw new Error(`Tried to decode calldata with mismatched selector. Expected '${this.selector}', got '${selectorHex}'`); - } - const remainingValue = value.slice(9); - const decodedValue = super.decodeValue(remainingValue); - return decodedValue; - }*/ - - public decode(calldata: string): any[] { + public decode(calldata: string, decodeStructsAsObjects: boolean = false): any[] | object { const calldata_ = new RawCalldata(calldata); if (this.selector !== calldata_.getSelector()) { throw new Error(`Tried to decode calldata with mismatched selector. Expected '${this.selector}', got '${calldata_.getSelector()}'`); } - const value = super.generateValue(calldata_); + let rules: GenerateValueRules = { + structsAsObjects: decodeStructsAsObjects + }; + const value = super.generateValue(calldata_, rules); return value; } diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 631255e683..137850b35a 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -26,7 +26,7 @@ const expect = chai.expect; describe.only('ABI Encoder', () => { describe.only('ABI Tests at Method Level', () => { - it('Crazy ABI', async () => { + it.only('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); @@ -78,7 +78,15 @@ describe.only('ABI Encoder', () => { }; const someArrayOfTuplesWithDynamicTypes = [someTupleWithDynamicTypes2, someTupleWithDynamicTypes3]; - const args = [someStaticArray, someStaticArrayWithDynamicMembers, someDynamicArrayWithDynamicMembers, some2DArray, someTuple, someTupleWithDynamicTypes, someArrayOfTuplesWithDynamicTypes]; + const args = { + someStaticArray: someStaticArray, + someStaticArrayWithDynamicMembers: someStaticArrayWithDynamicMembers, + someDynamicArrayWithDynamicMembers: someDynamicArrayWithDynamicMembers, + some2DArray: some2DArray, + someTuple: someTuple, + someTupleWithDynamicTypes: someTupleWithDynamicTypes, + someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes + }; const calldata = method.encode(args); console.log(calldata); @@ -91,13 +99,14 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); // Test decoding - /*const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata, true); const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson);*/ + console.log(`DECODED`, '*'.repeat(200), '\n', decodedValueJson); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); - it.only('Crazy ABI #1', async () => { + it('Crazy ABI #1', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi1); const args = [ From 7c733662e2669ca6682920f321c81e770605b3d5 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 13 Nov 2018 12:36:14 -0800 Subject: [PATCH 107/230] Annotated calldata (draft). --- packages/order-utils/test/abi/calldata.ts | 113 +++++++++++++++--- packages/order-utils/test/abi/data_type.ts | 21 ++-- .../order-utils/test/abi/evm_data_types.ts | 4 +- packages/order-utils/test/abi_encoder_test.ts | 7 +- 4 files changed, 118 insertions(+), 27 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 0445f68ecc..1173f90ccc 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -9,10 +9,12 @@ export abstract class CalldataBlock { private headerSizeInBytes: number; private bodySizeInBytes: number; private relocatable: boolean; + private parentName: string; - constructor(name: string, signature: string, /*offsetInBytes: number,*/ headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { + constructor(name: string, signature: string, parentName: string, /*offsetInBytes: number,*/ headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { this.name = name; this.signature = signature; + this.parentName = parentName; this.offsetInBytes = 0; this.headerSizeInBytes = headerSizeInBytes; this.bodySizeInBytes = bodySizeInBytes; @@ -31,6 +33,10 @@ export abstract class CalldataBlock { return this.name; } + public getParentName(): string { + return this.parentName; + } + public getSignature(): string { return this.signature; } @@ -65,10 +71,10 @@ export abstract class CalldataBlock { export class PayloadCalldataBlock extends CalldataBlock { private payload: Buffer; - constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, payload: Buffer) { + constructor(name: string, signature: string, parentName: string, /*offsetInBytes: number,*/ relocatable: boolean, payload: Buffer) { const headerSizeInBytes = 0; const bodySizeInBytes = payload.byteLength; - super(name, signature, /*offsetInBytes,*/ headerSizeInBytes, bodySizeInBytes, relocatable); + super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable); this.payload = payload; } @@ -82,10 +88,10 @@ export class DependentCalldataBlock extends CalldataBlock { private parent: CalldataBlock; private dependency: CalldataBlock; - constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, dependency: CalldataBlock, parent: CalldataBlock) { + constructor(name: string, signature: string, parentName: string, relocatable: boolean, dependency: CalldataBlock, parent: CalldataBlock) { const headerSizeInBytes = 0; const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES; - super(name, signature, /*offsetInBytes,*/ headerSizeInBytes, bodySizeInBytes, relocatable); + super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable); this.parent = parent; this.dependency = dependency; } @@ -112,8 +118,8 @@ export class MemberCalldataBlock extends CalldataBlock { private members: CalldataBlock[]; private contiguous: boolean; - constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, contiguous: boolean) { - super(name, signature, /*offsetInBytes,*/ 0, 0, relocatable); + constructor(name: string, signature: string, parentName: string, relocatable: boolean, contiguous: boolean) { + super(name, signature, parentName, 0, 0, relocatable); this.members = []; this.header = undefined; this.contiguous = contiguous; @@ -231,12 +237,95 @@ export class Calldata { }) }*/ - public toHexString(optimize: boolean = false): string { + private generateAnnotatedHexString(): string { + let hexValue = `${this.selector}`; + if (this.root === undefined) { + throw new Error('expected root'); + } + + const valueQueue = this.createQueue(this.root); + + let block: CalldataBlock | undefined; + let offset = 0; + const functionBlock = valueQueue.peek(); + let functionName: string = functionBlock === undefined ? '' : functionBlock.getName(); + while ((block = valueQueue.pop()) !== undefined) { + // Set f + + // Process each block 1 word at a time + const size = block.getSizeInBytes(); + const name = block.getName(); + const parentName = block.getParentName(); + console.log('*'.repeat(50), parentName, ' vs ', name); + + //const ancestrialNamesOffset = name.startsWith('ptr<') ? 4 : 0; + //const parentOffset = name.lastIndexOf(parentName); + const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, '');//.replace(`${parentName}[`, '['); + const signature = block.getSignature(); + + // Current offset + let offsetStr = ''; + + // If this block is empty then it's a newline + let value = ''; + let nameStr = ''; + let line = ''; + if (size === 0) { + offsetStr = ' '.repeat(10); + value = ' '.repeat(74); + nameStr = `### ${prettyName.padEnd(80)}`; + line = `\n${offsetStr}${value}${nameStr}`; + } else { + offsetStr = `0x${offset.toString(16)}`.padEnd(10, ' '); + value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(0, 32))).padEnd(74); + if (block instanceof MemberCalldataBlock) { + nameStr = `### ${prettyName.padEnd(80)}`; + line = `\n${offsetStr}${value}${nameStr}`; + } else { + nameStr = ` ${prettyName.padEnd(80)}`; + line = `${offsetStr}${value}${nameStr}`; + } + } + + for (let j = 32; j < size; j += 32) { + offsetStr = `0x${(offset + j).toString(16)}`.padEnd(10, ' '); + value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(j, j + 32))).padEnd(74); + nameStr = ' '.repeat(40); + + line = `${line}\n${offsetStr}${value}${nameStr}`; + } + + // Append to hex value + hexValue = `${hexValue}\n${line}`; + offset += size; + } + + return hexValue; + } + + private generateCondensedHexString(): string { let selectorBuffer = ethUtil.toBuffer(this.selector); if (this.root === undefined) { throw new Error('expected root'); } + const valueQueue = this.createQueue(this.root); + const valueBufs: Buffer[] = [selectorBuffer]; + let block: CalldataBlock | undefined; + while ((block = valueQueue.pop()) !== undefined) { + valueBufs.push(block.toBuffer()); + } + + const combinedBuffers = Buffer.concat(valueBufs); + const hexValue = ethUtil.bufferToHex(combinedBuffers); + return hexValue; + } + + public toHexString(optimize: boolean = false, annotate: boolean = false): string { + if (this.root === undefined) { + throw new Error('expected root'); + } + const offsetQueue = this.createQueue(this.root); let block: CalldataBlock | undefined; let offset = 0; @@ -245,16 +334,10 @@ export class Calldata { offset += block.getSizeInBytes(); } - const valueQueue = this.createQueue(this.root); - const valueBufs: Buffer[] = [selectorBuffer]; - while ((block = valueQueue.pop()) !== undefined) { - valueBufs.push(block.toBuffer()); - } // if (optimize) this.optimize(valueQueue.getStore()); - const combinedBuffers = Buffer.concat(valueBufs); - const hexValue = ethUtil.bufferToHex(combinedBuffers); + const hexValue = annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString(); return hexValue; } diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 725d314f36..25f792c7ac 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -38,9 +38,9 @@ export abstract class PayloadDataType extends DataType { const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); - // const offsetInBytes = calldata.getSizeInBytes(); + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); const relocatable = false; - const block = new PayloadCalldataBlock(name, signature, /*offsetInBytes,*/ relocatable, encodedValue); + const block = new PayloadCalldataBlock(name, signature, parentName, /*offsetInBytes,*/ relocatable, encodedValue); return block; } @@ -77,11 +77,12 @@ export abstract class DependentDataType extends DataType { if (parentBlock === undefined) { throw new Error(`DependentDataType requires a parent block to generate its block`); } - const dependencyBlock = this.dependency.generateCalldataBlock(value); + const dependencyBlock = this.dependency.generateCalldataBlock(value, parentBlock); const name = this.getDataItem().name; const signature = this.getSignature(); + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); const relocatable = false; - const block = new DependentCalldataBlock(name, signature, /*offsetInBytes,*/ relocatable, dependencyBlock, parentBlock); + const block = new DependentCalldataBlock(name, signature, parentName, relocatable, dependencyBlock, parentBlock); return block; } @@ -179,7 +180,7 @@ export abstract class MemberDataType extends DataType { return [members, memberMap]; } - protected generateCalldataBlockFromArray(value: any[]): MemberCalldataBlock { + protected generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): MemberCalldataBlock { // Sanity check length if (this.arrayLength !== undefined && value.length !== this.arrayLength) { throw new Error( @@ -189,7 +190,8 @@ export abstract class MemberDataType extends DataType { ); } - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), this.isStatic(), false); + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), parentName, this.isStatic(), false); let members = this.members; if (this.isArray && this.arrayLength === undefined) { @@ -208,8 +210,9 @@ export abstract class MemberDataType extends DataType { return methodBlock; } - protected generateCalldataBlockFromObject(obj: object): MemberCalldataBlock { - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), this.isStatic(), false); + protected generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock { + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), parentName, this.isStatic(), false); const memberBlocks: CalldataBlock[] = []; let childMap = _.cloneDeep(this.memberMap); _.forOwn(obj, (value: any, key: string) => { @@ -230,7 +233,7 @@ export abstract class MemberDataType extends DataType { } public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { - const block = (value instanceof Array) ? this.generateCalldataBlockFromArray(value) : this.generateCalldataBlockFromObject(value); + const block = (value instanceof Array) ? this.generateCalldataBlockFromArray(value, parentBlock) : this.generateCalldataBlockFromObject(value, parentBlock); return block; } diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index 7b366a985e..ea401247b5 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -489,10 +489,10 @@ export class Method extends MemberDataType { return selector; } - public encode(value: any[] | object, calldata = new Calldata()): string { + public encode(value: any[] | object, calldata = new Calldata(), annotate: boolean = false): string { calldata.setSelector(this.methodSelector); super.encode(value, calldata); - return calldata.toHexString(); + return calldata.toHexString(false, annotate); } public decode(calldata: string, decodeStructsAsObjects: boolean = false): any[] | object { diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 137850b35a..8b836f77f8 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -17,6 +17,7 @@ import { assert } from '@0x/order-utils/src/assert'; import * as AbiEncoder from './abi/abi_encoder'; import * as AbiSamples from './abi_samples'; +import { Calldata } from './abi/calldata'; AbiEncoder.DataTypeFactory.setImpl(new AbiEncoder.EvmDataTypeFactoryImpl()); @@ -88,8 +89,12 @@ describe.only('ABI Encoder', () => { someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes }; - const calldata = method.encode(args); + const calldata = method.encode(args, new Calldata(), true); console.log(calldata); + + + throw new Error(`done`); + console.log('*'.repeat(40)); console.log(JSON.stringify(args)); console.log(method.getSignature()); From 457cb1dc843511d6e987b0ffce0f985d4e97c968 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 13 Nov 2018 15:55:07 -0800 Subject: [PATCH 108/230] optimizer works for basic case --- packages/order-utils/test/abi/calldata.ts | 98 +++++++++++++++++-- .../order-utils/test/abi/evm_data_types.ts | 4 +- packages/order-utils/test/abi_encoder_test.ts | 16 ++- 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 1173f90ccc..ab42b7d735 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -29,6 +29,10 @@ export abstract class CalldataBlock { this.bodySizeInBytes = bodySizeInBytes; } + protected setName(name: string) { + this.name = name; + } + public getName(): string { return this.name; } @@ -65,7 +69,14 @@ export abstract class CalldataBlock { this.offsetInBytes = offsetInBytes; } + public computeHash(): Buffer { + const rawData = this.getRawData(); + const hash = ethUtil.sha3(rawData); + return hash; + } + public abstract toBuffer(): Buffer; + public abstract getRawData(): Buffer; } export class PayloadCalldataBlock extends CalldataBlock { @@ -81,12 +92,19 @@ export class PayloadCalldataBlock extends CalldataBlock { public toBuffer(): Buffer { return this.payload; } + + public getRawData(): Buffer { + return this.payload; + } } export class DependentCalldataBlock extends CalldataBlock { public static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; + public static RAW_DATA_START = new Buffer('<'); + public static RAW_DATA_END = new Buffer('>'); private parent: CalldataBlock; private dependency: CalldataBlock; + private aliasFor: CalldataBlock | undefined; constructor(name: string, signature: string, parentName: string, relocatable: boolean, dependency: CalldataBlock, parent: CalldataBlock) { const headerSizeInBytes = 0; @@ -94,13 +112,14 @@ export class DependentCalldataBlock extends CalldataBlock { super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable); this.parent = parent; this.dependency = dependency; + this.aliasFor = undefined; } public toBuffer(): Buffer { - const dependencyOffset = this.dependency.getOffsetInBytes(); + const destinationOffset = (this.aliasFor !== undefined) ? this.aliasFor.getOffsetInBytes() : this.dependency.getOffsetInBytes(); const parentOffset = this.parent.getOffsetInBytes(); const parentHeaderSize = this.parent.getHeaderSizeInBytes(); - const pointer: number = dependencyOffset - (parentOffset + parentHeaderSize); + const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(16)}`); const evmWordWidthInBytes = 32; const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); @@ -110,6 +129,25 @@ export class DependentCalldataBlock extends CalldataBlock { public getDependency(): CalldataBlock { return this.dependency; } + + public setAlias(block: CalldataBlock) { + this.aliasFor = block; + this.setName(`${this.getName()} (alias for ${block.getName()})`); + } + + public getAlias(): CalldataBlock | undefined { + return this.aliasFor; + } + + public getRawData(): Buffer { + const dependencyRawData = this.dependency.getRawData(); + const rawDataComponents: Buffer[] = []; + rawDataComponents.push(DependentCalldataBlock.RAW_DATA_START); + rawDataComponents.push(dependencyRawData); + rawDataComponents.push(DependentCalldataBlock.RAW_DATA_END); + const rawData = Buffer.concat(rawDataComponents); + return rawData; + } } export class MemberCalldataBlock extends CalldataBlock { @@ -125,6 +163,20 @@ export class MemberCalldataBlock extends CalldataBlock { this.contiguous = contiguous; } + public getRawData(): Buffer { + const rawDataComponents: Buffer[] = []; + if (this.header !== undefined) { + rawDataComponents.push(this.header); + } + _.each(this.members, (member: CalldataBlock) => { + const memberBuffer = member.getRawData(); + rawDataComponents.push(memberBuffer); + }); + + const rawData = Buffer.concat(rawDataComponents); + return rawData; + } + public setMembers(members: CalldataBlock[]) { let bodySizeInBytes = 0; _.each(members, (member: CalldataBlock) => { @@ -201,8 +253,8 @@ export class Calldata { // Children _.each(memberBlock.getMembers(), (member: CalldataBlock) => { - if (member instanceof DependentCalldataBlock) { - const dependency = member.getDependency(); + if (member instanceof DependentCalldataBlock && member.getAlias() === undefined) { + let dependency = member.getDependency(); if (dependency instanceof MemberCalldataBlock) { blockQueue.merge(this.createQueue(dependency)); } else { @@ -321,6 +373,41 @@ export class Calldata { return hexValue; } + public optimize() { + if (this.root === undefined) { + throw new Error('expected root'); + } + + const subtreesByHash: { [key: string]: DependentCalldataBlock[] } = {}; + + // 1. Create a queue of subtrees by hash + // Note that they are ordered the same as + const subtreeQueue = this.createQueue(this.root); + let block: CalldataBlock | undefined; + while ((block = subtreeQueue.pop()) !== undefined) { + console.log(block.getName()); + + if (block instanceof DependentCalldataBlock === false) continue; + const blockHashBuf = block.computeHash(); + const blockHashHex = ethUtil.bufferToHex(blockHashBuf); + if (blockHashHex in subtreesByHash === false) { + subtreesByHash[blockHashHex] = [block as DependentCalldataBlock]; + } else { + subtreesByHash[blockHashHex].push(block as DependentCalldataBlock); + } + } + + // Iterate through trees that have the same hash and + _.each(subtreesByHash, (subtrees: DependentCalldataBlock[], hash: string) => { + if (subtrees.length === 1) return; // No optimization + // Optimize + const lastSubtree = subtrees[subtrees.length - 1]; + for (let i = 0; i < subtrees.length - 1; ++i) { + subtrees[i].setAlias(lastSubtree); + } + }); + } + public toHexString(optimize: boolean = false, annotate: boolean = false): string { if (this.root === undefined) { throw new Error('expected root'); @@ -334,8 +421,7 @@ export class Calldata { offset += block.getSizeInBytes(); } - - // if (optimize) this.optimize(valueQueue.getStore()); + if (optimize) this.optimize(); const hexValue = annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString(); return hexValue; diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index ea401247b5..a240466648 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -489,10 +489,10 @@ export class Method extends MemberDataType { return selector; } - public encode(value: any[] | object, calldata = new Calldata(), annotate: boolean = false): string { + public encode(value: any[] | object, calldata = new Calldata(), annotate: boolean = false, optimize: boolean = false): string { calldata.setSelector(this.methodSelector); super.encode(value, calldata); - return calldata.toHexString(false, annotate); + return calldata.toHexString(optimize, annotate); } public decode(calldata: string, decodeStructsAsObjects: boolean = false): any[] | object { diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 8b836f77f8..5ef4203b78 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -27,7 +27,21 @@ const expect = chai.expect; describe.only('ABI Encoder', () => { describe.only('ABI Tests at Method Level', () => { - it.only('Crazy ABI', async () => { + it.only('Optimizer', async () => { + const method = new AbiEncoder.Method(AbiSamples.stringAbi); + const strings = [ + "Test String", + "Test String 2", + "Test String", + "Test String 2", + ]; + const args = [strings]; + + const optimizedCalldata = method.encode(args, new Calldata(), true, true); + console.log(optimizedCalldata); + }); + + it('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); From 27cb966991c9408025106624f36927548b9e873c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 13 Nov 2018 16:17:24 -0800 Subject: [PATCH 109/230] Alias now points to dependency, not pointer --- packages/order-utils/test/abi/calldata.ts | 6 ++--- packages/order-utils/test/abi_encoder_test.ts | 25 ++++++++++++++++++- packages/order-utils/test/abi_samples.ts | 19 ++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index ab42b7d735..04bea9628f 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -403,7 +403,7 @@ export class Calldata { // Optimize const lastSubtree = subtrees[subtrees.length - 1]; for (let i = 0; i < subtrees.length - 1; ++i) { - subtrees[i].setAlias(lastSubtree); + subtrees[i].setAlias(lastSubtree.getDependency()); } }); } @@ -413,6 +413,8 @@ export class Calldata { throw new Error('expected root'); } + if (optimize) this.optimize(); + const offsetQueue = this.createQueue(this.root); let block: CalldataBlock | undefined; let offset = 0; @@ -421,8 +423,6 @@ export class Calldata { offset += block.getSizeInBytes(); } - if (optimize) this.optimize(); - const hexValue = annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString(); return hexValue; } diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 5ef4203b78..661fb62a6c 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -27,7 +27,7 @@ const expect = chai.expect; describe.only('ABI Encoder', () => { describe.only('ABI Tests at Method Level', () => { - it.only('Optimizer', async () => { + it('Optimizer #1', async () => { const method = new AbiEncoder.Method(AbiSamples.stringAbi); const strings = [ "Test String", @@ -41,6 +41,29 @@ describe.only('ABI Encoder', () => { console.log(optimizedCalldata); }); + it.only('Optimizer #2', async () => { + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi2); + const stringArray = [ + "Test String", + "Test String", + "Test String", + "Test String", + ]; + const string = 'Test String'; + const args = [stringArray, string]; + + + const TEST = method.encode(args, new Calldata(), true, true); + console.log(TEST); + + const optimizedCalldata = method.encode(args, new Calldata(), false, true); + + console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + const decodedArgs = method.decode(optimizedCalldata); + console.log(JSON.stringify(decodedArgs)); + //expect(decodedArgs).to.be.equal(args); + }); + it('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index 3f7b1a9278..fa20c38f07 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -34,6 +34,25 @@ export const stringAbi = { type: 'function', } as MethodAbi; +export const optimizerAbi2 = { + constant: false, + inputs: [ + { + name: 'stringArray', + type: 'string[]', + }, + { + name: 'string', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const typesWithDefaultWidthsAbi = { constant: false, inputs: [ From b71577cc52d7f933ee7e5dc50e5b61457f04e3f4 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 13 Nov 2018 17:06:41 -0800 Subject: [PATCH 110/230] More robust implementation for optimizer --- packages/order-utils/test/abi/calldata.ts | 41 +++++++++++++---------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 04bea9628f..754d8f8235 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -216,6 +216,11 @@ class Queue { pop(): T | undefined { return this.store.shift(); } + popBack(): T | undefined { + if (this.store.length === 0) return undefined; + const backElement = this.store.splice(-1, 1)[0]; + return backElement; + } merge(q: Queue) { this.store = this.store.concat(q.store); } @@ -378,34 +383,34 @@ export class Calldata { throw new Error('expected root'); } - const subtreesByHash: { [key: string]: DependentCalldataBlock[] } = {}; + const blocksByHash: { [key: string]: CalldataBlock } = {}; // 1. Create a queue of subtrees by hash // Note that they are ordered the same as const subtreeQueue = this.createQueue(this.root); let block: CalldataBlock | undefined; - while ((block = subtreeQueue.pop()) !== undefined) { + console.log('*'.repeat(100), ' OPTIMIZING ', '*'.repeat(100)); + while ((block = subtreeQueue.popBack()) !== undefined) { console.log(block.getName()); + if (block instanceof DependentCalldataBlock) { + const blockHashBuf = block.getDependency().computeHash(); + const blockHash = ethUtil.bufferToHex(blockHashBuf); + if (blockHash in blocksByHash) { + const blockWithSameHash = blocksByHash[blockHash]; + if (blockWithSameHash !== block.getDependency()) { + block.setAlias(blockWithSameHash); + } + } + continue; + } - if (block instanceof DependentCalldataBlock === false) continue; const blockHashBuf = block.computeHash(); - const blockHashHex = ethUtil.bufferToHex(blockHashBuf); - if (blockHashHex in subtreesByHash === false) { - subtreesByHash[blockHashHex] = [block as DependentCalldataBlock]; - } else { - subtreesByHash[blockHashHex].push(block as DependentCalldataBlock); + const blockHash = ethUtil.bufferToHex(blockHashBuf); + if (blockHash in blocksByHash === false) { + blocksByHash[blockHash] = block; } } - - // Iterate through trees that have the same hash and - _.each(subtreesByHash, (subtrees: DependentCalldataBlock[], hash: string) => { - if (subtrees.length === 1) return; // No optimization - // Optimize - const lastSubtree = subtrees[subtrees.length - 1]; - for (let i = 0; i < subtrees.length - 1; ++i) { - subtrees[i].setAlias(lastSubtree.getDependency()); - } - }); + console.log('*'.repeat(100), ' FINISHED OPTIMIZING ', '*'.repeat(100)); } public toHexString(optimize: boolean = false, annotate: boolean = false): string { From 93e967c3b356f0081254946bbf8d6875fc791a09 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 13 Nov 2018 17:40:14 -0800 Subject: [PATCH 111/230] Some tests --- packages/order-utils/test/abi_encoder_test.ts | 57 ++++++++++++++++++- packages/order-utils/test/abi_samples.ts | 50 ++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 661fb62a6c..78e28015dd 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -41,7 +41,7 @@ describe.only('ABI Encoder', () => { console.log(optimizedCalldata); }); - it.only('Optimizer #2', async () => { + it('Optimizer #2', async () => { const method = new AbiEncoder.Method(AbiSamples.optimizerAbi2); const stringArray = [ "Test String", @@ -60,8 +60,61 @@ describe.only('ABI Encoder', () => { console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); console.log(JSON.stringify(decodedArgs)); - //expect(decodedArgs).to.be.equal(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + + + it('Optimizer #3 (tuple should point to array)', async () => { + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi3); + const uint8Array = [ + new BigNumber(100), + new BigNumber(150), + new BigNumber(200), + new BigNumber(225), + ]; + const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; + const args = [uint8Array, uintTupleArray]; + + + const TEST = method.encode(args, new Calldata(), true, true); + console.log('*'.repeat(50), ' ENCODED DATA ', TEST); + + const optimizedCalldata = method.encode(args, new Calldata(), false, true); + + console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + console.log(JSON.stringify(decodedArgs)); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + + it.only('Optimizer #4 (Expect no optimization)', async () => { + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi4); + const uint8Array = [ + new BigNumber(100), + new BigNumber(150), + new BigNumber(200), + new BigNumber(225), + ]; + const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; + const args = [uint8Array, uintTupleArray]; + + + const TEST = method.encode(args, new Calldata(), true, true); + console.log('*'.repeat(50), ' ENCODED DATA ', TEST); + + const optimizedCalldata = method.encode(args, new Calldata(), false, true); + + console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + console.log(JSON.stringify(decodedArgs)); + expect(decodedArgsJson).to.be.equal(argsJson); }); it('Crazy ABI', async () => { diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts index fa20c38f07..5e8268f1a0 100644 --- a/packages/order-utils/test/abi_samples.ts +++ b/packages/order-utils/test/abi_samples.ts @@ -53,6 +53,56 @@ export const optimizerAbi2 = { type: 'function', } as MethodAbi; +export const optimizerAbi3 = { + constant: false, + inputs: [ + { + name: 'uint8Array', + type: 'uint8[]', + }, + { + components: [ + { + name: 'uint', + type: 'uint', + }, + ], + name: 'uintTuple', + type: 'tuple[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const optimizerAbi4 = { + constant: false, + inputs: [ + { + name: 'uint8Array', + type: 'uint8[4]', + }, + { + components: [ + { + name: 'uint', + type: 'uint', + }, + ], + name: 'uintTuple', + type: 'tuple[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const typesWithDefaultWidthsAbi = { constant: false, inputs: [ From 2d2255e9af42f6441ee620c4aa74bd1981dfe03a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 13:51:08 -0800 Subject: [PATCH 112/230] Cleaner interface for encoding/decoding. Moved encode/decode parameters into a struct. --- packages/order-utils/test/abi/calldata.ts | 43 ++++++---------- packages/order-utils/test/abi/data_type.ts | 49 ++++++++++--------- .../order-utils/test/abi/evm_data_types.ts | 25 +++++----- packages/order-utils/test/abi_encoder_test.ts | 25 +++++----- 4 files changed, 63 insertions(+), 79 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 754d8f8235..7eb4e0026c 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -2,6 +2,15 @@ import ethUtil = require('ethereumjs-util'); import CommunicationChatBubbleOutline from 'material-ui/SvgIcon'; var _ = require('lodash'); +export interface DecodingRules { + structsAsObjects: boolean; +} + +export interface EncodingRules { + optimize?: boolean; + annotate?: boolean; +} + export abstract class CalldataBlock { private name: string; private signature: string; @@ -237,11 +246,13 @@ class Queue { export class Calldata { private selector: string; + private rules: EncodingRules; private sizeInBytes: number; private root: MemberCalldataBlock | undefined; - constructor() { - this.selector = '0x'; + constructor(rules: EncodingRules) { + this.selector = ''; + this.rules = rules; this.sizeInBytes = 0; this.root = undefined; } @@ -272,28 +283,6 @@ export class Calldata { return blockQueue; } - /* - - // Basic optimize method that prunes duplicate branches of the tree - // Notes: - // 1. Pruning is at the calldata block level, so it is independent of type - // 2. - private optimize(blocks: CalldataBlock[]) { - // Build hash table of blocks - const blockLookupTable: { [key: string]: string } = {}; - _.each(blocks, (block: CalldataBlock) => { - if (blocks instanceof DependentCalldataBlock === false) { - - return; - } - - const leavesHash = block.hashLeaves(); - if (leavesHash in blockLookupTable) { - - } - }) - }*/ - private generateAnnotatedHexString(): string { let hexValue = `${this.selector}`; if (this.root === undefined) { @@ -413,12 +402,12 @@ export class Calldata { console.log('*'.repeat(100), ' FINISHED OPTIMIZING ', '*'.repeat(100)); } - public toHexString(optimize: boolean = false, annotate: boolean = false): string { + public toHexString(): string { if (this.root === undefined) { throw new Error('expected root'); } - if (optimize) this.optimize(); + if (this.rules.optimize) this.optimize(); const offsetQueue = this.createQueue(this.root); let block: CalldataBlock | undefined; @@ -428,7 +417,7 @@ export class Calldata { offset += block.getSizeInBytes(); } - const hexValue = annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString(); + const hexValue = this.rules.annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString(); return hexValue; } diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 25f792c7ac..6ad0b27360 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -1,14 +1,14 @@ import { RawCalldata, Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata"; import { MethodAbi, DataItem } from 'ethereum-types'; +import { DecodingRules, EncodingRules } from './calldata'; import { BigNumber } from '@0x/utils'; import ethUtil = require('ethereumjs-util'); var _ = require('lodash'); -export interface GenerateValueRules { - structsAsObjects: boolean; -} - export abstract class DataType { + private static DEFAULT_ENCODING_RULES = { optimize: false, annotate: false } as EncodingRules; + private static DEFAULT_DECODING_RULES = { structsAsObjects: false } as DecodingRules; + private dataItem: DataItem; constructor(dataItem: DataItem) { @@ -19,9 +19,25 @@ export abstract class DataType { return this.dataItem; } + public encode(value: any, rules?: EncodingRules, selector?: string): string { + const rules_ = rules ? rules : DataType.DEFAULT_ENCODING_RULES; + const calldata = new Calldata(rules_); + if (selector) calldata.setSelector(selector); + const block = this.generateCalldataBlock(value); + calldata.setRoot(block as MemberCalldataBlock); // @TODO CHANGE + const calldataHex = calldata.toHexString(); + return calldataHex; + } + + public decode(calldata: string, rules?: DecodingRules): any { + const rawCalldata = new RawCalldata(calldata); + const rules_ = rules ? rules : DataType.DEFAULT_DECODING_RULES; + const value = this.generateValue(rawCalldata, rules_); + return value; + } + public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; - public abstract generateValue(calldata: RawCalldata, rules: GenerateValueRules): any; - public abstract encode(value: any, calldata: Calldata): void; + public abstract generateValue(calldata: RawCalldata, rules: DecodingRules): any; public abstract getSignature(): string; public abstract isStatic(): boolean; } @@ -44,12 +60,7 @@ export abstract class PayloadDataType extends DataType { return block; } - public encode(value: any, calldata: Calldata): void { - const block = this.generateCalldataBlock(value); - // calldata.setRoot(block); - } - - public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any { + public generateValue(calldata: RawCalldata, rules: DecodingRules): any { const value = this.decodeValue(calldata); return value; } @@ -86,12 +97,7 @@ export abstract class DependentDataType extends DataType { return block; } - public encode(value: any, calldata: Calldata = new Calldata()): void { - const block = this.generateCalldataBlock(value); - //calldata.setRoot(block); - } - - public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any { + public generateValue(calldata: RawCalldata, rules: DecodingRules): any { const destinationOffsetBuf = calldata.popWord(); const currentOffset = calldata.getOffset(); const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), 16); @@ -237,12 +243,7 @@ export abstract class MemberDataType extends DataType { return block; } - public encode(value: any, calldata: Calldata = new Calldata()): void { - const block = this.generateCalldataBlock(value); - calldata.setRoot(block); - } - - public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any[] | object { + public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object { let members = this.members; if (this.isArray && this.arrayLength === undefined) { const arrayLengthBuf = calldata.popWord(); diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index a240466648..9504b1a101 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -1,4 +1,6 @@ -import { GenerateValueRules, DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; +import { DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; + +import { DecodingRules, EncodingRules } from './calldata'; import { MethodAbi, DataItem } from 'ethereum-types'; @@ -13,7 +15,7 @@ var _ = require('lodash'); export interface DataTypeStaticInterface { matchGrammar: (type: string) => boolean; encodeValue: (value: any) => Buffer; - // decodeValue: (value: Buffer) => [any, Buffer]; + decodeValue: (rawCalldata: RawCalldata) => any; } export class Address extends PayloadDataType { @@ -489,21 +491,16 @@ export class Method extends MemberDataType { return selector; } - public encode(value: any[] | object, calldata = new Calldata(), annotate: boolean = false, optimize: boolean = false): string { - calldata.setSelector(this.methodSelector); - super.encode(value, calldata); - return calldata.toHexString(optimize, annotate); + public encode(value: any, rules?: EncodingRules): string { + const calldata = super.encode(value, rules, this.selector); + return calldata; } - public decode(calldata: string, decodeStructsAsObjects: boolean = false): any[] | object { - const calldata_ = new RawCalldata(calldata); - if (this.selector !== calldata_.getSelector()) { - throw new Error(`Tried to decode calldata with mismatched selector. Expected '${this.selector}', got '${calldata_.getSelector()}'`); + public decode(calldata: string, rules?: DecodingRules): any[] | object { + if (!calldata.startsWith(this.selector)) { + throw new Error(`Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`); } - let rules: GenerateValueRules = { - structsAsObjects: decodeStructsAsObjects - }; - const value = super.generateValue(calldata_, rules); + const value = super.decode(calldata, rules); return value; } diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 78e28015dd..52b6bc067c 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -27,7 +27,7 @@ const expect = chai.expect; describe.only('ABI Encoder', () => { describe.only('ABI Tests at Method Level', () => { - it('Optimizer #1', async () => { + it('Should reuse duplicated strings in string array', async () => { const method = new AbiEncoder.Method(AbiSamples.stringAbi); const strings = [ "Test String", @@ -37,7 +37,7 @@ describe.only('ABI Encoder', () => { ]; const args = [strings]; - const optimizedCalldata = method.encode(args, new Calldata(), true, true); + const optimizedCalldata = method.encode(args, { optimize: true, annotate: true }); console.log(optimizedCalldata); }); @@ -53,10 +53,10 @@ describe.only('ABI Encoder', () => { const args = [stringArray, string]; - const TEST = method.encode(args, new Calldata(), true, true); + const TEST = method.encode(args, { optimize: true, annotate: true }); console.log(TEST); - const optimizedCalldata = method.encode(args, new Calldata(), false, true); + const optimizedCalldata = method.encode(args, { optimize: true }); console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); const decodedArgs = method.decode(optimizedCalldata); @@ -79,10 +79,10 @@ describe.only('ABI Encoder', () => { const args = [uint8Array, uintTupleArray]; - const TEST = method.encode(args, new Calldata(), true, true); + const TEST = method.encode(args, { optimize: true, annotate: true }); console.log('*'.repeat(50), ' ENCODED DATA ', TEST); - const optimizedCalldata = method.encode(args, new Calldata(), false, true); + const optimizedCalldata = method.encode(args, { optimize: true }); console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); const decodedArgs = method.decode(optimizedCalldata); @@ -92,7 +92,7 @@ describe.only('ABI Encoder', () => { expect(decodedArgsJson).to.be.equal(argsJson); }); - it.only('Optimizer #4 (Expect no optimization)', async () => { + it('Optimizer #4 (Expect no optimization)', async () => { const method = new AbiEncoder.Method(AbiSamples.optimizerAbi4); const uint8Array = [ new BigNumber(100), @@ -104,10 +104,10 @@ describe.only('ABI Encoder', () => { const args = [uint8Array, uintTupleArray]; - const TEST = method.encode(args, new Calldata(), true, true); + const TEST = method.encode(args, { optimize: true, annotate: true }); console.log('*'.repeat(50), ' ENCODED DATA ', TEST); - const optimizedCalldata = method.encode(args, new Calldata(), false, true); + const optimizedCalldata = method.encode(args, { optimize: true }); console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); const decodedArgs = method.decode(optimizedCalldata); @@ -179,12 +179,9 @@ describe.only('ABI Encoder', () => { someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes }; - const calldata = method.encode(args, new Calldata(), true); + const calldata = method.encode(args); console.log(calldata); - - throw new Error(`done`); - console.log('*'.repeat(40)); console.log(JSON.stringify(args)); console.log(method.getSignature()); @@ -195,7 +192,7 @@ describe.only('ABI Encoder', () => { // Test decoding const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata, true); + const decodedValue = method.decode(calldata, { structsAsObjects: true }); const decodedValueJson = JSON.stringify(decodedValue); console.log(`DECODED`, '*'.repeat(200), '\n', decodedValueJson); expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); From 063871e549483ba36e5ee2317249eb6dccda24a4 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 13:53:15 -0800 Subject: [PATCH 113/230] removed old log messages --- packages/order-utils/test/abi/calldata.ts | 5 ----- packages/order-utils/test/abi/data_type.ts | 1 - packages/order-utils/test/abi/evm_data_types.ts | 5 ----- 3 files changed, 11 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 7eb4e0026c..32278e5c5b 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -302,7 +302,6 @@ export class Calldata { const size = block.getSizeInBytes(); const name = block.getName(); const parentName = block.getParentName(); - console.log('*'.repeat(50), parentName, ' vs ', name); //const ancestrialNamesOffset = name.startsWith('ptr<') ? 4 : 0; //const parentOffset = name.lastIndexOf(parentName); @@ -378,9 +377,7 @@ export class Calldata { // Note that they are ordered the same as const subtreeQueue = this.createQueue(this.root); let block: CalldataBlock | undefined; - console.log('*'.repeat(100), ' OPTIMIZING ', '*'.repeat(100)); while ((block = subtreeQueue.popBack()) !== undefined) { - console.log(block.getName()); if (block instanceof DependentCalldataBlock) { const blockHashBuf = block.getDependency().computeHash(); const blockHash = ethUtil.bufferToHex(blockHashBuf); @@ -399,7 +396,6 @@ export class Calldata { blocksByHash[blockHash] = block; } } - console.log('*'.repeat(100), ' FINISHED OPTIMIZING ', '*'.repeat(100)); } public toHexString(): string { @@ -495,7 +491,6 @@ export class RawCalldata { public setOffset(offsetInBytes: number) { this.offset = offsetInBytes; - console.log('0'.repeat(100), this.offset); } public startScope() { diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 6ad0b27360..7884cf3cf2 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -165,7 +165,6 @@ export abstract class MemberDataType extends DataType { } private createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { - console.log('!'.repeat(30), dataItem); let members: DataType[] = []; let memberMap: MemberMap = {}; const range = _.range(length); diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index 9504b1a101..aff4bc991c 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -107,7 +107,6 @@ abstract class Number extends PayloadDataType { } public encodeValue(value: BigNumber): Buffer { - console.log(value); if (value.greaterThan(this.getMaxValue())) { throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; } else if (value.lessThan(this.getMinValue())) { @@ -366,7 +365,6 @@ export class SolString extends PayloadDataType { const wordsForValue = Math.ceil(length / 32); const paddedValueBuf = calldata.popWords(wordsForValue); const valueBuf = paddedValueBuf.slice(0, length); - console.log('LENGTH UPINYA === ', length); const value = valueBuf.toString('ascii'); return value; } @@ -397,7 +395,6 @@ export class Tuple extends MemberDataType { private tupleSignature: string; constructor(dataItem: DataItem) { - console.log(dataItem); super(dataItem); if (!Tuple.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); @@ -516,8 +513,6 @@ export class Method extends MemberDataType { export class EvmDataTypeFactoryImpl implements DataTypeFactoryImpl { public mapDataItemToDataType(dataItem: DataItem): DataType { - console.log(`Type: ${dataItem.type}`); - if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); From 3ae434c31e720cb1caf65d776b75524dab388a2f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 14:27:33 -0800 Subject: [PATCH 114/230] Adding optimizer tests --- packages/order-utils/test/abi_encoder_test.ts | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 52b6bc067c..8ae23a2220 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -25,6 +25,10 @@ chaiSetup.configure(); const expect = chai.expect; describe.only('ABI Encoder', () => { + describe.only('Optimizer', () => { + + }); + describe.only('ABI Tests at Method Level', () => { it('Should reuse duplicated strings in string array', async () => { @@ -37,33 +41,45 @@ describe.only('ABI Encoder', () => { ]; const args = [strings]; - const optimizedCalldata = method.encode(args, { optimize: true, annotate: true }); - console.log(optimizedCalldata); + // Verify optimized calldata is expected + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + + // Verify args decode properly + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); }); - it('Optimizer #2', async () => { + it.only('Should point array elements to a duplicated value from another parameter', async () => { const method = new AbiEncoder.Method(AbiSamples.optimizerAbi2); const stringArray = [ "Test String", "Test String", "Test String", - "Test String", + "Test String 2", ]; const string = 'Test String'; const args = [stringArray, string]; - - const TEST = method.encode(args, { optimize: true, annotate: true }); - console.log(TEST); - + // Verify optimized calldata is expected const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + // Verify args decode properly const decodedArgs = method.decode(optimizedCalldata); const decodedArgsJson = JSON.stringify(decodedArgs); const argsJson = JSON.stringify(args); - console.log(JSON.stringify(decodedArgs)); expect(decodedArgsJson).to.be.equal(argsJson); + + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); }); From 1f22ce60610617b8176be0f49588067b809907cb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 14:46:12 -0800 Subject: [PATCH 115/230] Removed config.ts --- packages/order-utils/test/abi/abi_encoder.ts | 1 - packages/order-utils/test/abi/config.ts | 4 -- packages/order-utils/test/abi/data_type.ts | 72 ++++++------------- .../order-utils/test/abi/evm_data_types.ts | 36 ++++++---- packages/order-utils/test/abi_encoder_test.ts | 4 +- 5 files changed, 44 insertions(+), 73 deletions(-) delete mode 100644 packages/order-utils/test/abi/config.ts diff --git a/packages/order-utils/test/abi/abi_encoder.ts b/packages/order-utils/test/abi/abi_encoder.ts index ddcfb1fd12..4f4906550f 100644 --- a/packages/order-utils/test/abi/abi_encoder.ts +++ b/packages/order-utils/test/abi/abi_encoder.ts @@ -1,4 +1,3 @@ -export * from './config'; export * from './calldata'; export * from './data_type'; export * from './evm_data_types'; \ No newline at end of file diff --git a/packages/order-utils/test/abi/config.ts b/packages/order-utils/test/abi/config.ts deleted file mode 100644 index 015cee59ad..0000000000 --- a/packages/order-utils/test/abi/config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DataTypeFactory } from './data_type'; -import { EvmDataTypeFactoryImpl } from './evm_data_types'; - -DataTypeFactory.setImpl(new EvmDataTypeFactoryImpl()); \ No newline at end of file diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 7884cf3cf2..4c4537a69b 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -5,20 +5,31 @@ import { BigNumber } from '@0x/utils'; import ethUtil = require('ethereumjs-util'); var _ = require('lodash'); +export interface DataTypeFactory { + create: (dataItem: DataItem, parentDataType: DataType) => DataType; + mapDataItemToDataType: (dataItem: DataItem) => DataType; +} + export abstract class DataType { private static DEFAULT_ENCODING_RULES = { optimize: false, annotate: false } as EncodingRules; private static DEFAULT_DECODING_RULES = { structsAsObjects: false } as DecodingRules; private dataItem: DataItem; + private factory: DataTypeFactory; - constructor(dataItem: DataItem) { + constructor(dataItem: DataItem, factory: DataTypeFactory) { this.dataItem = dataItem; + this.factory = factory; } public getDataItem(): DataItem { return this.dataItem; } + public getFactory(): DataTypeFactory { + return this.factory; + } + public encode(value: any, rules?: EncodingRules, selector?: string): string { const rules_ = rules ? rules : DataType.DEFAULT_ENCODING_RULES; const calldata = new Calldata(rules_); @@ -45,8 +56,8 @@ export abstract class DataType { export abstract class PayloadDataType extends DataType { protected hasConstantSize: boolean; - public constructor(dataItem: DataItem, hasConstantSize: boolean) { - super(dataItem); + public constructor(dataItem: DataItem, factory: DataTypeFactory, hasConstantSize: boolean) { + super(dataItem, factory); this.hasConstantSize = hasConstantSize; } @@ -78,8 +89,8 @@ export abstract class DependentDataType extends DataType { protected dependency: DataType; protected parent: DataType; - public constructor(dataItem: DataItem, dependency: DataType, parent: DataType) { - super(dataItem); + public constructor(dataItem: DataItem, factory: DataTypeFactory, dependency: DataType, parent: DataType) { + super(dataItem, factory); this.dependency = dependency; this.parent = parent; } @@ -125,8 +136,8 @@ export abstract class MemberDataType extends DataType { protected arrayElementType: string | undefined; - public constructor(dataItem: DataItem, isArray: boolean = false, arrayLength?: number, arrayElementType?: string) { - super(dataItem); + public constructor(dataItem: DataItem, factory: DataTypeFactory, isArray: boolean = false, arrayLength?: number, arrayElementType?: string) { + super(dataItem, factory); this.memberMap = {}; this.members = []; this.isArray = isArray; @@ -156,7 +167,7 @@ export abstract class MemberDataType extends DataType { if (components !== undefined) { childDataItem.components = components; } - const child = DataTypeFactory.create(childDataItem, this); + const child = this.getFactory().create(childDataItem, this); memberMap[memberItem.name] = members.length; members.push(child); }); @@ -177,7 +188,7 @@ export abstract class MemberDataType extends DataType { if (components !== undefined) { childDataItem.components = components; } - const child = DataTypeFactory.create(childDataItem, this); + const child = this.getFactory().create(childDataItem, this); memberMap[idx.toString(10)] = members.length; members.push(child); }); @@ -309,46 +320,3 @@ export abstract class MemberDataType extends DataType { return isStatic; } } - -export interface DataTypeFactoryImpl { - create: (dataItem: DataItem, parentDataType: DataType) => DataType; - mapDataItemToDataType: (dataItem: DataItem) => DataType; -} - -export class DataTypeFactory { - private static instance: DataTypeFactory; - private provider: DataTypeFactoryImpl | undefined; - - private constructor() { } - - private static getInstance(): DataTypeFactory { - if (!DataTypeFactory.instance) { - DataTypeFactory.instance = new DataTypeFactory(); - } - return DataTypeFactory.instance; - } - - public static setImpl(provider: DataTypeFactoryImpl) { - const instance = DataTypeFactory.getInstance(); - if (instance.provider !== undefined) { - throw new Error(`Tried to set implementation more than once`); - } - DataTypeFactory.getInstance().provider = provider; - } - - public static create(dataItem: DataItem, parentDataType: DataType): DataType { - const instance = DataTypeFactory.getInstance(); - if (instance.provider === undefined) { - throw new Error(`Tried to create before implementation is set`); - } - return instance.provider.create(dataItem, parentDataType); - } - - public static mapDataItemToDataType(dataItem: DataItem): DataType { - const instance = DataTypeFactory.getInstance(); - if (instance.provider === undefined) { - throw new Error(`Tried to create before implementation is set`); - } - return instance.provider.mapDataItemToDataType(dataItem); - } -} \ No newline at end of file diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index aff4bc991c..bb7c1d81d0 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -1,4 +1,4 @@ -import { DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; +import { DataType, DataTypeFactory, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; import { DecodingRules, EncodingRules } from './calldata'; @@ -22,7 +22,7 @@ export class Address extends PayloadDataType { private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; constructor(dataItem: DataItem) { - super(dataItem, Address.SIZE_KNOWN_AT_COMPILE_TIME); + super(dataItem, EvmDataTypeFactory.getInstance(), Address.SIZE_KNOWN_AT_COMPILE_TIME); if (!Address.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`); } @@ -54,7 +54,7 @@ export class Bool extends PayloadDataType { private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; constructor(dataItem: DataItem) { - super(dataItem, Bool.SIZE_KNOWN_AT_COMPILE_TIME); + super(dataItem, EvmDataTypeFactory.getInstance(), Bool.SIZE_KNOWN_AT_COMPILE_TIME); if (!Bool.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`); } @@ -94,7 +94,7 @@ abstract class Number extends PayloadDataType { width: number = Number.DEFAULT_WIDTH; constructor(dataItem: DataItem, matcher: RegExp) { - super(dataItem, Number.SIZE_KNOWN_AT_COMPILE_TIME); + super(dataItem, EvmDataTypeFactory.getInstance(), Number.SIZE_KNOWN_AT_COMPILE_TIME); const matches = matcher.exec(dataItem.type); if (matches === null) { throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`); @@ -243,7 +243,7 @@ export class Byte extends PayloadDataType { width: number = Byte.DEFAULT_WIDTH; constructor(dataItem: DataItem) { - super(dataItem, Byte.SIZE_KNOWN_AT_COMPILE_TIME); + super(dataItem, EvmDataTypeFactory.getInstance(), Byte.SIZE_KNOWN_AT_COMPILE_TIME); const matches = Byte.matcher.exec(dataItem.type); if (!Byte.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`); @@ -297,7 +297,7 @@ export class Bytes extends PayloadDataType { length: BigNumber = Bytes.UNDEFINED_LENGTH; constructor(dataItem: DataItem) { - super(dataItem, Bytes.SIZE_KNOWN_AT_COMPILE_TIME); + super(dataItem, EvmDataTypeFactory.getInstance(), Bytes.SIZE_KNOWN_AT_COMPILE_TIME); if (!Bytes.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Bytes with bad input: ${dataItem}`); } @@ -343,7 +343,7 @@ export class Bytes extends PayloadDataType { export class SolString extends PayloadDataType { private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; constructor(dataItem: DataItem) { - super(dataItem, SolString.SIZE_KNOWN_AT_COMPILE_TIME); + super(dataItem, EvmDataTypeFactory.getInstance(), SolString.SIZE_KNOWN_AT_COMPILE_TIME); if (!SolString.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate String with bad input: ${dataItem}`); } @@ -383,7 +383,7 @@ export class Pointer extends DependentDataType { constructor(destDataType: DataType, parentDataType: DataType) { const destDataItem = destDataType.getDataItem(); const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; - super(dataItem, destDataType, parentDataType); + super(dataItem, EvmDataTypeFactory.getInstance(), destDataType, parentDataType); } public getSignature(): string { @@ -395,7 +395,7 @@ export class Tuple extends MemberDataType { private tupleSignature: string; constructor(dataItem: DataItem) { - super(dataItem); + super(dataItem, EvmDataTypeFactory.getInstance()); if (!Tuple.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); } @@ -430,7 +430,7 @@ export class SolArray extends MemberDataType { const isArray = true; const arrayElementType = matches[1]; const arrayLength = (matches[2] === '') ? undefined : parseInt(matches[2], 10); - super(dataItem, isArray, arrayLength, arrayElementType); + super(dataItem, EvmDataTypeFactory.getInstance(), isArray, arrayLength, arrayElementType); this.elementType = arrayElementType; this.arraySignature = this.computeSignature(); } @@ -444,7 +444,7 @@ export class SolArray extends MemberDataType { if (components !== undefined) { dataItem.components = components; } - const elementDataType = DataTypeFactory.mapDataItemToDataType(dataItem); + const elementDataType = this.getFactory().mapDataItemToDataType(dataItem); const type = elementDataType.getSignature(); if (this.arrayLength === undefined) { return `${type}[]`; @@ -470,7 +470,7 @@ export class Method extends MemberDataType { public selector: string; constructor(abi: MethodAbi) { - super({ type: 'method', name: abi.name, components: abi.inputs }); + super({ type: 'method', name: abi.name, components: abi.inputs }, EvmDataTypeFactory.getInstance()); this.methodSignature = this.computeSignature(); this.selector = this.methodSelector = this.computeSelector(); @@ -510,7 +510,17 @@ export class Method extends MemberDataType { } } -export class EvmDataTypeFactoryImpl implements DataTypeFactoryImpl { +export class EvmDataTypeFactory implements DataTypeFactory { + private static instance: DataTypeFactory; + + private constructor() { } + + public static getInstance(): DataTypeFactory { + if (!EvmDataTypeFactory.instance) { + EvmDataTypeFactory.instance = new EvmDataTypeFactory(); + } + return EvmDataTypeFactory.instance; + } public mapDataItemToDataType(dataItem: DataItem): DataType { if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 8ae23a2220..295311accf 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -19,8 +19,6 @@ import * as AbiEncoder from './abi/abi_encoder'; import * as AbiSamples from './abi_samples'; import { Calldata } from './abi/calldata'; -AbiEncoder.DataTypeFactory.setImpl(new AbiEncoder.EvmDataTypeFactoryImpl()); - chaiSetup.configure(); const expect = chai.expect; @@ -56,7 +54,7 @@ describe.only('ABI Encoder', () => { console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); }); - it.only('Should point array elements to a duplicated value from another parameter', async () => { + it('Should point array elements to a duplicated value from another parameter', async () => { const method = new AbiEncoder.Method(AbiSamples.optimizerAbi2); const stringArray = [ "Test String", From 71c050375b23336e7bc2700b595032eefe0ca4c1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 15:15:01 -0800 Subject: [PATCH 116/230] Restructured to use index.ts for easier imports by client --- packages/order-utils/test/abi/abi_encoder.ts | 3 --- packages/order-utils/test/{abi => abi_encoder}/calldata.ts | 0 packages/order-utils/test/{abi => abi_encoder}/data_type.ts | 0 .../order-utils/test/{abi => abi_encoder}/evm_data_types.ts | 0 packages/order-utils/test/abi_encoder/index.ts | 2 ++ packages/order-utils/test/abi_encoder_test.ts | 5 ++--- 6 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 packages/order-utils/test/abi/abi_encoder.ts rename packages/order-utils/test/{abi => abi_encoder}/calldata.ts (100%) rename packages/order-utils/test/{abi => abi_encoder}/data_type.ts (100%) rename packages/order-utils/test/{abi => abi_encoder}/evm_data_types.ts (100%) create mode 100644 packages/order-utils/test/abi_encoder/index.ts diff --git a/packages/order-utils/test/abi/abi_encoder.ts b/packages/order-utils/test/abi/abi_encoder.ts deleted file mode 100644 index 4f4906550f..0000000000 --- a/packages/order-utils/test/abi/abi_encoder.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './calldata'; -export * from './data_type'; -export * from './evm_data_types'; \ No newline at end of file diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi_encoder/calldata.ts similarity index 100% rename from packages/order-utils/test/abi/calldata.ts rename to packages/order-utils/test/abi_encoder/calldata.ts diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi_encoder/data_type.ts similarity index 100% rename from packages/order-utils/test/abi/data_type.ts rename to packages/order-utils/test/abi_encoder/data_type.ts diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi_encoder/evm_data_types.ts similarity index 100% rename from packages/order-utils/test/abi/evm_data_types.ts rename to packages/order-utils/test/abi_encoder/evm_data_types.ts diff --git a/packages/order-utils/test/abi_encoder/index.ts b/packages/order-utils/test/abi_encoder/index.ts new file mode 100644 index 0000000000..991edb8c59 --- /dev/null +++ b/packages/order-utils/test/abi_encoder/index.ts @@ -0,0 +1,2 @@ +export { EncodingRules, DecodingRules } from './calldata'; +export * from './evm_data_types'; \ No newline at end of file diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 295311accf..f1e562bc68 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -14,10 +14,9 @@ import { BigNumber } from '@0x/utils'; import { assert } from '@0x/order-utils/src/assert'; //import * as AbiEncoder from './abi_encoder'; -import * as AbiEncoder from './abi/abi_encoder'; +import * as AbiEncoder from './abi_encoder'; import * as AbiSamples from './abi_samples'; -import { Calldata } from './abi/calldata'; chaiSetup.configure(); const expect = chai.expect; @@ -42,7 +41,7 @@ describe.only('ABI Encoder', () => { // Verify optimized calldata is expected const optimizedCalldata = method.encode(args, { optimize: true }); const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + //expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Verify args decode properly const decodedArgs = method.decode(optimizedCalldata); From 5b0d554f7baec54837d795b6568ae5ba8d8a0908 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 15:27:07 -0800 Subject: [PATCH 117/230] Moved Abi Encoder into utils package --- .../test => utils/src}/abi_encoder/calldata.ts | 0 .../test => utils/src}/abi_encoder/data_type.ts | 2 +- .../src}/abi_encoder/evm_data_types.ts | 2 +- .../test => utils/src}/abi_encoder/index.ts | 0 packages/utils/src/index.ts | 1 + .../{order-utils => utils}/test/abi_encoder_test.ts | 12 ++---------- packages/{order-utils => utils}/test/abi_samples.ts | 0 packages/utils/test/utils/chai_setup.ts | 13 +++++++++++++ 8 files changed, 18 insertions(+), 12 deletions(-) rename packages/{order-utils/test => utils/src}/abi_encoder/calldata.ts (100%) rename packages/{order-utils/test => utils/src}/abi_encoder/data_type.ts (99%) rename packages/{order-utils/test => utils/src}/abi_encoder/evm_data_types.ts (99%) rename packages/{order-utils/test => utils/src}/abi_encoder/index.ts (100%) rename packages/{order-utils => utils}/test/abi_encoder_test.ts (99%) rename packages/{order-utils => utils}/test/abi_samples.ts (100%) create mode 100644 packages/utils/test/utils/chai_setup.ts diff --git a/packages/order-utils/test/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts similarity index 100% rename from packages/order-utils/test/abi_encoder/calldata.ts rename to packages/utils/src/abi_encoder/calldata.ts diff --git a/packages/order-utils/test/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts similarity index 99% rename from packages/order-utils/test/abi_encoder/data_type.ts rename to packages/utils/src/abi_encoder/data_type.ts index 4c4537a69b..3b4028abde 100644 --- a/packages/order-utils/test/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -1,7 +1,7 @@ import { RawCalldata, Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata"; import { MethodAbi, DataItem } from 'ethereum-types'; import { DecodingRules, EncodingRules } from './calldata'; -import { BigNumber } from '@0x/utils'; +import { BigNumber } from '../configured_bignumber'; import ethUtil = require('ethereumjs-util'); var _ = require('lodash'); diff --git a/packages/order-utils/test/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts similarity index 99% rename from packages/order-utils/test/abi_encoder/evm_data_types.ts rename to packages/utils/src/abi_encoder/evm_data_types.ts index bb7c1d81d0..2973596fe9 100644 --- a/packages/order-utils/test/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -8,7 +8,7 @@ import ethUtil = require('ethereumjs-util'); import { Calldata, RawCalldata } from './calldata'; -import { BigNumber } from '@0x/utils'; +import { BigNumber } from '../configured_bignumber'; var _ = require('lodash'); diff --git a/packages/order-utils/test/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts similarity index 100% rename from packages/order-utils/test/abi_encoder/index.ts rename to packages/utils/src/abi_encoder/index.ts diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 0723e57886..c44530bbc8 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -10,3 +10,4 @@ export { NULL_BYTES } from './constants'; export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; +export * from './abi_encoder'; diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts similarity index 99% rename from packages/order-utils/test/abi_encoder_test.ts rename to packages/utils/test/abi_encoder_test.ts index f1e562bc68..2a8fba450c 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -1,21 +1,13 @@ import * as chai from 'chai'; import 'mocha'; -import ethUtil = require('ethereumjs-util'); - -var _ = require('lodash'); // import { assert } from '@0x/order-utils/src/assert'; import { chaiSetup } from './utils/chai_setup'; - -import { MethodAbi, DataItem } from 'ethereum-types'; - -import { BigNumber } from '@0x/utils'; -import { assert } from '@0x/order-utils/src/assert'; +import { BigNumber } from '../src/'; //import * as AbiEncoder from './abi_encoder'; -import * as AbiEncoder from './abi_encoder'; - +import * as AbiEncoder from '../src/abi_encoder'; import * as AbiSamples from './abi_samples'; chaiSetup.configure(); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/utils/test/abi_samples.ts similarity index 100% rename from packages/order-utils/test/abi_samples.ts rename to packages/utils/test/abi_samples.ts diff --git a/packages/utils/test/utils/chai_setup.ts b/packages/utils/test/utils/chai_setup.ts new file mode 100644 index 0000000000..1a87330932 --- /dev/null +++ b/packages/utils/test/utils/chai_setup.ts @@ -0,0 +1,13 @@ +import * as chai from 'chai'; +import chaiAsPromised = require('chai-as-promised'); +import ChaiBigNumber = require('chai-bignumber'); +import * as dirtyChai from 'dirty-chai'; + +export const chaiSetup = { + configure(): void { + chai.config.includeStack = true; + chai.use(ChaiBigNumber()); + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, +}; From 1f9ea7ed1b3d56a13b5e518adeed907eb830c02a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 15:34:27 -0800 Subject: [PATCH 118/230] Removed unnecessary imports from abi encoder tets --- packages/utils/src/index.ts | 3 +-- packages/utils/test/abi_encoder_test.ts | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index c44530bbc8..2edc6a331c 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -9,5 +9,4 @@ export { abiUtils } from './abi_utils'; export { NULL_BYTES } from './constants'; export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; -export { signTypedDataUtils } from './sign_typed_data_utils'; -export * from './abi_encoder'; +export { signTypedDataUtils } from './sign_typed_data_utils'; \ No newline at end of file diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 2a8fba450c..0961458da9 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -1,12 +1,8 @@ import * as chai from 'chai'; import 'mocha'; -// import { assert } from '@0x/order-utils/src/assert'; - import { chaiSetup } from './utils/chai_setup'; import { BigNumber } from '../src/'; -//import * as AbiEncoder from './abi_encoder'; - import * as AbiEncoder from '../src/abi_encoder'; import * as AbiSamples from './abi_samples'; From ebd4dbc6c6ebdf776ba1582367d3eda32717ba65 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 15:55:10 -0800 Subject: [PATCH 119/230] Exports AbiEncoder as 1 unit --- packages/utils/src/index.ts | 4 +++- packages/utils/test/abi_encoder_test.ts | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 2edc6a331c..f59cbec8c4 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -9,4 +9,6 @@ export { abiUtils } from './abi_utils'; export { NULL_BYTES } from './constants'; export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; -export { signTypedDataUtils } from './sign_typed_data_utils'; \ No newline at end of file +export { signTypedDataUtils } from './sign_typed_data_utils'; +import * as AbiEncoder from './abi_encoder'; +export { AbiEncoder }; diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 0961458da9..8d78b61b50 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -2,8 +2,7 @@ import * as chai from 'chai'; import 'mocha'; import { chaiSetup } from './utils/chai_setup'; -import { BigNumber } from '../src/'; -import * as AbiEncoder from '../src/abi_encoder'; +import { BigNumber, AbiEncoder } from '../src/'; import * as AbiSamples from './abi_samples'; chaiSetup.configure(); @@ -29,7 +28,7 @@ describe.only('ABI Encoder', () => { // Verify optimized calldata is expected const optimizedCalldata = method.encode(args, { optimize: true }); const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; - //expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Verify args decode properly const decodedArgs = method.decode(optimizedCalldata); From 8a8b904a292063d1adb3df0a84023610a3985f7f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 15 Nov 2018 14:33:40 -0800 Subject: [PATCH 120/230] Use new ABI Encoder for contracts --- packages/utils/src/abi_encoder/calldata.ts | 12 +++++-- packages/utils/src/abi_encoder/data_type.ts | 2 +- .../utils/src/abi_encoder/evm_data_types.ts | 32 +++++++++++++++-- packages/utils/test/abi_encoder_test.ts | 29 ++++++++++++++++ packages/utils/test/abi_samples.ts | 34 +++++++++++++++++++ 5 files changed, 102 insertions(+), 7 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts index 32278e5c5b..078767c221 100644 --- a/packages/utils/src/abi_encoder/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata.ts @@ -456,13 +456,19 @@ export class RawCalldata { private selector: string; private scopes: Queue; - constructor(value: string | Buffer) { + constructor(value: string | Buffer, hasSelectorPrefix: boolean = true) { if (typeof value === 'string' && !value.startsWith('0x')) { throw new Error(`Expected raw calldata to start with '0x'`); } const valueBuf = ethUtil.toBuffer(value); - this.selector = ethUtil.bufferToHex(valueBuf.slice(0, 4)); - this.value = valueBuf.slice(4); // disregard selector + if (hasSelectorPrefix) { + this.selector = ethUtil.bufferToHex(valueBuf.slice(0, 4)); + this.value = valueBuf.slice(4); // disregard selector + } else { + this.selector = '0x'; + this.value = valueBuf; + } + this.offset = 0; this.scopes = new Queue(); this.scopes.push(0); diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 3b4028abde..f5a8087ea0 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -6,7 +6,7 @@ import ethUtil = require('ethereumjs-util'); var _ = require('lodash'); export interface DataTypeFactory { - create: (dataItem: DataItem, parentDataType: DataType) => DataType; + create: (dataItem: DataItem, parentDataType?: DataType) => DataType; mapDataItemToDataType: (dataItem: DataItem) => DataType; } diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 2973596fe9..d024a9bfa2 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -106,7 +106,8 @@ abstract class Number extends PayloadDataType { } } - public encodeValue(value: BigNumber): Buffer { + public encodeValue(value_: BigNumber | string | number): Buffer { + const value = new BigNumber(value_, 10); if (value.greaterThan(this.getMaxValue())) { throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; } else if (value.lessThan(this.getMinValue())) { @@ -465,6 +466,7 @@ export class SolArray extends MemberDataType { export class Method extends MemberDataType { private methodSignature: string; private methodSelector: string; + private returnDataTypes: DataType[]; // TMP public selector: string; @@ -473,7 +475,11 @@ export class Method extends MemberDataType { super({ type: 'method', name: abi.name, components: abi.inputs }, EvmDataTypeFactory.getInstance()); this.methodSignature = this.computeSignature(); this.selector = this.methodSelector = this.computeSelector(); - + this.returnDataTypes = []; + const dummy = new Byte({ type: 'byte', name: 'DUMMY' }); // @TODO TMP + _.each(abi.outputs, (dataItem: DataItem) => { + this.returnDataTypes.push(this.getFactory().create(dataItem, dummy)); + }); } private computeSignature(): string { @@ -501,6 +507,23 @@ export class Method extends MemberDataType { return value; } + public decodeReturnValues(returndata: string, rules?: DecodingRules): any { + //console.log('O'.repeat(100), '\n', returndata, '\n', this.returnDataTypes, 'P'.repeat(100)); + + const returnValues: any[] = []; + const rules_ = rules ? rules : { structsAsObjects: false } as DecodingRules; + const rawReturnData = new RawCalldata(returndata, false); + _.each(this.returnDataTypes, (dataType: DataType) => { + returnValues.push(dataType.generateValue(rawReturnData, rules_)); + }); + + //console.log('*'.repeat(40), '\n', JSON.stringify(returnValues), '\n', '*'.repeat(100)); + /*if (returnValues.length === 1) { + return returnValues[0]; + }*/ + return returnValues; + } + public getSignature(): string { return this.methodSignature; } @@ -538,12 +561,15 @@ export class EvmDataTypeFactory implements DataTypeFactory { throw new Error(`Unrecognized data type: '${dataItem.type}'`); } - public create(dataItem: DataItem, parentDataType: DataType): DataType { + public create(dataItem: DataItem, parentDataType?: DataType): DataType { const dataType = this.mapDataItemToDataType(dataItem); if (dataType.isStatic()) { return dataType; } + if (parentDataType === undefined) { // @Todo -- will this work for return values? + throw new Error(`Trying to create a pointer`); + } const pointer = new Pointer(dataType, parentDataType); return pointer; } diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 8d78b61b50..8add41af62 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -15,6 +15,35 @@ describe.only('ABI Encoder', () => { describe.only('ABI Tests at Method Level', () => { + it.only('Should reuse duplicated strings in string array', async () => { + const method = new AbiEncoder.Method(AbiSamples.GAbi); + + const args = [ + { + a: new BigNumber(5), + e: '0x616161', + b: 'aaa', + f: '0xe41d2489571d322189246dafa5ebde1f4699f498' + } + ] + + // Verify optimized calldata is expected + const optimizedCalldata = method.encode(args, { optimize: true }); + //const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; + //expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + + // Verify args decode properly + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + //expect(decodedArgsJson).to.be.equal(argsJson); + + console.log(method.getSignature()); + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); + + }); + it('Should reuse duplicated strings in string array', async () => { const method = new AbiEncoder.Method(AbiSamples.stringAbi); const strings = [ diff --git a/packages/utils/test/abi_samples.ts b/packages/utils/test/abi_samples.ts index 5e8268f1a0..aa38711cd8 100644 --- a/packages/utils/test/abi_samples.ts +++ b/packages/utils/test/abi_samples.ts @@ -34,6 +34,40 @@ export const stringAbi = { type: 'function', } as MethodAbi; + +export const GAbi = { + constant: false, + inputs: [ + { + components: [{ + name: 'a', + type: 'uint256', + }, + { + name: 'b', + type: 'string', + }, + { + name: 'e', + type: 'bytes', + }, + { + name: 'f', + type: 'address', + }], + + name: 'f', + type: 'tuple', + + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const optimizerAbi2 = { constant: false, inputs: [ From 880540f4b8fd8dd7a7f1a93cf7fbcdc17e1fbb13 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 10:34:04 -0800 Subject: [PATCH 121/230] Fixed issue with decoding negative ints with width < 256 --- packages/utils/src/abi_encoder/data_type.ts | 4 +- .../utils/src/abi_encoder/evm_data_types.ts | 5 +- packages/utils/test/abi_encoder_test.ts | 471 +++++++++--------- 3 files changed, 244 insertions(+), 236 deletions(-) diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index f5a8087ea0..15de12a2f2 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -41,7 +41,8 @@ export abstract class DataType { } public decode(calldata: string, rules?: DecodingRules): any { - const rawCalldata = new RawCalldata(calldata); + const rawCalldata = new RawCalldata(calldata, false); + console.log(`HERE DUDE ${JSON.stringify(rawCalldata)}`); const rules_ = rules ? rules : DataType.DEFAULT_DECODING_RULES; const value = this.generateValue(rawCalldata, rules_); return value; @@ -277,6 +278,7 @@ export abstract class MemberDataType extends DataType { value = []; _.each(members, (member: DataType, idx: number) => { let memberValue = member.generateValue(calldata, rules); + console.log(`MEMBER VALUE: ${memberValue}`); (value as any[]).push(memberValue); }); } diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index d024a9bfa2..bfb2808daf 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -154,9 +154,8 @@ abstract class Number extends PayloadDataType { if (this instanceof Int) { // Check if we're negative const binBase = 2; - const paddedValueBin = value.toString(binBase); - const valueBin = paddedValueBin.slice(paddedValueBin.length - this.width); - if (valueBin[0].startsWith('1')) { + const valueBin = value.toString(2); + if (valueBin.length === 256 && valueBin[0].startsWith('1')) { // Negative // Step 1/3: Invert binary value let invertedValueBin = ''; diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 8add41af62..d2d7b9825c 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -13,9 +13,9 @@ describe.only('ABI Encoder', () => { }); - describe.only('ABI Tests at Method Level', () => { + describe('ABI Tests at Method Level', () => { - it.only('Should reuse duplicated strings in string array', async () => { + it('Should reuse duplicated strings in string array', async () => { const method = new AbiEncoder.Method(AbiSamples.GAbi); const args = [ @@ -723,237 +723,244 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata); }); }); - /* - describe('Array', () => { - it('sample', async () => { - const testDataItem = { name: 'testArray', type: 'int[2]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue([new BigNumber(5), new BigNumber(6)]); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); - }); - - it('sample undefined size', async () => { - const testDataItem = { name: 'testArray', type: 'int[]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue([new BigNumber(5), new BigNumber(6)]); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); - }); - - it('sample dynamic types', async () => { - const testDataItem = { name: 'testArray', type: 'string[]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue(['five', 'six', 'seven']); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); - const calldata = new AbiEncoder.Calldata('0x01020304', 1); - dataType.bind(calldata, AbiEncoder.CalldataSection.PARAMS); - console.log('*'.repeat(60)); - console.log(calldata.getHexValue()); - }); + + describe.only('Array', () => { + it.only('sample', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'int[2]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const args = [new BigNumber(5), new BigNumber(6)]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - - describe('Address', () => { - const testAddressDataItem = { name: 'testAddress', type: 'address' }; - it('Valid Address', async () => { - const addressDataType = new AbiEncoder.Address(testAddressDataItem); - addressDataType.assignValue('0xe41d2489571d322189246dafa5ebde1f4699f498'); - const expectedAbiEncodedAddress = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; - - console.log(addressDataType.getHexValue()); - console.log(expectedAbiEncodedAddress); - expect(addressDataType.getHexValue()).to.be.equal(expectedAbiEncodedAddress); - }); + + /* + it('sample undefined size', async () => { + const testDataItem = { name: 'testArray', type: 'int[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue([new BigNumber(5), new BigNumber(6)]); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); }); - - describe('Bool', () => { - const testBoolDataItem = { name: 'testBool', type: 'bool' }; - it('True', async () => { - const boolDataType = new AbiEncoder.Bool(testBoolDataItem); - boolDataType.assignValue(true); - const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); - }); - - it('False', async () => { - const boolDataType = new AbiEncoder.Bool(testBoolDataItem); - boolDataType.assignValue(false); - const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); - }); - }); - - describe('Integer', () => { - const testIntDataItem = { name: 'testInt', type: 'int' }; - it('Positive - Base case', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(1)); - const expectedAbiEncodedInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); - }); - - it('Positive', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(437829473)); - const expectedAbiEncodedInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); - }); - - it('Negative - Base case', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(-1)); - const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); - }); - - it('Negative', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(-437829473)); - const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); - }); - - // TODO: Add bounds tests + tests for different widths - }); - - describe('Unsigned Integer', () => { - const testIntDataItem = { name: 'testUInt', type: 'uint' }; - it('Lower Bound', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(0)); - const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); - }); - - it('Base Case', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(1)); - const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); - }); - - it('Random value', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(437829473)); - const expectedAbiEncodedUInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); - }); - - // TODO: Add bounds tests + tests for different widths - }); - - describe('Static Bytes', () => { - it('Byte (padded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x05'); - const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it.skip('Byte (no padding)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - - // @TODO: This does not catch the Error - expect(byteDataType.assignValue('0x5')).to.throw(); - }); - - it('Bytes1', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes1' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x05'); - const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it('Bytes32 (padded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x0001020304050607080911121314151617181920212223242526272829303132'); - const expectedAbiEncodedByte = '0x0001020304050607080911121314151617181920212223242526272829303132'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it('Bytes32 (unpadded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x1a18bf61'); - const expectedAbiEncodedByte = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); - }); - - it.skip('Bytes32 - Too long', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - - // @TODO: This does not catch the Error - expect( - byteDataType.assignValue('0x000102030405060708091112131415161718192021222324252627282930313233'), - ).to.throw( - `Tried to assign 0x000102030405060708091112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32`, - ); - }); - }); - - describe('Bytes (Dynamic)', () => { - const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; - it('Less than 32 bytes', async () => { - const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); - bytesDataType.assignValue('0x010203'); - const expectedAbiEncodedBytes = - '0x00000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000'; - - expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); - }); - - it('Greater than 32 bytes', async () => { - const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); - const testValue = '0x' + '61'.repeat(40); - bytesDataType.assignValue(testValue); - const expectedAbiEncodedBytes = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); - }); - - // @TODO: Add test for throw on half-byte - // @TODO: Test with no 0x prefix - // @TODO: Test with Buffer as input - }); - - describe('String', () => { - const testStringDataItem = { name: 'testString', type: 'string' }; - it('Less than 32 bytes', async () => { - const stringDataType = new AbiEncoder.SolString(testStringDataItem); - stringDataType.assignValue('five'); - const expectedAbiEncodedString = - '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - - console.log(stringDataType.getHexValue()); - console.log(expectedAbiEncodedString); - expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); - }); - - it('Greater than 32 bytes', async () => { - const stringDataType = new AbiEncoder.SolString(testStringDataItem); - const testValue = 'a'.repeat(40); - stringDataType.assignValue(testValue); - const expectedAbiEncodedString = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); - }); + + it('sample dynamic types', async () => { + const testDataItem = { name: 'testArray', type: 'string[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue(['five', 'six', 'seven']); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + const calldata = new AbiEncoder.Calldata('0x01020304', 1); + dataType.bind(calldata, AbiEncoder.CalldataSection.PARAMS); + console.log('*'.repeat(60)); + console.log(calldata.getHexValue()); });*/ + }); + + /* + describe('Address', () => { + const testAddressDataItem = { name: 'testAddress', type: 'address' }; + it('Valid Address', async () => { + const addressDataType = new AbiEncoder.Address(testAddressDataItem); + addressDataType.assignValue('0xe41d2489571d322189246dafa5ebde1f4699f498'); + const expectedAbiEncodedAddress = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; + + console.log(addressDataType.getHexValue()); + console.log(expectedAbiEncodedAddress); + expect(addressDataType.getHexValue()).to.be.equal(expectedAbiEncodedAddress); + }); + }); + + describe('Bool', () => { + const testBoolDataItem = { name: 'testBool', type: 'bool' }; + it('True', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(true); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); + + it('False', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(false); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); + }); + + describe('Integer', () => { + const testIntDataItem = { name: 'testInt', type: 'int' }; + it('Positive - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Positive', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-1)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-437829473)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + // TODO: Add bounds tests + tests for different widths + }); + + describe('Unsigned Integer', () => { + const testIntDataItem = { name: 'testUInt', type: 'uint' }; + it('Lower Bound', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(0)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Base Case', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Random value', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedUInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + // TODO: Add bounds tests + tests for different widths + }); + + describe('Static Bytes', () => { + it('Byte (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Byte (no padding)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect(byteDataType.assignValue('0x5')).to.throw(); + }); + + it('Bytes1', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes1' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x0001020304050607080911121314151617181920212223242526272829303132'); + const expectedAbiEncodedByte = '0x0001020304050607080911121314151617181920212223242526272829303132'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (unpadded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x1a18bf61'); + const expectedAbiEncodedByte = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Bytes32 - Too long', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect( + byteDataType.assignValue('0x000102030405060708091112131415161718192021222324252627282930313233'), + ).to.throw( + `Tried to assign 0x000102030405060708091112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32`, + ); + }); + }); + + describe('Bytes (Dynamic)', () => { + const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; + it('Less than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + bytesDataType.assignValue('0x010203'); + const expectedAbiEncodedBytes = + '0x00000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000'; + + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + it('Greater than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + const testValue = '0x' + '61'.repeat(40); + bytesDataType.assignValue(testValue); + const expectedAbiEncodedBytes = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + // @TODO: Add test for throw on half-byte + // @TODO: Test with no 0x prefix + // @TODO: Test with Buffer as input + }); + + describe('String', () => { + const testStringDataItem = { name: 'testString', type: 'string' }; + it('Less than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + stringDataType.assignValue('five'); + const expectedAbiEncodedString = + '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + console.log(stringDataType.getHexValue()); + console.log(expectedAbiEncodedString); + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + + it('Greater than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + const testValue = 'a'.repeat(40); + stringDataType.assignValue(testValue); + const expectedAbiEncodedString = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + });*/ }); From bc538c71fcab68578a82b08b4467240f8e79b96b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 10:44:17 -0800 Subject: [PATCH 122/230] Tests - Encode/Decode Array --- packages/utils/src/abi_encoder/data_type.ts | 4 +- packages/utils/test/abi_encoder_test.ts | 68 ++++++++++++++------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 15de12a2f2..022f5621f5 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -41,8 +41,7 @@ export abstract class DataType { } public decode(calldata: string, rules?: DecodingRules): any { - const rawCalldata = new RawCalldata(calldata, false); - console.log(`HERE DUDE ${JSON.stringify(rawCalldata)}`); + const rawCalldata = new RawCalldata(calldata, false); // @TODO Sohuld not hardcode false here const rules_ = rules ? rules : DataType.DEFAULT_DECODING_RULES; const value = this.generateValue(rawCalldata, rules_); return value; @@ -278,7 +277,6 @@ export abstract class MemberDataType extends DataType { value = []; _.each(members, (member: DataType, idx: number) => { let memberValue = member.generateValue(calldata, rules); - console.log(`MEMBER VALUE: ${memberValue}`); (value as any[]).push(memberValue); }); } diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index d2d7b9825c..bf62507132 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -725,7 +725,7 @@ describe.only('ABI Encoder', () => { }); describe.only('Array', () => { - it.only('sample', async () => { + it.only('Fixed size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[2]' }; const dataType = new AbiEncoder.SolArray(testDataItem); @@ -742,34 +742,56 @@ describe.only('ABI Encoder', () => { expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - /* - it('sample undefined size', async () => { + it.only('Dynamic size; Static elements', async () => { + // Create DataType object const testDataItem = { name: 'testArray', type: 'int[]' }; const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue([new BigNumber(5), new BigNumber(6)]); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); + // Construct args to be encoded + const args = [new BigNumber(5), new BigNumber(6)]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('sample dynamic types', async () => { + it.only('Fixed size; Dynamic elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'string[2]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const args = ["Hello", "world"]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it.only('Dynamic size; Dynamic elements', async () => { + // Create DataType object const testDataItem = { name: 'testArray', type: 'string[]' }; const dataType = new AbiEncoder.SolArray(testDataItem); - console.log(JSON.stringify(dataType, null, 4)); - console.log('*'.repeat(60)); - dataType.assignValue(['five', 'six', 'seven']); - console.log(JSON.stringify(dataType, null, 4)); - const hexValue = dataType.getHexValue(); - console.log('*'.repeat(60)); - console.log(hexValue); - const calldata = new AbiEncoder.Calldata('0x01020304', 1); - dataType.bind(calldata, AbiEncoder.CalldataSection.PARAMS); - console.log('*'.repeat(60)); - console.log(calldata.getHexValue()); - });*/ + // Construct args to be encoded + const args = ["Hello", "world"]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); }); /* From a630312074758b31951c194533dcae596424592d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 10:57:08 -0800 Subject: [PATCH 123/230] Tests for Tuple --- packages/utils/test/abi_encoder_test.ts | 67 +++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index bf62507132..cf1f0327a6 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -4,6 +4,7 @@ import 'mocha'; import { chaiSetup } from './utils/chai_setup'; import { BigNumber, AbiEncoder } from '../src/'; import * as AbiSamples from './abi_samples'; +import { DecodingRules } from '../src/abi_encoder'; chaiSetup.configure(); const expect = chai.expect; @@ -724,8 +725,8 @@ describe.only('ABI Encoder', () => { }); }); - describe.only('Array', () => { - it.only('Fixed size; Static elements', async () => { + describe('Array', () => { + it('Fixed size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[2]' }; const dataType = new AbiEncoder.SolArray(testDataItem); @@ -742,7 +743,7 @@ describe.only('ABI Encoder', () => { expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it.only('Dynamic size; Static elements', async () => { + it('Dynamic size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[]' }; const dataType = new AbiEncoder.SolArray(testDataItem); @@ -759,7 +760,7 @@ describe.only('ABI Encoder', () => { expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it.only('Fixed size; Dynamic elements', async () => { + it('Fixed size; Dynamic elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[2]' }; const dataType = new AbiEncoder.SolArray(testDataItem); @@ -776,7 +777,7 @@ describe.only('ABI Encoder', () => { expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it.only('Dynamic size; Dynamic elements', async () => { + it('Dynamic size; Dynamic elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[]' }; const dataType = new AbiEncoder.SolArray(testDataItem); @@ -794,6 +795,62 @@ describe.only('ABI Encoder', () => { }); }); + describe.only('Tuple', () => { + it('Static elements only', async () => { + // Create DataType object + const testDataItem = { name: 'Tuple', type: 'tuple', components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }] }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field_1: new BigNumber(-5), field_2: true }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('Dynamic elements only', async () => { + // Create DataType object + const testDataItem = { name: 'Tuple', type: 'tuple', components: [{ name: 'field_1', type: 'string' }, { name: 'field_2', type: 'bytes' }] }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field_1: "Hello, World!", field_2: '0xabcdef0123456789' }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('Static and dynamic elements mixed', async () => { + // Create DataType object + const testDataItem = { name: 'Tuple', type: 'tuple', components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'string' }, { name: 'field_3', type: 'bool' }, { name: 'field_4', type: 'bytes' }] }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field_1: new BigNumber(-5), field_2: "Hello, World!", field_3: true, field_4: '0xabcdef0123456789' }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + }); + /* describe('Address', () => { const testAddressDataItem = { name: 'testAddress', type: 'address' }; From a2ad15be0dc500fa196021a5b32e80af8500043c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 11:18:34 -0800 Subject: [PATCH 124/230] Tests for Address --- packages/utils/src/abi_encoder/calldata.ts | 15 ++++-- packages/utils/src/abi_encoder/data_type.ts | 2 +- .../utils/src/abi_encoder/evm_data_types.ts | 13 ++++- packages/utils/test/abi_encoder_test.ts | 52 +++++++++++++++---- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts index 078767c221..11288064e7 100644 --- a/packages/utils/src/abi_encoder/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata.ts @@ -248,7 +248,7 @@ export class Calldata { private selector: string; private rules: EncodingRules; private sizeInBytes: number; - private root: MemberCalldataBlock | undefined; + private root: CalldataBlock | undefined; constructor(rules: EncodingRules) { this.selector = ''; @@ -257,8 +257,17 @@ export class Calldata { this.root = undefined; } - private createQueue(memberBlock: MemberCalldataBlock): Queue { + private createQueue(block: CalldataBlock): Queue { const blockQueue = new Queue(); + + // Base Case + if (block instanceof MemberCalldataBlock === false) { + blockQueue.push(block); + return blockQueue; + } + + // This is a Member Block + const memberBlock = block as MemberCalldataBlock; _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => { if (member instanceof MemberCalldataBlock) { blockQueue.mergeFront(this.createQueue(member)); @@ -429,7 +438,7 @@ export class Calldata { return ""; } - public setRoot(block: MemberCalldataBlock) { + public setRoot(block: CalldataBlock) { this.root = block; this.sizeInBytes += block.getSizeInBytes(); } diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 022f5621f5..0f3cecac51 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -35,7 +35,7 @@ export abstract class DataType { const calldata = new Calldata(rules_); if (selector) calldata.setSelector(selector); const block = this.generateCalldataBlock(value); - calldata.setRoot(block as MemberCalldataBlock); // @TODO CHANGE + calldata.setRoot(block); // @TODO CHANGE const calldataHex = calldata.toHexString(); return calldataHex; } diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index bfb2808daf..1ee95863b1 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -20,6 +20,8 @@ export interface DataTypeStaticInterface { export class Address extends PayloadDataType { private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; + public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = "Address must be 20 bytes"; constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance(), Address.SIZE_KNOWN_AT_COMPILE_TIME); @@ -36,9 +38,16 @@ export class Address extends PayloadDataType { return type === 'address'; } - public encodeValue(value: boolean): Buffer { + public encodeValue(value: string): Buffer { + if (value.startsWith('0x') === false) { + throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); + } + const valueAsBuffer = ethUtil.toBuffer(value); + if (valueAsBuffer.byteLength !== 20) { + throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); + } const evmWordWidth = 32; - const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth); + const encodedValueBuf = ethUtil.setLengthLeft(valueAsBuffer, evmWordWidth); return encodedValueBuf; } diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index cf1f0327a6..5b341545e1 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -725,7 +725,7 @@ describe.only('ABI Encoder', () => { }); }); - describe('Array', () => { + describe.only('Array', () => { it('Fixed size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[2]' }; @@ -793,6 +793,9 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); + + // @TODO: Add test that fails if we pass in the wrong number of elements + // @TODO: Add test that fails if we pass in an element of incorrecrt type }); describe.only('Tuple', () => { @@ -849,22 +852,51 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); + + // @TODO: Add test that fails if we pass in the wrong number of elements + // @TODO: Add test that fails if we pass in arguments in wrong order }); - /* - describe('Address', () => { - const testAddressDataItem = { name: 'testAddress', type: 'address' }; + describe.only('Address', () => { it('Valid Address', async () => { - const addressDataType = new AbiEncoder.Address(testAddressDataItem); - addressDataType.assignValue('0xe41d2489571d322189246dafa5ebde1f4699f498'); - const expectedAbiEncodedAddress = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; + // Create DataType object + const testDataItem = { name: 'Address', type: 'address' }; + const dataType = new AbiEncoder.Address(testDataItem); + // Construct args to be encoded + const args = '0xe41d2489571d322189246dafa5ebde1f4699f498'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); - console.log(addressDataType.getHexValue()); - console.log(expectedAbiEncodedAddress); - expect(addressDataType.getHexValue()).to.be.equal(expectedAbiEncodedAddress); + it('Invalid Address - input is not valid hex', async () => { + // Create DataType object + const testDataItem = { name: 'Address', type: 'address' }; + const dataType = new AbiEncoder.Address(testDataItem); + // Construct args to be encoded + const args = 'e4'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); + }); + + it('Invalid Address - input is not 20 bytes', async () => { + // Create DataType object + const testDataItem = { name: 'Address', type: 'address' }; + const dataType = new AbiEncoder.Address(testDataItem); + // Construct args to be encoded + const args = '0xe4'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); }); }); + /* describe('Bool', () => { const testBoolDataItem = { name: 'testBool', type: 'bool' }; it('True', async () => { From b28f26916fc51fa13308f335e3cd0bd5d3b075fc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 11:20:35 -0800 Subject: [PATCH 125/230] Tests for Bool --- packages/utils/test/abi_encoder_test.ts | 43 ++++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 5b341545e1..123ebe11d5 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -896,24 +896,43 @@ describe.only('ABI Encoder', () => { }); }); - /* - describe('Bool', () => { - const testBoolDataItem = { name: 'testBool', type: 'bool' }; + describe.only('Bool', () => { it('True', async () => { - const boolDataType = new AbiEncoder.Bool(testBoolDataItem); - boolDataType.assignValue(true); - const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + // Create DataType object + const testDataItem = { name: 'Boolean', type: 'bool' }; + const dataType = new AbiEncoder.Bool(testDataItem); + // Construct args to be encoded + const args = true; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); it('False', async () => { - const boolDataType = new AbiEncoder.Bool(testBoolDataItem); - boolDataType.assignValue(false); - const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + // Create DataType object + const testDataItem = { name: 'Boolean', type: 'bool' }; + const dataType = new AbiEncoder.Bool(testDataItem); + // Construct args to be encoded + const args = false; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); }); + /* describe('Integer', () => { const testIntDataItem = { name: 'testInt', type: 'int' }; it('Positive - Base case', async () => { @@ -1073,5 +1092,5 @@ describe.only('ABI Encoder', () => { '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); }); - });*/ + }); */ }); From 666075a87e8f7dfd5ef3553c5f8e08d826ac0641 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 11:47:32 -0800 Subject: [PATCH 126/230] Tests for Integer (tested 256 / 32 bit integers) --- .../utils/src/abi_encoder/evm_data_types.ts | 4 +- packages/utils/test/abi_encoder_test.ts | 202 +++++++++++++++--- 2 files changed, 180 insertions(+), 26 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 1ee95863b1..38878f63ec 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -118,9 +118,9 @@ abstract class Number extends PayloadDataType { public encodeValue(value_: BigNumber | string | number): Buffer { const value = new BigNumber(value_, 10); if (value.greaterThan(this.getMaxValue())) { - throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; + throw `Tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; } else if (value.lessThan(this.getMinValue())) { - throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; + throw `Tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; } const hexBase = 16; diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 123ebe11d5..21ad89711a 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -932,40 +932,194 @@ describe.only('ABI Encoder', () => { }); }); - /* - describe('Integer', () => { - const testIntDataItem = { name: 'testInt', type: 'int' }; - it('Positive - Base case', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(1)); - const expectedAbiEncodedInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + describe.only('Integer', () => { + it('Int256 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Positive', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(437829473)); - const expectedAbiEncodedInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + it('Int256 - Negative Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(-1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Negative - Base case', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(-1)); - const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + it('Int256 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const max256BitInteger = (new BigNumber(2)).pow(255).minus(1); + const args = max256BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Negative', async () => { - const intDataType = new AbiEncoder.Int(testIntDataItem); - intDataType.assignValue(new BigNumber(-437829473)); - const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; - expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + it('Int256 - Negative Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const min256BitInteger = (new BigNumber(2)).pow(255).times(-1); + const args = min256BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0x8000000000000000000000000000000000000000000000000000000000000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - // TODO: Add bounds tests + tests for different widths + it('Int256 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const max256BitInteger = (new BigNumber(2)).pow(255).minus(1); + const args = max256BitInteger.plus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); + + it('Int256 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const min256BitInteger = (new BigNumber(2)).pow(255).times(-1); + const args = min256BitInteger.minus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); + + it('Int32 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('Int32 - Negative Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(-1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('Int32 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const max32BitInteger = (new BigNumber(2)).pow(31).minus(1); + const args = max32BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000007fffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('Int32 - Negative Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const min32BitInteger = (new BigNumber(2)).pow(31).times(-1); + const args = min32BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('Int32 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const max32BitInteger = (new BigNumber(2)).pow(31).minus(1); + const args = max32BitInteger.plus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); + + it('Int32 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const min32BitInteger = (new BigNumber(2)).pow(31).times(-1); + const args = min32BitInteger.minus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); }); + /* + describe('Unsigned Integer', () => { const testIntDataItem = { name: 'testUInt', type: 'uint' }; it('Lower Bound', async () => { From 9a0bd05c4ca98dd24d71cdbd53175bfdd5250107 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 11:55:56 -0800 Subject: [PATCH 127/230] Unsigned Integers --- packages/utils/test/abi_encoder_test.ts | 166 +++++++++++++++++++++--- 1 file changed, 146 insertions(+), 20 deletions(-) diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 21ad89711a..4b85ad9385 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -1118,34 +1118,160 @@ describe.only('ABI Encoder', () => { }); }); - /* - - describe('Unsigned Integer', () => { - const testIntDataItem = { name: 'testUInt', type: 'uint' }; - it('Lower Bound', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(0)); - const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + describe.only('Unsigned Integer', () => { + it('UInt256 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Base Case', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(1)); - const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + it('UInt256 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const max256BitUnsignedInteger = (new BigNumber(2)).pow(256).minus(1); + const args = max256BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Random value', async () => { - const uintDataType = new AbiEncoder.UInt(testIntDataItem); - uintDataType.assignValue(new BigNumber(437829473)); - const expectedAbiEncodedUInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; - expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + it('UInt256 - Zero Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const min256BitUnsignedInteger = new BigNumber(0); + const args = min256BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - // TODO: Add bounds tests + tests for different widths + it('UInt256 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const max256BitUnsignedInteger = (new BigNumber(2)).pow(256).minus(1); + const args = max256BitUnsignedInteger.plus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); + + it('UInt256 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const min256BitUnsignedInteger = new BigNumber(0) + const args = min256BitUnsignedInteger.minus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); + + it('UInt32 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('UInt32 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const max32BitUnsignedInteger = (new BigNumber(2)).pow(32).minus(1); + const args = max32BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000ffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('UInt32 - Zero Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const min32BitUnsignedInteger = new BigNumber(0); + const args = min32BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('UInt32 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const max32BitUnsignedInteger = (new BigNumber(2)).pow(32).minus(1); + const args = max32BitUnsignedInteger.plus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); + + it('UInt32 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const min32BitUnsignedInteger = new BigNumber(0); + const args = min32BitUnsignedInteger.minus(1); + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw(); + }); }); + /* + describe('Static Bytes', () => { it('Byte (padded)', async () => { const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; From d70d07366fcc9ed2a750a5427af9d210c486f344 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 12:43:16 -0800 Subject: [PATCH 128/230] Tests for Static Bytes --- .../utils/src/abi_encoder/evm_data_types.ts | 4 + packages/utils/test/abi_encoder_test.ts | 180 ++++++++++++++---- 2 files changed, 142 insertions(+), 42 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 38878f63ec..ff1bc594d7 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -270,6 +270,10 @@ export class Byte extends PayloadDataType { } public encodeValue(value: string | Buffer): Buffer { + // Sanity check if string + if (typeof value === 'string' && value.startsWith('0x') === false) { + throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); + } // Convert value into a buffer and do bounds checking const valueBuf = ethUtil.toBuffer(value); if (valueBuf.byteLength > this.width) { diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 4b85ad9385..5fe625092a 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -1270,62 +1270,158 @@ describe.only('ABI Encoder', () => { }); }); - /* - - describe('Static Bytes', () => { - it('Byte (padded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x05'); - const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + describe.only('Static Bytes', () => { + it('Single Byte (byte)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Byte', type: 'byte' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0x05'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it.skip('Byte (no padding)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - - // @TODO: This does not catch the Error - expect(byteDataType.assignValue('0x5')).to.throw(); + it('Single Byte (bytes1)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes1', type: 'bytes1' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0x05'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Bytes1', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes1' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x05'); - const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + it('4 Bytes (bytes4)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0x00010203'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0001020300000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Bytes32 (padded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x0001020304050607080911121314151617181920212223242526272829303132'); - const expectedAbiEncodedByte = '0x0001020304050607080911121314151617181920212223242526272829303132'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + it('4 Bytes (bytes4); Encoder must pad input', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x1a18000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const paddedArgs = '0x1a180000'; + const paddedArgsAsJson = JSON.stringify(paddedArgs); + expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); }); - it('Bytes32 (unpadded)', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); - byteDataType.assignValue('0x1a18bf61'); - const expectedAbiEncodedByte = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; - expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + it('32 Bytes (bytes32)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0x0001020304050607080911121314151617181920212223242526272829303132'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0001020304050607080911121314151617181920212223242526272829303132'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it.skip('Bytes32 - Too long', async () => { - const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; - const byteDataType = new AbiEncoder.Byte(testByteDataItem); + it('32 Bytes (bytes32); Encoder must pad input', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18bf61'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const paddedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + const paddedArgsAsJson = JSON.stringify(paddedArgs); + expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); + }); - // @TODO: This does not catch the Error - expect( - byteDataType.assignValue('0x000102030405060708091112131415161718192021222324252627282930313233'), - ).to.throw( - `Tried to assign 0x000102030405060708091112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32`, - ); + it('Should throw when pass in too many bytes (bytes4)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0x0102030405'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x0102030405 (5 bytes), which exceeds max bytes that can be stored in a bytes4'); + }); + + it('Should throw when pass in too many bytes (bytes32)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0x010203040506070809101112131415161718192021222324252627282930313233'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x010203040506070809101112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32'); + }); + + it('Should throw when pass in bad hex (no 0x prefix)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0102030405060708091011121314151617181920212223242526272829303132'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); + }); + + it('Should throw when pass in bad hex (include a half-byte)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.Byte(testDataItem); + // Construct args to be encoded + const args = '0x010'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); }); }); + /* + + + describe('Bytes (Dynamic)', () => { const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; it('Less than 32 bytes', async () => { From 5b187935dc6bf194555e1652dbf53d1c637d4add Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 13:01:26 -0800 Subject: [PATCH 129/230] tests for String --- .../utils/src/abi_encoder/evm_data_types.ts | 4 +- packages/utils/test/abi_encoder_test.ts | 166 +++++++++++++----- 2 files changed, 126 insertions(+), 44 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index ff1bc594d7..b6a62a5d4c 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -318,7 +318,7 @@ export class Bytes extends PayloadDataType { public encodeValue(value: string | Buffer): Buffer { if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`); + throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '${value}'`); } const valueBuf = ethUtil.toBuffer(value); if (value.length % 2 !== 0) { @@ -365,7 +365,7 @@ export class SolString extends PayloadDataType { public encodeValue(value: string): Buffer { const wordsForValue = Math.ceil(value.length / 32); const paddedBytesForValue = wordsForValue * 32; - const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); + const valueBuf = ethUtil.setLengthRight(new Buffer(value), paddedBytesForValue); const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); return encodedValueBuf; diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 5fe625092a..b541cd7580 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -5,6 +5,7 @@ import { chaiSetup } from './utils/chai_setup'; import { BigNumber, AbiEncoder } from '../src/'; import * as AbiSamples from './abi_samples'; import { DecodingRules } from '../src/abi_encoder'; +import ethUtil = require('ethereumjs-util'); chaiSetup.configure(); const expect = chai.expect; @@ -1418,55 +1419,136 @@ describe.only('ABI Encoder', () => { }); }); - /* - - - - describe('Bytes (Dynamic)', () => { - const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; - it('Less than 32 bytes', async () => { - const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); - bytesDataType.assignValue('0x010203'); - const expectedAbiEncodedBytes = - '0x00000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000'; - - expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + describe.only('Dynamic Bytes Dynamic', () => { + it('Fits into one EVM word', async () => { + // Create DataType object + const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.Bytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18bf61'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Greater than 32 bytes', async () => { - const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); - const testValue = '0x' + '61'.repeat(40); - bytesDataType.assignValue(testValue); - const expectedAbiEncodedBytes = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + it('Spans multiple EVM words', async () => { + // Create DataType object + const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.Bytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x' + '61'.repeat(40); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - // @TODO: Add test for throw on half-byte - // @TODO: Test with no 0x prefix - // @TODO: Test with Buffer as input + it('Input as Buffer', async () => { + // Create DataType object + const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.Bytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18bf61'; + const argsAsBuffer = ethUtil.toBuffer(args); + // Encode Args and validate result + const encodedArgs = dataType.encode(argsAsBuffer); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + + it('Should throw when pass in bad hex (no 0x prefix)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.Bytes(testDataItem); + // Construct args to be encoded + const args = '01'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '01'"); + }); + + it('Should throw when pass in bad hex (include a half-byte)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.Bytes(testDataItem); + // Construct args to be encoded + const args = '0x010'; + // Encode Args and validate result + expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); + }); }); - describe('String', () => { - const testStringDataItem = { name: 'testString', type: 'string' }; - it('Less than 32 bytes', async () => { - const stringDataType = new AbiEncoder.SolString(testStringDataItem); - stringDataType.assignValue('five'); - const expectedAbiEncodedString = - '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - - console.log(stringDataType.getHexValue()); - console.log(expectedAbiEncodedString); - expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + describe.only('String', () => { + it('Fits into one EVM word', async () => { + // Create DataType object + const testDataItem = { name: 'String', type: 'string' }; + const dataType = new AbiEncoder.SolString(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = 'five'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Greater than 32 bytes', async () => { - const stringDataType = new AbiEncoder.SolString(testStringDataItem); - const testValue = 'a'.repeat(40); - stringDataType.assignValue(testValue); - const expectedAbiEncodedString = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + it('Spans multiple EVM words', async () => { + // Create DataType object + const testDataItem = { name: 'String', type: 'string' }; + const dataType = new AbiEncoder.SolString(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = 'a'.repeat(40); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - }); */ + + it('String that begins with 0x prefix', async () => { + // Create DataType object + const testDataItem = { name: 'String', type: 'string' }; + const dataType = new AbiEncoder.SolString(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x' + 'a'.repeat(40); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002a30786161616161616161616161616161616161616161616161616161616161616161616161616161616100000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + }); }); From b213cb39747ba0b78138d5a1db07b511bd9fd3b8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 13:05:36 -0800 Subject: [PATCH 130/230] Temporary change for testing functions --- packages/utils/src/abi_encoder/data_type.ts | 2 +- packages/utils/test/abi_encoder_test.ts | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 0f3cecac51..21c08ef54b 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -41,7 +41,7 @@ export abstract class DataType { } public decode(calldata: string, rules?: DecodingRules): any { - const rawCalldata = new RawCalldata(calldata, false); // @TODO Sohuld not hardcode false here + const rawCalldata = new RawCalldata(calldata, true); // @TODO Sohuld not hardcode false here const rules_ = rules ? rules : DataType.DEFAULT_DECODING_RULES; const value = this.generateValue(rawCalldata, rules_); return value; diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index b541cd7580..6d0d0c3902 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -11,12 +11,7 @@ chaiSetup.configure(); const expect = chai.expect; describe.only('ABI Encoder', () => { - describe.only('Optimizer', () => { - - }); - - describe('ABI Tests at Method Level', () => { - + describe('Optimizer', () => { it('Should reuse duplicated strings in string array', async () => { const method = new AbiEncoder.Method(AbiSamples.GAbi); @@ -147,7 +142,9 @@ describe.only('ABI Encoder', () => { console.log(JSON.stringify(decodedArgs)); expect(decodedArgsJson).to.be.equal(argsJson); }); + }); + describe.only('ABI Tests at Method Level', () => { it('Crazy ABI', async () => { const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); From 0c0bcb44d3ef6d68c9c7c05be25641ef57a7287a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 13:07:34 -0800 Subject: [PATCH 131/230] All 71 tests passing. Both for function encoding and individual types. --- packages/utils/src/abi_encoder/data_type.ts | 4 ++-- packages/utils/src/abi_encoder/evm_data_types.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 21c08ef54b..243b221ef4 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -40,8 +40,8 @@ export abstract class DataType { return calldataHex; } - public decode(calldata: string, rules?: DecodingRules): any { - const rawCalldata = new RawCalldata(calldata, true); // @TODO Sohuld not hardcode false here + public decode(calldata: string, rules?: DecodingRules, hasSelector: boolean = false): any { + const rawCalldata = new RawCalldata(calldata, hasSelector); const rules_ = rules ? rules : DataType.DEFAULT_DECODING_RULES; const value = this.generateValue(rawCalldata, rules_); return value; diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index b6a62a5d4c..2895ee00f7 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -515,7 +515,8 @@ export class Method extends MemberDataType { if (!calldata.startsWith(this.selector)) { throw new Error(`Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`); } - const value = super.decode(calldata, rules); + const hasSelector = true; + const value = super.decode(calldata, rules, hasSelector); return value; } From 67dd062a2f6a936cad18ff81afac398cd6a1ab97 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 14:35:40 -0800 Subject: [PATCH 132/230] Cleaning up optimizer tests --- packages/utils/src/abi_encoder/calldata.ts | 37 +- packages/utils/src/abi_encoder/data_type.ts | 68 +- .../utils/src/abi_encoder/evm_data_types.ts | 27 +- packages/utils/src/abi_encoder/index.ts | 2 +- packages/utils/test/abi_encoder_test.ts | 1345 +++++++++-------- packages/utils/test/abi_samples.ts | 73 +- 6 files changed, 817 insertions(+), 735 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts index 11288064e7..e6ce589579 100644 --- a/packages/utils/src/abi_encoder/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata.ts @@ -20,7 +20,14 @@ export abstract class CalldataBlock { private relocatable: boolean; private parentName: string; - constructor(name: string, signature: string, parentName: string, /*offsetInBytes: number,*/ headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { + constructor( + name: string, + signature: string, + parentName: string, + /*offsetInBytes: number,*/ headerSizeInBytes: number, + bodySizeInBytes: number, + relocatable: boolean, + ) { this.name = name; this.signature = signature; this.parentName = parentName; @@ -91,7 +98,13 @@ export abstract class CalldataBlock { export class PayloadCalldataBlock extends CalldataBlock { private payload: Buffer; - constructor(name: string, signature: string, parentName: string, /*offsetInBytes: number,*/ relocatable: boolean, payload: Buffer) { + constructor( + name: string, + signature: string, + parentName: string, + /*offsetInBytes: number,*/ relocatable: boolean, + payload: Buffer, + ) { const headerSizeInBytes = 0; const bodySizeInBytes = payload.byteLength; super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable); @@ -115,7 +128,14 @@ export class DependentCalldataBlock extends CalldataBlock { private dependency: CalldataBlock; private aliasFor: CalldataBlock | undefined; - constructor(name: string, signature: string, parentName: string, relocatable: boolean, dependency: CalldataBlock, parent: CalldataBlock) { + constructor( + name: string, + signature: string, + parentName: string, + relocatable: boolean, + dependency: CalldataBlock, + parent: CalldataBlock, + ) { const headerSizeInBytes = 0; const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES; super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable); @@ -125,7 +145,8 @@ export class DependentCalldataBlock extends CalldataBlock { } public toBuffer(): Buffer { - const destinationOffset = (this.aliasFor !== undefined) ? this.aliasFor.getOffsetInBytes() : this.dependency.getOffsetInBytes(); + const destinationOffset = + this.aliasFor !== undefined ? this.aliasFor.getOffsetInBytes() : this.dependency.getOffsetInBytes(); const parentOffset = this.parent.getOffsetInBytes(); const parentHeaderSize = this.parent.getHeaderSizeInBytes(); const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); @@ -314,7 +335,7 @@ export class Calldata { //const ancestrialNamesOffset = name.startsWith('ptr<') ? 4 : 0; //const parentOffset = name.lastIndexOf(parentName); - const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, '');//.replace(`${parentName}[`, '['); + const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, ''); //.replace(`${parentName}[`, '['); const signature = block.getSignature(); // Current offset @@ -383,7 +404,7 @@ export class Calldata { const blocksByHash: { [key: string]: CalldataBlock } = {}; // 1. Create a queue of subtrees by hash - // Note that they are ordered the same as + // Note that they are ordered the same as const subtreeQueue = this.createQueue(this.root); let block: CalldataBlock | undefined; while ((block = subtreeQueue.popBack()) !== undefined) { @@ -435,7 +456,7 @@ export class Calldata { } public toAnnotatedString(): string { - return ""; + return ''; } public setRoot(block: CalldataBlock) { @@ -532,4 +553,4 @@ export class RawCalldata { public getSelector(): string { return this.selector; } -} \ No newline at end of file +} diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 243b221ef4..80797c5630 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -1,4 +1,11 @@ -import { RawCalldata, Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata"; +import { + RawCalldata, + Calldata, + CalldataBlock, + PayloadCalldataBlock, + DependentCalldataBlock, + MemberCalldataBlock, +} from './calldata'; import { MethodAbi, DataItem } from 'ethereum-types'; import { DecodingRules, EncodingRules } from './calldata'; import { BigNumber } from '../configured_bignumber'; @@ -67,7 +74,13 @@ export abstract class PayloadDataType extends DataType { const signature = this.getSignature(); const parentName = parentBlock === undefined ? '' : parentBlock.getName(); const relocatable = false; - const block = new PayloadCalldataBlock(name, signature, parentName, /*offsetInBytes,*/ relocatable, encodedValue); + const block = new PayloadCalldataBlock( + name, + signature, + parentName, + /*offsetInBytes,*/ relocatable, + encodedValue, + ); return block; } @@ -104,7 +117,14 @@ export abstract class DependentDataType extends DataType { const signature = this.getSignature(); const parentName = parentBlock === undefined ? '' : parentBlock.getName(); const relocatable = false; - const block = new DependentCalldataBlock(name, signature, parentName, relocatable, dependencyBlock, parentBlock); + const block = new DependentCalldataBlock( + name, + signature, + parentName, + relocatable, + dependencyBlock, + parentBlock, + ); return block; } @@ -135,8 +155,13 @@ export abstract class MemberDataType extends DataType { protected arrayLength: number | undefined; protected arrayElementType: string | undefined; - - public constructor(dataItem: DataItem, factory: DataTypeFactory, isArray: boolean = false, arrayLength?: number, arrayElementType?: string) { + public constructor( + dataItem: DataItem, + factory: DataTypeFactory, + isArray: boolean = false, + arrayLength?: number, + arrayElementType?: string, + ) { super(dataItem, factory); this.memberMap = {}; this.members = []; @@ -207,11 +232,17 @@ export abstract class MemberDataType extends DataType { } const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), parentName, this.isStatic(), false); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + this.getDataItem().name, + this.getSignature(), + parentName, + this.isStatic(), + false, + ); let members = this.members; if (this.isArray && this.arrayLength === undefined) { - [members,] = this.createMembersWithLength(this.getDataItem(), value.length); + [members] = this.createMembersWithLength(this.getDataItem(), value.length); const lenBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.length.toString(16)}`), 32); methodBlock.setHeader(lenBuf); @@ -228,12 +259,20 @@ export abstract class MemberDataType extends DataType { protected generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock { const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), parentName, this.isStatic(), false); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + this.getDataItem().name, + this.getSignature(), + parentName, + this.isStatic(), + false, + ); const memberBlocks: CalldataBlock[] = []; let childMap = _.cloneDeep(this.memberMap); _.forOwn(obj, (value: any, key: string) => { if (key in childMap === false) { - throw new Error(`Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`); + throw new Error( + `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, + ); } const block = this.members[this.memberMap[key]].generateCalldataBlock(value, methodBlock); memberBlocks.push(block); @@ -249,7 +288,10 @@ export abstract class MemberDataType extends DataType { } public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { - const block = (value instanceof Array) ? this.generateCalldataBlockFromArray(value, parentBlock) : this.generateCalldataBlockFromObject(value, parentBlock); + const block = + value instanceof Array + ? this.generateCalldataBlockFromArray(value, parentBlock) + : this.generateCalldataBlockFromObject(value, parentBlock); return block; } @@ -261,7 +303,7 @@ export abstract class MemberDataType extends DataType { const hexBase = 16; const arrayLength = new BigNumber(arrayLengthHex, hexBase); - [members,] = this.createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); + [members] = this.createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); } calldata.startScope(); @@ -314,9 +356,9 @@ export abstract class MemberDataType extends DataType { // Search for dependent members const dependentMember = _.find(this.members, (member: DataType) => { - return (member instanceof DependentDataType); + return member instanceof DependentDataType; }); - const isStatic = (dependentMember === undefined); // static if we couldn't find a dependent member + const isStatic = dependentMember === undefined; // static if we couldn't find a dependent member return isStatic; } } diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 2895ee00f7..10e7b987bf 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -21,7 +21,7 @@ export interface DataTypeStaticInterface { export class Address extends PayloadDataType { private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; - public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = "Address must be 20 bytes"; + public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = 'Address must be 20 bytes'; constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance(), Address.SIZE_KNOWN_AT_COMPILE_TIME); @@ -88,7 +88,7 @@ export class Bool extends PayloadDataType { const valueBuf = calldata.popWord(); const valueHex = ethUtil.bufferToHex(valueBuf); const valueNumber = new BigNumber(valueHex, 16); - let value: boolean = (valueNumber.equals(0)) ? false : true; + let value: boolean = valueNumber.equals(0) ? false : true; if (!(valueNumber.equals(0) || valueNumber.equals(1))) { throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`); } @@ -147,10 +147,7 @@ abstract class Number extends PayloadDataType { const negativeValue = invertedValue.plus(1); // Convert the negated value to a hex string - valueBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), - evmWordWidth, - ); + valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), evmWordWidth); } return valueBuf; @@ -279,7 +276,7 @@ export class Byte extends PayloadDataType { if (valueBuf.byteLength > this.width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { @@ -392,7 +389,6 @@ export class SolString extends PayloadDataType { } export class Pointer extends DependentDataType { - constructor(destDataType: DataType, parentDataType: DataType) { const destDataItem = destDataType.getDataItem(); const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; @@ -442,7 +438,7 @@ export class SolArray extends MemberDataType { const isArray = true; const arrayElementType = matches[1]; - const arrayLength = (matches[2] === '') ? undefined : parseInt(matches[2], 10); + const arrayLength = matches[2] === '' ? undefined : parseInt(matches[2], 10); super(dataItem, EvmDataTypeFactory.getInstance(), isArray, arrayLength, arrayElementType); this.elementType = arrayElementType; this.arraySignature = this.computeSignature(); @@ -513,7 +509,9 @@ export class Method extends MemberDataType { public decode(calldata: string, rules?: DecodingRules): any[] | object { if (!calldata.startsWith(this.selector)) { - throw new Error(`Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`); + throw new Error( + `Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`, + ); } const hasSelector = true; const value = super.decode(calldata, rules, hasSelector); @@ -524,7 +522,7 @@ export class Method extends MemberDataType { //console.log('O'.repeat(100), '\n', returndata, '\n', this.returnDataTypes, 'P'.repeat(100)); const returnValues: any[] = []; - const rules_ = rules ? rules : { structsAsObjects: false } as DecodingRules; + const rules_ = rules ? rules : ({ structsAsObjects: false } as DecodingRules); const rawReturnData = new RawCalldata(returndata, false); _.each(this.returnDataTypes, (dataType: DataType) => { returnValues.push(dataType.generateValue(rawReturnData, rules_)); @@ -549,7 +547,7 @@ export class Method extends MemberDataType { export class EvmDataTypeFactory implements DataTypeFactory { private static instance: DataTypeFactory; - private constructor() { } + private constructor() {} public static getInstance(): DataTypeFactory { if (!EvmDataTypeFactory.instance) { @@ -580,10 +578,11 @@ export class EvmDataTypeFactory implements DataTypeFactory { return dataType; } - if (parentDataType === undefined) { // @Todo -- will this work for return values? + if (parentDataType === undefined) { + // @Todo -- will this work for return values? throw new Error(`Trying to create a pointer`); } const pointer = new Pointer(dataType, parentDataType); return pointer; } -} \ No newline at end of file +} diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts index 991edb8c59..95ad84ac91 100644 --- a/packages/utils/src/abi_encoder/index.ts +++ b/packages/utils/src/abi_encoder/index.ts @@ -1,2 +1,2 @@ export { EncodingRules, DecodingRules } from './calldata'; -export * from './evm_data_types'; \ No newline at end of file +export * from './evm_data_types'; diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 6d0d0c3902..9925abcc32 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -12,143 +12,490 @@ const expect = chai.expect; describe.only('ABI Encoder', () => { describe('Optimizer', () => { - it('Should reuse duplicated strings in string array', async () => { - const method = new AbiEncoder.Method(AbiSamples.GAbi); - - const args = [ - { - a: new BigNumber(5), - e: '0x616161', - b: 'aaa', - f: '0xe41d2489571d322189246dafa5ebde1f4699f498' - } - ] - - // Verify optimized calldata is expected - const optimizedCalldata = method.encode(args, { optimize: true }); - //const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; - //expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - - // Verify args decode properly - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - //expect(decodedArgsJson).to.be.equal(argsJson); - - console.log(method.getSignature()); - console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); - console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); - - }); - - it('Should reuse duplicated strings in string array', async () => { + it('Should reuse duplicate strings in string array', async () => { + // Description: + // There are two unique values in the array `strings`. + // There should exist only one copy of each string in the optimized calldata. + // In unoptimized calldata, two copies of each string will be created. + // Generate calldata const method = new AbiEncoder.Method(AbiSamples.stringAbi); - const strings = [ - "Test String", - "Test String 2", - "Test String", - "Test String 2", - ]; + const strings = ['Test String', 'Test String 2', 'Test String', 'Test String 2']; const args = [strings]; - - // Verify optimized calldata is expected + // Validate calldata const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; + const expectedOptimizedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - - // Verify args decode properly + // Validate decoding const decodedArgs = method.decode(optimizedCalldata); const decodedArgsJson = JSON.stringify(decodedArgs); const argsJson = JSON.stringify(args); expect(decodedArgsJson).to.be.equal(argsJson); - - console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); - console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); }); - - it('Should point array elements to a duplicated value from another parameter', async () => { + it('Should point array elements to a duplicate value from another parameter', async () => { + // Description: + // There are two unique values in the array `strings`. + // The value "Test String" appears three times in this array. + // There should exist only one copy of this string in the optimized calldata. + // In unoptimized calldata, "Test String" would be written three times. + // Generate calldata const method = new AbiEncoder.Method(AbiSamples.optimizerAbi2); - const stringArray = [ - "Test String", - "Test String", - "Test String", - "Test String 2", - ]; + const strings = ['Test String', 'Test String', 'Test String', 'Test String 2']; const string = 'Test String'; - const args = [stringArray, string]; - - // Verify optimized calldata is expected + const args = [strings, string]; + // Validate calldata const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000'; + const expectedOptimizedCalldata = + '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - - // Verify args decode properly + // Validate decoding const decodedArgs = method.decode(optimizedCalldata); const decodedArgsJson = JSON.stringify(decodedArgs); const argsJson = JSON.stringify(args); expect(decodedArgsJson).to.be.equal(argsJson); - - console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); - console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); }); - - - it('Optimizer #3 (tuple should point to array)', async () => { + it('Dynamic Array of uints should point to Dynamic Array of Tuple(Uint)s', async () => { + // Description: + // There are two dynamic arrays, one of uint's and one of tuples. + // Each tuple is simply a wrapper for a uint - tuple := {key: uintValue} + // While the elements of these arrays have different types, they + // have the same representation in calldata. + // That is, a `uint` and a `tuple{uint}` both consume exactly one word of calldata. + // In the optimized calldata, only the elements of one array should be included. + // Both arrays will then point to the same set of elements. + // Generate calldata const method = new AbiEncoder.Method(AbiSamples.optimizerAbi3); - const uint8Array = [ - new BigNumber(100), - new BigNumber(150), - new BigNumber(200), - new BigNumber(225), - ]; + const uint8Array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)]; const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; const args = [uint8Array, uintTupleArray]; - - - const TEST = method.encode(args, { optimize: true, annotate: true }); - console.log('*'.repeat(50), ' ENCODED DATA ', TEST); - + // Validata calldata const optimizedCalldata = method.encode(args, { optimize: true }); - - console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + const expectedOptimizedCalldata = + '0x5b5c78fd0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000e1'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Dynamic Arrays', async () => { + // Description: + // Two dynamic arrays with the same values. + // In the optimized calldata, only one set of elements should be included. + // Both arrays should point to this set. + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi5); + const array1 = [new BigNumber(100), new BigNumber(150)]; + const array2 = [array1[0], array1[1]]; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x7bc4226e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding const decodedArgs = method.decode(optimizedCalldata); const decodedArgsJson = JSON.stringify(decodedArgs); const argsJson = JSON.stringify(args); - console.log(JSON.stringify(decodedArgs)); expect(decodedArgsJson).to.be.equal(argsJson); }); - it('Optimizer #4 (Expect no optimization)', async () => { + /* + it.only('Duplicate Static Arrays', async () => { + // Description: + // Two dynamic arrays with the same values. + // In the optimized calldata, only one set of elements should be included. + // Both arrays should point to this set. + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi5); + const array1 = [new BigNumber(100), new BigNumber(150)]; + const array2 = [array1[0], array1[1]]; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x7bc4226e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + + it.only('Duplicate Tuples', async () => { + // Description: + // Two dynamic arrays with the same values. + // In the optimized calldata, only one set of elements should be included. + // Both arrays should point to this set. + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi5); + const array1 = [new BigNumber(100), new BigNumber(150)]; + const array2 = [array1[0], array1[1]]; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x7bc4226e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + });*/ + + it('Static Array of static types should not be optimized', async () => { + // Generate calldata const method = new AbiEncoder.Method(AbiSamples.optimizerAbi4); - const uint8Array = [ - new BigNumber(100), - new BigNumber(150), - new BigNumber(200), - new BigNumber(225), - ]; + const uint8Array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)]; const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; const args = [uint8Array, uintTupleArray]; - - - const TEST = method.encode(args, { optimize: true, annotate: true }); - console.log('*'.repeat(50), ' ENCODED DATA ', TEST); - + // Validate calldata const optimizedCalldata = method.encode(args, { optimize: true }); - - console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + const unoptimizedCalldata = method.encode(args); + expect(optimizedCalldata).to.be.equal(unoptimizedCalldata); + // Validate decoding const decodedArgs = method.decode(optimizedCalldata); const decodedArgsJson = JSON.stringify(decodedArgs); const argsJson = JSON.stringify(args); - console.log(JSON.stringify(decodedArgs)); expect(decodedArgsJson).to.be.equal(argsJson); }); + + // Todo: Unfixed array points to fixed array + // Todo: Unfixed to unfixed array + // Todo: Duplicate tuples }); - describe.only('ABI Tests at Method Level', () => { - it('Crazy ABI', async () => { - const method = new AbiEncoder.Method(AbiSamples.crazyAbi); - console.log(method.getSignature()); - + describe('Method ABIs', () => { + it('Types with default widths', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); + const args = [ + new BigNumber(1), + new BigNumber(-1), + '0x56', + [new BigNumber(1)], + [new BigNumber(-1)], + ['0x56'], + ]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Static Tuples (Array has defined length)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Static Tuples (Array has dynamic length)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi); + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Dynamic Tuples (Array has defined length)', async () => { + // Generate Calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi); + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Dynamic Tuples (Array has dynamic length)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi); + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Multidimensional Arrays / Static Members', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); + // Eight 3-dimensional arrays of uint8[2][2][2] + let value = 0; + const args = []; + for (let i = 0; i < 8; ++i) { + args.push([ + [ + [new BigNumber(++value), new BigNumber(++value)], + [new BigNumber(++value), new BigNumber(++value)], + ], + [ + [new BigNumber(++value), new BigNumber(++value)], + [new BigNumber(++value), new BigNumber(++value)], + ], + ]); + } + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; + expect(calldata).to.be.equal(expectedCalldata); + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Multidimensional Arrays / Dynamic Members', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi); + // Eight 3-dimensional arrays of string[2][2][2] + let value = 0; + const args = []; + for (let i = 0; i < 4; ++i) { + args.push([ + [ + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + ], + [ + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + ], + ]); + } + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Fixed Length Array / Dynamic Members', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [['Brave', 'New', 'World']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Fixed Length Array / Dynamic Members', async () => { + // Generaet calldata + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [['Brave', 'New', 'World']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Unfixed Length Array / Dynamic Members ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); + const args = [['Brave', 'New', 'World']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Unfixed Length Array / Static Members ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Fixed Length Array / Static Members ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.stringAbi); + const args = [['five', 'six', 'seven']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Static Tuple', async () => { + // Generate calldata + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); + const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Dynamic Tuple (Array input)', async () => { + // Generate calldata + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); + const args = [[new BigNumber(5), 'five']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Dynamic Tuple (Object input)', async () => { + // Generate Calldata + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); + const args = [[new BigNumber(5), 'five']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Large, Flat ABI', async () => { + // Construct calldata + const method = new AbiEncoder.Method(AbiSamples.largeFlatAbi); + const args = [ + new BigNumber(256745454), + new BigNumber(-256745454), + new BigNumber(434244), + '0x43', + '0x0001020304050607080911121314151617181920212223242526272829303132', + '0x0001020304050607080911121314151617181920212223242526272829303132080911121314151617181920212223242526272829303132', + 'Little peter piper piped a piping pepper pot', + '0xe41d2489571d322189246dafa5ebde1f4699f498', + true, + ]; + // Validate calldata + const calldata = method.encode(args); + const expectedCalldata = + '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Large, Nested ABI', async () => { + // Construct Calldata + const method = new AbiEncoder.Method(AbiSamples.largeNestedAbi); const someStaticArray = [new BigNumber(127), new BigNumber(14), new BigNumber(54)]; const someStaticArrayWithDynamicMembers = [ 'the little piping piper piped a piping pipper papper', @@ -175,7 +522,8 @@ describe.only('ABI Encoder', () => { ]; const someTuple = { someUint32: new BigNumber(4037824789), - someStr: 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.' + someStr: + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', }; const someTupleWithDynamicTypes = { someUint: new BigNumber(4024789), @@ -196,7 +544,6 @@ describe.only('ABI Encoder', () => { someAddress: '0x89571d322189e415ebde1f4699f498d24246dafa', }; const someArrayOfTuplesWithDynamicTypes = [someTupleWithDynamicTypes2, someTupleWithDynamicTypes3]; - const args = { someStaticArray: someStaticArray, someStaticArrayWithDynamicMembers: someStaticArrayWithDynamicMembers, @@ -204,421 +551,21 @@ describe.only('ABI Encoder', () => { some2DArray: some2DArray, someTuple: someTuple, someTupleWithDynamicTypes: someTupleWithDynamicTypes, - someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes + someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes, }; - const calldata = method.encode(args); - console.log(calldata); - - console.log('*'.repeat(40)); - console.log(JSON.stringify(args)); - console.log(method.getSignature()); - - const expectedCalldata = '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; - //const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; + // Validate calldata + const expectedCalldata = + '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding + // Validate decoding const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata, { structsAsObjects: true }); const decodedValueJson = JSON.stringify(decodedValue); - console.log(`DECODED`, '*'.repeat(200), '\n', decodedValueJson); expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); - it('Crazy ABI #1', async () => { - const method = new AbiEncoder.Method(AbiSamples.crazyAbi1); - - const args = [ - new BigNumber(256745454), - new BigNumber(-256745454), - new BigNumber(434244), - '0x43', - '0x0001020304050607080911121314151617181920212223242526272829303132', - '0x0001020304050607080911121314151617181920212223242526272829303132080911121314151617181920212223242526272829303132', - 'Little peter piper piped a piping pepper pot', - '0xe41d2489571d322189246dafa5ebde1f4699f498', - true - ]; - - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - - it('Types with default widths', async () => { - const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); - console.log(method); - const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Array of Static Tuples (Array has defined length)', async () => { - const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); - - let value = 0; - const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { - arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Array of Static Tuples (Array has dynamic length)', async () => { - const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi); - - let value = 0; - const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { - arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Array of Dynamic Tuples (Array has defined length)', async () => { - const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi); - - let value = 0; - const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { - arrayOfTuples.push([new BigNumber(++value), (new BigNumber(++value)).toString()]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Array of Dynamic Tuples (Array has dynamic length)', async () => { - const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi); - - let value = 0; - const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { - arrayOfTuples.push([new BigNumber(++value), (new BigNumber(++value)).toString()]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Multidimensional Arrays / Static Members', async () => { - const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); - - // Eight 3-dimensional arrays of uint8[2][2][2] - let value = 0; - const args = []; - for (let i = 0; i < 8; ++i) { - args.push( - [ - [ - [new BigNumber(++value), new BigNumber(++value)], - [new BigNumber(++value), new BigNumber(++value)], - ], - [ - [new BigNumber(++value), new BigNumber(++value)], - [new BigNumber(++value), new BigNumber(++value)], - ] - ] - ); - } - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; expect(calldata).to.be.equal(expectedCalldata); - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Multidimensional Arrays / Dynamic Members', async () => { - const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi); - - // Eight 3-dimensional arrays of string[2][2][2] - let value = 0; - const args = []; - for (let i = 0; i < 4; ++i) { - args.push( - [ - [ - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - ], - [ - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - ] - ] - ); - } - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(method.getSignature()); - console.log(JSON.stringify(args)); - const expectedCalldata = '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Fixed Lenfgth Array / Dynamic Members', async () => { - const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); - const args = [["Brave", "New", "World"]]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(JSON.stringify(args)); - const expectedCalldata = - '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Fixed Lenfgth Array / Dynamic Members', async () => { - const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); - const args = [["Brave", "New", "World"]]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(JSON.stringify(args)); - const expectedCalldata = - '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Unfixed Length Array / Dynamic Members ABI', async () => { - const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); - const args = [["Brave", "New", "World"]]; - const calldata = method.encode(args); - console.log(calldata); - console.log('*'.repeat(40)); - console.log(JSON.stringify(args)); - const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Unfixed Length Array / Static Members ABI', async () => { - const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - const calldata = method.encode(args); - const expectedCalldata = '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - - it('Fixed Length Array / Static Members ABI', async () => { - const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - const calldata = method.encode(args); - const expectedCalldata = - '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - - it('Simple ABI 2', async () => { - const method = new AbiEncoder.Method(AbiSamples.simpleAbi2); - - const args = [ - '0xaf', // e (bytes1) - '0x0001020304050607080911121314151617181920212223242526272829303132', // f (bytes32) - '0x616161616161616161616161616161616161616161616161616161616161616161616161616161611114f324567838475647382938475677448899338457668899002020202020', // g - 'My first name is Greg and my last name is Hysen, what do ya know!', // h - ]; - - const calldata = method.encode(args); - const expectedCalldata = - '0x7ac2bd96af000000000000000000000000000000000000000000000000000000000000000001020304050607080911121314151617181920212223242526272829303132000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000047616161616161616161616161616161616161616161616161616161616161616161616161616161611114f3245678384756473829384756774488993384576688990020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414d79206669727374206e616d65206973204772656720616e64206d79206c617374206e616d6520697320487973656e2c207768617420646f207961206b6e6f772100000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Array ABI', async () => { - const method = new AbiEncoder.Method(AbiSamples.stringAbi); - console.log(method); - const args = [['five', 'six', 'seven']]; - const calldata = method.encode(args); - console.log(method.getSignature()); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = - '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Static Tuple', async () => { - // This is dynamic because it has dynamic members - const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); - const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]; - const calldata = method.encode(args); - console.log(method.getSignature()); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Dynamic Tuple (Array input)', async () => { - // This is dynamic because it has dynamic members - const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); - const args = [[new BigNumber(5), 'five']]; - const calldata = method.encode(args); - console.log(method.getSignature()); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = - '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - - // Test decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - - it('Dynamic Tuple (Object input)', async () => { - // This is dynamic because it has dynamic members - const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); - const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); - console.log(method.getSignature()); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = - '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - }); + // @TODO: Test Nested Tuples (Not Supported on ) it.skip('Nested Tuples', async () => { // Couldn't get nested tuples to work with Remix @@ -628,8 +575,8 @@ describe.only('ABI Encoder', () => { someUint32: new BigNumber(30472), nestedTuple: { someUint: new BigNumber('48384725243211555532'), - someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498' - } + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', + }, }; const secondTuple = { someUint: new BigNumber(2984237422), @@ -640,50 +587,47 @@ describe.only('ABI Encoder', () => { someUint: new BigNumber(234324), someStr: 'Im also a short string -- oops I just got loooooooooooooooooonger!', someBytes: '0x23847287fff3472984723498ff23487324987aaa237438911873429472ba', - someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498' - } + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', + }, }, someBytes: '0x2834y3947289423u489aaaff4783924739847489', someAddress: '0xe41d2489571d322189246dafa5ebde1f4699afaf', }; const thirdTuple = { - 'someUint': new BigNumber(37422), - 'someStr': 'This into the next word of memory. string will exceed 256 bits, so it will spill.', - 'nestedTuple': { + someUint: new BigNumber(37422), + someStr: 'This into the next word of memory. string will exceed 256 bits, so it will spill.', + nestedTuple: { someUint32: new BigNumber(23999222), - 'secondNestedTuple': { - 'someUint': new BigNumber(324), - 'someStr': 'Im also a short st', - 'someBytes': '0x723498ff2348732498723847287fff3472984aaa237438911873429472ba', - 'someAddress': '0x46dafa5ebde1f4699f498e41d2489571d3221892' - } + secondNestedTuple: { + someUint: new BigNumber(324), + someStr: 'Im also a short st', + someBytes: '0x723498ff2348732498723847287fff3472984aaa237438911873429472ba', + someAddress: '0x46dafa5ebde1f4699f498e41d2489571d3221892', + }, }, - 'someBytes': '0x947289423u489aaaff472834y383924739847489', - 'someAddress': '0x46dafa5ebde1f46e41d2489571d322189299afaf', + someBytes: '0x947289423u489aaaff472834y383924739847489', + someAddress: '0x46dafa5ebde1f46e41d2489571d322189299afaf', }; const fourthTuple = { - 'someUint': new BigNumber(222283488822), - 'someStr': 'exceed 256 bits, so it will spill into the. This string will next word of memory.', - 'nestedTuple': { + someUint: new BigNumber(222283488822), + someStr: 'exceed 256 bits, so it will spill into the. This string will next word of memory.', + nestedTuple: { someUint32: new BigNumber(2300), - 'secondNestedTuple': { - 'someUint': new BigNumber(343224), - 'someStr': 'The alphabet backwards is arguably easier to say if thats the way you learned the first time.', - 'someBytes': '0x87324987aaa23743891187323847287fff3472984723498ff234429472ba', - 'someAddress': '0x71d322189246dafa5ebe41d24895de1f4699f498' - } + secondNestedTuple: { + someUint: new BigNumber(343224), + someStr: + 'The alphabet backwards is arguably easier to say if thats the way you learned the first time.', + someBytes: '0x87324987aaa23743891187323847287fff3472984723498ff234429472ba', + someAddress: '0x71d322189246dafa5ebe41d24895de1f4699f498', + }, }, - 'someBytes': '0x2783924739847488343947289423u489aaaff490', - 'someAddress': '0xebde1d322189246dafa1f4699afafe41d2489575', + someBytes: '0x2783924739847488343947289423u489aaaff490', + someAddress: '0xebde1d322189246dafa1f4699afafe41d2489575', }; - const args = [ - [firstTuple], - [secondTuple, thirdTuple, fourthTuple] - ]; + const args = [[firstTuple], [secondTuple, thirdTuple, fourthTuple]]; console.log('*'.repeat(250), method, '*'.repeat(250)); - const calldata = method.encode(args); console.log(method.getSignature()); console.log(method.selector); @@ -693,37 +637,9 @@ describe.only('ABI Encoder', () => { const expectedCalldata = '0x'; expect(calldata).to.be.equal(expectedCalldata); }); - - it.skip('Object ABI (Object input - Missing Key)', async () => { - const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); - const calldata = method.encode([{ someUint: new BigNumber(5) }]); - console.log(method.getSignature()); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = - '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - - // @TODO: Figure out how to catch throw - expect(calldata).to.be.equal(expectedCalldata); - }); - - it.skip('Object ABI (Object input - Too Many Keys)', async () => { - const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); - const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five', unwantedKey: 14 }]); - console.log(method.getSignature()); - console.log(method.selector); - - console.log(calldata); - const expectedCalldata = - '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - - // @TODO: Figure out how to catch throw - expect(calldata).to.be.equal(expectedCalldata); - }); }); - describe.only('Array', () => { + describe('Array', () => { it('Fixed size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[2]' }; @@ -732,7 +648,8 @@ describe.only('ABI Encoder', () => { const args = [new BigNumber(5), new BigNumber(6)]; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -740,7 +657,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Dynamic size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[]' }; @@ -749,7 +665,8 @@ describe.only('ABI Encoder', () => { const args = [new BigNumber(5), new BigNumber(6)]; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -757,16 +674,16 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Fixed size; Dynamic elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[2]' }; const dataType = new AbiEncoder.SolArray(testDataItem); // Construct args to be encoded - const args = ["Hello", "world"]; + const args = ['Hello', 'world']; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -774,16 +691,16 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Dynamic size; Dynamic elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[]' }; const dataType = new AbiEncoder.SolArray(testDataItem); // Construct args to be encoded - const args = ["Hello", "world"]; + const args = ['Hello', 'world']; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -791,21 +708,56 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - - // @TODO: Add test that fails if we pass in the wrong number of elements - // @TODO: Add test that fails if we pass in an element of incorrecrt type + it('Static size; Too Few Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'string[3]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const args = ['Hello', 'world']; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Expected array of 3 elements, but got array of length 2'); + }); + it('Static size; Too Many Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'string[1]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const args = ['Hello', 'world']; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Expected array of 1 elements, but got array of length 2'); + }); + it('Element Type Mismatch', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'uint[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const args = [new BigNumber(1), 'Bad Argument']; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); }); - describe.only('Tuple', () => { + describe('Tuple', () => { it('Static elements only', async () => { // Create DataType object - const testDataItem = { name: 'Tuple', type: 'tuple', components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }] }; + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], + }; const dataType = new AbiEncoder.Tuple(testDataItem); // Construct args to be encoded const args = { field_1: new BigNumber(-5), field_2: true }; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; + const expectedEncodedArgs = + '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodingRules = { structsAsObjects: true } as DecodingRules; @@ -814,16 +766,20 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Dynamic elements only', async () => { // Create DataType object - const testDataItem = { name: 'Tuple', type: 'tuple', components: [{ name: 'field_1', type: 'string' }, { name: 'field_2', type: 'bytes' }] }; + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'string' }, { name: 'field_2', type: 'bytes' }], + }; const dataType = new AbiEncoder.Tuple(testDataItem); // Construct args to be encoded - const args = { field_1: "Hello, World!", field_2: '0xabcdef0123456789' }; + const args = { field_1: 'Hello, World!', field_2: '0xabcdef0123456789' }; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodingRules = { structsAsObjects: true } as DecodingRules; @@ -832,16 +788,30 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Static and dynamic elements mixed', async () => { // Create DataType object - const testDataItem = { name: 'Tuple', type: 'tuple', components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'string' }, { name: 'field_3', type: 'bool' }, { name: 'field_4', type: 'bytes' }] }; + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [ + { name: 'field_1', type: 'int32' }, + { name: 'field_2', type: 'string' }, + { name: 'field_3', type: 'bool' }, + { name: 'field_4', type: 'bytes' }, + ], + }; const dataType = new AbiEncoder.Tuple(testDataItem); // Construct args to be encoded - const args = { field_1: new BigNumber(-5), field_2: "Hello, World!", field_3: true, field_4: '0xabcdef0123456789' }; + const args = { + field_1: new BigNumber(-5), + field_2: 'Hello, World!', + field_3: true, + field_4: '0xabcdef0123456789', + }; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodingRules = { structsAsObjects: true } as DecodingRules; @@ -850,12 +820,39 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - - // @TODO: Add test that fails if we pass in the wrong number of elements - // @TODO: Add test that fails if we pass in arguments in wrong order + it('Missing Key', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field_1: new BigNumber(-5) }; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Could not assign tuple to object: missing keys field_2'); + }); + it('Bad Key', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { unknown_field: new BigNumber(-5) }; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw("Could not assign tuple to object: unrecognized key 'unknown_field' in object Tuple"); + }); }); - describe.only('Address', () => { + describe('Address', () => { it('Valid Address', async () => { // Create DataType object const testDataItem = { name: 'Address', type: 'address' }; @@ -872,7 +869,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Invalid Address - input is not valid hex', async () => { // Create DataType object const testDataItem = { name: 'Address', type: 'address' }; @@ -880,9 +876,10 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = 'e4'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); + expect(() => { + dataType.encode(args); + }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); }); - it('Invalid Address - input is not 20 bytes', async () => { // Create DataType object const testDataItem = { name: 'Address', type: 'address' }; @@ -890,11 +887,13 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = '0xe4'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); + expect(() => { + dataType.encode(args); + }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); }); }); - describe.only('Bool', () => { + describe('Bool', () => { it('True', async () => { // Create DataType object const testDataItem = { name: 'Boolean', type: 'bool' }; @@ -911,7 +910,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('False', async () => { // Create DataType object const testDataItem = { name: 'Boolean', type: 'bool' }; @@ -930,7 +928,7 @@ describe.only('ABI Encoder', () => { }); }); - describe.only('Integer', () => { + describe('Integer', () => { it('Int256 - Positive Base Case', async () => { // Create DataType object const testDataItem = { name: 'Integer (256)', type: 'int' }; @@ -947,7 +945,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int256 - Negative Base Case', async () => { // Create DataType object const testDataItem = { name: 'Integer (256)', type: 'int' }; @@ -964,13 +961,12 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int256 - Positive Value', async () => { // Create DataType object const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max256BitInteger = (new BigNumber(2)).pow(255).minus(1); + const max256BitInteger = new BigNumber(2).pow(255).minus(1); const args = max256BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -982,13 +978,12 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int256 - Negative Value', async () => { // Create DataType object const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min256BitInteger = (new BigNumber(2)).pow(255).times(-1); + const min256BitInteger = new BigNumber(2).pow(255).times(-1); const args = min256BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1000,29 +995,30 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int256 - Value too large', async () => { // Create DataType object const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max256BitInteger = (new BigNumber(2)).pow(255).minus(1); + const max256BitInteger = new BigNumber(2).pow(255).minus(1); const args = max256BitInteger.plus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); - it('Int256 - Value too small', async () => { // Create DataType object const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min256BitInteger = (new BigNumber(2)).pow(255).times(-1); + const min256BitInteger = new BigNumber(2).pow(255).times(-1); const args = min256BitInteger.minus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); - it('Int32 - Positive Base Case', async () => { // Create DataType object const testDataItem = { name: 'Integer (32)', type: 'int32' }; @@ -1039,7 +1035,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int32 - Negative Base Case', async () => { // Create DataType object const testDataItem = { name: 'Integer (32)', type: 'int32' }; @@ -1056,13 +1051,12 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int32 - Positive Value', async () => { // Create DataType object const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max32BitInteger = (new BigNumber(2)).pow(31).minus(1); + const max32BitInteger = new BigNumber(2).pow(31).minus(1); const args = max32BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1074,13 +1068,12 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int32 - Negative Value', async () => { // Create DataType object const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min32BitInteger = (new BigNumber(2)).pow(31).times(-1); + const min32BitInteger = new BigNumber(2).pow(31).times(-1); const args = min32BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1092,31 +1085,33 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Int32 - Value too large', async () => { // Create DataType object const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max32BitInteger = (new BigNumber(2)).pow(31).minus(1); + const max32BitInteger = new BigNumber(2).pow(31).minus(1); const args = max32BitInteger.plus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); - it('Int32 - Value too small', async () => { // Create DataType object const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min32BitInteger = (new BigNumber(2)).pow(31).times(-1); + const min32BitInteger = new BigNumber(2).pow(31).times(-1); const args = min32BitInteger.minus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); }); - describe.only('Unsigned Integer', () => { + describe('Unsigned Integer', () => { it('UInt256 - Positive Base Case', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; @@ -1133,13 +1128,12 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('UInt256 - Positive Value', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max256BitUnsignedInteger = (new BigNumber(2)).pow(256).minus(1); + const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1); const args = max256BitUnsignedInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1151,7 +1145,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('UInt256 - Zero Value', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; @@ -1169,29 +1162,30 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('UInt256 - Value too large', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max256BitUnsignedInteger = (new BigNumber(2)).pow(256).minus(1); + const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1); const args = max256BitUnsignedInteger.plus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); - it('UInt256 - Value too small', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const min256BitUnsignedInteger = new BigNumber(0) + const min256BitUnsignedInteger = new BigNumber(0); const args = min256BitUnsignedInteger.minus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); - it('UInt32 - Positive Base Case', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; @@ -1208,13 +1202,12 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('UInt32 - Positive Value', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max32BitUnsignedInteger = (new BigNumber(2)).pow(32).minus(1); + const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1); const args = max32BitUnsignedInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1226,7 +1219,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('UInt32 - Zero Value', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; @@ -1244,18 +1236,18 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('UInt32 - Value too large', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max32BitUnsignedInteger = (new BigNumber(2)).pow(32).minus(1); + const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1); const args = max32BitUnsignedInteger.plus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); - it('UInt32 - Value too small', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; @@ -1264,11 +1256,13 @@ describe.only('ABI Encoder', () => { const min32BitUnsignedInteger = new BigNumber(0); const args = min32BitUnsignedInteger.minus(1); // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw(); + expect(() => { + dataType.encode(args); + }).to.throw(); }); }); - describe.only('Static Bytes', () => { + describe('Static Bytes', () => { it('Single Byte (byte)', async () => { // Create DataType object const testDataItem = { name: 'Static Byte', type: 'byte' }; @@ -1285,7 +1279,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Single Byte (bytes1)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes1', type: 'bytes1' }; @@ -1302,7 +1295,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('4 Bytes (bytes4)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; @@ -1319,7 +1311,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('4 Bytes (bytes4); Encoder must pad input', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; @@ -1338,7 +1329,6 @@ describe.only('ABI Encoder', () => { const paddedArgsAsJson = JSON.stringify(paddedArgs); expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); }); - it('32 Bytes (bytes32)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; @@ -1355,7 +1345,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('32 Bytes (bytes32); Encoder must pad input', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; @@ -1374,7 +1363,6 @@ describe.only('ABI Encoder', () => { const paddedArgsAsJson = JSON.stringify(paddedArgs); expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); }); - it('Should throw when pass in too many bytes (bytes4)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; @@ -1382,9 +1370,12 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = '0x0102030405'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x0102030405 (5 bytes), which exceeds max bytes that can be stored in a bytes4'); + expect(() => { + dataType.encode(args); + }).to.throw( + 'Tried to assign 0x0102030405 (5 bytes), which exceeds max bytes that can be stored in a bytes4', + ); }); - it('Should throw when pass in too many bytes (bytes32)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; @@ -1392,9 +1383,12 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = '0x010203040506070809101112131415161718192021222324252627282930313233'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x010203040506070809101112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32'); + expect(() => { + dataType.encode(args); + }).to.throw( + 'Tried to assign 0x010203040506070809101112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32', + ); }); - it('Should throw when pass in bad hex (no 0x prefix)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; @@ -1402,9 +1396,10 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = '0102030405060708091011121314151617181920212223242526272829303132'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); + expect(() => { + dataType.encode(args); + }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); }); - it('Should throw when pass in bad hex (include a half-byte)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; @@ -1412,11 +1407,13 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = '0x010'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); + expect(() => { + dataType.encode(args); + }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); }); }); - describe.only('Dynamic Bytes Dynamic', () => { + describe('Dynamic Bytes', () => { it('Fits into one EVM word', async () => { // Create DataType object const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; @@ -1426,7 +1423,8 @@ describe.only('ABI Encoder', () => { const args = '0x1a18bf61'; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -1434,7 +1432,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Spans multiple EVM words', async () => { // Create DataType object const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; @@ -1444,7 +1441,8 @@ describe.only('ABI Encoder', () => { const args = '0x' + '61'.repeat(40); // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -1452,7 +1450,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Input as Buffer', async () => { // Create DataType object const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; @@ -1463,7 +1460,8 @@ describe.only('ABI Encoder', () => { const argsAsBuffer = ethUtil.toBuffer(args); // Encode Args and validate result const encodedArgs = dataType.encode(argsAsBuffer); - const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -1471,7 +1469,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Should throw when pass in bad hex (no 0x prefix)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes', type: 'bytes' }; @@ -1479,9 +1476,10 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = '01'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '01'"); + expect(() => { + dataType.encode(args); + }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '01'"); }); - it('Should throw when pass in bad hex (include a half-byte)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes', type: 'bytes' }; @@ -1489,11 +1487,13 @@ describe.only('ABI Encoder', () => { // Construct args to be encoded const args = '0x010'; // Encode Args and validate result - expect(() => { dataType.encode(args) }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); + expect(() => { + dataType.encode(args); + }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); }); }); - describe.only('String', () => { + describe('String', () => { it('Fits into one EVM word', async () => { // Create DataType object const testDataItem = { name: 'String', type: 'string' }; @@ -1503,7 +1503,8 @@ describe.only('ABI Encoder', () => { const args = 'five'; // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -1511,7 +1512,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('Spans multiple EVM words', async () => { // Create DataType object const testDataItem = { name: 'String', type: 'string' }; @@ -1521,7 +1521,8 @@ describe.only('ABI Encoder', () => { const args = 'a'.repeat(40); // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); @@ -1529,7 +1530,6 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); - it('String that begins with 0x prefix', async () => { // Create DataType object const testDataItem = { name: 'String', type: 'string' }; @@ -1539,7 +1539,8 @@ describe.only('ABI Encoder', () => { const args = '0x' + 'a'.repeat(40); // Encode Args and validate result const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002a30786161616161616161616161616161616161616161616161616161616161616161616161616161616100000000000000000000000000000000000000000000'; + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002a30786161616161616161616161616161616161616161616161616161616161616161616161616161616100000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); diff --git a/packages/utils/test/abi_samples.ts b/packages/utils/test/abi_samples.ts index aa38711cd8..806ad17004 100644 --- a/packages/utils/test/abi_samples.ts +++ b/packages/utils/test/abi_samples.ts @@ -34,32 +34,32 @@ export const stringAbi = { type: 'function', } as MethodAbi; - export const GAbi = { constant: false, inputs: [ { - components: [{ - name: 'a', - type: 'uint256', - }, - { - name: 'b', - type: 'string', - }, - { - name: 'e', - type: 'bytes', - }, - { - name: 'f', - type: 'address', - }], + components: [ + { + name: 'a', + type: 'uint256', + }, + { + name: 'b', + type: 'string', + }, + { + name: 'e', + type: 'bytes', + }, + { + name: 'f', + type: 'address', + }, + ], name: 'f', type: 'tuple', - - } + }, ], name: 'simpleFunction', outputs: [], @@ -137,6 +137,25 @@ export const optimizerAbi4 = { type: 'function', } as MethodAbi; +export const optimizerAbi5 = { + constant: false, + inputs: [ + { + name: 'array1', + type: 'uint8[]', + }, + { + name: 'array2', + type: 'uint8[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + export const typesWithDefaultWidthsAbi = { constant: false, inputs: [ @@ -456,7 +475,7 @@ export const staticArrayAbi = { { name: 'someStaticArray', type: 'uint8[3]', - } + }, ], name: 'simpleFunction', outputs: [], @@ -471,7 +490,7 @@ export const staticArrayDynamicMembersAbi = { { name: 'someStaticArray', type: 'string[3]', - } + }, ], name: 'simpleFunction', outputs: [], @@ -486,7 +505,7 @@ export const dynamicArrayDynamicMembersAbi = { { name: 'someStaticArray', type: 'string[]', - } + }, ], name: 'simpleFunction', outputs: [], @@ -501,7 +520,7 @@ export const dynamicArrayStaticMembersAbi = { { name: 'someStaticArray', type: 'uint8[]', - } + }, ], name: 'simpleFunction', outputs: [], @@ -510,7 +529,7 @@ export const dynamicArrayStaticMembersAbi = { type: 'function', } as MethodAbi; -export const crazyAbi1 = { +export const largeFlatAbi = { constant: false, inputs: [ { @@ -557,7 +576,7 @@ export const crazyAbi1 = { type: 'function', } as MethodAbi; -export const crazyAbi = { +export const largeNestedAbi = { constant: false, inputs: [ { @@ -641,7 +660,7 @@ export const crazyAbi = { type: 'address', }, ], - } + }, ], name: 'simpleFunction', outputs: [], @@ -730,7 +749,7 @@ export const nestedTuples = { type: 'address', }, ], - } + }, ], name: 'simpleFunction', outputs: [], From 0d65c9da4ad6d925779b9b6e8ff99bea4c25eedf Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 16:52:45 -0800 Subject: [PATCH 133/230] Optimizer Tests --- packages/utils/test/abi_encoder_test.ts | 348 ++++++++++++++++-------- packages/utils/test/abi_samples.ts | 88 ------ 2 files changed, 238 insertions(+), 198 deletions(-) diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 9925abcc32..c5be35d5e1 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -4,6 +4,7 @@ import 'mocha'; import { chaiSetup } from './utils/chai_setup'; import { BigNumber, AbiEncoder } from '../src/'; import * as AbiSamples from './abi_samples'; +import * as OptimizedAbis from './optimizer_abis'; import { DecodingRules } from '../src/abi_encoder'; import ethUtil = require('ethereumjs-util'); @@ -12,19 +13,86 @@ const expect = chai.expect; describe.only('ABI Encoder', () => { describe('Optimizer', () => { - it('Should reuse duplicate strings in string array', async () => { - // Description: - // There are two unique values in the array `strings`. - // There should exist only one copy of each string in the optimized calldata. - // In unoptimized calldata, two copies of each string will be created. + it('Duplicate Dynamic Arrays with Static Elements', async () => { // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.stringAbi); - const strings = ['Test String', 'Test String 2', 'Test String', 'Test String 2']; + const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithStaticElements); + const array1 = [new BigNumber(100), new BigNumber(150)]; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + console.log(optimizedCalldata); + const expectedOptimizedCalldata = + '0x7221063300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Dynamic Arrays with Dynamic Elements', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithDynamicElements); + const array1 = ["Hello", "World"]; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0xbb4f12e300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Static Arrays with Static Elements (should not optimize)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithStaticElements); + const array1 = [new BigNumber(100), new BigNumber(150)]; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x7f8130430000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + const unoptimizedCalldata = method.encode(args); + expect(optimizedCalldata).to.be.equal(unoptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Static Arrays with Dynamic Elements', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithDynamicElements); + const array1 = ["Hello", "World"]; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x9fe31f8e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Array Elements (should optimize)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateArrayElements); + const strings = ['Hello', 'World', 'Hello', 'World']; const args = [strings]; // Validate calldata const optimizedCalldata = method.encode(args, { optimize: true }); const expectedOptimizedCalldata = - '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); @@ -32,21 +100,15 @@ describe.only('ABI Encoder', () => { const argsJson = JSON.stringify(args); expect(decodedArgsJson).to.be.equal(argsJson); }); - it('Should point array elements to a duplicate value from another parameter', async () => { - // Description: - // There are two unique values in the array `strings`. - // The value "Test String" appears three times in this array. - // There should exist only one copy of this string in the optimized calldata. - // In unoptimized calldata, "Test String" would be written three times. + it('Duplicate Tuple Fields', async () => { // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.optimizerAbi2); - const strings = ['Test String', 'Test String', 'Test String', 'Test String 2']; - const string = 'Test String'; - const args = [strings, string]; - // Validate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTupleFields); + const tuple = ['Hello', 'Hello']; + const args = [tuple]; + // Validata calldata const optimizedCalldata = method.encode(args, { optimize: true }); const expectedOptimizedCalldata = - '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000'; + '0x16780a5e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); @@ -54,20 +116,156 @@ describe.only('ABI Encoder', () => { const argsJson = JSON.stringify(args); expect(decodedArgsJson).to.be.equal(argsJson); }); - it('Dynamic Array of uints should point to Dynamic Array of Tuple(Uint)s', async () => { + it('Duplicate Strings', async () => { // Description: - // There are two dynamic arrays, one of uint's and one of tuples. - // Each tuple is simply a wrapper for a uint - tuple := {key: uintValue} - // While the elements of these arrays have different types, they - // have the same representation in calldata. - // That is, a `uint` and a `tuple{uint}` both consume exactly one word of calldata. - // In the optimized calldata, only the elements of one array should be included. - // Both arrays will then point to the same set of elements. + // Two dynamic arrays with the same values. + // In the optimized calldata, only one set of elements should be included. + // Both arrays should point to this set. // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.optimizerAbi3); - const uint8Array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)]; - const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; - const args = [uint8Array, uintTupleArray]; + const method = new AbiEncoder.Method(OptimizedAbis.duplicateStrings); + const args = ['Hello', 'Hello']; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x07370bfa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Bytes', async () => { + // Description: + // Two dynamic arrays with the same values. + // In the optimized calldata, only one set of elements should be included. + // Both arrays should point to this set. + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateBytes); + const value = '0x01020304050607080910111213141516171819202122232425262728293031323334353637383940'; + const args = [value, value]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x6045e42900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002801020304050607080910111213141516171819202122232425262728293031323334353637383940000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Tuples', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples); + const tuple1 = ['Hello, World!', new BigNumber(424234)]; + const tuple2 = tuple1; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000006792a000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Fields Across Two Tuples', async () => { + // Description: + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples); + const tuple1 = ['Hello, World!', new BigNumber(1)]; + const tuple2 = [tuple1[0], new BigNumber(2)]; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Arrays, Nested in Separate Tuples', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateArraysNestedInTuples); + const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200)]; + const tuple1 = [array]; + const tuple2 = [array, 'extra argument to prevent exactly matching the tuples']; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x18970a9e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c80000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Tuples, Nested in Separate Tuples', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuplesNestedInTuples); + const nestedTuple = ['Hello, World!']; + const tuple1 = [nestedTuple]; + const tuple2 = [nestedTuple, 'extra argument to prevent exactly matching the tuples']; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x0b4d2e6a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Two-Dimensional Arrays', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays); + const twoDimArray1 = [['Hello', 'World'], ['Foo', 'Bar', 'Zaa']]; + const twoDimArray2 = twoDimArray1; + const args = [twoDimArray1, twoDimArray2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: false }); + const expectedOptimizedCalldata = + '0x0d28c4f9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Array, Nested within Separate Two-Dimensional Arrays', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays); + const twoDimArray1 = [['Hello', 'World'], ['Foo']]; + const twoDimArray2 = [['Hello', 'World'], ['Bar']]; + const args = [twoDimArray1, twoDimArray2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x0d28c4f900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003466f6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034261720000000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Array Elements Duplicated as Tuple Fields', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsTupleFields); + const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)]; + const tuple = [[array[0]], [array[1]], [array[2]], [array[3]]]; + const args = [array, tuple]; // Validata calldata const optimizedCalldata = method.encode(args, { optimize: true }); const expectedOptimizedCalldata = @@ -79,93 +277,23 @@ describe.only('ABI Encoder', () => { const argsJson = JSON.stringify(args); expect(decodedArgsJson).to.be.equal(argsJson); }); - it('Duplicate Dynamic Arrays', async () => { - // Description: - // Two dynamic arrays with the same values. - // In the optimized calldata, only one set of elements should be included. - // Both arrays should point to this set. + it('Array Elements Duplicated as Separate Parameter', async () => { // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.optimizerAbi5); - const array1 = [new BigNumber(100), new BigNumber(150)]; - const array2 = [array1[0], array1[1]]; - const args = [array1, array2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x7bc4226e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - - /* - it.only('Duplicate Static Arrays', async () => { - // Description: - // Two dynamic arrays with the same values. - // In the optimized calldata, only one set of elements should be included. - // Both arrays should point to this set. - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.optimizerAbi5); - const array1 = [new BigNumber(100), new BigNumber(150)]; - const array2 = [array1[0], array1[1]]; - const args = [array1, array2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x7bc4226e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - - it.only('Duplicate Tuples', async () => { - // Description: - // Two dynamic arrays with the same values. - // In the optimized calldata, only one set of elements should be included. - // Both arrays should point to this set. - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.optimizerAbi5); - const array1 = [new BigNumber(100), new BigNumber(150)]; - const array2 = [array1[0], array1[1]]; - const args = [array1, array2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x7bc4226e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - });*/ - - it('Static Array of static types should not be optimized', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.optimizerAbi4); - const uint8Array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)]; - const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; - const args = [uint8Array, uintTupleArray]; + const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsSeparateParameter); + const array = ['Hello', 'Hello', 'Hello', 'World']; + const string = 'Hello'; + const args = [array, string]; // Validate calldata const optimizedCalldata = method.encode(args, { optimize: true }); - const unoptimizedCalldata = method.encode(args); - expect(optimizedCalldata).to.be.equal(unoptimizedCalldata); + const expectedOptimizedCalldata = + '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); const decodedArgsJson = JSON.stringify(decodedArgs); const argsJson = JSON.stringify(args); expect(decodedArgsJson).to.be.equal(argsJson); }); - - // Todo: Unfixed array points to fixed array - // Todo: Unfixed to unfixed array - // Todo: Duplicate tuples }); describe('Method ABIs', () => { diff --git a/packages/utils/test/abi_samples.ts b/packages/utils/test/abi_samples.ts index 806ad17004..0c33540443 100644 --- a/packages/utils/test/abi_samples.ts +++ b/packages/utils/test/abi_samples.ts @@ -68,94 +68,6 @@ export const GAbi = { type: 'function', } as MethodAbi; -export const optimizerAbi2 = { - constant: false, - inputs: [ - { - name: 'stringArray', - type: 'string[]', - }, - { - name: 'string', - type: 'string', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -export const optimizerAbi3 = { - constant: false, - inputs: [ - { - name: 'uint8Array', - type: 'uint8[]', - }, - { - components: [ - { - name: 'uint', - type: 'uint', - }, - ], - name: 'uintTuple', - type: 'tuple[]', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -export const optimizerAbi4 = { - constant: false, - inputs: [ - { - name: 'uint8Array', - type: 'uint8[4]', - }, - { - components: [ - { - name: 'uint', - type: 'uint', - }, - ], - name: 'uintTuple', - type: 'tuple[]', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -export const optimizerAbi5 = { - constant: false, - inputs: [ - { - name: 'array1', - type: 'uint8[]', - }, - { - name: 'array2', - type: 'uint8[]', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - export const typesWithDefaultWidthsAbi = { constant: false, inputs: [ From acd570b2b30b9ffcc2333291dead9ffd5d1bd62a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 17:31:32 -0800 Subject: [PATCH 134/230] Multidimensional Array tests --- packages/utils/test/abi_encoder_test.ts | 154 ++++++++++++------------ 1 file changed, 80 insertions(+), 74 deletions(-) diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index c5be35d5e1..d75d1ae506 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -21,7 +21,6 @@ describe.only('ABI Encoder', () => { const args = [array1, array2]; // Validata calldata const optimizedCalldata = method.encode(args, { optimize: true }); - console.log(optimizedCalldata); const expectedOptimizedCalldata = '0x7221063300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -692,79 +691,6 @@ describe.only('ABI Encoder', () => { const decodedValueJson = JSON.stringify(decodedValue); expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); }); - - // @TODO: Test Nested Tuples (Not Supported on ) - - it.skip('Nested Tuples', async () => { - // Couldn't get nested tuples to work with Remix - // This is dynamic because it has dynamic members - const method = new AbiEncoder.Method(AbiSamples.nestedTuples); - const firstTuple = { - someUint32: new BigNumber(30472), - nestedTuple: { - someUint: new BigNumber('48384725243211555532'), - someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', - }, - }; - const secondTuple = { - someUint: new BigNumber(2984237422), - someStr: 'This string will exceed 256 bits, so it will spill into the next word of memory.', - nestedTuple: { - someUint32: new BigNumber(23), - secondNestedTuple: { - someUint: new BigNumber(234324), - someStr: 'Im also a short string -- oops I just got loooooooooooooooooonger!', - someBytes: '0x23847287fff3472984723498ff23487324987aaa237438911873429472ba', - someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', - }, - }, - someBytes: '0x2834y3947289423u489aaaff4783924739847489', - someAddress: '0xe41d2489571d322189246dafa5ebde1f4699afaf', - }; - const thirdTuple = { - someUint: new BigNumber(37422), - someStr: 'This into the next word of memory. string will exceed 256 bits, so it will spill.', - nestedTuple: { - someUint32: new BigNumber(23999222), - secondNestedTuple: { - someUint: new BigNumber(324), - someStr: 'Im also a short st', - someBytes: '0x723498ff2348732498723847287fff3472984aaa237438911873429472ba', - someAddress: '0x46dafa5ebde1f4699f498e41d2489571d3221892', - }, - }, - someBytes: '0x947289423u489aaaff472834y383924739847489', - someAddress: '0x46dafa5ebde1f46e41d2489571d322189299afaf', - }; - const fourthTuple = { - someUint: new BigNumber(222283488822), - someStr: 'exceed 256 bits, so it will spill into the. This string will next word of memory.', - nestedTuple: { - someUint32: new BigNumber(2300), - secondNestedTuple: { - someUint: new BigNumber(343224), - someStr: - 'The alphabet backwards is arguably easier to say if thats the way you learned the first time.', - someBytes: '0x87324987aaa23743891187323847287fff3472984723498ff234429472ba', - someAddress: '0x71d322189246dafa5ebe41d24895de1f4699f498', - }, - }, - someBytes: '0x2783924739847488343947289423u489aaaff490', - someAddress: '0xebde1d322189246dafa1f4699afafe41d2489575', - }; - const args = [[firstTuple], [secondTuple, thirdTuple, fourthTuple]]; - - console.log('*'.repeat(250), method, '*'.repeat(250)); - - const calldata = method.encode(args); - console.log(method.getSignature()); - console.log(method.selector); - console.log(JSON.stringify(args)); - - console.log(calldata); - const expectedCalldata = '0x'; - expect(calldata).to.be.equal(expectedCalldata); - }); }); describe('Array', () => { @@ -836,6 +762,86 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); + it('Dynamic Size; Multidimensional; Dynamic Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes[][]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617']; + const array3 = ['0x18192021']; + const args = [array1, array2, array3]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405060708000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000041011121300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414151617000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Dynamic Size; Multidimensional; Static Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes4[][]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617']; + const array3 = ['0x18192021']; + const args = [array1, array2, array3]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000301020304000000000000000000000000000000000000000000000000000000000506070800000000000000000000000000000000000000000000000000000000091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021011121300000000000000000000000000000000000000000000000000000000141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Static Size; Multidimensional; Static Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes4[3][2]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617', '0x18192021']; + const args = [array1, array2]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x010203040000000000000000000000000000000000000000000000000000000005060708000000000000000000000000000000000000000000000000000000000910111200000000000000000000000000000000000000000000000000000000101112130000000000000000000000000000000000000000000000000000000014151617000000000000000000000000000000000000000000000000000000001819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Static Size; Multidimensional; Dynamic Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes[3][2]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617', '0x18192021']; + const args = [array1, array2]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + console.log(encodedArgs); + console.log(dataType.encode(args, { annotate: true })); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000401020304000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004050607080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040910111200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000410111213000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); it('Static size; Too Few Elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[3]' }; From 6daa79ec12557c06a5d4d6876dd086d554addb24 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 17:43:30 -0800 Subject: [PATCH 135/230] Arrays nested in tuples --- packages/utils/test/abi_encoder_test.ts | 92 +++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index d75d1ae506..b672d00235 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -922,6 +922,98 @@ describe.only('ABI Encoder', () => { const argsAsJson = JSON.stringify(args); expect(decodedArgsAsJson).to.be.equal(argsAsJson); }); + it('Nested Static Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'uint[2]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field: [new BigNumber(1), new BigNumber(2)] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Nested Dynamic Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'uint[]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field: [new BigNumber(1), new BigNumber(2)] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Nested Static Multidimensional Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'bytes4[2][2]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708']; + const array2 = ['0x09101112', '0x13141516']; + const args = { field: [array1, array2] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Nested Dynamic Multidimensional Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'bytes[2][2]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708']; + const array2 = ['0x09101112', '0x13141516']; + const args = { field: [array1, array2] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); it('Static and dynamic elements mixed', async () => { // Create DataType object const testDataItem = { From b0ebc6fa14adc08c074bea4d275cc31504c95d55 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 19 Nov 2018 18:42:40 -0800 Subject: [PATCH 136/230] Tests for decoding return values + Ability to encode return values --- .../utils/src/abi_encoder/evm_data_types.ts | 19 ++--- packages/utils/test/abi_encoder_test.ts | 69 +++++++++++++++++++ 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 10e7b987bf..b862e93966 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -276,7 +276,7 @@ export class Byte extends PayloadDataType { if (valueBuf.byteLength > this.width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { @@ -475,6 +475,7 @@ export class Method extends MemberDataType { private methodSignature: string; private methodSelector: string; private returnDataTypes: DataType[]; + private returnDataItem: DataItem; // TMP public selector: string; @@ -484,6 +485,7 @@ export class Method extends MemberDataType { this.methodSignature = this.computeSignature(); this.selector = this.methodSelector = this.computeSelector(); this.returnDataTypes = []; + this.returnDataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; const dummy = new Byte({ type: 'byte', name: 'DUMMY' }); // @TODO TMP _.each(abi.outputs, (dataItem: DataItem) => { this.returnDataTypes.push(this.getFactory().create(dataItem, dummy)); @@ -518,20 +520,19 @@ export class Method extends MemberDataType { return value; } - public decodeReturnValues(returndata: string, rules?: DecodingRules): any { - //console.log('O'.repeat(100), '\n', returndata, '\n', this.returnDataTypes, 'P'.repeat(100)); + public encodeReturnValues(value: any, rules?: EncodingRules): string { + const returnDataType = new Tuple(this.returnDataItem); + const returndata = returnDataType.encode(value, rules); + return returndata; + } + public decodeReturnValues(returndata: string, rules?: DecodingRules): any { const returnValues: any[] = []; const rules_ = rules ? rules : ({ structsAsObjects: false } as DecodingRules); const rawReturnData = new RawCalldata(returndata, false); _.each(this.returnDataTypes, (dataType: DataType) => { returnValues.push(dataType.generateValue(rawReturnData, rules_)); }); - - //console.log('*'.repeat(40), '\n', JSON.stringify(returnValues), '\n', '*'.repeat(100)); - /*if (returnValues.length === 1) { - return returnValues[0]; - }*/ return returnValues; } @@ -547,7 +548,7 @@ export class Method extends MemberDataType { export class EvmDataTypeFactory implements DataTypeFactory { private static instance: DataTypeFactory; - private constructor() {} + private constructor() { } public static getInstance(): DataTypeFactory { if (!EvmDataTypeFactory.instance) { diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index b672d00235..0220984b0e 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -5,6 +5,7 @@ import { chaiSetup } from './utils/chai_setup'; import { BigNumber, AbiEncoder } from '../src/'; import * as AbiSamples from './abi_samples'; import * as OptimizedAbis from './optimizer_abis'; +import * as ReturnValueAbis from './return_value_abis'; import { DecodingRules } from '../src/abi_encoder'; import ethUtil = require('ethereumjs-util'); @@ -12,6 +13,74 @@ chaiSetup.configure(); const expect = chai.expect; describe.only('ABI Encoder', () => { + describe('Decode Return Values', () => { + it('No Return Value', async () => { + // Decode return value + const method = new AbiEncoder.Method(ReturnValueAbis.noReturnValues); + const returnValue = '0x'; + const decodedReturnValue = method.decodeReturnValues(returnValue); + const expectedDecodedReturnValue: any[] = []; + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(expectedDecodedReturnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Single static return value', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.singleStaticReturnValue); + const returnValue = ['0x01020304']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Multiple static return values', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.multipleStaticReturnValues); + const returnValue = ['0x01020304', '0x05060708']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Single dynamic return value', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.singleDynamicReturnValue); + const returnValue = ['0x01020304']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Multiple dynamic return values', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.multipleDynamicReturnValues); + const returnValue = ['0x01020304', '0x05060708']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Mixed static/dynamic return values', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.mixedStaticAndDynamicReturnValues); + const returnValue = ['0x01020304', '0x05060708']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + }); + describe('Optimizer', () => { it('Duplicate Dynamic Arrays with Static Elements', async () => { // Generate calldata From 2164b34bf9ca6f38fff93a527ee162b0624b818e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 10:07:57 -0800 Subject: [PATCH 137/230] Ran linter on Calldata --- packages/utils/src/abi_encoder/calldata.ts | 509 +++++++++----------- packages/utils/src/abi_encoder/data_type.ts | 11 +- 2 files changed, 243 insertions(+), 277 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts index e6ce589579..9f91f84955 100644 --- a/packages/utils/src/abi_encoder/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata.ts @@ -1,6 +1,6 @@ import ethUtil = require('ethereumjs-util'); -import CommunicationChatBubbleOutline from 'material-ui/SvgIcon'; -var _ = require('lodash'); +const _ = require('lodash'); +import * as Constants from './constants'; export interface DecodingRules { structsAsObjects: boolean; @@ -12,77 +12,69 @@ export interface EncodingRules { } export abstract class CalldataBlock { - private name: string; - private signature: string; - private offsetInBytes: number; - private headerSizeInBytes: number; - private bodySizeInBytes: number; - private relocatable: boolean; - private parentName: string; + private readonly _signature: string; + private readonly _parentName: string; + private _name: string; + private _offsetInBytes: number; + private _headerSizeInBytes: number; + private _bodySizeInBytes: number; constructor( name: string, signature: string, parentName: string, - /*offsetInBytes: number,*/ headerSizeInBytes: number, + headerSizeInBytes: number, bodySizeInBytes: number, - relocatable: boolean, ) { - this.name = name; - this.signature = signature; - this.parentName = parentName; - this.offsetInBytes = 0; - this.headerSizeInBytes = headerSizeInBytes; - this.bodySizeInBytes = bodySizeInBytes; - this.relocatable = relocatable; + this._name = name; + this._signature = signature; + this._parentName = parentName; + this._offsetInBytes = 0; + this._headerSizeInBytes = headerSizeInBytes; + this._bodySizeInBytes = bodySizeInBytes; } - protected setHeaderSize(headerSizeInBytes: number) { - this.headerSizeInBytes = headerSizeInBytes; + protected _setHeaderSize(headerSizeInBytes: number): void { + this._headerSizeInBytes = headerSizeInBytes; } - protected setBodySize(bodySizeInBytes: number) { - this.bodySizeInBytes = bodySizeInBytes; + protected _setBodySize(bodySizeInBytes: number): void { + this._bodySizeInBytes = bodySizeInBytes; } - protected setName(name: string) { - this.name = name; + protected _setName(name: string): void { + this._name = name; } public getName(): string { - return this.name; + return this._name; } public getParentName(): string { - return this.parentName; + return this._parentName; } public getSignature(): string { - return this.signature; + return this._signature; } - - public isRelocatable(): boolean { - return this.relocatable; - } - public getHeaderSizeInBytes(): number { - return this.headerSizeInBytes; + return this._headerSizeInBytes; } public getBodySizeInBytes(): number { - return this.bodySizeInBytes; + return this._bodySizeInBytes; } public getSizeInBytes(): number { - return this.headerSizeInBytes + this.bodySizeInBytes; + return this.getHeaderSizeInBytes() + this.getBodySizeInBytes(); } public getOffsetInBytes(): number { - return this.offsetInBytes; + return this._offsetInBytes; } - public setOffset(offsetInBytes: number) { - this.offsetInBytes = offsetInBytes; + public setOffset(offsetInBytes: number): void { + this._offsetInBytes = offsetInBytes; } public computeHash(): Buffer { @@ -96,81 +88,80 @@ export abstract class CalldataBlock { } export class PayloadCalldataBlock extends CalldataBlock { - private payload: Buffer; + private readonly _payload: Buffer; constructor( name: string, signature: string, parentName: string, - /*offsetInBytes: number,*/ relocatable: boolean, payload: Buffer, ) { const headerSizeInBytes = 0; const bodySizeInBytes = payload.byteLength; - super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable); - this.payload = payload; + super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); + this._payload = payload; } public toBuffer(): Buffer { - return this.payload; + return this._payload; } public getRawData(): Buffer { - return this.payload; + return this._payload; } } export class DependentCalldataBlock extends CalldataBlock { - public static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; - public static RAW_DATA_START = new Buffer('<'); - public static RAW_DATA_END = new Buffer('>'); - private parent: CalldataBlock; - private dependency: CalldataBlock; - private aliasFor: CalldataBlock | undefined; + public static readonly RAW_DATA_START = new Buffer('<'); + public static readonly RAW_DATA_END = new Buffer('>'); + private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; + private static readonly _EMPTY_HEADER_SIZE = 0; + private readonly _parent: CalldataBlock; + private readonly _dependency: CalldataBlock; + private _aliasFor: CalldataBlock | undefined; constructor( name: string, signature: string, parentName: string, - relocatable: boolean, dependency: CalldataBlock, parent: CalldataBlock, ) { - const headerSizeInBytes = 0; - const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES; - super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable); - this.parent = parent; - this.dependency = dependency; - this.aliasFor = undefined; + const headerSizeInBytes = DependentCalldataBlock._EMPTY_HEADER_SIZE; + const bodySizeInBytes = DependentCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; + super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); + this._parent = parent; + this._dependency = dependency; + this._aliasFor = undefined; } public toBuffer(): Buffer { const destinationOffset = - this.aliasFor !== undefined ? this.aliasFor.getOffsetInBytes() : this.dependency.getOffsetInBytes(); - const parentOffset = this.parent.getOffsetInBytes(); - const parentHeaderSize = this.parent.getHeaderSizeInBytes(); + this._aliasFor !== undefined ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes(); + const parentOffset = this._parent.getOffsetInBytes(); + const parentHeaderSize = this._parent.getHeaderSizeInBytes(); const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); - const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(16)}`); + const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(Constants.HEX_BASE)}`); const evmWordWidthInBytes = 32; const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); return pointerBufPadded; } public getDependency(): CalldataBlock { - return this.dependency; + return this._dependency; } - public setAlias(block: CalldataBlock) { - this.aliasFor = block; - this.setName(`${this.getName()} (alias for ${block.getName()})`); + public setAlias(block: CalldataBlock): void { + this._aliasFor = block; + this._setName(`${this.getName()} (alias for ${block.getName()})`); } public getAlias(): CalldataBlock | undefined { - return this.aliasFor; + return this._aliasFor; } public getRawData(): Buffer { - const dependencyRawData = this.dependency.getRawData(); + const dependencyRawData = this._dependency.getRawData(); const rawDataComponents: Buffer[] = []; rawDataComponents.push(DependentCalldataBlock.RAW_DATA_START); rawDataComponents.push(dependencyRawData); @@ -181,24 +172,21 @@ export class DependentCalldataBlock extends CalldataBlock { } export class MemberCalldataBlock extends CalldataBlock { - private static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; - private header: Buffer | undefined; - private members: CalldataBlock[]; - private contiguous: boolean; + private _header: Buffer | undefined; + private _members: CalldataBlock[]; - constructor(name: string, signature: string, parentName: string, relocatable: boolean, contiguous: boolean) { - super(name, signature, parentName, 0, 0, relocatable); - this.members = []; - this.header = undefined; - this.contiguous = contiguous; + constructor(name: string, signature: string, parentName: string) { + super(name, signature, parentName, 0, 0); + this._members = []; + this._header = undefined; } public getRawData(): Buffer { const rawDataComponents: Buffer[] = []; - if (this.header !== undefined) { - rawDataComponents.push(this.header); + if (this._header !== undefined) { + rawDataComponents.push(this._header); } - _.each(this.members, (member: CalldataBlock) => { + _.each(this._members, (member: CalldataBlock) => { const memberBuffer = member.getRawData(); rawDataComponents.push(memberBuffer); }); @@ -207,91 +195,79 @@ export class MemberCalldataBlock extends CalldataBlock { return rawData; } - public setMembers(members: CalldataBlock[]) { - let bodySizeInBytes = 0; - _.each(members, (member: CalldataBlock) => { - bodySizeInBytes += member.getSizeInBytes(); - }); - this.members = members; - this.setBodySize(0); + public setMembers(members: CalldataBlock[]): void { + this._members = members; } - public isContiguous(): boolean { - return true; - } - - public setHeader(header: Buffer) { - this.setHeaderSize(header.byteLength); - this.header = header; + public setHeader(header: Buffer): void { + this._setHeaderSize(header.byteLength); + this._header = header; } public toBuffer(): Buffer { - if (this.header !== undefined) return this.header; + if (this._header !== undefined) { + return this._header; + } return new Buffer(''); } public getMembers(): CalldataBlock[] { - return this.members; + return this._members; } } class Queue { - private store: T[] = []; - push(val: T) { - this.store.push(val); + private _store: T[] = []; + public push(val: T): void { + this._store.push(val); } - pushFront(val: T) { - this.store.unshift(val); + public pushFront(val: T): void { + this._store.unshift(val); } - pop(): T | undefined { - return this.store.shift(); + public pop(): T | undefined { + return this._store.shift(); } - popBack(): T | undefined { - if (this.store.length === 0) return undefined; - const backElement = this.store.splice(-1, 1)[0]; + public popBack(): T | undefined { + if (this._store.length === 0) { + return undefined; + } + const backElement = this._store.splice(-1, 1)[0]; return backElement; } - merge(q: Queue) { - this.store = this.store.concat(q.store); + public merge(q: Queue): void { + this._store = this._store.concat(q._store); } - mergeFront(q: Queue) { - this.store = q.store.concat(this.store); + public mergeFront(q: Queue): void { + this._store = q._store.concat(this._store); } - getStore(): T[] { - return this.store; + public getStore(): T[] { + return this._store; } - peek(): T | undefined { - return this.store.length >= 0 ? this.store[0] : undefined; + public peek(): T | undefined { + return this._store.length >= 0 ? this._store[0] : undefined; } } export class Calldata { - private selector: string; - private rules: EncodingRules; - private sizeInBytes: number; - private root: CalldataBlock | undefined; + private readonly _rules: EncodingRules; + private _selector: string; + private _sizeInBytes: number; + private _root: CalldataBlock | undefined; - constructor(rules: EncodingRules) { - this.selector = ''; - this.rules = rules; - this.sizeInBytes = 0; - this.root = undefined; - } - - private createQueue(block: CalldataBlock): Queue { + private static _createQueue(block: CalldataBlock): Queue { const blockQueue = new Queue(); // Base Case - if (block instanceof MemberCalldataBlock === false) { + if (!(block instanceof MemberCalldataBlock)) { blockQueue.push(block); return blockQueue; } // This is a Member Block - const memberBlock = block as MemberCalldataBlock; + const memberBlock = block; _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => { if (member instanceof MemberCalldataBlock) { - blockQueue.mergeFront(this.createQueue(member)); + blockQueue.mergeFront(Calldata._createQueue(member)); } else { blockQueue.pushFront(member); } @@ -300,9 +276,9 @@ export class Calldata { // Children _.each(memberBlock.getMembers(), (member: CalldataBlock) => { if (member instanceof DependentCalldataBlock && member.getAlias() === undefined) { - let dependency = member.getDependency(); + const dependency = member.getDependency(); if (dependency instanceof MemberCalldataBlock) { - blockQueue.merge(this.createQueue(dependency)); + blockQueue.merge(Calldata._createQueue(dependency)); } else { blockQueue.push(dependency); } @@ -313,60 +289,139 @@ export class Calldata { return blockQueue; } - private generateAnnotatedHexString(): string { - let hexValue = `${this.selector}`; - if (this.root === undefined) { + public constructor(rules: EncodingRules) { + this._rules = rules; + this._selector = ''; + this._sizeInBytes = 0; + this._root = undefined; + } + + public optimize(): void { + if (this._root === undefined) { throw new Error('expected root'); } - const valueQueue = this.createQueue(this.root); + const blocksByHash: { [key: string]: CalldataBlock } = {}; + + // 1. Create a queue of subtrees by hash + // Note that they are ordered the same as + const subtreeQueue = Calldata._createQueue(this._root); + let block: CalldataBlock | undefined; + for (block = subtreeQueue.popBack(); block !== undefined; block = subtreeQueue.popBack()) { + if (block instanceof DependentCalldataBlock) { + const dependencyBlockHashBuf = block.getDependency().computeHash(); + const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); + if (dependencyBlockHash in blocksByHash) { + const blockWithSameHash = blocksByHash[dependencyBlockHash]; + if (blockWithSameHash !== block.getDependency()) { + block.setAlias(blockWithSameHash); + } + } + continue; + } + + const blockHashBuf = block.computeHash(); + const blockHash = ethUtil.bufferToHex(blockHashBuf); + if (!(blockHash in blocksByHash)) { + blocksByHash[blockHash] = block; + } + } + } + + public toHexString(): string { + if (this._root === undefined) { + throw new Error('expected root'); + } + + if (this._rules.optimize) { + this.optimize(); + } + + const offsetQueue = Calldata._createQueue(this._root); + let block: CalldataBlock | undefined; + let offset = 0; + for (block = offsetQueue.pop(); block !== undefined; block = offsetQueue.pop()) { + block.setOffset(offset); + offset += block.getSizeInBytes(); + } + + const hexValue = this._rules.annotate ? this._generateAnnotatedHexString() : this._generateCondensedHexString(); + return hexValue; + } + + public getSelectorHex(): string { + return this._selector; + } + + public getSizeInBytes(): number { + return this._sizeInBytes; + } + + public setRoot(block: CalldataBlock): void { + this._root = block; + this._sizeInBytes += block.getSizeInBytes(); + } + + public setSelector(selector: string): void { + this._selector = selector.startsWith('0x') ? selector : `$0x${selector}`; + if (this._selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) { + throw new Error(`Invalid selector '${this._selector}'`); + } + this._sizeInBytes += Constants.HEX_SELECTOR_LENGTH_IN_BYTES; // @TODO: Used to be += 8. Bad? + } + + private _generateAnnotatedHexString(): string { + let hexValue = `${this._selector}`; + if (this._root === undefined) { + throw new Error('expected root'); + } + + const valueQueue = Calldata._createQueue(this._root); let block: CalldataBlock | undefined; let offset = 0; const functionBlock = valueQueue.peek(); - let functionName: string = functionBlock === undefined ? '' : functionBlock.getName(); - while ((block = valueQueue.pop()) !== undefined) { - // Set f - + const functionName: string = functionBlock === undefined ? '' : functionBlock.getName(); + for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { // Process each block 1 word at a time const size = block.getSizeInBytes(); const name = block.getName(); const parentName = block.getParentName(); - - //const ancestrialNamesOffset = name.startsWith('ptr<') ? 4 : 0; - //const parentOffset = name.lastIndexOf(parentName); - const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, ''); //.replace(`${parentName}[`, '['); - const signature = block.getSignature(); + const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, ''); // Current offset let offsetStr = ''; // If this block is empty then it's a newline + const offsetPadding = 10; + const valuePadding = 74; + const namePadding = 80; + const evmWordStartIndex = 0; + const emptySize = 0; let value = ''; let nameStr = ''; let line = ''; - if (size === 0) { - offsetStr = ' '.repeat(10); - value = ' '.repeat(74); - nameStr = `### ${prettyName.padEnd(80)}`; + if (size === emptySize) { + offsetStr = ' '.repeat(offsetPadding); + value = ' '.repeat(valuePadding); + nameStr = `### ${prettyName.padEnd(namePadding)}`; line = `\n${offsetStr}${value}${nameStr}`; } else { - offsetStr = `0x${offset.toString(16)}`.padEnd(10, ' '); - value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(0, 32))).padEnd(74); + offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); + value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES))).padEnd(valuePadding); if (block instanceof MemberCalldataBlock) { - nameStr = `### ${prettyName.padEnd(80)}`; + nameStr = `### ${prettyName.padEnd(namePadding)}`; line = `\n${offsetStr}${value}${nameStr}`; } else { - nameStr = ` ${prettyName.padEnd(80)}`; + nameStr = ` ${prettyName.padEnd(namePadding)}`; line = `${offsetStr}${value}${nameStr}`; } } - for (let j = 32; j < size; j += 32) { - offsetStr = `0x${(offset + j).toString(16)}`.padEnd(10, ' '); - value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(j, j + 32))).padEnd(74); - nameStr = ' '.repeat(40); - + for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) { + offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); + value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES))).padEnd(valuePadding); + nameStr = ' '.repeat(namePadding); line = `${line}\n${offsetStr}${value}${nameStr}`; } @@ -378,16 +433,16 @@ export class Calldata { return hexValue; } - private generateCondensedHexString(): string { - let selectorBuffer = ethUtil.toBuffer(this.selector); - if (this.root === undefined) { + private _generateCondensedHexString(): string { + const selectorBuffer = ethUtil.toBuffer(this._selector); + if (this._root === undefined) { throw new Error('expected root'); } - const valueQueue = this.createQueue(this.root); + const valueQueue = Calldata._createQueue(this._root); const valueBufs: Buffer[] = [selectorBuffer]; let block: CalldataBlock | undefined; - while ((block = valueQueue.pop()) !== undefined) { + for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { valueBufs.push(block.toBuffer()); } @@ -395,96 +450,14 @@ export class Calldata { const hexValue = ethUtil.bufferToHex(combinedBuffers); return hexValue; } - - public optimize() { - if (this.root === undefined) { - throw new Error('expected root'); - } - - const blocksByHash: { [key: string]: CalldataBlock } = {}; - - // 1. Create a queue of subtrees by hash - // Note that they are ordered the same as - const subtreeQueue = this.createQueue(this.root); - let block: CalldataBlock | undefined; - while ((block = subtreeQueue.popBack()) !== undefined) { - if (block instanceof DependentCalldataBlock) { - const blockHashBuf = block.getDependency().computeHash(); - const blockHash = ethUtil.bufferToHex(blockHashBuf); - if (blockHash in blocksByHash) { - const blockWithSameHash = blocksByHash[blockHash]; - if (blockWithSameHash !== block.getDependency()) { - block.setAlias(blockWithSameHash); - } - } - continue; - } - - const blockHashBuf = block.computeHash(); - const blockHash = ethUtil.bufferToHex(blockHashBuf); - if (blockHash in blocksByHash === false) { - blocksByHash[blockHash] = block; - } - } - } - - public toHexString(): string { - if (this.root === undefined) { - throw new Error('expected root'); - } - - if (this.rules.optimize) this.optimize(); - - const offsetQueue = this.createQueue(this.root); - let block: CalldataBlock | undefined; - let offset = 0; - while ((block = offsetQueue.pop()) !== undefined) { - block.setOffset(offset); - offset += block.getSizeInBytes(); - } - - const hexValue = this.rules.annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString(); - return hexValue; - } - - public getSelectorHex(): string { - return this.selector; - } - - public getSizeInBytes(): number { - return this.sizeInBytes; - } - - public toAnnotatedString(): string { - return ''; - } - - public setRoot(block: CalldataBlock) { - this.root = block; - this.sizeInBytes += block.getSizeInBytes(); - } - - public setSelector(selector: string) { - // Ensure we have a 0x prefix - if (selector.startsWith('0x')) { - this.selector = selector; - } else { - this.selector = `$0x${selector}`; - } - - // The selector must be 10 characters: '0x' followed by 4 bytes (two hex chars per byte) - if (this.selector.length !== 10) { - throw new Error(`Invalid selector '${this.selector}'`); - } - this.sizeInBytes += 8; - } } export class RawCalldata { - private value: Buffer; - private offset: number; // tracks current offset into raw calldata; used for parsing - private selector: string; - private scopes: Queue; + private static readonly _INITIAL_OFFSET = 0; + private readonly _value: Buffer; + private readonly _selector: string; + private readonly _scopes: Queue; + private _offset: number; // tracks current offset into raw calldata; used for parsing constructor(value: string | Buffer, hasSelectorPrefix: boolean = true) { if (typeof value === 'string' && !value.startsWith('0x')) { @@ -492,21 +465,21 @@ export class RawCalldata { } const valueBuf = ethUtil.toBuffer(value); if (hasSelectorPrefix) { - this.selector = ethUtil.bufferToHex(valueBuf.slice(0, 4)); - this.value = valueBuf.slice(4); // disregard selector + this._selector = ethUtil.bufferToHex(valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES)); + this._value = valueBuf.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); // disregard selector } else { - this.selector = '0x'; - this.value = valueBuf; + this._selector = '0x'; + this._value = valueBuf; } - this.offset = 0; - this.scopes = new Queue(); - this.scopes.push(0); + this._scopes = new Queue(); + this._scopes.push(RawCalldata._INITIAL_OFFSET); + this._offset = RawCalldata._INITIAL_OFFSET; } public popBytes(lengthInBytes: number): Buffer { - const value = this.value.slice(this.offset, this.offset + lengthInBytes); - this.setOffset(this.offset + lengthInBytes); + const value = this._value.slice(this._offset, this._offset + lengthInBytes); + this.setOffset(this._offset + lengthInBytes); return value; } @@ -521,28 +494,28 @@ export class RawCalldata { } public readBytes(from: number, to: number): Buffer { - const value = this.value.slice(from, to); + const value = this._value.slice(from, to); return value; } - public setOffset(offsetInBytes: number) { - this.offset = offsetInBytes; + public setOffset(offsetInBytes: number): void { + this._offset = offsetInBytes; } - public startScope() { - this.scopes.pushFront(this.offset); + public startScope(): void { + this._scopes.pushFront(this._offset); } - public endScope() { - this.scopes.pop(); + public endScope(): void { + this._scopes.pop(); } public getOffset(): number { - return this.offset; + return this._offset; } - public toAbsoluteOffset(relativeOffset: number) { - const scopeOffset = this.scopes.peek(); + public toAbsoluteOffset(relativeOffset: number): number { + const scopeOffset = this._scopes.peek(); if (scopeOffset === undefined) { throw new Error(`Tried to access undefined scope.`); } @@ -551,6 +524,6 @@ export class RawCalldata { } public getSelector(): string { - return this.selector; + return this._selector; } } diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 80797c5630..9260234681 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -78,7 +78,6 @@ export abstract class PayloadDataType extends DataType { name, signature, parentName, - /*offsetInBytes,*/ relocatable, encodedValue, ); return block; @@ -116,12 +115,10 @@ export abstract class DependentDataType extends DataType { const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const relocatable = false; const block = new DependentCalldataBlock( name, signature, parentName, - relocatable, dependencyBlock, parentBlock, ); @@ -235,9 +232,7 @@ export abstract class MemberDataType extends DataType { const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( this.getDataItem().name, this.getSignature(), - parentName, - this.isStatic(), - false, + parentName ); let members = this.members; @@ -262,9 +257,7 @@ export abstract class MemberDataType extends DataType { const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( this.getDataItem().name, this.getSignature(), - parentName, - this.isStatic(), - false, + parentName ); const memberBlocks: CalldataBlock[] = []; let childMap = _.cloneDeep(this.memberMap); From 0f7abcd59bcbad5a74c7984b199158f5c9f03934 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 10:08:17 -0800 Subject: [PATCH 138/230] Moved global constants to separate file --- packages/utils/src/abi_encoder/constants.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/utils/src/abi_encoder/constants.ts diff --git a/packages/utils/src/abi_encoder/constants.ts b/packages/utils/src/abi_encoder/constants.ts new file mode 100644 index 0000000000..b52630d746 --- /dev/null +++ b/packages/utils/src/abi_encoder/constants.ts @@ -0,0 +1,6 @@ +export const EVM_WORD_WIDTH_IN_BYTES = 32; +export const HEX_BASE = 16; +export const BIN_BASE = 2; +export const HEX_SELECTOR_LENGTH_IN_CHARS = 10; +export const HEX_SELECTOR_LENGTH_IN_BYTES = 4; +export const HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA = 0; \ No newline at end of file From fbbca8e2837b8036b8dfd7985c702fd05073daa3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 10:09:38 -0800 Subject: [PATCH 139/230] Ran prettier on utils --- packages/utils/src/abi_encoder/calldata.ts | 33 +- packages/utils/src/abi_encoder/constants.ts | 2 +- packages/utils/src/abi_encoder/data_type.ts | 19 +- .../utils/src/abi_encoder/evm_data_types.ts | 4 +- packages/utils/test/abi_encoder_test.ts | 4 +- packages/utils/test/optimizer_abis.ts | 339 ++++++++++++++++++ packages/utils/test/return_value_abis.ts | 98 +++++ 7 files changed, 463 insertions(+), 36 deletions(-) create mode 100644 packages/utils/test/optimizer_abis.ts create mode 100644 packages/utils/test/return_value_abis.ts diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts index 9f91f84955..0c45d6198b 100644 --- a/packages/utils/src/abi_encoder/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata.ts @@ -90,12 +90,7 @@ export abstract class CalldataBlock { export class PayloadCalldataBlock extends CalldataBlock { private readonly _payload: Buffer; - constructor( - name: string, - signature: string, - parentName: string, - payload: Buffer, - ) { + constructor(name: string, signature: string, parentName: string, payload: Buffer) { const headerSizeInBytes = 0; const bodySizeInBytes = payload.byteLength; super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); @@ -120,13 +115,7 @@ export class DependentCalldataBlock extends CalldataBlock { private readonly _dependency: CalldataBlock; private _aliasFor: CalldataBlock | undefined; - constructor( - name: string, - signature: string, - parentName: string, - dependency: CalldataBlock, - parent: CalldataBlock, - ) { + constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) { const headerSizeInBytes = DependentCalldataBlock._EMPTY_HEADER_SIZE; const bodySizeInBytes = DependentCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); @@ -408,7 +397,13 @@ export class Calldata { line = `\n${offsetStr}${value}${nameStr}`; } else { offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); - value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES))).padEnd(valuePadding); + value = ethUtil + .stripHexPrefix( + ethUtil.bufferToHex( + block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES), + ), + ) + .padEnd(valuePadding); if (block instanceof MemberCalldataBlock) { nameStr = `### ${prettyName.padEnd(namePadding)}`; line = `\n${offsetStr}${value}${nameStr}`; @@ -420,7 +415,11 @@ export class Calldata { for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) { offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); - value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES))).padEnd(valuePadding); + value = ethUtil + .stripHexPrefix( + ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES)), + ) + .padEnd(valuePadding); nameStr = ' '.repeat(namePadding); line = `${line}\n${offsetStr}${value}${nameStr}`; } @@ -465,7 +464,9 @@ export class RawCalldata { } const valueBuf = ethUtil.toBuffer(value); if (hasSelectorPrefix) { - this._selector = ethUtil.bufferToHex(valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES)); + this._selector = ethUtil.bufferToHex( + valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES), + ); this._value = valueBuf.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); // disregard selector } else { this._selector = '0x'; diff --git a/packages/utils/src/abi_encoder/constants.ts b/packages/utils/src/abi_encoder/constants.ts index b52630d746..bc5b985e0d 100644 --- a/packages/utils/src/abi_encoder/constants.ts +++ b/packages/utils/src/abi_encoder/constants.ts @@ -3,4 +3,4 @@ export const HEX_BASE = 16; export const BIN_BASE = 2; export const HEX_SELECTOR_LENGTH_IN_CHARS = 10; export const HEX_SELECTOR_LENGTH_IN_BYTES = 4; -export const HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA = 0; \ No newline at end of file +export const HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA = 0; diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 9260234681..4370cb253b 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -74,12 +74,7 @@ export abstract class PayloadDataType extends DataType { const signature = this.getSignature(); const parentName = parentBlock === undefined ? '' : parentBlock.getName(); const relocatable = false; - const block = new PayloadCalldataBlock( - name, - signature, - parentName, - encodedValue, - ); + const block = new PayloadCalldataBlock(name, signature, parentName, encodedValue); return block; } @@ -115,13 +110,7 @@ export abstract class DependentDataType extends DataType { const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const block = new DependentCalldataBlock( - name, - signature, - parentName, - dependencyBlock, - parentBlock, - ); + const block = new DependentCalldataBlock(name, signature, parentName, dependencyBlock, parentBlock); return block; } @@ -232,7 +221,7 @@ export abstract class MemberDataType extends DataType { const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( this.getDataItem().name, this.getSignature(), - parentName + parentName, ); let members = this.members; @@ -257,7 +246,7 @@ export abstract class MemberDataType extends DataType { const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( this.getDataItem().name, this.getSignature(), - parentName + parentName, ); const memberBlocks: CalldataBlock[] = []; let childMap = _.cloneDeep(this.memberMap); diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index b862e93966..76837361ed 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -276,7 +276,7 @@ export class Byte extends PayloadDataType { if (valueBuf.byteLength > this.width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { @@ -548,7 +548,7 @@ export class Method extends MemberDataType { export class EvmDataTypeFactory implements DataTypeFactory { private static instance: DataTypeFactory; - private constructor() { } + private constructor() {} public static getInstance(): DataTypeFactory { if (!EvmDataTypeFactory.instance) { diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index 0220984b0e..c7986fa003 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -102,7 +102,7 @@ describe.only('ABI Encoder', () => { it('Duplicate Dynamic Arrays with Dynamic Elements', async () => { // Generate calldata const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithDynamicElements); - const array1 = ["Hello", "World"]; + const array1 = ['Hello', 'World']; const array2 = array1; const args = [array1, array2]; // Validata calldata @@ -138,7 +138,7 @@ describe.only('ABI Encoder', () => { it('Duplicate Static Arrays with Dynamic Elements', async () => { // Generate calldata const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithDynamicElements); - const array1 = ["Hello", "World"]; + const array1 = ['Hello', 'World']; const array2 = array1; const args = [array1, array2]; // Validata calldata diff --git a/packages/utils/test/optimizer_abis.ts b/packages/utils/test/optimizer_abis.ts new file mode 100644 index 0000000000..ea562e5b58 --- /dev/null +++ b/packages/utils/test/optimizer_abis.ts @@ -0,0 +1,339 @@ +import { MethodAbi } from 'ethereum-types'; + +export const duplicateDynamicArraysWithStaticElements = { + constant: false, + inputs: [ + { + name: 'array1', + type: 'uint[]', + }, + { + name: 'array2', + type: 'uint[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateDynamicArraysWithDynamicElements = { + constant: false, + inputs: [ + { + name: 'array1', + type: 'string[]', + }, + { + name: 'array2', + type: 'string[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateStaticArraysWithStaticElements = { + constant: false, + inputs: [ + { + name: 'array1', + type: 'uint[2]', + }, + { + name: 'array2', + type: 'uint[2]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateStaticArraysWithDynamicElements = { + constant: false, + inputs: [ + { + name: 'array1', + type: 'string[2]', + }, + { + name: 'array2', + type: 'string[2]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateArrayElements = { + constant: false, + inputs: [ + { + name: 'array', + type: 'string[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateTupleFields = { + constant: false, + inputs: [ + { + components: [ + { + name: 'field1', + type: 'string', + }, + { + name: 'field2', + type: 'string', + }, + ], + name: 'Tuple', + type: 'tuple', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateStrings = { + constant: false, + inputs: [ + { + name: 'string1', + type: 'string', + }, + { + name: 'string2', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateBytes = { + constant: false, + inputs: [ + { + name: 'bytes1', + type: 'bytes', + }, + { + name: 'bytes2', + type: 'bytes', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateTuples = { + constant: false, + inputs: [ + { + components: [ + { + name: 'field1', + type: 'string', + }, + { + name: 'field2', + type: 'uint', + }, + ], + name: 'Tuple', + type: 'tuple', + }, + { + components: [ + { + name: 'field1', + type: 'string', + }, + { + name: 'field2', + type: 'uint', + }, + ], + name: 'Tuple', + type: 'tuple', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateArraysNestedInTuples = { + constant: false, + inputs: [ + { + components: [ + { + name: 'field', + type: 'uint[]', + }, + ], + name: 'Tuple1', + type: 'tuple', + }, + { + components: [ + { + name: 'field', + type: 'uint[]', + }, + { + name: 'extraField', + type: 'string', + }, + ], + name: 'Tuple2', + type: 'tuple', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateTuplesNestedInTuples = { + constant: false, + inputs: [ + { + components: [ + { + components: [ + { + name: 'nestedField', + type: 'string', + }, + ], + name: 'field', + type: 'tuple', + }, + ], + name: 'Tuple1', + type: 'tuple', + }, + { + components: [ + { + components: [ + { + name: 'nestedField', + type: 'string', + }, + ], + name: 'field', + type: 'tuple', + }, + { + name: 'extraField', + type: 'string', + }, + ], + name: 'Tuple1', + type: 'tuple', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const duplicateTwoDimensionalArrays = { + constant: false, + inputs: [ + { + name: 'array1', + type: 'string[][]', + }, + { + name: 'array2', + type: 'string[][]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const arrayElementsDuplicatedAsSeparateParameter = { + constant: false, + inputs: [ + { + name: 'stringArray', + type: 'string[]', + }, + { + name: 'string', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const arrayElementsDuplicatedAsTupleFields = { + constant: false, + inputs: [ + { + name: 'uint8Array', + type: 'uint8[]', + }, + { + components: [ + { + name: 'uint', + type: 'uint', + }, + ], + name: 'uintTuple', + type: 'tuple[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; diff --git a/packages/utils/test/return_value_abis.ts b/packages/utils/test/return_value_abis.ts new file mode 100644 index 0000000000..847559dac7 --- /dev/null +++ b/packages/utils/test/return_value_abis.ts @@ -0,0 +1,98 @@ +import { MethodAbi } from 'ethereum-types'; + +export const noReturnValues = { + constant: false, + inputs: [], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const singleStaticReturnValue = { + constant: false, + inputs: [], + name: 'simpleFunction', + outputs: [ + { + name: 'Bytes4', + type: 'bytes4', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const multipleStaticReturnValues = { + constant: false, + inputs: [], + name: 'simpleFunction', + outputs: [ + { + name: 'val1', + type: 'bytes4', + }, + { + name: 'val2', + type: 'bytes4', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const singleDynamicReturnValue = { + constant: false, + inputs: [], + name: 'simpleFunction', + outputs: [ + { + name: 'val', + type: 'bytes', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const multipleDynamicReturnValues = { + constant: false, + inputs: [], + name: 'simpleFunction', + outputs: [ + { + name: 'val1', + type: 'bytes', + }, + { + name: 'val2', + type: 'bytes', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const mixedStaticAndDynamicReturnValues = { + constant: false, + inputs: [], + name: 'simpleFunction', + outputs: [ + { + name: 'val1', + type: 'bytes4', + }, + { + name: 'val2', + type: 'bytes', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; From 406b5739be91d2a25337b247d31cef720ddd0ae3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 10:32:31 -0800 Subject: [PATCH 140/230] Fixed linter errors on data_type.ts --- packages/utils/src/abi_encoder/calldata.ts | 6 +- packages/utils/src/abi_encoder/constants.ts | 1 + packages/utils/src/abi_encoder/data_type.ts | 360 +++++++++--------- .../utils/src/abi_encoder/evm_data_types.ts | 14 +- 4 files changed, 195 insertions(+), 186 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts index 0c45d6198b..994b0fb81f 100644 --- a/packages/utils/src/abi_encoder/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata.ts @@ -1,5 +1,7 @@ -import ethUtil = require('ethereumjs-util'); -const _ = require('lodash'); + +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + import * as Constants from './constants'; export interface DecodingRules { diff --git a/packages/utils/src/abi_encoder/constants.ts b/packages/utils/src/abi_encoder/constants.ts index bc5b985e0d..52029695f6 100644 --- a/packages/utils/src/abi_encoder/constants.ts +++ b/packages/utils/src/abi_encoder/constants.ts @@ -1,5 +1,6 @@ export const EVM_WORD_WIDTH_IN_BYTES = 32; export const HEX_BASE = 16; +export const DEC_BASE = 10; export const BIN_BASE = 2; export const HEX_SELECTOR_LENGTH_IN_CHARS = 10; export const HEX_SELECTOR_LENGTH_IN_BYTES = 4; diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 4370cb253b..ce7b11ef6c 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -1,16 +1,21 @@ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { BigNumber } from '../configured_bignumber'; + +import * as Constants from './constants'; + import { - RawCalldata, Calldata, CalldataBlock, - PayloadCalldataBlock, + DecodingRules, DependentCalldataBlock, + EncodingRules, MemberCalldataBlock, + PayloadCalldataBlock, + RawCalldata, } from './calldata'; -import { MethodAbi, DataItem } from 'ethereum-types'; -import { DecodingRules, EncodingRules } from './calldata'; -import { BigNumber } from '../configured_bignumber'; -import ethUtil = require('ethereumjs-util'); -var _ = require('lodash'); export interface DataTypeFactory { create: (dataItem: DataItem, parentDataType?: DataType) => DataType; @@ -18,29 +23,30 @@ export interface DataTypeFactory { } export abstract class DataType { - private static DEFAULT_ENCODING_RULES = { optimize: false, annotate: false } as EncodingRules; - private static DEFAULT_DECODING_RULES = { structsAsObjects: false } as DecodingRules; - - private dataItem: DataItem; - private factory: DataTypeFactory; + private static readonly _DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false }; + private static readonly _DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false }; + private readonly _dataItem: DataItem; + private readonly _factory: DataTypeFactory; constructor(dataItem: DataItem, factory: DataTypeFactory) { - this.dataItem = dataItem; - this.factory = factory; + this._dataItem = dataItem; + this._factory = factory; } public getDataItem(): DataItem { - return this.dataItem; + return this._dataItem; } public getFactory(): DataTypeFactory { - return this.factory; + return this._factory; } public encode(value: any, rules?: EncodingRules, selector?: string): string { - const rules_ = rules ? rules : DataType.DEFAULT_ENCODING_RULES; + const rules_ = rules ? rules : DataType._DEFAULT_ENCODING_RULES; const calldata = new Calldata(rules_); - if (selector) calldata.setSelector(selector); + if (selector) { + calldata.setSelector(selector); + } const block = this.generateCalldataBlock(value); calldata.setRoot(block); // @TODO CHANGE const calldataHex = calldata.toHexString(); @@ -49,7 +55,7 @@ export abstract class DataType { public decode(calldata: string, rules?: DecodingRules, hasSelector: boolean = false): any { const rawCalldata = new RawCalldata(calldata, hasSelector); - const rules_ = rules ? rules : DataType.DEFAULT_DECODING_RULES; + const rules_ = rules ? rules : DataType._DEFAULT_DECODING_RULES; const value = this.generateValue(rawCalldata, rules_); return value; } @@ -61,11 +67,11 @@ export abstract class DataType { } export abstract class PayloadDataType extends DataType { - protected hasConstantSize: boolean; + protected _hasConstantSize: boolean; public constructor(dataItem: DataItem, factory: DataTypeFactory, hasConstantSize: boolean) { super(dataItem, factory); - this.hasConstantSize = hasConstantSize; + this._hasConstantSize = hasConstantSize; } public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { @@ -73,7 +79,6 @@ export abstract class PayloadDataType extends DataType { const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const relocatable = false; const block = new PayloadCalldataBlock(name, signature, parentName, encodedValue); return block; } @@ -84,8 +89,7 @@ export abstract class PayloadDataType extends DataType { } public isStatic(): boolean { - // If a payload has a constant size then it's static - return this.hasConstantSize; + return this._hasConstantSize; } public abstract encodeValue(value: any): Buffer; @@ -93,20 +97,22 @@ export abstract class PayloadDataType extends DataType { } export abstract class DependentDataType extends DataType { - protected dependency: DataType; - protected parent: DataType; + protected _dependency: DataType; + protected _parent: DataType; + private readonly _isStatic: boolean; public constructor(dataItem: DataItem, factory: DataTypeFactory, dependency: DataType, parent: DataType) { super(dataItem, factory); - this.dependency = dependency; - this.parent = parent; + this._dependency = dependency; + this._parent = parent; + this._isStatic = true; } public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): DependentCalldataBlock { if (parentBlock === undefined) { throw new Error(`DependentDataType requires a parent block to generate its block`); } - const dependencyBlock = this.dependency.generateCalldataBlock(value, parentBlock); + const dependencyBlock = this._dependency.generateCalldataBlock(value, parentBlock); const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = parentBlock === undefined ? '' : parentBlock.getName(); @@ -117,16 +123,16 @@ export abstract class DependentDataType extends DataType { public generateValue(calldata: RawCalldata, rules: DecodingRules): any { const destinationOffsetBuf = calldata.popWord(); const currentOffset = calldata.getOffset(); - const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), 16); + const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), Constants.HEX_BASE); const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); calldata.setOffset(destinationOffsetAbsolute); - const value = this.dependency.generateValue(calldata, rules); + const value = this._dependency.generateValue(calldata, rules); calldata.setOffset(currentOffset); return value; } public isStatic(): boolean { - return true; + return this._isStatic; } } @@ -135,11 +141,11 @@ export interface MemberMap { } export abstract class MemberDataType extends DataType { - private memberMap: MemberMap; - private members: DataType[]; - private isArray: boolean; - protected arrayLength: number | undefined; - protected arrayElementType: string | undefined; + protected readonly _arrayLength: number | undefined; + protected readonly _arrayElementType: string | undefined; + private readonly _memberMap: MemberMap; + private readonly _members: DataType[]; + private readonly _isArray: boolean; public constructor( dataItem: DataItem, @@ -149,158 +155,50 @@ export abstract class MemberDataType extends DataType { arrayElementType?: string, ) { super(dataItem, factory); - this.memberMap = {}; - this.members = []; - this.isArray = isArray; - this.arrayLength = arrayLength; - this.arrayElementType = arrayElementType; + this._memberMap = {}; + this._members = []; + this._isArray = isArray; + this._arrayLength = arrayLength; + this._arrayElementType = arrayElementType; if (isArray && arrayLength !== undefined) { - [this.members, this.memberMap] = this.createMembersWithLength(dataItem, arrayLength); + [this._members, this._memberMap] = this._createMembersWithLength(dataItem, arrayLength); } else if (!isArray) { - [this.members, this.memberMap] = this.createMembersWithKeys(dataItem); + [this._members, this._memberMap] = this._createMembersWithKeys(dataItem); } } - private createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { - // Sanity check - if (dataItem.components === undefined) { - throw new Error(`Expected components`); - } - - let members: DataType[] = []; - let memberMap: MemberMap = {}; - _.each(dataItem.components, (memberItem: DataItem) => { - const childDataItem = { - type: memberItem.type, - name: `${dataItem.name}.${memberItem.name}`, - } as DataItem; - const components = memberItem.components; - if (components !== undefined) { - childDataItem.components = components; - } - const child = this.getFactory().create(childDataItem, this); - memberMap[memberItem.name] = members.length; - members.push(child); - }); - - return [members, memberMap]; - } - - private createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { - let members: DataType[] = []; - let memberMap: MemberMap = {}; - const range = _.range(length); - _.each(range, (idx: number) => { - const childDataItem = { - type: this.arrayElementType, - name: `${dataItem.name}[${idx.toString(10)}]`, - } as DataItem; - const components = dataItem.components; - if (components !== undefined) { - childDataItem.components = components; - } - const child = this.getFactory().create(childDataItem, this); - memberMap[idx.toString(10)] = members.length; - members.push(child); - }); - - return [members, memberMap]; - } - - protected generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): MemberCalldataBlock { - // Sanity check length - if (this.arrayLength !== undefined && value.length !== this.arrayLength) { - throw new Error( - `Expected array of ${JSON.stringify( - this.arrayLength, - )} elements, but got array of length ${JSON.stringify(value.length)}`, - ); - } - - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( - this.getDataItem().name, - this.getSignature(), - parentName, - ); - - let members = this.members; - if (this.isArray && this.arrayLength === undefined) { - [members] = this.createMembersWithLength(this.getDataItem(), value.length); - - const lenBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.length.toString(16)}`), 32); - methodBlock.setHeader(lenBuf); - } - - const memberBlocks: CalldataBlock[] = []; - _.each(members, (member: DataType, idx: number) => { - const block = member.generateCalldataBlock(value[idx], methodBlock); - memberBlocks.push(block); - }); - methodBlock.setMembers(memberBlocks); - return methodBlock; - } - - protected generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock { - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( - this.getDataItem().name, - this.getSignature(), - parentName, - ); - const memberBlocks: CalldataBlock[] = []; - let childMap = _.cloneDeep(this.memberMap); - _.forOwn(obj, (value: any, key: string) => { - if (key in childMap === false) { - throw new Error( - `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, - ); - } - const block = this.members[this.memberMap[key]].generateCalldataBlock(value, methodBlock); - memberBlocks.push(block); - delete childMap[key]; - }); - - if (Object.keys(childMap).length !== 0) { - throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); - } - - methodBlock.setMembers(memberBlocks); - return methodBlock; - } - public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { const block = value instanceof Array - ? this.generateCalldataBlockFromArray(value, parentBlock) - : this.generateCalldataBlockFromObject(value, parentBlock); + ? this._generateCalldataBlockFromArray(value, parentBlock) + : this._generateCalldataBlockFromObject(value, parentBlock); return block; } public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object { - let members = this.members; - if (this.isArray && this.arrayLength === undefined) { + let members = this._members; + if (this._isArray && this._arrayLength === undefined) { const arrayLengthBuf = calldata.popWord(); const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); const hexBase = 16; const arrayLength = new BigNumber(arrayLengthHex, hexBase); - [members] = this.createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); + [members] = this._createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); } calldata.startScope(); let value: any[] | object; - if (rules.structsAsObjects && !this.isArray) { + if (rules.structsAsObjects && !this._isArray) { value = {}; - _.each(this.memberMap, (idx: number, key: string) => { - const member = this.members[idx]; - let memberValue = member.generateValue(calldata, rules); + _.each(this._memberMap, (idx: number, key: string) => { + const member = this._members[idx]; + const memberValue = member.generateValue(calldata, rules); (value as { [key: string]: any })[key] = memberValue; }); } else { value = []; _.each(members, (member: DataType, idx: number) => { - let memberValue = member.generateValue(calldata, rules); + const memberValue = member.generateValue(calldata, rules); (value as any[]).push(memberValue); }); } @@ -308,19 +206,6 @@ export abstract class MemberDataType extends DataType { return value; } - protected computeSignatureOfMembers(): string { - // Compute signature of members - let signature = `(`; - _.each(this.members, (member: DataType, i: number) => { - signature += member.getSignature(); - if (i < this.members.length - 1) { - signature += ','; - } - }); - signature += ')'; - return signature; - } - public isStatic(): boolean { /* For Tuple: const isStaticTuple = this.children.length === 0; @@ -332,15 +217,136 @@ export abstract class MemberDataType extends DataType { Otherwise if the first element is a Pointer then false */ - if (this.isArray && this.arrayLength === undefined) { + if (this._isArray && this._arrayLength === undefined) { return false; } // Search for dependent members - const dependentMember = _.find(this.members, (member: DataType) => { + const dependentMember = _.find(this._members, (member: DataType) => { return member instanceof DependentDataType; }); const isStatic = dependentMember === undefined; // static if we couldn't find a dependent member return isStatic; } + + protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): MemberCalldataBlock { + // Sanity check length + if (this._arrayLength !== undefined && value.length !== this._arrayLength) { + throw new Error( + `Expected array of ${JSON.stringify( + this._arrayLength, + )} elements, but got array of length ${JSON.stringify(value.length)}`, + ); + } + + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + this.getDataItem().name, + this.getSignature(), + parentName, + ); + + let members = this._members; + if (this._isArray && this._arrayLength === undefined) { + [members] = this._createMembersWithLength(this.getDataItem(), value.length); + + const lenBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), Constants.EVM_WORD_WIDTH_IN_BYTES); + methodBlock.setHeader(lenBuf); + } + + const memberBlocks: CalldataBlock[] = []; + _.each(members, (member: DataType, idx: number) => { + const block = member.generateCalldataBlock(value[idx], methodBlock); + memberBlocks.push(block); + }); + methodBlock.setMembers(memberBlocks); + return methodBlock; + } + + protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock { + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + this.getDataItem().name, + this.getSignature(), + parentName, + ); + const memberBlocks: CalldataBlock[] = []; + const childMap = _.cloneDeep(this._memberMap); + _.forOwn(obj, (value: any, key: string) => { + if (!(key in childMap)) { + throw new Error( + `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, + ); + } + const block = this._members[this._memberMap[key]].generateCalldataBlock(value, methodBlock); + memberBlocks.push(block); + delete childMap[key]; + }); + + if (Object.keys(childMap).length !== 0) { + throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); + } + + methodBlock.setMembers(memberBlocks); + return methodBlock; + } + + protected _computeSignatureOfMembers(): string { + // Compute signature of members + let signature = `(`; + _.each(this._members, (member: DataType, i: number) => { + signature += member.getSignature(); + if (i < this._members.length - 1) { + signature += ','; + } + }); + signature += ')'; + return signature; + } + + private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { + // Sanity check + if (dataItem.components === undefined) { + throw new Error(`Expected components`); + } + + const members: DataType[] = []; + const memberMap: MemberMap = {}; + _.each(dataItem.components, (memberItem: DataItem) => { + const childDataItem: DataItem = { + type: memberItem.type, + name: `${dataItem.name}.${memberItem.name}`, + }; + const components = memberItem.components; + if (components !== undefined) { + childDataItem.components = components; + } + const child = this.getFactory().create(childDataItem, this); + memberMap[memberItem.name] = members.length; + members.push(child); + }); + + return [members, memberMap]; + } + + private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { + const members: DataType[] = []; + const memberMap: MemberMap = {}; + const range = _.range(length); + _.each(range, (idx: number) => { + const childDataItem: DataItem = { + type: this._arrayElementType ? this._arrayElementType : '', + name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`, + }; + const components = dataItem.components; + if (components !== undefined) { + childDataItem.components = components; + } + const child = this.getFactory().create(childDataItem, this); + memberMap[idx.toString(Constants.DEC_BASE)] = members.length; + members.push(child); + }); + + return [members, memberMap]; + } } diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 76837361ed..1f5dff56fd 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -276,7 +276,7 @@ export class Byte extends PayloadDataType { if (valueBuf.byteLength > this.width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { @@ -396,7 +396,7 @@ export class Pointer extends DependentDataType { } public getSignature(): string { - return this.dependency.getSignature(); + return this._dependency.getSignature(); } } @@ -408,7 +408,7 @@ export class Tuple extends MemberDataType { if (!Tuple.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); } - this.tupleSignature = this.computeSignatureOfMembers(); + this.tupleSignature = this._computeSignatureOfMembers(); } public getSignature(): string { @@ -455,10 +455,10 @@ export class SolArray extends MemberDataType { } const elementDataType = this.getFactory().mapDataItemToDataType(dataItem); const type = elementDataType.getSignature(); - if (this.arrayLength === undefined) { + if (this._arrayLength === undefined) { return `${type}[]`; } else { - return `${type}[${this.arrayLength}]`; + return `${type}[${this._arrayLength}]`; } } @@ -493,7 +493,7 @@ export class Method extends MemberDataType { } private computeSignature(): string { - const memberSignature = this.computeSignatureOfMembers(); + const memberSignature = this._computeSignatureOfMembers(); const methodSignature = `${this.getDataItem().name}${memberSignature}`; return methodSignature; } @@ -548,7 +548,7 @@ export class Method extends MemberDataType { export class EvmDataTypeFactory implements DataTypeFactory { private static instance: DataTypeFactory; - private constructor() {} + private constructor() { } public static getInstance(): DataTypeFactory { if (!EvmDataTypeFactory.instance) { From 0ed1819143fa82163502fb412bcb5ed44c041456 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 11:14:02 -0800 Subject: [PATCH 141/230] Fixed linter errors on evm_data_types --- packages/utils/src/abi_encoder/constants.ts | 1 + .../utils/src/abi_encoder/evm_data_types.ts | 395 +++++++++--------- 2 files changed, 196 insertions(+), 200 deletions(-) diff --git a/packages/utils/src/abi_encoder/constants.ts b/packages/utils/src/abi_encoder/constants.ts index 52029695f6..3d85fbdb82 100644 --- a/packages/utils/src/abi_encoder/constants.ts +++ b/packages/utils/src/abi_encoder/constants.ts @@ -1,4 +1,5 @@ export const EVM_WORD_WIDTH_IN_BYTES = 32; +export const EVM_WORD_WIDTH_IN_BITS = 256; export const HEX_BASE = 16; export const DEC_BASE = 10; export const BIN_BASE = 2; diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 1f5dff56fd..24f6051b04 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -1,16 +1,12 @@ -import { DataType, DataTypeFactory, PayloadDataType, DependentDataType, MemberDataType } from './data_type'; - -import { DecodingRules, EncodingRules } from './calldata'; - -import { MethodAbi, DataItem } from 'ethereum-types'; - -import ethUtil = require('ethereumjs-util'); - -import { Calldata, RawCalldata } from './calldata'; +import { DataItem, MethodAbi } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; import { BigNumber } from '../configured_bignumber'; -var _ = require('lodash'); +import { DecodingRules, EncodingRules, RawCalldata } from './calldata'; +import * as Constants from './constants'; +import { DataType, DataTypeFactory, DependentDataType, MemberDataType, PayloadDataType } from './data_type'; export interface DataTypeStaticInterface { matchGrammar: (type: string) => boolean; @@ -19,12 +15,18 @@ export interface DataTypeStaticInterface { } export class Address extends PayloadDataType { - private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = 'Address must be 20 bytes'; + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _ADDRESS_SIZE_IN_BYTES = 20; + private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - Address._ADDRESS_SIZE_IN_BYTES; - constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Address.SIZE_KNOWN_AT_COMPILE_TIME); + public static matchGrammar(type: string): boolean { + return type === 'address'; + } + + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance(), Address._SIZE_KNOWN_AT_COMPILE_TIME); if (!Address.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`); } @@ -34,36 +36,35 @@ export class Address extends PayloadDataType { return 'address'; } - public static matchGrammar(type: string): boolean { - return type === 'address'; - } - public encodeValue(value: string): Buffer { - if (value.startsWith('0x') === false) { + if (!value.startsWith('0x')) { throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); } const valueAsBuffer = ethUtil.toBuffer(value); - if (valueAsBuffer.byteLength !== 20) { + if (valueAsBuffer.byteLength !== Address._ADDRESS_SIZE_IN_BYTES) { throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); } - const evmWordWidth = 32; - const encodedValueBuf = ethUtil.setLengthLeft(valueAsBuffer, evmWordWidth); + const encodedValueBuf = ethUtil.setLengthLeft(valueAsBuffer, Constants.EVM_WORD_WIDTH_IN_BYTES); return encodedValueBuf; } public decodeValue(calldata: RawCalldata): string { const paddedValueBuf = calldata.popWord(); - const valueBuf = paddedValueBuf.slice(12); + const valueBuf = paddedValueBuf.slice(Address._DECODED_ADDRESS_OFFSET_IN_BYTES); const value = ethUtil.bufferToHex(valueBuf); return value; } } export class Bool extends PayloadDataType { - private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Bool.SIZE_KNOWN_AT_COMPILE_TIME); + public static matchGrammar(type: string): boolean { + return type === 'bool'; + } + + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance(), Bool._SIZE_KNOWN_AT_COMPILE_TIME); if (!Bool.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`); } @@ -73,14 +74,9 @@ export class Bool extends PayloadDataType { return 'bool'; } - public static matchGrammar(type: string): boolean { - return type === 'bool'; - } - public encodeValue(value: boolean): Buffer { - const evmWordWidth = 32; - const encodedValue = value === true ? '0x1' : '0x0'; - const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth); + const encodedValue = value ? '0x1' : '0x0'; + const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), Constants.EVM_WORD_WIDTH_IN_BYTES); return encodedValueBuf; } @@ -88,46 +84,44 @@ export class Bool extends PayloadDataType { const valueBuf = calldata.popWord(); const valueHex = ethUtil.bufferToHex(valueBuf); const valueNumber = new BigNumber(valueHex, 16); - let value: boolean = valueNumber.equals(0) ? false : true; if (!(valueNumber.equals(0) || valueNumber.equals(1))) { throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`); } + /* tslint:disable boolean-naming */ + const value: boolean = valueNumber.equals(0) ? false : true; + /* tslint:enable boolean-naming */ return value; } } abstract class Number extends PayloadDataType { - private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - static MAX_WIDTH: number = 256; - static DEFAULT_WIDTH: number = Number.MAX_WIDTH; - width: number = Number.DEFAULT_WIDTH; + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _MAX_WIDTH: number = 256; + private static readonly _DEFAULT_WIDTH: number = Number._MAX_WIDTH; + protected _width: number; constructor(dataItem: DataItem, matcher: RegExp) { - super(dataItem, EvmDataTypeFactory.getInstance(), Number.SIZE_KNOWN_AT_COMPILE_TIME); + super(dataItem, EvmDataTypeFactory.getInstance(), Number._SIZE_KNOWN_AT_COMPILE_TIME); const matches = matcher.exec(dataItem.type); if (matches === null) { throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`); } - if (matches !== null && matches.length === 2 && matches[1] !== undefined) { - this.width = parseInt(matches[1]); - } else { - this.width = 256; - } + this._width = (matches !== null && matches.length === 2 && matches[1] !== undefined) ? + parseInt(matches[1], Constants.DEC_BASE) : + this._width = Number._DEFAULT_WIDTH; } public encodeValue(value_: BigNumber | string | number): Buffer { const value = new BigNumber(value_, 10); if (value.greaterThan(this.getMaxValue())) { - throw `Tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; + throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`); } else if (value.lessThan(this.getMinValue())) { - throw `Tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`; + throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`); } - const hexBase = 16; - const evmWordWidth = 32; let valueBuf: Buffer; if (value.greaterThanOrEqualTo(0)) { - valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth); + valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(Constants.HEX_BASE)}`), Constants.EVM_WORD_WIDTH_IN_BYTES); } else { // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. // Step 1/3: Convert value to positive binary string @@ -135,8 +129,7 @@ abstract class Number extends PayloadDataType { const valueBin = value.times(-1).toString(binBase); // Step 2/3: Invert binary value - const bitsInEvmWord = 256; - let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length); + let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length); _.each(valueBin, (bit: string) => { invertedValueBin += bit === '1' ? '0' : '1'; }); @@ -147,7 +140,7 @@ abstract class Number extends PayloadDataType { const negativeValue = invertedValue.plus(1); // Convert the negated value to a hex string - valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`), evmWordWidth); + valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${negativeValue.toString(Constants.HEX_BASE)}`), Constants.EVM_WORD_WIDTH_IN_BYTES); } return valueBuf; @@ -159,16 +152,15 @@ abstract class Number extends PayloadDataType { let value = new BigNumber(paddedValueHex, 16); if (this instanceof Int) { // Check if we're negative - const binBase = 2; - const valueBin = value.toString(2); - if (valueBin.length === 256 && valueBin[0].startsWith('1')) { + const valueBin = value.toString(Constants.BIN_BASE); + if (valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1')) { // Negative // Step 1/3: Invert binary value let invertedValueBin = ''; _.each(valueBin, (bit: string) => { invertedValueBin += bit === '1' ? '0' : '1'; }); - const invertedValue = new BigNumber(invertedValueBin, binBase); + const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); // Step 2/3: Add 1 to inverted value // The result is the two's-complement represent of the input value. @@ -188,42 +180,46 @@ abstract class Number extends PayloadDataType { } export class Int extends Number { - static matcher = RegExp( + private static readonly _matcher = RegExp( '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); - constructor(dataItem: DataItem) { - super(dataItem, Int.matcher); + public static matchGrammar(type: string): boolean { + return Int._matcher.test(type); + } + + public constructor(dataItem: DataItem) { + super(dataItem, Int._matcher); } public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).sub(1); + return new BigNumber(2).toPower(this._width - 1).sub(1); } public getMinValue(): BigNumber { - return new BigNumber(2).toPower(this.width - 1).times(-1); + return new BigNumber(2).toPower(this._width - 1).times(-1); } public getSignature(): string { - return `int${this.width}`; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); + return `int${this._width}`; } } export class UInt extends Number { - static matcher = RegExp( + private static readonly _matcher = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); - constructor(dataItem: DataItem) { - super(dataItem, UInt.matcher); + public static matchGrammar(type: string): boolean { + return UInt._matcher.test(type); + } + + public constructor(dataItem: DataItem) { + super(dataItem, UInt._matcher); } public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this.width).sub(1); + return new BigNumber(2).toPower(this._width).sub(1); } public getMinValue(): BigNumber { @@ -231,49 +227,45 @@ export class UInt extends Number { } public getSignature(): string { - return `uint${this.width}`; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); + return `uint${this._width}`; } } export class Byte extends PayloadDataType { - private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - static matcher = RegExp( + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _matcher = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', ); - static DEFAULT_WIDTH = 1; - width: number = Byte.DEFAULT_WIDTH; + private static readonly _DEFAULT_WIDTH = 1; + private readonly _width: number; - constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Byte.SIZE_KNOWN_AT_COMPILE_TIME); - const matches = Byte.matcher.exec(dataItem.type); + public static matchGrammar(type: string): boolean { + return Byte._matcher.test(type); + } + + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance(), Byte._SIZE_KNOWN_AT_COMPILE_TIME); + const matches = Byte._matcher.exec(dataItem.type); if (!Byte.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`); } - if (matches !== null && matches.length === 3 && matches[2] !== undefined) { - this.width = parseInt(matches[2]); - } else { - this.width = Byte.DEFAULT_WIDTH; - } + this._width = (matches !== null && matches.length === 3 && matches[2] !== undefined) ? parseInt(matches[2], Constants.DEC_BASE) : Byte._DEFAULT_WIDTH; } public getSignature(): string { // Note that `byte` reduces to `bytes1` - return `bytes${this.width}`; + return `bytes${this._width}`; } public encodeValue(value: string | Buffer): Buffer { // Sanity check if string - if (typeof value === 'string' && value.startsWith('0x') === false) { + if (typeof value === 'string' && !value.startsWith('0x')) { throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); } // Convert value into a buffer and do bounds checking const valueBuf = ethUtil.toBuffer(value); - if (valueBuf.byteLength > this.width) { + if (valueBuf.byteLength > this._width) { throw new Error( `Tried to assign ${value} (${ valueBuf.byteLength @@ -291,23 +283,21 @@ export class Byte extends PayloadDataType { public decodeValue(calldata: RawCalldata): string { const paddedValueBuf = calldata.popWord(); - const valueBuf = paddedValueBuf.slice(0, this.width); + const valueBuf = paddedValueBuf.slice(0, this._width); const value = ethUtil.bufferToHex(valueBuf); return value; } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } } export class Bytes extends PayloadDataType { - private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = Bytes.UNDEFINED_LENGTH; + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; - constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Bytes.SIZE_KNOWN_AT_COMPILE_TIME); + public static matchGrammar(type: string): boolean { + return type === 'bytes'; + } + + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance(), Bytes._SIZE_KNOWN_AT_COMPILE_TIME); if (!Bytes.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate Bytes with bad input: ${dataItem}`); } @@ -322,10 +312,10 @@ export class Bytes extends PayloadDataType { throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); } - const wordsForValue = Math.ceil(valueBuf.byteLength / 32); - const paddedBytesForValue = wordsForValue * 32; + const wordsForValue = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); + const paddedBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedBytesForValue); - const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); + const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), Constants.EVM_WORD_WIDTH_IN_BYTES); const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); return encodedValueBuf; } @@ -333,8 +323,8 @@ export class Bytes extends PayloadDataType { public decodeValue(calldata: RawCalldata): string { const lengthBuf = calldata.popWord(); const lengthHex = ethUtil.bufferToHex(lengthBuf); - const length = parseInt(lengthHex, 16); - const wordsForValue = Math.ceil(length / 32); + const length = parseInt(lengthHex, Constants.HEX_BASE); + const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); const paddedValueBuf = calldata.popWords(wordsForValue); const valueBuf = paddedValueBuf.slice(0, length); const decodedValue = ethUtil.bufferToHex(valueBuf); @@ -344,26 +334,27 @@ export class Bytes extends PayloadDataType { public getSignature(): string { return 'bytes'; } - - public static matchGrammar(type: string): boolean { - return type === 'bytes'; - } } export class SolString extends PayloadDataType { - private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; - constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), SolString.SIZE_KNOWN_AT_COMPILE_TIME); + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; + + public static matchGrammar(type: string): boolean { + return type === 'string'; + } + + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance(), SolString._SIZE_KNOWN_AT_COMPILE_TIME); if (!SolString.matchGrammar(dataItem.type)) { throw new Error(`Tried to instantiate String with bad input: ${dataItem}`); } } public encodeValue(value: string): Buffer { - const wordsForValue = Math.ceil(value.length / 32); - const paddedBytesForValue = wordsForValue * 32; + const wordsForValue = Math.ceil(value.length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const paddedBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; const valueBuf = ethUtil.setLengthRight(new Buffer(value), paddedBytesForValue); - const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32); + const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), Constants.EVM_WORD_WIDTH_IN_BYTES); const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); return encodedValueBuf; } @@ -371,8 +362,8 @@ export class SolString extends PayloadDataType { public decodeValue(calldata: RawCalldata): string { const lengthBuf = calldata.popWord(); const lengthHex = ethUtil.bufferToHex(lengthBuf); - const length = parseInt(lengthHex, 16); - const wordsForValue = Math.ceil(length / 32); + const length = parseInt(lengthHex, Constants.HEX_BASE); + const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); const paddedValueBuf = calldata.popWords(wordsForValue); const valueBuf = paddedValueBuf.slice(0, length); const value = valueBuf.toString('ascii'); @@ -382,16 +373,12 @@ export class SolString extends PayloadDataType { public getSignature(): string { return 'string'; } - - public static matchGrammar(type: string): boolean { - return type === 'string'; - } } export class Pointer extends DependentDataType { constructor(destDataType: DataType, parentDataType: DataType) { const destDataItem = destDataType.getDataItem(); - const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; + const dataItem: DataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` }; super(dataItem, EvmDataTypeFactory.getInstance(), destDataType, parentDataType); } @@ -401,33 +388,37 @@ export class Pointer extends DependentDataType { } export class Tuple extends MemberDataType { - private tupleSignature: string; - - constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance()); - if (!Tuple.matchGrammar(dataItem.type)) { - throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); - } - this.tupleSignature = this._computeSignatureOfMembers(); - } - - public getSignature(): string { - return this.tupleSignature; - } + private readonly _tupleSignature: string; public static matchGrammar(type: string): boolean { return type === 'tuple'; } + + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + if (!Tuple.matchGrammar(dataItem.type)) { + throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); + } + this._tupleSignature = this._computeSignatureOfMembers(); + } + + public getSignature(): string { + return this._tupleSignature; + } } export class SolArray extends MemberDataType { - static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); - private arraySignature: string; - private elementType: string; + private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); + private readonly _arraySignature: string; + private readonly _elementType: string; - constructor(dataItem: DataItem) { + public static matchGrammar(type: string): boolean { + return SolArray._matcher.test(type); + } + + public constructor(dataItem: DataItem) { // Sanity check - const matches = SolArray.matcher.exec(dataItem.type); + const matches = SolArray._matcher.exec(dataItem.type); if (matches === null || matches.length !== 3) { throw new Error(`Could not parse array: ${dataItem.type}`); } else if (matches[1] === undefined) { @@ -438,17 +429,21 @@ export class SolArray extends MemberDataType { const isArray = true; const arrayElementType = matches[1]; - const arrayLength = matches[2] === '' ? undefined : parseInt(matches[2], 10); + const arrayLength = matches[2] === '' ? undefined : parseInt(matches[2], Constants.DEC_BASE); super(dataItem, EvmDataTypeFactory.getInstance(), isArray, arrayLength, arrayElementType); - this.elementType = arrayElementType; - this.arraySignature = this.computeSignature(); + this._elementType = arrayElementType; + this._arraySignature = this._computeSignature(); } - private computeSignature(): string { - let dataItem = { - type: this.elementType, + public getSignature(): string { + return this._arraySignature; + } + + private _computeSignature(): string { + const dataItem: DataItem = { + type: this._elementType, name: 'N/A', - } as DataItem; + }; const components = this.getDataItem().components; if (components !== undefined) { dataItem.components = components; @@ -461,49 +456,29 @@ export class SolArray extends MemberDataType { return `${type}[${this._arrayLength}]`; } } - - public getSignature(): string { - return this.arraySignature; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } } export class Method extends MemberDataType { - private methodSignature: string; - private methodSelector: string; - private returnDataTypes: DataType[]; - private returnDataItem: DataItem; - // TMP public selector: string; - constructor(abi: MethodAbi) { + private readonly _methodSignature: string; + private readonly _methodSelector: string; + private readonly _returnDataTypes: DataType[]; + private readonly _returnDataItem: DataItem; + + public constructor(abi: MethodAbi) { super({ type: 'method', name: abi.name, components: abi.inputs }, EvmDataTypeFactory.getInstance()); - this.methodSignature = this.computeSignature(); - this.selector = this.methodSelector = this.computeSelector(); - this.returnDataTypes = []; - this.returnDataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; + this._methodSignature = this._computeSignature(); + this.selector = this._methodSelector = this._computeSelector(); + this._returnDataTypes = []; + this._returnDataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; const dummy = new Byte({ type: 'byte', name: 'DUMMY' }); // @TODO TMP _.each(abi.outputs, (dataItem: DataItem) => { - this.returnDataTypes.push(this.getFactory().create(dataItem, dummy)); + this._returnDataTypes.push(this.getFactory().create(dataItem, dummy)); }); } - private computeSignature(): string { - const memberSignature = this._computeSignatureOfMembers(); - const methodSignature = `${this.getDataItem().name}${memberSignature}`; - return methodSignature; - } - - private computeSelector(): string { - const signature = this.computeSignature(); - const selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(signature).slice(0, 4))); - return selector; - } - public encode(value: any, rules?: EncodingRules): string { const calldata = super.encode(value, rules, this.selector); return calldata; @@ -521,55 +496,73 @@ export class Method extends MemberDataType { } public encodeReturnValues(value: any, rules?: EncodingRules): string { - const returnDataType = new Tuple(this.returnDataItem); + const returnDataType = new Tuple(this._returnDataItem); const returndata = returnDataType.encode(value, rules); return returndata; } public decodeReturnValues(returndata: string, rules?: DecodingRules): any { const returnValues: any[] = []; - const rules_ = rules ? rules : ({ structsAsObjects: false } as DecodingRules); + const rules_: DecodingRules = rules ? rules : { structsAsObjects: false }; const rawReturnData = new RawCalldata(returndata, false); - _.each(this.returnDataTypes, (dataType: DataType) => { + _.each(this._returnDataTypes, (dataType: DataType) => { returnValues.push(dataType.generateValue(rawReturnData, rules_)); }); return returnValues; } public getSignature(): string { - return this.methodSignature; + return this._methodSignature; } public getSelector(): string { - return this.methodSelector; + return this._methodSelector; + } + + private _computeSignature(): string { + const memberSignature = this._computeSignatureOfMembers(); + const methodSignature = `${this.getDataItem().name}${memberSignature}`; + return methodSignature; + } + + private _computeSelector(): string { + const signature = this._computeSignature(); + const selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(signature).slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES))); + return selector; } } export class EvmDataTypeFactory implements DataTypeFactory { - private static instance: DataTypeFactory; - - private constructor() { } + private static _instance: DataTypeFactory; public static getInstance(): DataTypeFactory { - if (!EvmDataTypeFactory.instance) { - EvmDataTypeFactory.instance = new EvmDataTypeFactory(); + if (!EvmDataTypeFactory._instance) { + EvmDataTypeFactory._instance = new EvmDataTypeFactory(); } - return EvmDataTypeFactory.instance; + return EvmDataTypeFactory._instance; } public mapDataItemToDataType(dataItem: DataItem): DataType { - if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem); - if (Address.matchGrammar(dataItem.type)) return new Address(dataItem); - if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem); - if (Int.matchGrammar(dataItem.type)) return new Int(dataItem); - if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem); - if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem); - if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem); - if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem); - if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem); - //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem); - //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem); - + if (SolArray.matchGrammar(dataItem.type)) { + return new SolArray(dataItem); + } else if (Address.matchGrammar(dataItem.type)) { + return new Address(dataItem); + } else if (Bool.matchGrammar(dataItem.type)) { + return new Bool(dataItem); + } else if (Int.matchGrammar(dataItem.type)) { + return new Int(dataItem); + } else if (UInt.matchGrammar(dataItem.type)) { + return new UInt(dataItem); + } else if (Byte.matchGrammar(dataItem.type)) { + return new Byte(dataItem); + } else if (Tuple.matchGrammar(dataItem.type)) { + return new Tuple(dataItem); + } else if (Bytes.matchGrammar(dataItem.type)) { + return new Bytes(dataItem); + } else if (SolString.matchGrammar(dataItem.type)) { + return new SolString(dataItem); + } + // @TODO: Implement Fixed/UFixed types throw new Error(`Unrecognized data type: '${dataItem.type}'`); } @@ -586,4 +579,6 @@ export class EvmDataTypeFactory implements DataTypeFactory { const pointer = new Pointer(dataType, parentDataType); return pointer; } + + private constructor() { } } From 62e6b22789b349dda7885d3317c76c90ecb3c882 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 11:35:16 -0800 Subject: [PATCH 142/230] Fixed linter errors on tests --- packages/utils/test/abi_encoder_test.ts | 99 +++++++++++++----------- packages/utils/test/abi_samples.ts | 93 +++++++++++----------- packages/utils/test/optimizer_abis.ts | 57 +++++++------- packages/utils/test/return_value_abis.ts | 25 +++--- 4 files changed, 141 insertions(+), 133 deletions(-) diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index c7986fa003..e99528b060 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -1,13 +1,13 @@ import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; import 'mocha'; -import { chaiSetup } from './utils/chai_setup'; -import { BigNumber, AbiEncoder } from '../src/'; +import { AbiEncoder, BigNumber } from '../src/'; + import * as AbiSamples from './abi_samples'; import * as OptimizedAbis from './optimizer_abis'; import * as ReturnValueAbis from './return_value_abis'; -import { DecodingRules } from '../src/abi_encoder'; -import ethUtil = require('ethereumjs-util'); +import { chaiSetup } from './utils/chai_setup'; chaiSetup.configure(); const expect = chai.expect; @@ -349,8 +349,8 @@ describe.only('ABI Encoder', () => { // Generate calldata const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsSeparateParameter); const array = ['Hello', 'Hello', 'Hello', 'World']; - const string = 'Hello'; - const args = [array, string]; + const str = 'Hello'; + const args = [array, str]; // Validate calldata const optimizedCalldata = method.encode(args, { optimize: true }); const expectedOptimizedCalldata = @@ -392,7 +392,8 @@ describe.only('ABI Encoder', () => { const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); let value = 0; const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); } const args = [arrayOfTuples]; @@ -412,7 +413,8 @@ describe.only('ABI Encoder', () => { const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi); let value = 0; const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); } const args = [arrayOfTuples]; @@ -432,7 +434,8 @@ describe.only('ABI Encoder', () => { const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi); let value = 0; const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); } const args = [arrayOfTuples]; @@ -452,7 +455,8 @@ describe.only('ABI Encoder', () => { const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi); let value = 0; const arrayOfTuples = []; - for (let i = 0; i < 8; ++i) { + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); } const args = [arrayOfTuples]; @@ -473,7 +477,8 @@ describe.only('ABI Encoder', () => { // Eight 3-dimensional arrays of uint8[2][2][2] let value = 0; const args = []; - for (let i = 0; i < 8; ++i) { + const argsLength = 8; + for (let i = 0; i < argsLength; ++i) { args.push([ [ [new BigNumber(++value), new BigNumber(++value)], @@ -503,7 +508,8 @@ describe.only('ABI Encoder', () => { // Eight 3-dimensional arrays of string[2][2][2] let value = 0; const args = []; - for (let i = 0; i < 4; ++i) { + const argsLength = 4; + for (let i = 0; i < argsLength; ++i) { args.push([ [ [new BigNumber(++value).toString(), new BigNumber(++value).toString()], @@ -741,13 +747,13 @@ describe.only('ABI Encoder', () => { }; const someArrayOfTuplesWithDynamicTypes = [someTupleWithDynamicTypes2, someTupleWithDynamicTypes3]; const args = { - someStaticArray: someStaticArray, - someStaticArrayWithDynamicMembers: someStaticArrayWithDynamicMembers, - someDynamicArrayWithDynamicMembers: someDynamicArrayWithDynamicMembers, - some2DArray: some2DArray, - someTuple: someTuple, - someTupleWithDynamicTypes: someTupleWithDynamicTypes, - someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes, + someStaticArray, + someStaticArrayWithDynamicMembers, + someDynamicArrayWithDynamicMembers, + some2DArray, + someTuple, + someTupleWithDynamicTypes, + someArrayOfTuplesWithDynamicTypes, }; const calldata = method.encode(args); // Validate calldata @@ -900,8 +906,6 @@ describe.only('ABI Encoder', () => { const args = [array1, array2]; // Encode Args and validate result const encodedArgs = dataType.encode(args); - console.log(encodedArgs); - console.log(dataType.encode(args, { annotate: true })); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000401020304000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004050607080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040910111200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000410111213000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -963,7 +967,7 @@ describe.only('ABI Encoder', () => { '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result - const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgsAsJson = JSON.stringify(decodedArgs); const argsAsJson = JSON.stringify(args); @@ -985,7 +989,7 @@ describe.only('ABI Encoder', () => { '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result - const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgsAsJson = JSON.stringify(decodedArgs); const argsAsJson = JSON.stringify(args); @@ -1007,7 +1011,7 @@ describe.only('ABI Encoder', () => { '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result - const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgsAsJson = JSON.stringify(decodedArgs); const argsAsJson = JSON.stringify(args); @@ -1029,7 +1033,7 @@ describe.only('ABI Encoder', () => { '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result - const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgsAsJson = JSON.stringify(decodedArgs); const argsAsJson = JSON.stringify(args); @@ -1053,7 +1057,7 @@ describe.only('ABI Encoder', () => { '0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result - const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgsAsJson = JSON.stringify(decodedArgs); const argsAsJson = JSON.stringify(args); @@ -1077,7 +1081,7 @@ describe.only('ABI Encoder', () => { '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result - const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgsAsJson = JSON.stringify(decodedArgs); const argsAsJson = JSON.stringify(args); @@ -1109,7 +1113,7 @@ describe.only('ABI Encoder', () => { '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result - const decodingRules = { structsAsObjects: true } as DecodingRules; + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgsAsJson = JSON.stringify(decodedArgs); const argsAsJson = JSON.stringify(args); @@ -1224,6 +1228,13 @@ describe.only('ABI Encoder', () => { }); describe('Integer', () => { + /* tslint:disable custom-no-magic-numbers */ + const max256BitInteger = new BigNumber(2).pow(255).minus(1); + const min256BitInteger = new BigNumber(2).pow(255).times(-1); + const max32BitInteger = new BigNumber(2).pow(31).minus(1); + const min32BitInteger = new BigNumber(2).pow(31).times(-1); + /* tslint:enable custom-no-magic-numbers */ + it('Int256 - Positive Base Case', async () => { // Create DataType object const testDataItem = { name: 'Integer (256)', type: 'int' }; @@ -1261,7 +1272,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max256BitInteger = new BigNumber(2).pow(255).minus(1); const args = max256BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1278,7 +1288,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min256BitInteger = new BigNumber(2).pow(255).times(-1); const args = min256BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1295,7 +1304,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max256BitInteger = new BigNumber(2).pow(255).minus(1); const args = max256BitInteger.plus(1); // Encode Args and validate result expect(() => { @@ -1307,7 +1315,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (256)', type: 'int' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min256BitInteger = new BigNumber(2).pow(255).times(-1); const args = min256BitInteger.minus(1); // Encode Args and validate result expect(() => { @@ -1351,7 +1358,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max32BitInteger = new BigNumber(2).pow(31).minus(1); const args = max32BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1368,7 +1374,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min32BitInteger = new BigNumber(2).pow(31).times(-1); const args = min32BitInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1385,7 +1390,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const max32BitInteger = new BigNumber(2).pow(31).minus(1); const args = max32BitInteger.plus(1); // Encode Args and validate result expect(() => { @@ -1397,7 +1401,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Integer (32)', type: 'int32' }; const dataType = new AbiEncoder.Int(testDataItem); // Construct args to be encoded - const min32BitInteger = new BigNumber(2).pow(31).times(-1); const args = min32BitInteger.minus(1); // Encode Args and validate result expect(() => { @@ -1407,6 +1410,13 @@ describe.only('ABI Encoder', () => { }); describe('Unsigned Integer', () => { + /* tslint:disable custom-no-magic-numbers */ + const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1); + const min256BitUnsignedInteger = new BigNumber(0); + const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1); + const min32BitUnsignedInteger = new BigNumber(0); + /* tslint:enable custom-no-magic-numbers */ + it('UInt256 - Positive Base Case', async () => { // Create DataType object const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; @@ -1428,7 +1438,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1); const args = max256BitUnsignedInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1445,7 +1454,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const min256BitUnsignedInteger = new BigNumber(0); const args = min256BitUnsignedInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1462,7 +1470,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1); const args = max256BitUnsignedInteger.plus(1); // Encode Args and validate result expect(() => { @@ -1474,7 +1481,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const min256BitUnsignedInteger = new BigNumber(0); const args = min256BitUnsignedInteger.minus(1); // Encode Args and validate result expect(() => { @@ -1502,7 +1508,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1); const args = max32BitUnsignedInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1519,7 +1524,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const min32BitUnsignedInteger = new BigNumber(0); const args = min32BitUnsignedInteger; // Encode Args and validate result const encodedArgs = dataType.encode(args); @@ -1536,7 +1540,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1); const args = max32BitUnsignedInteger.plus(1); // Encode Args and validate result expect(() => { @@ -1548,7 +1551,6 @@ describe.only('ABI Encoder', () => { const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; const dataType = new AbiEncoder.UInt(testDataItem); // Construct args to be encoded - const min32BitUnsignedInteger = new BigNumber(0); const args = min32BitUnsignedInteger.minus(1); // Encode Args and validate result expect(() => { @@ -1733,7 +1735,8 @@ describe.only('ABI Encoder', () => { const dataType = new AbiEncoder.Bytes(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = '0x' + '61'.repeat(40); + const bytesLength = 40; + const args = '0x' + '61'.repeat(bytesLength); // Encode Args and validate result const encodedArgs = dataType.encode(args); const expectedEncodedArgs = @@ -1813,7 +1816,8 @@ describe.only('ABI Encoder', () => { const dataType = new AbiEncoder.SolString(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = 'a'.repeat(40); + const bytesLength = 40; + const args = 'a'.repeat(bytesLength); // Encode Args and validate result const encodedArgs = dataType.encode(args); const expectedEncodedArgs = @@ -1831,7 +1835,8 @@ describe.only('ABI Encoder', () => { const dataType = new AbiEncoder.SolString(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = '0x' + 'a'.repeat(40); + const strLength = 40; + const args = '0x' + 'a'.repeat(strLength); // Encode Args and validate result const encodedArgs = dataType.encode(args); const expectedEncodedArgs = diff --git a/packages/utils/test/abi_samples.ts b/packages/utils/test/abi_samples.ts index 0c33540443..fc552c1275 100644 --- a/packages/utils/test/abi_samples.ts +++ b/packages/utils/test/abi_samples.ts @@ -1,6 +1,7 @@ +/* tslint:disable max-file-line-count */ import { MethodAbi } from 'ethereum-types'; -export const simpleAbi = { +export const simpleAbi: MethodAbi = { constant: false, inputs: [ { @@ -17,9 +18,9 @@ export const simpleAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const stringAbi = { +export const stringAbi: MethodAbi = { constant: false, inputs: [ { @@ -32,9 +33,9 @@ export const stringAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const GAbi = { +export const GAbi: MethodAbi = { constant: false, inputs: [ { @@ -66,9 +67,9 @@ export const GAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const typesWithDefaultWidthsAbi = { +export const typesWithDefaultWidthsAbi: MethodAbi = { constant: false, inputs: [ { @@ -101,9 +102,9 @@ export const typesWithDefaultWidthsAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const multiDimensionalArraysStaticTypeAbi = { +export const multiDimensionalArraysStaticTypeAbi: MethodAbi = { constant: false, inputs: [ { @@ -144,9 +145,9 @@ export const multiDimensionalArraysStaticTypeAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const multiDimensionalArraysDynamicTypeAbi = { +export const multiDimensionalArraysDynamicTypeAbi: MethodAbi = { constant: false, inputs: [ { @@ -171,9 +172,9 @@ export const multiDimensionalArraysDynamicTypeAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const dynamicTupleAbi = { +export const dynamicTupleAbi: MethodAbi = { constant: false, inputs: [ { @@ -196,9 +197,9 @@ export const dynamicTupleAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const arrayOfStaticTuplesWithDefinedLengthAbi = { +export const arrayOfStaticTuplesWithDefinedLengthAbi: MethodAbi = { constant: false, inputs: [ { @@ -221,9 +222,9 @@ export const arrayOfStaticTuplesWithDefinedLengthAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const arrayOfStaticTuplesWithDynamicLengthAbi = { +export const arrayOfStaticTuplesWithDynamicLengthAbi: MethodAbi = { constant: false, inputs: [ { @@ -246,9 +247,9 @@ export const arrayOfStaticTuplesWithDynamicLengthAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const arrayOfDynamicTuplesWithDefinedLengthAbi = { +export const arrayOfDynamicTuplesWithDefinedLengthAbi: MethodAbi = { constant: false, inputs: [ { @@ -271,9 +272,9 @@ export const arrayOfDynamicTuplesWithDefinedLengthAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const arrayOfDynamicTuplesWithUndefinedLengthAbi = { +export const arrayOfDynamicTuplesWithUndefinedLengthAbi: MethodAbi = { constant: false, inputs: [ { @@ -296,9 +297,9 @@ export const arrayOfDynamicTuplesWithUndefinedLengthAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const arrayOfDynamicTuplesAbi = { +export const arrayOfDynamicTuplesAbi: MethodAbi = { constant: false, inputs: [ { @@ -321,9 +322,9 @@ export const arrayOfDynamicTuplesAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const multidimensionalArrayOfDynamicTuplesAbi = { +export const multidimensionalArrayOfDynamicTuplesAbi: MethodAbi = { constant: false, inputs: [ { @@ -346,9 +347,9 @@ export const multidimensionalArrayOfDynamicTuplesAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const staticTupleAbi = { +export const staticTupleAbi: MethodAbi = { constant: false, inputs: [ { @@ -379,9 +380,9 @@ export const staticTupleAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const staticArrayAbi = { +export const staticArrayAbi: MethodAbi = { constant: false, inputs: [ { @@ -394,9 +395,9 @@ export const staticArrayAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const staticArrayDynamicMembersAbi = { +export const staticArrayDynamicMembersAbi: MethodAbi = { constant: false, inputs: [ { @@ -409,9 +410,9 @@ export const staticArrayDynamicMembersAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const dynamicArrayDynamicMembersAbi = { +export const dynamicArrayDynamicMembersAbi: MethodAbi = { constant: false, inputs: [ { @@ -424,9 +425,9 @@ export const dynamicArrayDynamicMembersAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const dynamicArrayStaticMembersAbi = { +export const dynamicArrayStaticMembersAbi: MethodAbi = { constant: false, inputs: [ { @@ -439,9 +440,9 @@ export const dynamicArrayStaticMembersAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const largeFlatAbi = { +export const largeFlatAbi: MethodAbi = { constant: false, inputs: [ { @@ -486,9 +487,9 @@ export const largeFlatAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const largeNestedAbi = { +export const largeNestedAbi: MethodAbi = { constant: false, inputs: [ { @@ -579,9 +580,9 @@ export const largeNestedAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const nestedTuples = { +export const nestedTuples: MethodAbi = { constant: false, inputs: [ { @@ -668,9 +669,9 @@ export const nestedTuples = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const simpleAbi2 = { +export const simpleAbi2: MethodAbi = { constant: false, inputs: [ { @@ -695,9 +696,9 @@ export const simpleAbi2 = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const fillOrderAbi = { +export const fillOrderAbi: MethodAbi = { constant: false, inputs: [ { @@ -776,4 +777,4 @@ export const fillOrderAbi = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; diff --git a/packages/utils/test/optimizer_abis.ts b/packages/utils/test/optimizer_abis.ts index ea562e5b58..7cfd7a1183 100644 --- a/packages/utils/test/optimizer_abis.ts +++ b/packages/utils/test/optimizer_abis.ts @@ -1,6 +1,7 @@ +/* tslint:disable max-file-line-count */ import { MethodAbi } from 'ethereum-types'; -export const duplicateDynamicArraysWithStaticElements = { +export const duplicateDynamicArraysWithStaticElements: MethodAbi = { constant: false, inputs: [ { @@ -17,9 +18,9 @@ export const duplicateDynamicArraysWithStaticElements = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateDynamicArraysWithDynamicElements = { +export const duplicateDynamicArraysWithDynamicElements: MethodAbi = { constant: false, inputs: [ { @@ -36,9 +37,9 @@ export const duplicateDynamicArraysWithDynamicElements = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateStaticArraysWithStaticElements = { +export const duplicateStaticArraysWithStaticElements: MethodAbi = { constant: false, inputs: [ { @@ -55,9 +56,9 @@ export const duplicateStaticArraysWithStaticElements = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateStaticArraysWithDynamicElements = { +export const duplicateStaticArraysWithDynamicElements: MethodAbi = { constant: false, inputs: [ { @@ -74,9 +75,9 @@ export const duplicateStaticArraysWithDynamicElements = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateArrayElements = { +export const duplicateArrayElements: MethodAbi = { constant: false, inputs: [ { @@ -89,9 +90,9 @@ export const duplicateArrayElements = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateTupleFields = { +export const duplicateTupleFields: MethodAbi = { constant: false, inputs: [ { @@ -114,9 +115,9 @@ export const duplicateTupleFields = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateStrings = { +export const duplicateStrings: MethodAbi = { constant: false, inputs: [ { @@ -133,9 +134,9 @@ export const duplicateStrings = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateBytes = { +export const duplicateBytes: MethodAbi = { constant: false, inputs: [ { @@ -152,9 +153,9 @@ export const duplicateBytes = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateTuples = { +export const duplicateTuples: MethodAbi = { constant: false, inputs: [ { @@ -191,9 +192,9 @@ export const duplicateTuples = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateArraysNestedInTuples = { +export const duplicateArraysNestedInTuples: MethodAbi = { constant: false, inputs: [ { @@ -226,9 +227,9 @@ export const duplicateArraysNestedInTuples = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateTuplesNestedInTuples = { +export const duplicateTuplesNestedInTuples: MethodAbi = { constant: false, inputs: [ { @@ -273,9 +274,9 @@ export const duplicateTuplesNestedInTuples = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const duplicateTwoDimensionalArrays = { +export const duplicateTwoDimensionalArrays: MethodAbi = { constant: false, inputs: [ { @@ -292,9 +293,9 @@ export const duplicateTwoDimensionalArrays = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const arrayElementsDuplicatedAsSeparateParameter = { +export const arrayElementsDuplicatedAsSeparateParameter: MethodAbi = { constant: false, inputs: [ { @@ -311,9 +312,9 @@ export const arrayElementsDuplicatedAsSeparateParameter = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const arrayElementsDuplicatedAsTupleFields = { +export const arrayElementsDuplicatedAsTupleFields: MethodAbi = { constant: false, inputs: [ { @@ -336,4 +337,4 @@ export const arrayElementsDuplicatedAsTupleFields = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; diff --git a/packages/utils/test/return_value_abis.ts b/packages/utils/test/return_value_abis.ts index 847559dac7..ac21240112 100644 --- a/packages/utils/test/return_value_abis.ts +++ b/packages/utils/test/return_value_abis.ts @@ -1,6 +1,7 @@ +/* tslint:disable max-file-line-count */ import { MethodAbi } from 'ethereum-types'; -export const noReturnValues = { +export const noReturnValues: MethodAbi = { constant: false, inputs: [], name: 'simpleFunction', @@ -8,9 +9,9 @@ export const noReturnValues = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const singleStaticReturnValue = { +export const singleStaticReturnValue: MethodAbi = { constant: false, inputs: [], name: 'simpleFunction', @@ -23,9 +24,9 @@ export const singleStaticReturnValue = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const multipleStaticReturnValues = { +export const multipleStaticReturnValues: MethodAbi = { constant: false, inputs: [], name: 'simpleFunction', @@ -42,9 +43,9 @@ export const multipleStaticReturnValues = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const singleDynamicReturnValue = { +export const singleDynamicReturnValue: MethodAbi = { constant: false, inputs: [], name: 'simpleFunction', @@ -57,9 +58,9 @@ export const singleDynamicReturnValue = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const multipleDynamicReturnValues = { +export const multipleDynamicReturnValues: MethodAbi = { constant: false, inputs: [], name: 'simpleFunction', @@ -76,9 +77,9 @@ export const multipleDynamicReturnValues = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; -export const mixedStaticAndDynamicReturnValues = { +export const mixedStaticAndDynamicReturnValues: MethodAbi = { constant: false, inputs: [], name: 'simpleFunction', @@ -95,4 +96,4 @@ export const mixedStaticAndDynamicReturnValues = { payable: false, stateMutability: 'nonpayable', type: 'function', -} as MethodAbi; +}; From 29d63cdf976e4cb10b738f7cbb1a57f43c5530ef Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 11:38:47 -0800 Subject: [PATCH 143/230] Fixed linter errors in package.json --- packages/utils/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/utils/package.json b/packages/utils/package.json index 8dc1f07392..1f4d85843b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -33,6 +33,9 @@ "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^2.0.1", + "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", "npm-run-all": "^4.1.2", @@ -57,4 +60,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file From 5934e5a57b5897594e22efc9e6ff28841c6951bf Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 11:45:24 -0800 Subject: [PATCH 144/230] Renaming some EVM data types for clarity --- .../utils/src/abi_encoder/evm_data_types.ts | 94 +++++++++---------- packages/utils/test/abi_encoder_test.ts | 58 ++++++------ 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts index 24f6051b04..7ad7f403e7 100644 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ b/packages/utils/src/abi_encoder/evm_data_types.ts @@ -9,7 +9,7 @@ import * as Constants from './constants'; import { DataType, DataTypeFactory, DependentDataType, MemberDataType, PayloadDataType } from './data_type'; export interface DataTypeStaticInterface { - matchGrammar: (type: string) => boolean; + matchType: (type: string) => boolean; encodeValue: (value: any) => Buffer; decodeValue: (rawCalldata: RawCalldata) => any; } @@ -21,13 +21,13 @@ export class Address extends PayloadDataType { private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - Address._ADDRESS_SIZE_IN_BYTES; - public static matchGrammar(type: string): boolean { + public static matchType(type: string): boolean { return type === 'address'; } public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance(), Address._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Address.matchGrammar(dataItem.type)) { + if (!Address.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`); } } @@ -59,13 +59,13 @@ export class Address extends PayloadDataType { export class Bool extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - public static matchGrammar(type: string): boolean { + public static matchType(type: string): boolean { return type === 'bool'; } public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance(), Bool._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Bool.matchGrammar(dataItem.type)) { + if (!Bool.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`); } } @@ -184,7 +184,7 @@ export class Int extends Number { '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); - public static matchGrammar(type: string): boolean { + public static matchType(type: string): boolean { return Int._matcher.test(type); } @@ -210,7 +210,7 @@ export class UInt extends Number { '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); - public static matchGrammar(type: string): boolean { + public static matchType(type: string): boolean { return UInt._matcher.test(type); } @@ -231,7 +231,7 @@ export class UInt extends Number { } } -export class Byte extends PayloadDataType { +export class StaticBytes extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _matcher = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', @@ -240,17 +240,17 @@ export class Byte extends PayloadDataType { private static readonly _DEFAULT_WIDTH = 1; private readonly _width: number; - public static matchGrammar(type: string): boolean { - return Byte._matcher.test(type); + public static matchType(type: string): boolean { + return StaticBytes._matcher.test(type); } public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Byte._SIZE_KNOWN_AT_COMPILE_TIME); - const matches = Byte._matcher.exec(dataItem.type); - if (!Byte.matchGrammar(dataItem.type)) { + super(dataItem, EvmDataTypeFactory.getInstance(), StaticBytes._SIZE_KNOWN_AT_COMPILE_TIME); + const matches = StaticBytes._matcher.exec(dataItem.type); + if (!StaticBytes.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`); } - this._width = (matches !== null && matches.length === 3 && matches[2] !== undefined) ? parseInt(matches[2], Constants.DEC_BASE) : Byte._DEFAULT_WIDTH; + this._width = (matches !== null && matches.length === 3 && matches[2] !== undefined) ? parseInt(matches[2], Constants.DEC_BASE) : StaticBytes._DEFAULT_WIDTH; } public getSignature(): string { @@ -289,17 +289,17 @@ export class Byte extends PayloadDataType { } } -export class Bytes extends PayloadDataType { +export class DynamicBytes extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; - public static matchGrammar(type: string): boolean { + public static matchType(type: string): boolean { return type === 'bytes'; } public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Bytes._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Bytes.matchGrammar(dataItem.type)) { - throw new Error(`Tried to instantiate Bytes with bad input: ${dataItem}`); + super(dataItem, EvmDataTypeFactory.getInstance(), DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME); + if (!DynamicBytes.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate DynamicBytes with bad input: ${dataItem}`); } } @@ -313,8 +313,8 @@ export class Bytes extends PayloadDataType { } const wordsForValue = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; - const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedBytesForValue); + const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; + const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedDynamicBytesForValue); const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), Constants.EVM_WORD_WIDTH_IN_BYTES); const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); return encodedValueBuf; @@ -336,24 +336,24 @@ export class Bytes extends PayloadDataType { } } -export class SolString extends PayloadDataType { +export class String extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; - public static matchGrammar(type: string): boolean { + public static matchType(type: string): boolean { return type === 'string'; } public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), SolString._SIZE_KNOWN_AT_COMPILE_TIME); - if (!SolString.matchGrammar(dataItem.type)) { + super(dataItem, EvmDataTypeFactory.getInstance(), String._SIZE_KNOWN_AT_COMPILE_TIME); + if (!String.matchType(dataItem.type)) { throw new Error(`Tried to instantiate String with bad input: ${dataItem}`); } } public encodeValue(value: string): Buffer { const wordsForValue = Math.ceil(value.length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; - const valueBuf = ethUtil.setLengthRight(new Buffer(value), paddedBytesForValue); + const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; + const valueBuf = ethUtil.setLengthRight(new Buffer(value), paddedDynamicBytesForValue); const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), Constants.EVM_WORD_WIDTH_IN_BYTES); const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); return encodedValueBuf; @@ -390,13 +390,13 @@ export class Pointer extends DependentDataType { export class Tuple extends MemberDataType { private readonly _tupleSignature: string; - public static matchGrammar(type: string): boolean { + public static matchType(type: string): boolean { return type === 'tuple'; } public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); - if (!Tuple.matchGrammar(dataItem.type)) { + if (!Tuple.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); } this._tupleSignature = this._computeSignatureOfMembers(); @@ -407,18 +407,18 @@ export class Tuple extends MemberDataType { } } -export class SolArray extends MemberDataType { +export class Array extends MemberDataType { private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); private readonly _arraySignature: string; private readonly _elementType: string; - public static matchGrammar(type: string): boolean { - return SolArray._matcher.test(type); + public static matchType(type: string): boolean { + return Array._matcher.test(type); } public constructor(dataItem: DataItem) { // Sanity check - const matches = SolArray._matcher.exec(dataItem.type); + const matches = Array._matcher.exec(dataItem.type); if (matches === null || matches.length !== 3) { throw new Error(`Could not parse array: ${dataItem.type}`); } else if (matches[1] === undefined) { @@ -473,7 +473,7 @@ export class Method extends MemberDataType { this.selector = this._methodSelector = this._computeSelector(); this._returnDataTypes = []; this._returnDataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; - const dummy = new Byte({ type: 'byte', name: 'DUMMY' }); // @TODO TMP + const dummy = new StaticBytes({ type: 'byte', name: 'DUMMY' }); // @TODO TMP _.each(abi.outputs, (dataItem: DataItem) => { this._returnDataTypes.push(this.getFactory().create(dataItem, dummy)); }); @@ -543,24 +543,24 @@ export class EvmDataTypeFactory implements DataTypeFactory { } public mapDataItemToDataType(dataItem: DataItem): DataType { - if (SolArray.matchGrammar(dataItem.type)) { - return new SolArray(dataItem); - } else if (Address.matchGrammar(dataItem.type)) { + if (Array.matchType(dataItem.type)) { + return new Array(dataItem); + } else if (Address.matchType(dataItem.type)) { return new Address(dataItem); - } else if (Bool.matchGrammar(dataItem.type)) { + } else if (Bool.matchType(dataItem.type)) { return new Bool(dataItem); - } else if (Int.matchGrammar(dataItem.type)) { + } else if (Int.matchType(dataItem.type)) { return new Int(dataItem); - } else if (UInt.matchGrammar(dataItem.type)) { + } else if (UInt.matchType(dataItem.type)) { return new UInt(dataItem); - } else if (Byte.matchGrammar(dataItem.type)) { - return new Byte(dataItem); - } else if (Tuple.matchGrammar(dataItem.type)) { + } else if (StaticBytes.matchType(dataItem.type)) { + return new StaticBytes(dataItem); + } else if (Tuple.matchType(dataItem.type)) { return new Tuple(dataItem); - } else if (Bytes.matchGrammar(dataItem.type)) { - return new Bytes(dataItem); - } else if (SolString.matchGrammar(dataItem.type)) { - return new SolString(dataItem); + } else if (DynamicBytes.matchType(dataItem.type)) { + return new DynamicBytes(dataItem); + } else if (String.matchType(dataItem.type)) { + return new String(dataItem); } // @TODO: Implement Fixed/UFixed types throw new Error(`Unrecognized data type: '${dataItem.type}'`); diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts index e99528b060..35eb8d0a98 100644 --- a/packages/utils/test/abi_encoder_test.ts +++ b/packages/utils/test/abi_encoder_test.ts @@ -772,7 +772,7 @@ describe.only('ABI Encoder', () => { it('Fixed size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[2]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const args = [new BigNumber(5), new BigNumber(6)]; // Encode Args and validate result @@ -789,7 +789,7 @@ describe.only('ABI Encoder', () => { it('Dynamic size; Static elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'int[]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const args = [new BigNumber(5), new BigNumber(6)]; // Encode Args and validate result @@ -806,7 +806,7 @@ describe.only('ABI Encoder', () => { it('Fixed size; Dynamic elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[2]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const args = ['Hello', 'world']; // Encode Args and validate result @@ -823,7 +823,7 @@ describe.only('ABI Encoder', () => { it('Dynamic size; Dynamic elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const args = ['Hello', 'world']; // Encode Args and validate result @@ -840,7 +840,7 @@ describe.only('ABI Encoder', () => { it('Dynamic Size; Multidimensional; Dynamic Elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'bytes[][]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const array1 = ['0x01020304', '0x05060708', '0x09101112']; const array2 = ['0x10111213', '0x14151617']; @@ -860,7 +860,7 @@ describe.only('ABI Encoder', () => { it('Dynamic Size; Multidimensional; Static Elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'bytes4[][]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const array1 = ['0x01020304', '0x05060708', '0x09101112']; const array2 = ['0x10111213', '0x14151617']; @@ -880,7 +880,7 @@ describe.only('ABI Encoder', () => { it('Static Size; Multidimensional; Static Elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'bytes4[3][2]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const array1 = ['0x01020304', '0x05060708', '0x09101112']; const array2 = ['0x10111213', '0x14151617', '0x18192021']; @@ -899,7 +899,7 @@ describe.only('ABI Encoder', () => { it('Static Size; Multidimensional; Dynamic Elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'bytes[3][2]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const array1 = ['0x01020304', '0x05060708', '0x09101112']; const array2 = ['0x10111213', '0x14151617', '0x18192021']; @@ -918,7 +918,7 @@ describe.only('ABI Encoder', () => { it('Static size; Too Few Elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[3]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const args = ['Hello', 'world']; // Encode Args and validate result @@ -929,7 +929,7 @@ describe.only('ABI Encoder', () => { it('Static size; Too Many Elements', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'string[1]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const args = ['Hello', 'world']; // Encode Args and validate result @@ -940,7 +940,7 @@ describe.only('ABI Encoder', () => { it('Element Type Mismatch', async () => { // Create DataType object const testDataItem = { name: 'testArray', type: 'uint[]' }; - const dataType = new AbiEncoder.SolArray(testDataItem); + const dataType = new AbiEncoder.Array(testDataItem); // Construct args to be encoded const args = [new BigNumber(1), 'Bad Argument']; // Encode Args and validate result @@ -1563,7 +1563,7 @@ describe.only('ABI Encoder', () => { it('Single Byte (byte)', async () => { // Create DataType object const testDataItem = { name: 'Static Byte', type: 'byte' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0x05'; // Encode Args and validate result @@ -1579,7 +1579,7 @@ describe.only('ABI Encoder', () => { it('Single Byte (bytes1)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes1', type: 'bytes1' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0x05'; // Encode Args and validate result @@ -1595,7 +1595,7 @@ describe.only('ABI Encoder', () => { it('4 Bytes (bytes4)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0x00010203'; // Encode Args and validate result @@ -1611,7 +1611,7 @@ describe.only('ABI Encoder', () => { it('4 Bytes (bytes4); Encoder must pad input', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = '0x1a18'; @@ -1629,7 +1629,7 @@ describe.only('ABI Encoder', () => { it('32 Bytes (bytes32)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0x0001020304050607080911121314151617181920212223242526272829303132'; // Encode Args and validate result @@ -1645,7 +1645,7 @@ describe.only('ABI Encoder', () => { it('32 Bytes (bytes32); Encoder must pad input', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = '0x1a18bf61'; @@ -1663,7 +1663,7 @@ describe.only('ABI Encoder', () => { it('Should throw when pass in too many bytes (bytes4)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0x0102030405'; // Encode Args and validate result @@ -1676,7 +1676,7 @@ describe.only('ABI Encoder', () => { it('Should throw when pass in too many bytes (bytes32)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0x010203040506070809101112131415161718192021222324252627282930313233'; // Encode Args and validate result @@ -1689,7 +1689,7 @@ describe.only('ABI Encoder', () => { it('Should throw when pass in bad hex (no 0x prefix)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0102030405060708091011121314151617181920212223242526272829303132'; // Encode Args and validate result @@ -1700,7 +1700,7 @@ describe.only('ABI Encoder', () => { it('Should throw when pass in bad hex (include a half-byte)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.Byte(testDataItem); + const dataType = new AbiEncoder.StaticBytes(testDataItem); // Construct args to be encoded const args = '0x010'; // Encode Args and validate result @@ -1714,7 +1714,7 @@ describe.only('ABI Encoder', () => { it('Fits into one EVM word', async () => { // Create DataType object const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.Bytes(testDataItem); + const dataType = new AbiEncoder.DynamicBytes(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = '0x1a18bf61'; @@ -1732,7 +1732,7 @@ describe.only('ABI Encoder', () => { it('Spans multiple EVM words', async () => { // Create DataType object const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.Bytes(testDataItem); + const dataType = new AbiEncoder.DynamicBytes(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const bytesLength = 40; @@ -1751,7 +1751,7 @@ describe.only('ABI Encoder', () => { it('Input as Buffer', async () => { // Create DataType object const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.Bytes(testDataItem); + const dataType = new AbiEncoder.DynamicBytes(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = '0x1a18bf61'; @@ -1770,7 +1770,7 @@ describe.only('ABI Encoder', () => { it('Should throw when pass in bad hex (no 0x prefix)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.Bytes(testDataItem); + const dataType = new AbiEncoder.DynamicBytes(testDataItem); // Construct args to be encoded const args = '01'; // Encode Args and validate result @@ -1781,7 +1781,7 @@ describe.only('ABI Encoder', () => { it('Should throw when pass in bad hex (include a half-byte)', async () => { // Create DataType object const testDataItem = { name: 'Static Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.Bytes(testDataItem); + const dataType = new AbiEncoder.DynamicBytes(testDataItem); // Construct args to be encoded const args = '0x010'; // Encode Args and validate result @@ -1795,7 +1795,7 @@ describe.only('ABI Encoder', () => { it('Fits into one EVM word', async () => { // Create DataType object const testDataItem = { name: 'String', type: 'string' }; - const dataType = new AbiEncoder.SolString(testDataItem); + const dataType = new AbiEncoder.String(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = 'five'; @@ -1813,7 +1813,7 @@ describe.only('ABI Encoder', () => { it('Spans multiple EVM words', async () => { // Create DataType object const testDataItem = { name: 'String', type: 'string' }; - const dataType = new AbiEncoder.SolString(testDataItem); + const dataType = new AbiEncoder.String(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const bytesLength = 40; @@ -1832,7 +1832,7 @@ describe.only('ABI Encoder', () => { it('String that begins with 0x prefix', async () => { // Create DataType object const testDataItem = { name: 'String', type: 'string' }; - const dataType = new AbiEncoder.SolString(testDataItem); + const dataType = new AbiEncoder.String(testDataItem); // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const strLength = 40; From e6ab6f38bacdec90c960ff1db4781d161b1f4103 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 12:58:49 -0800 Subject: [PATCH 145/230] Split EVM data types and factory into separate files --- packages/utils/src/abi_encoder/calldata.ts | 1 - packages/utils/src/abi_encoder/data_type.ts | 11 +- .../src/abi_encoder/evm_data_type_factory.ts | 124 ++++ .../utils/src/abi_encoder/evm_data_types.ts | 584 ------------------ .../src/abi_encoder/evm_data_types/address.ts | 51 ++ .../src/abi_encoder/evm_data_types/array.ts | 55 ++ .../src/abi_encoder/evm_data_types/bool.ts | 50 ++ .../evm_data_types/dynamic_bytes.ts | 58 ++ .../src/abi_encoder/evm_data_types/index.ts | 11 + .../src/abi_encoder/evm_data_types/int.ts | 33 + .../src/abi_encoder/evm_data_types/method.ts | 90 +++ .../src/abi_encoder/evm_data_types/number.ts | 100 +++ .../src/abi_encoder/evm_data_types/pointer.ts | 15 + .../evm_data_types/static_bytes.ts | 68 ++ .../src/abi_encoder/evm_data_types/string.ts | 47 ++ .../src/abi_encoder/evm_data_types/tuple.ts | 23 + .../src/abi_encoder/evm_data_types/uint.ts | 33 + packages/utils/src/abi_encoder/index.ts | 2 +- 18 files changed, 769 insertions(+), 587 deletions(-) create mode 100644 packages/utils/src/abi_encoder/evm_data_type_factory.ts delete mode 100644 packages/utils/src/abi_encoder/evm_data_types.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/address.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/array.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/bool.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/index.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/int.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/method.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/number.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/pointer.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/string.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/tuple.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types/uint.ts diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts index 994b0fb81f..d108ef0a70 100644 --- a/packages/utils/src/abi_encoder/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata.ts @@ -1,4 +1,3 @@ - import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index ce7b11ef6c..9890619e53 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -22,6 +22,12 @@ export interface DataTypeFactory { mapDataItemToDataType: (dataItem: DataItem) => DataType; } +export interface DataTypeStaticInterface { + matchType: (type: string) => boolean; + encodeValue: (value: any) => Buffer; + decodeValue: (rawCalldata: RawCalldata) => any; +} + export abstract class DataType { private static readonly _DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false }; private static readonly _DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false }; @@ -250,7 +256,10 @@ export abstract class MemberDataType extends DataType { if (this._isArray && this._arrayLength === undefined) { [members] = this._createMembersWithLength(this.getDataItem(), value.length); - const lenBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), Constants.EVM_WORD_WIDTH_IN_BYTES); + const lenBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), + Constants.EVM_WORD_WIDTH_IN_BYTES, + ); methodBlock.setHeader(lenBuf); } diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts new file mode 100644 index 0000000000..0f8dfb4a31 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -0,0 +1,124 @@ +/* tslint:disable prefer-function-over-method */ +/* tslint:disable max-classes-per-file */ +/* tslint:disable no-construct */ +import { DataItem, MethodAbi } from 'ethereum-types'; + +import { DataType, DataTypeFactory } from './data_type'; +import * as Impl from './evm_data_types'; + +export class Address extends Impl.Address { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class Bool extends Impl.Bool { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class Int extends Impl.Int { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class UInt extends Impl.UInt { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class StaticBytes extends Impl.StaticBytes { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class DynamicBytes extends Impl.DynamicBytes { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class String extends Impl.String { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class Pointer extends Impl.Pointer { + public constructor(destDataType: DataType, parentDataType: DataType) { + super(destDataType, parentDataType, EvmDataTypeFactory.getInstance()); + } +} + +export class Tuple extends Impl.Tuple { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class Array extends Impl.Array { + public constructor(dataItem: DataItem) { + super(dataItem, EvmDataTypeFactory.getInstance()); + } +} + +export class Method extends Impl.Method { + public constructor(abi: MethodAbi) { + super(abi, EvmDataTypeFactory.getInstance()); + } +} + +export class EvmDataTypeFactory implements DataTypeFactory { + private static _instance: DataTypeFactory; + + public static getInstance(): DataTypeFactory { + if (!EvmDataTypeFactory._instance) { + EvmDataTypeFactory._instance = new EvmDataTypeFactory(); + } + return EvmDataTypeFactory._instance; + } + + public mapDataItemToDataType(dataItem: DataItem): DataType { + if (Array.matchType(dataItem.type)) { + return new Array(dataItem); + } else if (Address.matchType(dataItem.type)) { + return new Address(dataItem); + } else if (Bool.matchType(dataItem.type)) { + return new Bool(dataItem); + } else if (Int.matchType(dataItem.type)) { + return new Int(dataItem); + } else if (UInt.matchType(dataItem.type)) { + return new UInt(dataItem); + } else if (StaticBytes.matchType(dataItem.type)) { + return new StaticBytes(dataItem); + } else if (Tuple.matchType(dataItem.type)) { + return new Tuple(dataItem); + } else if (DynamicBytes.matchType(dataItem.type)) { + return new DynamicBytes(dataItem); + } else if (String.matchType(dataItem.type)) { + return new String(dataItem); + } + // @TODO: Implement Fixed/UFixed types + throw new Error(`Unrecognized data type: '${dataItem.type}'`); + } + + public create(dataItem: DataItem, parentDataType?: DataType): DataType { + const dataType = this.mapDataItemToDataType(dataItem); + if (dataType.isStatic()) { + return dataType; + } + + if (parentDataType === undefined) { + // @Todo -- will this work for return values? + throw new Error(`Trying to create a pointer`); + } + const pointer = new Pointer(dataType, parentDataType); + return pointer; + } + + private constructor() { } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts deleted file mode 100644 index 7ad7f403e7..0000000000 --- a/packages/utils/src/abi_encoder/evm_data_types.ts +++ /dev/null @@ -1,584 +0,0 @@ -import { DataItem, MethodAbi } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { BigNumber } from '../configured_bignumber'; - -import { DecodingRules, EncodingRules, RawCalldata } from './calldata'; -import * as Constants from './constants'; -import { DataType, DataTypeFactory, DependentDataType, MemberDataType, PayloadDataType } from './data_type'; - -export interface DataTypeStaticInterface { - matchType: (type: string) => boolean; - encodeValue: (value: any) => Buffer; - decodeValue: (rawCalldata: RawCalldata) => any; -} - -export class Address extends PayloadDataType { - public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; - public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = 'Address must be 20 bytes'; - private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - private static readonly _ADDRESS_SIZE_IN_BYTES = 20; - private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - Address._ADDRESS_SIZE_IN_BYTES; - - public static matchType(type: string): boolean { - return type === 'address'; - } - - public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Address._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Address.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`); - } - } - - public getSignature(): string { - return 'address'; - } - - public encodeValue(value: string): Buffer { - if (!value.startsWith('0x')) { - throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); - } - const valueAsBuffer = ethUtil.toBuffer(value); - if (valueAsBuffer.byteLength !== Address._ADDRESS_SIZE_IN_BYTES) { - throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); - } - const encodedValueBuf = ethUtil.setLengthLeft(valueAsBuffer, Constants.EVM_WORD_WIDTH_IN_BYTES); - return encodedValueBuf; - } - - public decodeValue(calldata: RawCalldata): string { - const paddedValueBuf = calldata.popWord(); - const valueBuf = paddedValueBuf.slice(Address._DECODED_ADDRESS_OFFSET_IN_BYTES); - const value = ethUtil.bufferToHex(valueBuf); - return value; - } -} - -export class Bool extends PayloadDataType { - private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - - public static matchType(type: string): boolean { - return type === 'bool'; - } - - public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), Bool._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Bool.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`); - } - } - - public getSignature(): string { - return 'bool'; - } - - public encodeValue(value: boolean): Buffer { - const encodedValue = value ? '0x1' : '0x0'; - const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), Constants.EVM_WORD_WIDTH_IN_BYTES); - return encodedValueBuf; - } - - public decodeValue(calldata: RawCalldata): boolean { - const valueBuf = calldata.popWord(); - const valueHex = ethUtil.bufferToHex(valueBuf); - const valueNumber = new BigNumber(valueHex, 16); - if (!(valueNumber.equals(0) || valueNumber.equals(1))) { - throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`); - } - /* tslint:disable boolean-naming */ - const value: boolean = valueNumber.equals(0) ? false : true; - /* tslint:enable boolean-naming */ - return value; - } -} - -abstract class Number extends PayloadDataType { - private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - private static readonly _MAX_WIDTH: number = 256; - private static readonly _DEFAULT_WIDTH: number = Number._MAX_WIDTH; - protected _width: number; - - constructor(dataItem: DataItem, matcher: RegExp) { - super(dataItem, EvmDataTypeFactory.getInstance(), Number._SIZE_KNOWN_AT_COMPILE_TIME); - const matches = matcher.exec(dataItem.type); - if (matches === null) { - throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`); - } - this._width = (matches !== null && matches.length === 2 && matches[1] !== undefined) ? - parseInt(matches[1], Constants.DEC_BASE) : - this._width = Number._DEFAULT_WIDTH; - } - - public encodeValue(value_: BigNumber | string | number): Buffer { - const value = new BigNumber(value_, 10); - if (value.greaterThan(this.getMaxValue())) { - throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`); - } else if (value.lessThan(this.getMinValue())) { - throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`); - } - - let valueBuf: Buffer; - if (value.greaterThanOrEqualTo(0)) { - valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(Constants.HEX_BASE)}`), Constants.EVM_WORD_WIDTH_IN_BYTES); - } else { - // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. - // Step 1/3: Convert value to positive binary string - const binBase = 2; - const valueBin = value.times(-1).toString(binBase); - - // Step 2/3: Invert binary value - let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length); - _.each(valueBin, (bit: string) => { - invertedValueBin += bit === '1' ? '0' : '1'; - }); - const invertedValue = new BigNumber(invertedValueBin, binBase); - - // Step 3/3: Add 1 to inverted value - // The result is the two's-complement represent of the input value. - const negativeValue = invertedValue.plus(1); - - // Convert the negated value to a hex string - valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${negativeValue.toString(Constants.HEX_BASE)}`), Constants.EVM_WORD_WIDTH_IN_BYTES); - } - - return valueBuf; - } - - public decodeValue(calldata: RawCalldata): BigNumber { - const paddedValueBuf = calldata.popWord(); - const paddedValueHex = ethUtil.bufferToHex(paddedValueBuf); - let value = new BigNumber(paddedValueHex, 16); - if (this instanceof Int) { - // Check if we're negative - const valueBin = value.toString(Constants.BIN_BASE); - if (valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1')) { - // Negative - // Step 1/3: Invert binary value - let invertedValueBin = ''; - _.each(valueBin, (bit: string) => { - invertedValueBin += bit === '1' ? '0' : '1'; - }); - const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); - - // Step 2/3: Add 1 to inverted value - // The result is the two's-complement represent of the input value. - const positiveValue = invertedValue.plus(1); - - // Step 3/3: Invert positive value - const negativeValue = positiveValue.times(-1); - value = negativeValue; - } - } - - return value; - } - - public abstract getMaxValue(): BigNumber; - public abstract getMinValue(): BigNumber; -} - -export class Int extends Number { - private static readonly _matcher = RegExp( - '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', - ); - - public static matchType(type: string): boolean { - return Int._matcher.test(type); - } - - public constructor(dataItem: DataItem) { - super(dataItem, Int._matcher); - } - - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this._width - 1).sub(1); - } - - public getMinValue(): BigNumber { - return new BigNumber(2).toPower(this._width - 1).times(-1); - } - - public getSignature(): string { - return `int${this._width}`; - } -} - -export class UInt extends Number { - private static readonly _matcher = RegExp( - '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', - ); - - public static matchType(type: string): boolean { - return UInt._matcher.test(type); - } - - public constructor(dataItem: DataItem) { - super(dataItem, UInt._matcher); - } - - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this._width).sub(1); - } - - public getMinValue(): BigNumber { - return new BigNumber(0); - } - - public getSignature(): string { - return `uint${this._width}`; - } -} - -export class StaticBytes extends PayloadDataType { - private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - private static readonly _matcher = RegExp( - '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', - ); - - private static readonly _DEFAULT_WIDTH = 1; - private readonly _width: number; - - public static matchType(type: string): boolean { - return StaticBytes._matcher.test(type); - } - - public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), StaticBytes._SIZE_KNOWN_AT_COMPILE_TIME); - const matches = StaticBytes._matcher.exec(dataItem.type); - if (!StaticBytes.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`); - } - this._width = (matches !== null && matches.length === 3 && matches[2] !== undefined) ? parseInt(matches[2], Constants.DEC_BASE) : StaticBytes._DEFAULT_WIDTH; - } - - public getSignature(): string { - // Note that `byte` reduces to `bytes1` - return `bytes${this._width}`; - } - - public encodeValue(value: string | Buffer): Buffer { - // Sanity check if string - if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); - } - // Convert value into a buffer and do bounds checking - const valueBuf = ethUtil.toBuffer(value); - if (valueBuf.byteLength > this._width) { - throw new Error( - `Tried to assign ${value} (${ - valueBuf.byteLength - } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, - ); - } else if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - - // Store value as hex - const evmWordWidth = 32; - const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); - return paddedValue; - } - - public decodeValue(calldata: RawCalldata): string { - const paddedValueBuf = calldata.popWord(); - const valueBuf = paddedValueBuf.slice(0, this._width); - const value = ethUtil.bufferToHex(valueBuf); - return value; - } -} - -export class DynamicBytes extends PayloadDataType { - private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; - - public static matchType(type: string): boolean { - return type === 'bytes'; - } - - public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME); - if (!DynamicBytes.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate DynamicBytes with bad input: ${dataItem}`); - } - } - - public encodeValue(value: string | Buffer): Buffer { - if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '${value}'`); - } - const valueBuf = ethUtil.toBuffer(value); - if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - - const wordsForValue = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; - const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedDynamicBytesForValue); - const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), Constants.EVM_WORD_WIDTH_IN_BYTES); - const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); - return encodedValueBuf; - } - - public decodeValue(calldata: RawCalldata): string { - const lengthBuf = calldata.popWord(); - const lengthHex = ethUtil.bufferToHex(lengthBuf); - const length = parseInt(lengthHex, Constants.HEX_BASE); - const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedValueBuf = calldata.popWords(wordsForValue); - const valueBuf = paddedValueBuf.slice(0, length); - const decodedValue = ethUtil.bufferToHex(valueBuf); - return decodedValue; - } - - public getSignature(): string { - return 'bytes'; - } -} - -export class String extends PayloadDataType { - private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; - - public static matchType(type: string): boolean { - return type === 'string'; - } - - public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance(), String._SIZE_KNOWN_AT_COMPILE_TIME); - if (!String.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate String with bad input: ${dataItem}`); - } - } - - public encodeValue(value: string): Buffer { - const wordsForValue = Math.ceil(value.length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; - const valueBuf = ethUtil.setLengthRight(new Buffer(value), paddedDynamicBytesForValue); - const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), Constants.EVM_WORD_WIDTH_IN_BYTES); - const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); - return encodedValueBuf; - } - - public decodeValue(calldata: RawCalldata): string { - const lengthBuf = calldata.popWord(); - const lengthHex = ethUtil.bufferToHex(lengthBuf); - const length = parseInt(lengthHex, Constants.HEX_BASE); - const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedValueBuf = calldata.popWords(wordsForValue); - const valueBuf = paddedValueBuf.slice(0, length); - const value = valueBuf.toString('ascii'); - return value; - } - - public getSignature(): string { - return 'string'; - } -} - -export class Pointer extends DependentDataType { - constructor(destDataType: DataType, parentDataType: DataType) { - const destDataItem = destDataType.getDataItem(); - const dataItem: DataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` }; - super(dataItem, EvmDataTypeFactory.getInstance(), destDataType, parentDataType); - } - - public getSignature(): string { - return this._dependency.getSignature(); - } -} - -export class Tuple extends MemberDataType { - private readonly _tupleSignature: string; - - public static matchType(type: string): boolean { - return type === 'tuple'; - } - - public constructor(dataItem: DataItem) { - super(dataItem, EvmDataTypeFactory.getInstance()); - if (!Tuple.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); - } - this._tupleSignature = this._computeSignatureOfMembers(); - } - - public getSignature(): string { - return this._tupleSignature; - } -} - -export class Array extends MemberDataType { - private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); - private readonly _arraySignature: string; - private readonly _elementType: string; - - public static matchType(type: string): boolean { - return Array._matcher.test(type); - } - - public constructor(dataItem: DataItem) { - // Sanity check - const matches = Array._matcher.exec(dataItem.type); - if (matches === null || matches.length !== 3) { - throw new Error(`Could not parse array: ${dataItem.type}`); - } else if (matches[1] === undefined) { - throw new Error(`Could not parse array type: ${dataItem.type}`); - } else if (matches[2] === undefined) { - throw new Error(`Could not parse array length: ${dataItem.type}`); - } - - const isArray = true; - const arrayElementType = matches[1]; - const arrayLength = matches[2] === '' ? undefined : parseInt(matches[2], Constants.DEC_BASE); - super(dataItem, EvmDataTypeFactory.getInstance(), isArray, arrayLength, arrayElementType); - this._elementType = arrayElementType; - this._arraySignature = this._computeSignature(); - } - - public getSignature(): string { - return this._arraySignature; - } - - private _computeSignature(): string { - const dataItem: DataItem = { - type: this._elementType, - name: 'N/A', - }; - const components = this.getDataItem().components; - if (components !== undefined) { - dataItem.components = components; - } - const elementDataType = this.getFactory().mapDataItemToDataType(dataItem); - const type = elementDataType.getSignature(); - if (this._arrayLength === undefined) { - return `${type}[]`; - } else { - return `${type}[${this._arrayLength}]`; - } - } -} - -export class Method extends MemberDataType { - // TMP - public selector: string; - - private readonly _methodSignature: string; - private readonly _methodSelector: string; - private readonly _returnDataTypes: DataType[]; - private readonly _returnDataItem: DataItem; - - public constructor(abi: MethodAbi) { - super({ type: 'method', name: abi.name, components: abi.inputs }, EvmDataTypeFactory.getInstance()); - this._methodSignature = this._computeSignature(); - this.selector = this._methodSelector = this._computeSelector(); - this._returnDataTypes = []; - this._returnDataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; - const dummy = new StaticBytes({ type: 'byte', name: 'DUMMY' }); // @TODO TMP - _.each(abi.outputs, (dataItem: DataItem) => { - this._returnDataTypes.push(this.getFactory().create(dataItem, dummy)); - }); - } - - public encode(value: any, rules?: EncodingRules): string { - const calldata = super.encode(value, rules, this.selector); - return calldata; - } - - public decode(calldata: string, rules?: DecodingRules): any[] | object { - if (!calldata.startsWith(this.selector)) { - throw new Error( - `Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`, - ); - } - const hasSelector = true; - const value = super.decode(calldata, rules, hasSelector); - return value; - } - - public encodeReturnValues(value: any, rules?: EncodingRules): string { - const returnDataType = new Tuple(this._returnDataItem); - const returndata = returnDataType.encode(value, rules); - return returndata; - } - - public decodeReturnValues(returndata: string, rules?: DecodingRules): any { - const returnValues: any[] = []; - const rules_: DecodingRules = rules ? rules : { structsAsObjects: false }; - const rawReturnData = new RawCalldata(returndata, false); - _.each(this._returnDataTypes, (dataType: DataType) => { - returnValues.push(dataType.generateValue(rawReturnData, rules_)); - }); - return returnValues; - } - - public getSignature(): string { - return this._methodSignature; - } - - public getSelector(): string { - return this._methodSelector; - } - - private _computeSignature(): string { - const memberSignature = this._computeSignatureOfMembers(); - const methodSignature = `${this.getDataItem().name}${memberSignature}`; - return methodSignature; - } - - private _computeSelector(): string { - const signature = this._computeSignature(); - const selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(signature).slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES))); - return selector; - } -} - -export class EvmDataTypeFactory implements DataTypeFactory { - private static _instance: DataTypeFactory; - - public static getInstance(): DataTypeFactory { - if (!EvmDataTypeFactory._instance) { - EvmDataTypeFactory._instance = new EvmDataTypeFactory(); - } - return EvmDataTypeFactory._instance; - } - - public mapDataItemToDataType(dataItem: DataItem): DataType { - if (Array.matchType(dataItem.type)) { - return new Array(dataItem); - } else if (Address.matchType(dataItem.type)) { - return new Address(dataItem); - } else if (Bool.matchType(dataItem.type)) { - return new Bool(dataItem); - } else if (Int.matchType(dataItem.type)) { - return new Int(dataItem); - } else if (UInt.matchType(dataItem.type)) { - return new UInt(dataItem); - } else if (StaticBytes.matchType(dataItem.type)) { - return new StaticBytes(dataItem); - } else if (Tuple.matchType(dataItem.type)) { - return new Tuple(dataItem); - } else if (DynamicBytes.matchType(dataItem.type)) { - return new DynamicBytes(dataItem); - } else if (String.matchType(dataItem.type)) { - return new String(dataItem); - } - // @TODO: Implement Fixed/UFixed types - throw new Error(`Unrecognized data type: '${dataItem.type}'`); - } - - public create(dataItem: DataItem, parentDataType?: DataType): DataType { - const dataType = this.mapDataItemToDataType(dataItem); - if (dataType.isStatic()) { - return dataType; - } - - if (parentDataType === undefined) { - // @Todo -- will this work for return values? - throw new Error(`Trying to create a pointer`); - } - const pointer = new Pointer(dataType, parentDataType); - return pointer; - } - - private constructor() { } -} diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts new file mode 100644 index 0000000000..4bd992cab5 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -0,0 +1,51 @@ +/* tslint:disable prefer-function-over-method */ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DataTypeFactory, PayloadDataType } from '../data_type'; + +export class Address extends PayloadDataType { + public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; + public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = 'Address must be 20 bytes'; + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _ADDRESS_SIZE_IN_BYTES = 20; + private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - + Address._ADDRESS_SIZE_IN_BYTES; + + public static matchType(type: string): boolean { + return type === 'address'; + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, dataTypeFactory, Address._SIZE_KNOWN_AT_COMPILE_TIME); + if (!Address.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`); + } + } + + public getSignature(): string { + return 'address'; + } + + public encodeValue(value: string): Buffer { + if (!value.startsWith('0x')) { + throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); + } + const valueAsBuffer = ethUtil.toBuffer(value); + if (valueAsBuffer.byteLength !== Address._ADDRESS_SIZE_IN_BYTES) { + throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); + } + const encodedValueBuf = ethUtil.setLengthLeft(valueAsBuffer, Constants.EVM_WORD_WIDTH_IN_BYTES); + return encodedValueBuf; + } + + public decodeValue(calldata: RawCalldata): string { + const paddedValueBuf = calldata.popWord(); + const valueBuf = paddedValueBuf.slice(Address._DECODED_ADDRESS_OFFSET_IN_BYTES); + const value = ethUtil.bufferToHex(valueBuf); + return value; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts new file mode 100644 index 0000000000..707af7c7eb --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -0,0 +1,55 @@ +import { DataItem } from 'ethereum-types'; + +import * as Constants from '../constants'; +import { DataTypeFactory, MemberDataType } from '../data_type'; + +export class Array extends MemberDataType { + private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); + private readonly _arraySignature: string; + private readonly _elementType: string; + + public static matchType(type: string): boolean { + return Array._matcher.test(type); + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + // Sanity check + const matches = Array._matcher.exec(dataItem.type); + if (matches === null || matches.length !== 3) { + throw new Error(`Could not parse array: ${dataItem.type}`); + } else if (matches[1] === undefined) { + throw new Error(`Could not parse array type: ${dataItem.type}`); + } else if (matches[2] === undefined) { + throw new Error(`Could not parse array length: ${dataItem.type}`); + } + + const isArray = true; + const arrayElementType = matches[1]; + const arrayLength = matches[2] === '' ? undefined : parseInt(matches[2], Constants.DEC_BASE); + super(dataItem, dataTypeFactory, isArray, arrayLength, arrayElementType); + this._elementType = arrayElementType; + this._arraySignature = this._computeSignature(); + } + + public getSignature(): string { + return this._arraySignature; + } + + private _computeSignature(): string { + const dataItem: DataItem = { + type: this._elementType, + name: 'N/A', + }; + const components = this.getDataItem().components; + if (components !== undefined) { + dataItem.components = components; + } + const elementDataType = this.getFactory().mapDataItemToDataType(dataItem); + const type = elementDataType.getSignature(); + if (this._arrayLength === undefined) { + return `${type}[]`; + } else { + return `${type}[${this._arrayLength}]`; + } + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts new file mode 100644 index 0000000000..aee2727c7a --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -0,0 +1,50 @@ +/* tslint:disable prefer-function-over-method */ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { BigNumber } from '../../configured_bignumber'; +import { RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DataTypeFactory, PayloadDataType } from '../data_type'; + +export class Bool extends PayloadDataType { + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + + public static matchType(type: string): boolean { + return type === 'bool'; + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, dataTypeFactory, Bool._SIZE_KNOWN_AT_COMPILE_TIME); + if (!Bool.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`); + } + } + + public getSignature(): string { + return 'bool'; + } + + public encodeValue(value: boolean): Buffer { + const encodedValue = value ? '0x1' : '0x0'; + const encodedValueBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(encodedValue), + Constants.EVM_WORD_WIDTH_IN_BYTES, + ); + return encodedValueBuf; + } + + public decodeValue(calldata: RawCalldata): boolean { + const valueBuf = calldata.popWord(); + const valueHex = ethUtil.bufferToHex(valueBuf); + const valueNumber = new BigNumber(valueHex, Constants.HEX_BASE); + if (!(valueNumber.equals(0) || valueNumber.equals(1))) { + throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`); + } + /* tslint:disable boolean-naming */ + const value: boolean = valueNumber.equals(0) ? false : true; + /* tslint:enable boolean-naming */ + return value; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts new file mode 100644 index 0000000000..51165881a8 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -0,0 +1,58 @@ +/* tslint:disable prefer-function-over-method */ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DataTypeFactory, PayloadDataType } from '../data_type'; + +export class DynamicBytes extends PayloadDataType { + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; + + public static matchType(type: string): boolean { + return type === 'bytes'; + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, dataTypeFactory, DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME); + if (!DynamicBytes.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate DynamicBytes with bad input: ${dataItem}`); + } + } + + public encodeValue(value: string | Buffer): Buffer { + if (typeof value === 'string' && !value.startsWith('0x')) { + throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '${value}'`); + } + const valueBuf = ethUtil.toBuffer(value); + if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + const wordsForValue = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); + const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; + const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedDynamicBytesForValue); + const paddedLengthBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(valueBuf.byteLength), + Constants.EVM_WORD_WIDTH_IN_BYTES, + ); + const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); + return encodedValueBuf; + } + + public decodeValue(calldata: RawCalldata): string { + const lengthBuf = calldata.popWord(); + const lengthHex = ethUtil.bufferToHex(lengthBuf); + const length = parseInt(lengthHex, Constants.HEX_BASE); + const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const paddedValueBuf = calldata.popWords(wordsForValue); + const valueBuf = paddedValueBuf.slice(0, length); + const decodedValue = ethUtil.bufferToHex(valueBuf); + return decodedValue; + } + + public getSignature(): string { + return 'bytes'; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/index.ts b/packages/utils/src/abi_encoder/evm_data_types/index.ts new file mode 100644 index 0000000000..fc0edabf1c --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/index.ts @@ -0,0 +1,11 @@ +export * from './address'; +export * from './bool'; +export * from './int'; +export * from './uint'; +export * from './static_bytes'; +export * from './dynamic_bytes'; +export * from './string'; +export * from './pointer'; +export * from './tuple'; +export * from './array'; +export * from './method'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts new file mode 100644 index 0000000000..ba5b4cac99 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -0,0 +1,33 @@ +/* tslint:disable prefer-function-over-method */ +import { DataItem } from 'ethereum-types'; + +import { BigNumber } from '../../configured_bignumber'; +import { DataTypeFactory } from '../data_type'; + +import { Number } from './number'; + +export class Int extends Number { + private static readonly _matcher = RegExp( + '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + public static matchType(type: string): boolean { + return Int._matcher.test(type); + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, Int._matcher, dataTypeFactory); + } + + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this._width - 1).sub(1); + } + + public getMinValue(): BigNumber { + return new BigNumber(2).toPower(this._width - 1).times(-1); + } + + public getSignature(): string { + return `int${this._width}`; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts new file mode 100644 index 0000000000..e8e717bf1b --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -0,0 +1,90 @@ +import { DataItem, MethodAbi } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { DecodingRules, EncodingRules, RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DataType, DataTypeFactory, MemberDataType } from '../data_type'; + +import { StaticBytes } from './static_bytes'; +import { Tuple } from './tuple'; + +export class Method extends MemberDataType { + // TMP + public selector: string; + + private readonly _methodSignature: string; + private readonly _methodSelector: string; + private readonly _returnDataTypes: DataType[]; + private readonly _returnDataItem: DataItem; + + public constructor(abi: MethodAbi, dataTypeFactory: DataTypeFactory) { + super({ type: 'method', name: abi.name, components: abi.inputs }, dataTypeFactory); + this._methodSignature = this._computeSignature(); + this.selector = this._methodSelector = this._computeSelector(); + this._returnDataTypes = []; + this._returnDataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; + const dummy = new StaticBytes({ type: 'byte', name: 'DUMMY' }, dataTypeFactory); // @TODO TMP + _.each(abi.outputs, (dataItem: DataItem) => { + this._returnDataTypes.push(this.getFactory().create(dataItem, dummy)); + }); + } + + public encode(value: any, rules?: EncodingRules): string { + const calldata = super.encode(value, rules, this.selector); + return calldata; + } + + public decode(calldata: string, rules?: DecodingRules): any[] | object { + if (!calldata.startsWith(this.selector)) { + throw new Error( + `Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`, + ); + } + const hasSelector = true; + const value = super.decode(calldata, rules, hasSelector); + return value; + } + + public encodeReturnValues(value: any, rules?: EncodingRules): string { + const returnDataType = new Tuple(this._returnDataItem, this.getFactory()); + const returndata = returnDataType.encode(value, rules); + return returndata; + } + + public decodeReturnValues(returndata: string, rules?: DecodingRules): any { + const returnValues: any[] = []; + const rules_: DecodingRules = rules ? rules : { structsAsObjects: false }; + const rawReturnData = new RawCalldata(returndata, false); + _.each(this._returnDataTypes, (dataType: DataType) => { + returnValues.push(dataType.generateValue(rawReturnData, rules_)); + }); + return returnValues; + } + + public getSignature(): string { + return this._methodSignature; + } + + public getSelector(): string { + return this._methodSelector; + } + + private _computeSignature(): string { + const memberSignature = this._computeSignatureOfMembers(); + const methodSignature = `${this.getDataItem().name}${memberSignature}`; + return methodSignature; + } + + private _computeSelector(): string { + const signature = this._computeSignature(); + const selector = ethUtil.bufferToHex( + ethUtil.toBuffer( + ethUtil + .sha3(signature) + .slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES), + ), + ); + return selector; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/number.ts b/packages/utils/src/abi_encoder/evm_data_types/number.ts new file mode 100644 index 0000000000..17201362ee --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/number.ts @@ -0,0 +1,100 @@ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { BigNumber } from '../../configured_bignumber'; +import { RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DataTypeFactory, PayloadDataType } from '../data_type'; + +export abstract class Number extends PayloadDataType { + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _MAX_WIDTH: number = 256; + private static readonly _DEFAULT_WIDTH: number = Number._MAX_WIDTH; + protected _width: number; + + constructor(dataItem: DataItem, matcher: RegExp, dataTypeFactory: DataTypeFactory) { + super(dataItem, dataTypeFactory, Number._SIZE_KNOWN_AT_COMPILE_TIME); + const matches = matcher.exec(dataItem.type); + if (matches === null) { + throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`); + } + this._width = + matches !== null && matches.length === 2 && matches[1] !== undefined + ? parseInt(matches[1], Constants.DEC_BASE) + : (this._width = Number._DEFAULT_WIDTH); + } + + public encodeValue(value_: BigNumber | string | number): Buffer { + const value = new BigNumber(value_, 10); + if (value.greaterThan(this.getMaxValue())) { + throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`); + } else if (value.lessThan(this.getMinValue())) { + throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`); + } + + let valueBuf: Buffer; + if (value.greaterThanOrEqualTo(0)) { + valueBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(`0x${value.toString(Constants.HEX_BASE)}`), + Constants.EVM_WORD_WIDTH_IN_BYTES, + ); + } else { + // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. + // Step 1/3: Convert value to positive binary string + const binBase = 2; + const valueBin = value.times(-1).toString(binBase); + + // Step 2/3: Invert binary value + let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length); + _.each(valueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, binBase); + + // Step 3/3: Add 1 to inverted value + // The result is the two's-complement represent of the input value. + const negativeValue = invertedValue.plus(1); + + // Convert the negated value to a hex string + valueBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(`0x${negativeValue.toString(Constants.HEX_BASE)}`), + Constants.EVM_WORD_WIDTH_IN_BYTES, + ); + } + + return valueBuf; + } + + public decodeValue(calldata: RawCalldata): BigNumber { + const paddedValueBuf = calldata.popWord(); + const paddedValueHex = ethUtil.bufferToHex(paddedValueBuf); + let value = new BigNumber(paddedValueHex, 16); + if (this.getMinValue().lessThan(0)) { + // Check if we're negative + const valueBin = value.toString(Constants.BIN_BASE); + if (valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1')) { + // Negative + // Step 1/3: Invert binary value + let invertedValueBin = ''; + _.each(valueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); + + // Step 2/3: Add 1 to inverted value + // The result is the two's-complement represent of the input value. + const positiveValue = invertedValue.plus(1); + + // Step 3/3: Invert positive value + const negativeValue = positiveValue.times(-1); + value = negativeValue; + } + } + + return value; + } + + public abstract getMaxValue(): BigNumber; + public abstract getMinValue(): BigNumber; +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts new file mode 100644 index 0000000000..e0bd3509c4 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts @@ -0,0 +1,15 @@ +import { DataItem } from 'ethereum-types'; + +import { DataType, DataTypeFactory, DependentDataType } from '../data_type'; + +export class Pointer extends DependentDataType { + constructor(destDataType: DataType, parentDataType: DataType, dataTypeFactory: DataTypeFactory) { + const destDataItem = destDataType.getDataItem(); + const dataItem: DataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` }; + super(dataItem, dataTypeFactory, destDataType, parentDataType); + } + + public getSignature(): string { + return this._dependency.getSignature(); + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts new file mode 100644 index 0000000000..309dca2348 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -0,0 +1,68 @@ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DataTypeFactory, PayloadDataType } from '../data_type'; + +export class StaticBytes extends PayloadDataType { + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _matcher = RegExp( + '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', + ); + + private static readonly _DEFAULT_WIDTH = 1; + private readonly _width: number; + + public static matchType(type: string): boolean { + return StaticBytes._matcher.test(type); + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, dataTypeFactory, StaticBytes._SIZE_KNOWN_AT_COMPILE_TIME); + const matches = StaticBytes._matcher.exec(dataItem.type); + if (!StaticBytes.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`); + } + this._width = + matches !== null && matches.length === 3 && matches[2] !== undefined + ? parseInt(matches[2], Constants.DEC_BASE) + : StaticBytes._DEFAULT_WIDTH; + } + + public getSignature(): string { + // Note that `byte` reduces to `bytes1` + return `bytes${this._width}`; + } + + public encodeValue(value: string | Buffer): Buffer { + // Sanity check if string + if (typeof value === 'string' && !value.startsWith('0x')) { + throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); + } + // Convert value into a buffer and do bounds checking + const valueBuf = ethUtil.toBuffer(value); + if (valueBuf.byteLength > this._width) { + throw new Error( + `Tried to assign ${value} (${ + valueBuf.byteLength + } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, + ); + } else if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + + // Store value as hex + const evmWordWidth = 32; + const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); + return paddedValue; + } + + public decodeValue(calldata: RawCalldata): string { + const paddedValueBuf = calldata.popWord(); + const valueBuf = paddedValueBuf.slice(0, this._width); + const value = ethUtil.bufferToHex(valueBuf); + return value; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts new file mode 100644 index 0000000000..96b36e7350 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -0,0 +1,47 @@ +/* tslint:disable prefer-function-over-method */ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DataTypeFactory, PayloadDataType } from '../data_type'; + +export class String extends PayloadDataType { + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; + + public static matchType(type: string): boolean { + return type === 'string'; + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, dataTypeFactory, String._SIZE_KNOWN_AT_COMPILE_TIME); + if (!String.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate String with bad input: ${dataItem}`); + } + } + + public encodeValue(value: string): Buffer { + const wordsForValue = Math.ceil(value.length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; + const valueBuf = ethUtil.setLengthRight(new Buffer(value), paddedDynamicBytesForValue); + const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), Constants.EVM_WORD_WIDTH_IN_BYTES); + const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); + return encodedValueBuf; + } + + public decodeValue(calldata: RawCalldata): string { + const lengthBuf = calldata.popWord(); + const lengthHex = ethUtil.bufferToHex(lengthBuf); + const length = parseInt(lengthHex, Constants.HEX_BASE); + const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const paddedValueBuf = calldata.popWords(wordsForValue); + const valueBuf = paddedValueBuf.slice(0, length); + const value = valueBuf.toString('ascii'); + return value; + } + + public getSignature(): string { + return 'string'; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts new file mode 100644 index 0000000000..0db29c1eb4 --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -0,0 +1,23 @@ +import { DataItem } from 'ethereum-types'; + +import { DataTypeFactory, MemberDataType } from '../data_type'; + +export class Tuple extends MemberDataType { + private readonly _tupleSignature: string; + + public static matchType(type: string): boolean { + return type === 'tuple'; + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, dataTypeFactory); + if (!Tuple.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); + } + this._tupleSignature = this._computeSignatureOfMembers(); + } + + public getSignature(): string { + return this._tupleSignature; + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts new file mode 100644 index 0000000000..86b31ab4ca --- /dev/null +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -0,0 +1,33 @@ +/* tslint:disable prefer-function-over-method */ +import { DataItem } from 'ethereum-types'; + +import { BigNumber } from '../../configured_bignumber'; +import { DataTypeFactory } from '../data_type'; + +import { Number } from './number'; + +export class UInt extends Number { + private static readonly _matcher = RegExp( + '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', + ); + + public static matchType(type: string): boolean { + return UInt._matcher.test(type); + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + super(dataItem, UInt._matcher, dataTypeFactory); + } + + public getMaxValue(): BigNumber { + return new BigNumber(2).toPower(this._width).sub(1); + } + + public getMinValue(): BigNumber { + return new BigNumber(0); + } + + public getSignature(): string { + return `uint${this._width}`; + } +} diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts index 95ad84ac91..a62569fab4 100644 --- a/packages/utils/src/abi_encoder/index.ts +++ b/packages/utils/src/abi_encoder/index.ts @@ -1,2 +1,2 @@ export { EncodingRules, DecodingRules } from './calldata'; -export * from './evm_data_types'; +export * from './evm_data_type_factory'; From aed8b083b587e7b420ac6129b04004dea95c3f3a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 13:24:45 -0800 Subject: [PATCH 146/230] Split Calldata into multiple files - 1 class per file --- packages/utils/src/abi_encoder/calldata.ts | 531 ------------------ .../src/abi_encoder/calldata/calldata.ts | 224 ++++++++ .../abi_encoder/calldata/calldata_block.ts | 77 +++ .../abi_encoder/calldata/calldata_blocks.ts | 3 + .../calldata/dependent_calldata_block.ts | 59 ++ .../utils/src/abi_encoder/calldata/index.ts | 6 + .../calldata/member_calldata_block.ts | 48 ++ .../calldata/payload_calldata_block.ts | 20 + .../src/abi_encoder/calldata/raw_calldata.ts | 82 +++ packages/utils/src/abi_encoder/data_type.ts | 4 +- .../src/abi_encoder/evm_data_types/method.ts | 3 +- packages/utils/src/abi_encoder/index.ts | 2 +- packages/utils/src/abi_encoder/utils/queue.ts | 39 ++ packages/utils/src/abi_encoder/utils/rules.ts | 8 + 14 files changed, 571 insertions(+), 535 deletions(-) delete mode 100644 packages/utils/src/abi_encoder/calldata.ts create mode 100644 packages/utils/src/abi_encoder/calldata/calldata.ts create mode 100644 packages/utils/src/abi_encoder/calldata/calldata_block.ts create mode 100644 packages/utils/src/abi_encoder/calldata/calldata_blocks.ts create mode 100644 packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts create mode 100644 packages/utils/src/abi_encoder/calldata/index.ts create mode 100644 packages/utils/src/abi_encoder/calldata/member_calldata_block.ts create mode 100644 packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts create mode 100644 packages/utils/src/abi_encoder/calldata/raw_calldata.ts create mode 100644 packages/utils/src/abi_encoder/utils/queue.ts create mode 100644 packages/utils/src/abi_encoder/utils/rules.ts diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts deleted file mode 100644 index d108ef0a70..0000000000 --- a/packages/utils/src/abi_encoder/calldata.ts +++ /dev/null @@ -1,531 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import * as Constants from './constants'; - -export interface DecodingRules { - structsAsObjects: boolean; -} - -export interface EncodingRules { - optimize?: boolean; - annotate?: boolean; -} - -export abstract class CalldataBlock { - private readonly _signature: string; - private readonly _parentName: string; - private _name: string; - private _offsetInBytes: number; - private _headerSizeInBytes: number; - private _bodySizeInBytes: number; - - constructor( - name: string, - signature: string, - parentName: string, - headerSizeInBytes: number, - bodySizeInBytes: number, - ) { - this._name = name; - this._signature = signature; - this._parentName = parentName; - this._offsetInBytes = 0; - this._headerSizeInBytes = headerSizeInBytes; - this._bodySizeInBytes = bodySizeInBytes; - } - - protected _setHeaderSize(headerSizeInBytes: number): void { - this._headerSizeInBytes = headerSizeInBytes; - } - - protected _setBodySize(bodySizeInBytes: number): void { - this._bodySizeInBytes = bodySizeInBytes; - } - - protected _setName(name: string): void { - this._name = name; - } - - public getName(): string { - return this._name; - } - - public getParentName(): string { - return this._parentName; - } - - public getSignature(): string { - return this._signature; - } - public getHeaderSizeInBytes(): number { - return this._headerSizeInBytes; - } - - public getBodySizeInBytes(): number { - return this._bodySizeInBytes; - } - - public getSizeInBytes(): number { - return this.getHeaderSizeInBytes() + this.getBodySizeInBytes(); - } - - public getOffsetInBytes(): number { - return this._offsetInBytes; - } - - public setOffset(offsetInBytes: number): void { - this._offsetInBytes = offsetInBytes; - } - - public computeHash(): Buffer { - const rawData = this.getRawData(); - const hash = ethUtil.sha3(rawData); - return hash; - } - - public abstract toBuffer(): Buffer; - public abstract getRawData(): Buffer; -} - -export class PayloadCalldataBlock extends CalldataBlock { - private readonly _payload: Buffer; - - constructor(name: string, signature: string, parentName: string, payload: Buffer) { - const headerSizeInBytes = 0; - const bodySizeInBytes = payload.byteLength; - super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); - this._payload = payload; - } - - public toBuffer(): Buffer { - return this._payload; - } - - public getRawData(): Buffer { - return this._payload; - } -} - -export class DependentCalldataBlock extends CalldataBlock { - public static readonly RAW_DATA_START = new Buffer('<'); - public static readonly RAW_DATA_END = new Buffer('>'); - private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; - private static readonly _EMPTY_HEADER_SIZE = 0; - private readonly _parent: CalldataBlock; - private readonly _dependency: CalldataBlock; - private _aliasFor: CalldataBlock | undefined; - - constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) { - const headerSizeInBytes = DependentCalldataBlock._EMPTY_HEADER_SIZE; - const bodySizeInBytes = DependentCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; - super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); - this._parent = parent; - this._dependency = dependency; - this._aliasFor = undefined; - } - - public toBuffer(): Buffer { - const destinationOffset = - this._aliasFor !== undefined ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes(); - const parentOffset = this._parent.getOffsetInBytes(); - const parentHeaderSize = this._parent.getHeaderSizeInBytes(); - const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); - const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(Constants.HEX_BASE)}`); - const evmWordWidthInBytes = 32; - const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); - return pointerBufPadded; - } - - public getDependency(): CalldataBlock { - return this._dependency; - } - - public setAlias(block: CalldataBlock): void { - this._aliasFor = block; - this._setName(`${this.getName()} (alias for ${block.getName()})`); - } - - public getAlias(): CalldataBlock | undefined { - return this._aliasFor; - } - - public getRawData(): Buffer { - const dependencyRawData = this._dependency.getRawData(); - const rawDataComponents: Buffer[] = []; - rawDataComponents.push(DependentCalldataBlock.RAW_DATA_START); - rawDataComponents.push(dependencyRawData); - rawDataComponents.push(DependentCalldataBlock.RAW_DATA_END); - const rawData = Buffer.concat(rawDataComponents); - return rawData; - } -} - -export class MemberCalldataBlock extends CalldataBlock { - private _header: Buffer | undefined; - private _members: CalldataBlock[]; - - constructor(name: string, signature: string, parentName: string) { - super(name, signature, parentName, 0, 0); - this._members = []; - this._header = undefined; - } - - public getRawData(): Buffer { - const rawDataComponents: Buffer[] = []; - if (this._header !== undefined) { - rawDataComponents.push(this._header); - } - _.each(this._members, (member: CalldataBlock) => { - const memberBuffer = member.getRawData(); - rawDataComponents.push(memberBuffer); - }); - - const rawData = Buffer.concat(rawDataComponents); - return rawData; - } - - public setMembers(members: CalldataBlock[]): void { - this._members = members; - } - - public setHeader(header: Buffer): void { - this._setHeaderSize(header.byteLength); - this._header = header; - } - - public toBuffer(): Buffer { - if (this._header !== undefined) { - return this._header; - } - return new Buffer(''); - } - - public getMembers(): CalldataBlock[] { - return this._members; - } -} - -class Queue { - private _store: T[] = []; - public push(val: T): void { - this._store.push(val); - } - public pushFront(val: T): void { - this._store.unshift(val); - } - public pop(): T | undefined { - return this._store.shift(); - } - public popBack(): T | undefined { - if (this._store.length === 0) { - return undefined; - } - const backElement = this._store.splice(-1, 1)[0]; - return backElement; - } - public merge(q: Queue): void { - this._store = this._store.concat(q._store); - } - public mergeFront(q: Queue): void { - this._store = q._store.concat(this._store); - } - public getStore(): T[] { - return this._store; - } - public peek(): T | undefined { - return this._store.length >= 0 ? this._store[0] : undefined; - } -} - -export class Calldata { - private readonly _rules: EncodingRules; - private _selector: string; - private _sizeInBytes: number; - private _root: CalldataBlock | undefined; - - private static _createQueue(block: CalldataBlock): Queue { - const blockQueue = new Queue(); - - // Base Case - if (!(block instanceof MemberCalldataBlock)) { - blockQueue.push(block); - return blockQueue; - } - - // This is a Member Block - const memberBlock = block; - _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => { - if (member instanceof MemberCalldataBlock) { - blockQueue.mergeFront(Calldata._createQueue(member)); - } else { - blockQueue.pushFront(member); - } - }); - - // Children - _.each(memberBlock.getMembers(), (member: CalldataBlock) => { - if (member instanceof DependentCalldataBlock && member.getAlias() === undefined) { - const dependency = member.getDependency(); - if (dependency instanceof MemberCalldataBlock) { - blockQueue.merge(Calldata._createQueue(dependency)); - } else { - blockQueue.push(dependency); - } - } - }); - - blockQueue.pushFront(memberBlock); - return blockQueue; - } - - public constructor(rules: EncodingRules) { - this._rules = rules; - this._selector = ''; - this._sizeInBytes = 0; - this._root = undefined; - } - - public optimize(): void { - if (this._root === undefined) { - throw new Error('expected root'); - } - - const blocksByHash: { [key: string]: CalldataBlock } = {}; - - // 1. Create a queue of subtrees by hash - // Note that they are ordered the same as - const subtreeQueue = Calldata._createQueue(this._root); - let block: CalldataBlock | undefined; - for (block = subtreeQueue.popBack(); block !== undefined; block = subtreeQueue.popBack()) { - if (block instanceof DependentCalldataBlock) { - const dependencyBlockHashBuf = block.getDependency().computeHash(); - const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); - if (dependencyBlockHash in blocksByHash) { - const blockWithSameHash = blocksByHash[dependencyBlockHash]; - if (blockWithSameHash !== block.getDependency()) { - block.setAlias(blockWithSameHash); - } - } - continue; - } - - const blockHashBuf = block.computeHash(); - const blockHash = ethUtil.bufferToHex(blockHashBuf); - if (!(blockHash in blocksByHash)) { - blocksByHash[blockHash] = block; - } - } - } - - public toHexString(): string { - if (this._root === undefined) { - throw new Error('expected root'); - } - - if (this._rules.optimize) { - this.optimize(); - } - - const offsetQueue = Calldata._createQueue(this._root); - let block: CalldataBlock | undefined; - let offset = 0; - for (block = offsetQueue.pop(); block !== undefined; block = offsetQueue.pop()) { - block.setOffset(offset); - offset += block.getSizeInBytes(); - } - - const hexValue = this._rules.annotate ? this._generateAnnotatedHexString() : this._generateCondensedHexString(); - return hexValue; - } - - public getSelectorHex(): string { - return this._selector; - } - - public getSizeInBytes(): number { - return this._sizeInBytes; - } - - public setRoot(block: CalldataBlock): void { - this._root = block; - this._sizeInBytes += block.getSizeInBytes(); - } - - public setSelector(selector: string): void { - this._selector = selector.startsWith('0x') ? selector : `$0x${selector}`; - if (this._selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) { - throw new Error(`Invalid selector '${this._selector}'`); - } - this._sizeInBytes += Constants.HEX_SELECTOR_LENGTH_IN_BYTES; // @TODO: Used to be += 8. Bad? - } - - private _generateAnnotatedHexString(): string { - let hexValue = `${this._selector}`; - if (this._root === undefined) { - throw new Error('expected root'); - } - - const valueQueue = Calldata._createQueue(this._root); - - let block: CalldataBlock | undefined; - let offset = 0; - const functionBlock = valueQueue.peek(); - const functionName: string = functionBlock === undefined ? '' : functionBlock.getName(); - for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { - // Process each block 1 word at a time - const size = block.getSizeInBytes(); - const name = block.getName(); - const parentName = block.getParentName(); - const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, ''); - - // Current offset - let offsetStr = ''; - - // If this block is empty then it's a newline - const offsetPadding = 10; - const valuePadding = 74; - const namePadding = 80; - const evmWordStartIndex = 0; - const emptySize = 0; - let value = ''; - let nameStr = ''; - let line = ''; - if (size === emptySize) { - offsetStr = ' '.repeat(offsetPadding); - value = ' '.repeat(valuePadding); - nameStr = `### ${prettyName.padEnd(namePadding)}`; - line = `\n${offsetStr}${value}${nameStr}`; - } else { - offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); - value = ethUtil - .stripHexPrefix( - ethUtil.bufferToHex( - block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES), - ), - ) - .padEnd(valuePadding); - if (block instanceof MemberCalldataBlock) { - nameStr = `### ${prettyName.padEnd(namePadding)}`; - line = `\n${offsetStr}${value}${nameStr}`; - } else { - nameStr = ` ${prettyName.padEnd(namePadding)}`; - line = `${offsetStr}${value}${nameStr}`; - } - } - - for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) { - offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); - value = ethUtil - .stripHexPrefix( - ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES)), - ) - .padEnd(valuePadding); - nameStr = ' '.repeat(namePadding); - line = `${line}\n${offsetStr}${value}${nameStr}`; - } - - // Append to hex value - hexValue = `${hexValue}\n${line}`; - offset += size; - } - - return hexValue; - } - - private _generateCondensedHexString(): string { - const selectorBuffer = ethUtil.toBuffer(this._selector); - if (this._root === undefined) { - throw new Error('expected root'); - } - - const valueQueue = Calldata._createQueue(this._root); - const valueBufs: Buffer[] = [selectorBuffer]; - let block: CalldataBlock | undefined; - for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { - valueBufs.push(block.toBuffer()); - } - - const combinedBuffers = Buffer.concat(valueBufs); - const hexValue = ethUtil.bufferToHex(combinedBuffers); - return hexValue; - } -} - -export class RawCalldata { - private static readonly _INITIAL_OFFSET = 0; - private readonly _value: Buffer; - private readonly _selector: string; - private readonly _scopes: Queue; - private _offset: number; // tracks current offset into raw calldata; used for parsing - - constructor(value: string | Buffer, hasSelectorPrefix: boolean = true) { - if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Expected raw calldata to start with '0x'`); - } - const valueBuf = ethUtil.toBuffer(value); - if (hasSelectorPrefix) { - this._selector = ethUtil.bufferToHex( - valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES), - ); - this._value = valueBuf.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); // disregard selector - } else { - this._selector = '0x'; - this._value = valueBuf; - } - - this._scopes = new Queue(); - this._scopes.push(RawCalldata._INITIAL_OFFSET); - this._offset = RawCalldata._INITIAL_OFFSET; - } - - public popBytes(lengthInBytes: number): Buffer { - const value = this._value.slice(this._offset, this._offset + lengthInBytes); - this.setOffset(this._offset + lengthInBytes); - return value; - } - - public popWord(): Buffer { - const wordInBytes = 32; - return this.popBytes(wordInBytes); - } - - public popWords(length: number): Buffer { - const wordInBytes = 32; - return this.popBytes(length * wordInBytes); - } - - public readBytes(from: number, to: number): Buffer { - const value = this._value.slice(from, to); - return value; - } - - public setOffset(offsetInBytes: number): void { - this._offset = offsetInBytes; - } - - public startScope(): void { - this._scopes.pushFront(this._offset); - } - - public endScope(): void { - this._scopes.pop(); - } - - public getOffset(): number { - return this._offset; - } - - public toAbsoluteOffset(relativeOffset: number): number { - const scopeOffset = this._scopes.peek(); - if (scopeOffset === undefined) { - throw new Error(`Tried to access undefined scope.`); - } - const absoluteOffset = relativeOffset + scopeOffset; - return absoluteOffset; - } - - public getSelector(): string { - return this._selector; - } -} diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts new file mode 100644 index 0000000000..5ac4c1fe70 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -0,0 +1,224 @@ + +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import * as Constants from '../constants'; +import { Queue } from '../utils/queue'; +import { EncodingRules } from '../utils/rules'; + +import { CalldataBlock } from './calldata_block'; +import * as CalldataBlocks from './calldata_blocks'; + +export class Calldata { + private readonly _rules: EncodingRules; + private _selector: string; + private _sizeInBytes: number; + private _root: CalldataBlock | undefined; + + private static _createQueue(block: CalldataBlock): Queue { + const blockQueue = new Queue(); + + // Base Case + if (!(block instanceof CalldataBlocks.MemberCalldataBlock)) { + blockQueue.push(block); + return blockQueue; + } + + // This is a Member Block + const memberBlock = block; + _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => { + if (member instanceof CalldataBlocks.MemberCalldataBlock) { + blockQueue.mergeFront(Calldata._createQueue(member)); + } else { + blockQueue.pushFront(member); + } + }); + + // Children + _.each(memberBlock.getMembers(), (member: CalldataBlock) => { + if (member instanceof CalldataBlocks.DependentCalldataBlock && member.getAlias() === undefined) { + const dependency = member.getDependency(); + if (dependency instanceof CalldataBlocks.MemberCalldataBlock) { + blockQueue.merge(Calldata._createQueue(dependency)); + } else { + blockQueue.push(dependency); + } + } + }); + + blockQueue.pushFront(memberBlock); + return blockQueue; + } + + public constructor(rules: EncodingRules) { + this._rules = rules; + this._selector = ''; + this._sizeInBytes = 0; + this._root = undefined; + } + + public optimize(): void { + if (this._root === undefined) { + throw new Error('expected root'); + } + + const blocksByHash: { [key: string]: CalldataBlock } = {}; + + // 1. Create a queue of subtrees by hash + // Note that they are ordered the same as + const subtreeQueue = Calldata._createQueue(this._root); + let block: CalldataBlock | undefined; + for (block = subtreeQueue.popBack(); block !== undefined; block = subtreeQueue.popBack()) { + if (block instanceof CalldataBlocks.DependentCalldataBlock) { + const dependencyBlockHashBuf = block.getDependency().computeHash(); + const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); + if (dependencyBlockHash in blocksByHash) { + const blockWithSameHash = blocksByHash[dependencyBlockHash]; + if (blockWithSameHash !== block.getDependency()) { + block.setAlias(blockWithSameHash); + } + } + continue; + } + + const blockHashBuf = block.computeHash(); + const blockHash = ethUtil.bufferToHex(blockHashBuf); + if (!(blockHash in blocksByHash)) { + blocksByHash[blockHash] = block; + } + } + } + + public toHexString(): string { + if (this._root === undefined) { + throw new Error('expected root'); + } + + if (this._rules.optimize) { + this.optimize(); + } + + const offsetQueue = Calldata._createQueue(this._root); + let block: CalldataBlock | undefined; + let offset = 0; + for (block = offsetQueue.pop(); block !== undefined; block = offsetQueue.pop()) { + block.setOffset(offset); + offset += block.getSizeInBytes(); + } + + const hexValue = this._rules.annotate ? this._generateAnnotatedHexString() : this._generateCondensedHexString(); + return hexValue; + } + + public getSelectorHex(): string { + return this._selector; + } + + public getSizeInBytes(): number { + return this._sizeInBytes; + } + + public setRoot(block: CalldataBlock): void { + this._root = block; + this._sizeInBytes += block.getSizeInBytes(); + } + + public setSelector(selector: string): void { + this._selector = selector.startsWith('0x') ? selector : `$0x${selector}`; + if (this._selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) { + throw new Error(`Invalid selector '${this._selector}'`); + } + this._sizeInBytes += Constants.HEX_SELECTOR_LENGTH_IN_BYTES; // @TODO: Used to be += 8. Bad? + } + + private _generateAnnotatedHexString(): string { + let hexValue = `${this._selector}`; + if (this._root === undefined) { + throw new Error('expected root'); + } + + const valueQueue = Calldata._createQueue(this._root); + + let block: CalldataBlock | undefined; + let offset = 0; + const functionBlock = valueQueue.peek(); + const functionName: string = functionBlock === undefined ? '' : functionBlock.getName(); + for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { + // Process each block 1 word at a time + const size = block.getSizeInBytes(); + const name = block.getName(); + const parentName = block.getParentName(); + const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, ''); + + // Current offset + let offsetStr = ''; + + // If this block is empty then it's a newline + const offsetPadding = 10; + const valuePadding = 74; + const namePadding = 80; + const evmWordStartIndex = 0; + const emptySize = 0; + let value = ''; + let nameStr = ''; + let line = ''; + if (size === emptySize) { + offsetStr = ' '.repeat(offsetPadding); + value = ' '.repeat(valuePadding); + nameStr = `### ${prettyName.padEnd(namePadding)}`; + line = `\n${offsetStr}${value}${nameStr}`; + } else { + offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); + value = ethUtil + .stripHexPrefix( + ethUtil.bufferToHex( + block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES), + ), + ) + .padEnd(valuePadding); + if (block instanceof CalldataBlocks.MemberCalldataBlock) { + nameStr = `### ${prettyName.padEnd(namePadding)}`; + line = `\n${offsetStr}${value}${nameStr}`; + } else { + nameStr = ` ${prettyName.padEnd(namePadding)}`; + line = `${offsetStr}${value}${nameStr}`; + } + } + + for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) { + offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); + value = ethUtil + .stripHexPrefix( + ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES)), + ) + .padEnd(valuePadding); + nameStr = ' '.repeat(namePadding); + line = `${line}\n${offsetStr}${value}${nameStr}`; + } + + // Append to hex value + hexValue = `${hexValue}\n${line}`; + offset += size; + } + + return hexValue; + } + + private _generateCondensedHexString(): string { + const selectorBuffer = ethUtil.toBuffer(this._selector); + if (this._root === undefined) { + throw new Error('expected root'); + } + + const valueQueue = Calldata._createQueue(this._root); + const valueBufs: Buffer[] = [selectorBuffer]; + let block: CalldataBlock | undefined; + for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { + valueBufs.push(block.toBuffer()); + } + + const combinedBuffers = Buffer.concat(valueBufs); + const hexValue = ethUtil.bufferToHex(combinedBuffers); + return hexValue; + } +} diff --git a/packages/utils/src/abi_encoder/calldata/calldata_block.ts b/packages/utils/src/abi_encoder/calldata/calldata_block.ts new file mode 100644 index 0000000000..35bd994e53 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/calldata_block.ts @@ -0,0 +1,77 @@ +import * as ethUtil from 'ethereumjs-util'; + +export abstract class CalldataBlock { + private readonly _signature: string; + private readonly _parentName: string; + private _name: string; + private _offsetInBytes: number; + private _headerSizeInBytes: number; + private _bodySizeInBytes: number; + + constructor( + name: string, + signature: string, + parentName: string, + headerSizeInBytes: number, + bodySizeInBytes: number, + ) { + this._name = name; + this._signature = signature; + this._parentName = parentName; + this._offsetInBytes = 0; + this._headerSizeInBytes = headerSizeInBytes; + this._bodySizeInBytes = bodySizeInBytes; + } + + protected _setHeaderSize(headerSizeInBytes: number): void { + this._headerSizeInBytes = headerSizeInBytes; + } + + protected _setBodySize(bodySizeInBytes: number): void { + this._bodySizeInBytes = bodySizeInBytes; + } + + protected _setName(name: string): void { + this._name = name; + } + + public getName(): string { + return this._name; + } + + public getParentName(): string { + return this._parentName; + } + + public getSignature(): string { + return this._signature; + } + public getHeaderSizeInBytes(): number { + return this._headerSizeInBytes; + } + + public getBodySizeInBytes(): number { + return this._bodySizeInBytes; + } + + public getSizeInBytes(): number { + return this.getHeaderSizeInBytes() + this.getBodySizeInBytes(); + } + + public getOffsetInBytes(): number { + return this._offsetInBytes; + } + + public setOffset(offsetInBytes: number): void { + this._offsetInBytes = offsetInBytes; + } + + public computeHash(): Buffer { + const rawData = this.getRawData(); + const hash = ethUtil.sha3(rawData); + return hash; + } + + public abstract toBuffer(): Buffer; + public abstract getRawData(): Buffer; +} diff --git a/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts b/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts new file mode 100644 index 0000000000..37660ef218 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts @@ -0,0 +1,3 @@ +export * from './dependent_calldata_block'; +export * from './member_calldata_block'; +export * from './payload_calldata_block'; \ No newline at end of file diff --git a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts new file mode 100644 index 0000000000..d6870ec0b2 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts @@ -0,0 +1,59 @@ +import * as ethUtil from 'ethereumjs-util'; + +import * as Constants from '../constants'; + +import { CalldataBlock } from './calldata_block'; + +export class DependentCalldataBlock extends CalldataBlock { + public static readonly RAW_DATA_START = new Buffer('<'); + public static readonly RAW_DATA_END = new Buffer('>'); + private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; + private static readonly _EMPTY_HEADER_SIZE = 0; + private readonly _parent: CalldataBlock; + private readonly _dependency: CalldataBlock; + private _aliasFor: CalldataBlock | undefined; + + constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) { + const headerSizeInBytes = DependentCalldataBlock._EMPTY_HEADER_SIZE; + const bodySizeInBytes = DependentCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; + super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); + this._parent = parent; + this._dependency = dependency; + this._aliasFor = undefined; + } + + public toBuffer(): Buffer { + const destinationOffset = + this._aliasFor !== undefined ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes(); + const parentOffset = this._parent.getOffsetInBytes(); + const parentHeaderSize = this._parent.getHeaderSizeInBytes(); + const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); + const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(Constants.HEX_BASE)}`); + const evmWordWidthInBytes = 32; + const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); + return pointerBufPadded; + } + + public getDependency(): CalldataBlock { + return this._dependency; + } + + public setAlias(block: CalldataBlock): void { + this._aliasFor = block; + this._setName(`${this.getName()} (alias for ${block.getName()})`); + } + + public getAlias(): CalldataBlock | undefined { + return this._aliasFor; + } + + public getRawData(): Buffer { + const dependencyRawData = this._dependency.getRawData(); + const rawDataComponents: Buffer[] = []; + rawDataComponents.push(DependentCalldataBlock.RAW_DATA_START); + rawDataComponents.push(dependencyRawData); + rawDataComponents.push(DependentCalldataBlock.RAW_DATA_END); + const rawData = Buffer.concat(rawDataComponents); + return rawData; + } +} \ No newline at end of file diff --git a/packages/utils/src/abi_encoder/calldata/index.ts b/packages/utils/src/abi_encoder/calldata/index.ts new file mode 100644 index 0000000000..0e1f57a2df --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/index.ts @@ -0,0 +1,6 @@ +export * from './calldata_block'; +export * from './dependent_calldata_block'; +export * from './payload_calldata_block'; +export * from './member_calldata_block'; +export * from './calldata'; +export * from './raw_calldata'; \ No newline at end of file diff --git a/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts new file mode 100644 index 0000000000..e9bcb2f344 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts @@ -0,0 +1,48 @@ +import * as _ from 'lodash'; + +import { CalldataBlock } from './calldata_block'; + +export class MemberCalldataBlock extends CalldataBlock { + private _header: Buffer | undefined; + private _members: CalldataBlock[]; + + constructor(name: string, signature: string, parentName: string) { + super(name, signature, parentName, 0, 0); + this._members = []; + this._header = undefined; + } + + public getRawData(): Buffer { + const rawDataComponents: Buffer[] = []; + if (this._header !== undefined) { + rawDataComponents.push(this._header); + } + _.each(this._members, (member: CalldataBlock) => { + const memberBuffer = member.getRawData(); + rawDataComponents.push(memberBuffer); + }); + + const rawData = Buffer.concat(rawDataComponents); + return rawData; + } + + public setMembers(members: CalldataBlock[]): void { + this._members = members; + } + + public setHeader(header: Buffer): void { + this._setHeaderSize(header.byteLength); + this._header = header; + } + + public toBuffer(): Buffer { + if (this._header !== undefined) { + return this._header; + } + return new Buffer(''); + } + + public getMembers(): CalldataBlock[] { + return this._members; + } +} \ No newline at end of file diff --git a/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts new file mode 100644 index 0000000000..81a88bc196 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts @@ -0,0 +1,20 @@ +import { CalldataBlock } from './calldata_block'; + +export class PayloadCalldataBlock extends CalldataBlock { + private readonly _payload: Buffer; + + constructor(name: string, signature: string, parentName: string, payload: Buffer) { + const headerSizeInBytes = 0; + const bodySizeInBytes = payload.byteLength; + super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); + this._payload = payload; + } + + public toBuffer(): Buffer { + return this._payload; + } + + public getRawData(): Buffer { + return this._payload; + } +} \ No newline at end of file diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts new file mode 100644 index 0000000000..8b946ee4b7 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -0,0 +1,82 @@ +import * as ethUtil from 'ethereumjs-util'; + +import * as Constants from '../constants'; +import { Queue } from '../utils/queue'; + +export class RawCalldata { + private static readonly _INITIAL_OFFSET = 0; + private readonly _value: Buffer; + private readonly _selector: string; + private readonly _scopes: Queue; + private _offset: number; // tracks current offset into raw calldata; used for parsing + + constructor(value: string | Buffer, hasSelectorPrefix: boolean = true) { + if (typeof value === 'string' && !value.startsWith('0x')) { + throw new Error(`Expected raw calldata to start with '0x'`); + } + const valueBuf = ethUtil.toBuffer(value); + if (hasSelectorPrefix) { + this._selector = ethUtil.bufferToHex( + valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES), + ); + this._value = valueBuf.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); // disregard selector + } else { + this._selector = '0x'; + this._value = valueBuf; + } + + this._scopes = new Queue(); + this._scopes.push(RawCalldata._INITIAL_OFFSET); + this._offset = RawCalldata._INITIAL_OFFSET; + } + + public popBytes(lengthInBytes: number): Buffer { + const value = this._value.slice(this._offset, this._offset + lengthInBytes); + this.setOffset(this._offset + lengthInBytes); + return value; + } + + public popWord(): Buffer { + const wordInBytes = 32; + return this.popBytes(wordInBytes); + } + + public popWords(length: number): Buffer { + const wordInBytes = 32; + return this.popBytes(length * wordInBytes); + } + + public readBytes(from: number, to: number): Buffer { + const value = this._value.slice(from, to); + return value; + } + + public setOffset(offsetInBytes: number): void { + this._offset = offsetInBytes; + } + + public startScope(): void { + this._scopes.pushFront(this._offset); + } + + public endScope(): void { + this._scopes.pop(); + } + + public getOffset(): number { + return this._offset; + } + + public toAbsoluteOffset(relativeOffset: number): number { + const scopeOffset = this._scopes.peek(); + if (scopeOffset === undefined) { + throw new Error(`Tried to access undefined scope.`); + } + const absoluteOffset = relativeOffset + scopeOffset; + return absoluteOffset; + } + + public getSelector(): string { + return this._selector; + } +} \ No newline at end of file diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts index 9890619e53..0fbb9165ae 100644 --- a/packages/utils/src/abi_encoder/data_type.ts +++ b/packages/utils/src/abi_encoder/data_type.ts @@ -9,14 +9,14 @@ import * as Constants from './constants'; import { Calldata, CalldataBlock, - DecodingRules, DependentCalldataBlock, - EncodingRules, MemberCalldataBlock, PayloadCalldataBlock, RawCalldata, } from './calldata'; +import { DecodingRules, EncodingRules } from './utils/rules'; + export interface DataTypeFactory { create: (dataItem: DataItem, parentDataType?: DataType) => DataType; mapDataItemToDataType: (dataItem: DataItem) => DataType; diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index e8e717bf1b..c4e6ee93a8 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -2,9 +2,10 @@ import { DataItem, MethodAbi } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { DecodingRules, EncodingRules, RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; import { DataType, DataTypeFactory, MemberDataType } from '../data_type'; +import { DecodingRules, EncodingRules } from '../utils/rules'; import { StaticBytes } from './static_bytes'; import { Tuple } from './tuple'; diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts index a62569fab4..ea037b40a0 100644 --- a/packages/utils/src/abi_encoder/index.ts +++ b/packages/utils/src/abi_encoder/index.ts @@ -1,2 +1,2 @@ -export { EncodingRules, DecodingRules } from './calldata'; +export { EncodingRules, DecodingRules } from './utils/rules'; export * from './evm_data_type_factory'; diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts new file mode 100644 index 0000000000..2e27afd457 --- /dev/null +++ b/packages/utils/src/abi_encoder/utils/queue.ts @@ -0,0 +1,39 @@ +export class Queue { + private _store: T[] = []; + + public push(val: T): void { + this._store.push(val); + } + + public pushFront(val: T): void { + this._store.unshift(val); + } + + public pop(): T | undefined { + return this._store.shift(); + } + + public popBack(): T | undefined { + if (this._store.length === 0) { + return undefined; + } + const backElement = this._store.splice(-1, 1)[0]; + return backElement; + } + + public merge(q: Queue): void { + this._store = this._store.concat(q._store); + } + + public mergeFront(q: Queue): void { + this._store = q._store.concat(this._store); + } + + public getStore(): T[] { + return this._store; + } + + public peek(): T | undefined { + return this._store.length >= 0 ? this._store[0] : undefined; + } +} \ No newline at end of file diff --git a/packages/utils/src/abi_encoder/utils/rules.ts b/packages/utils/src/abi_encoder/utils/rules.ts new file mode 100644 index 0000000000..cf97ef6da5 --- /dev/null +++ b/packages/utils/src/abi_encoder/utils/rules.ts @@ -0,0 +1,8 @@ +export interface DecodingRules { + structsAsObjects: boolean; +} + +export interface EncodingRules { + optimize?: boolean; + annotate?: boolean; +} \ No newline at end of file From d4d917f350fb6916df50f3aa5cf09ce68b316aa1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 13:25:49 -0800 Subject: [PATCH 147/230] Ran prettier --- packages/utils/src/abi_encoder/calldata/calldata.ts | 1 - packages/utils/src/abi_encoder/calldata/calldata_blocks.ts | 2 +- .../utils/src/abi_encoder/calldata/dependent_calldata_block.ts | 2 +- packages/utils/src/abi_encoder/calldata/index.ts | 2 +- .../utils/src/abi_encoder/calldata/member_calldata_block.ts | 2 +- .../utils/src/abi_encoder/calldata/payload_calldata_block.ts | 2 +- packages/utils/src/abi_encoder/calldata/raw_calldata.ts | 2 +- packages/utils/src/abi_encoder/evm_data_type_factory.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/address.ts | 2 +- packages/utils/src/abi_encoder/utils/queue.ts | 2 +- packages/utils/src/abi_encoder/utils/rules.ts | 2 +- 11 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index 5ac4c1fe70..1abf1b6816 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -1,4 +1,3 @@ - import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; diff --git a/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts b/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts index 37660ef218..8d4e7d7ca8 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts @@ -1,3 +1,3 @@ export * from './dependent_calldata_block'; export * from './member_calldata_block'; -export * from './payload_calldata_block'; \ No newline at end of file +export * from './payload_calldata_block'; diff --git a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts index d6870ec0b2..4aec5eabcd 100644 --- a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts +++ b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts @@ -56,4 +56,4 @@ export class DependentCalldataBlock extends CalldataBlock { const rawData = Buffer.concat(rawDataComponents); return rawData; } -} \ No newline at end of file +} diff --git a/packages/utils/src/abi_encoder/calldata/index.ts b/packages/utils/src/abi_encoder/calldata/index.ts index 0e1f57a2df..2c786cd8dc 100644 --- a/packages/utils/src/abi_encoder/calldata/index.ts +++ b/packages/utils/src/abi_encoder/calldata/index.ts @@ -3,4 +3,4 @@ export * from './dependent_calldata_block'; export * from './payload_calldata_block'; export * from './member_calldata_block'; export * from './calldata'; -export * from './raw_calldata'; \ No newline at end of file +export * from './raw_calldata'; diff --git a/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts index e9bcb2f344..c35beb8de3 100644 --- a/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts +++ b/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts @@ -45,4 +45,4 @@ export class MemberCalldataBlock extends CalldataBlock { public getMembers(): CalldataBlock[] { return this._members; } -} \ No newline at end of file +} diff --git a/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts index 81a88bc196..0420b01d85 100644 --- a/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts +++ b/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts @@ -17,4 +17,4 @@ export class PayloadCalldataBlock extends CalldataBlock { public getRawData(): Buffer { return this._payload; } -} \ No newline at end of file +} diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts index 8b946ee4b7..b7bd357374 100644 --- a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -79,4 +79,4 @@ export class RawCalldata { public getSelector(): string { return this._selector; } -} \ No newline at end of file +} diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index 0f8dfb4a31..a3e1d404ee 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -120,5 +120,5 @@ export class EvmDataTypeFactory implements DataTypeFactory { return pointer; } - private constructor() { } + private constructor() {} } diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 4bd992cab5..3e2c9e78cc 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -13,7 +13,7 @@ export class Address extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - - Address._ADDRESS_SIZE_IN_BYTES; + Address._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { return type === 'address'; diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts index 2e27afd457..3309d8ba2a 100644 --- a/packages/utils/src/abi_encoder/utils/queue.ts +++ b/packages/utils/src/abi_encoder/utils/queue.ts @@ -36,4 +36,4 @@ export class Queue { public peek(): T | undefined { return this._store.length >= 0 ? this._store[0] : undefined; } -} \ No newline at end of file +} diff --git a/packages/utils/src/abi_encoder/utils/rules.ts b/packages/utils/src/abi_encoder/utils/rules.ts index cf97ef6da5..31471e97ae 100644 --- a/packages/utils/src/abi_encoder/utils/rules.ts +++ b/packages/utils/src/abi_encoder/utils/rules.ts @@ -5,4 +5,4 @@ export interface DecodingRules { export interface EncodingRules { optimize?: boolean; annotate?: boolean; -} \ No newline at end of file +} From a47901370bc69d6247e941c569bc9fe824516db9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 13:40:26 -0800 Subject: [PATCH 148/230] Ran prettier --- packages/utils/src/abi_encoder/data_type.ts | 361 ------------------ .../src/abi_encoder/evm_data_type_factory.ts | 2 +- .../src/abi_encoder/evm_data_types/address.ts | 4 +- .../src/abi_encoder/evm_data_types/array.ts | 2 +- .../src/abi_encoder/evm_data_types/bool.ts | 2 +- .../evm_data_types/dynamic_bytes.ts | 2 +- .../src/abi_encoder/evm_data_types/int.ts | 2 +- .../src/abi_encoder/evm_data_types/method.ts | 2 +- .../src/abi_encoder/evm_data_types/number.ts | 2 +- .../src/abi_encoder/evm_data_types/pointer.ts | 2 +- .../evm_data_types/static_bytes.ts | 4 +- .../src/abi_encoder/evm_data_types/string.ts | 2 +- .../src/abi_encoder/evm_data_types/tuple.ts | 2 +- .../src/abi_encoder/evm_data_types/uint.ts | 2 +- 14 files changed, 15 insertions(+), 376 deletions(-) delete mode 100644 packages/utils/src/abi_encoder/data_type.ts diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts deleted file mode 100644 index 0fbb9165ae..0000000000 --- a/packages/utils/src/abi_encoder/data_type.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { DataItem } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { BigNumber } from '../configured_bignumber'; - -import * as Constants from './constants'; - -import { - Calldata, - CalldataBlock, - DependentCalldataBlock, - MemberCalldataBlock, - PayloadCalldataBlock, - RawCalldata, -} from './calldata'; - -import { DecodingRules, EncodingRules } from './utils/rules'; - -export interface DataTypeFactory { - create: (dataItem: DataItem, parentDataType?: DataType) => DataType; - mapDataItemToDataType: (dataItem: DataItem) => DataType; -} - -export interface DataTypeStaticInterface { - matchType: (type: string) => boolean; - encodeValue: (value: any) => Buffer; - decodeValue: (rawCalldata: RawCalldata) => any; -} - -export abstract class DataType { - private static readonly _DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false }; - private static readonly _DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false }; - private readonly _dataItem: DataItem; - private readonly _factory: DataTypeFactory; - - constructor(dataItem: DataItem, factory: DataTypeFactory) { - this._dataItem = dataItem; - this._factory = factory; - } - - public getDataItem(): DataItem { - return this._dataItem; - } - - public getFactory(): DataTypeFactory { - return this._factory; - } - - public encode(value: any, rules?: EncodingRules, selector?: string): string { - const rules_ = rules ? rules : DataType._DEFAULT_ENCODING_RULES; - const calldata = new Calldata(rules_); - if (selector) { - calldata.setSelector(selector); - } - const block = this.generateCalldataBlock(value); - calldata.setRoot(block); // @TODO CHANGE - const calldataHex = calldata.toHexString(); - return calldataHex; - } - - public decode(calldata: string, rules?: DecodingRules, hasSelector: boolean = false): any { - const rawCalldata = new RawCalldata(calldata, hasSelector); - const rules_ = rules ? rules : DataType._DEFAULT_DECODING_RULES; - const value = this.generateValue(rawCalldata, rules_); - return value; - } - - public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; - public abstract generateValue(calldata: RawCalldata, rules: DecodingRules): any; - public abstract getSignature(): string; - public abstract isStatic(): boolean; -} - -export abstract class PayloadDataType extends DataType { - protected _hasConstantSize: boolean; - - public constructor(dataItem: DataItem, factory: DataTypeFactory, hasConstantSize: boolean) { - super(dataItem, factory); - this._hasConstantSize = hasConstantSize; - } - - public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { - const encodedValue = this.encodeValue(value); - const name = this.getDataItem().name; - const signature = this.getSignature(); - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const block = new PayloadCalldataBlock(name, signature, parentName, encodedValue); - return block; - } - - public generateValue(calldata: RawCalldata, rules: DecodingRules): any { - const value = this.decodeValue(calldata); - return value; - } - - public isStatic(): boolean { - return this._hasConstantSize; - } - - public abstract encodeValue(value: any): Buffer; - public abstract decodeValue(calldata: RawCalldata): any; -} - -export abstract class DependentDataType extends DataType { - protected _dependency: DataType; - protected _parent: DataType; - private readonly _isStatic: boolean; - - public constructor(dataItem: DataItem, factory: DataTypeFactory, dependency: DataType, parent: DataType) { - super(dataItem, factory); - this._dependency = dependency; - this._parent = parent; - this._isStatic = true; - } - - public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): DependentCalldataBlock { - if (parentBlock === undefined) { - throw new Error(`DependentDataType requires a parent block to generate its block`); - } - const dependencyBlock = this._dependency.generateCalldataBlock(value, parentBlock); - const name = this.getDataItem().name; - const signature = this.getSignature(); - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const block = new DependentCalldataBlock(name, signature, parentName, dependencyBlock, parentBlock); - return block; - } - - public generateValue(calldata: RawCalldata, rules: DecodingRules): any { - const destinationOffsetBuf = calldata.popWord(); - const currentOffset = calldata.getOffset(); - const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), Constants.HEX_BASE); - const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); - calldata.setOffset(destinationOffsetAbsolute); - const value = this._dependency.generateValue(calldata, rules); - calldata.setOffset(currentOffset); - return value; - } - - public isStatic(): boolean { - return this._isStatic; - } -} - -export interface MemberMap { - [key: string]: number; -} - -export abstract class MemberDataType extends DataType { - protected readonly _arrayLength: number | undefined; - protected readonly _arrayElementType: string | undefined; - private readonly _memberMap: MemberMap; - private readonly _members: DataType[]; - private readonly _isArray: boolean; - - public constructor( - dataItem: DataItem, - factory: DataTypeFactory, - isArray: boolean = false, - arrayLength?: number, - arrayElementType?: string, - ) { - super(dataItem, factory); - this._memberMap = {}; - this._members = []; - this._isArray = isArray; - this._arrayLength = arrayLength; - this._arrayElementType = arrayElementType; - if (isArray && arrayLength !== undefined) { - [this._members, this._memberMap] = this._createMembersWithLength(dataItem, arrayLength); - } else if (!isArray) { - [this._members, this._memberMap] = this._createMembersWithKeys(dataItem); - } - } - - public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { - const block = - value instanceof Array - ? this._generateCalldataBlockFromArray(value, parentBlock) - : this._generateCalldataBlockFromObject(value, parentBlock); - return block; - } - - public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object { - let members = this._members; - if (this._isArray && this._arrayLength === undefined) { - const arrayLengthBuf = calldata.popWord(); - const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); - const hexBase = 16; - const arrayLength = new BigNumber(arrayLengthHex, hexBase); - - [members] = this._createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); - } - - calldata.startScope(); - let value: any[] | object; - if (rules.structsAsObjects && !this._isArray) { - value = {}; - _.each(this._memberMap, (idx: number, key: string) => { - const member = this._members[idx]; - const memberValue = member.generateValue(calldata, rules); - (value as { [key: string]: any })[key] = memberValue; - }); - } else { - value = []; - _.each(members, (member: DataType, idx: number) => { - const memberValue = member.generateValue(calldata, rules); - (value as any[]).push(memberValue); - }); - } - calldata.endScope(); - return value; - } - - public isStatic(): boolean { - /* For Tuple: - const isStaticTuple = this.children.length === 0; - return isStaticTuple; // @TODO: True in every case or only when dynamic data? - - For Array: - if isLengthDefined = false then this is false - - Otherwise if the first element is a Pointer then false - */ - - if (this._isArray && this._arrayLength === undefined) { - return false; - } - - // Search for dependent members - const dependentMember = _.find(this._members, (member: DataType) => { - return member instanceof DependentDataType; - }); - const isStatic = dependentMember === undefined; // static if we couldn't find a dependent member - return isStatic; - } - - protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): MemberCalldataBlock { - // Sanity check length - if (this._arrayLength !== undefined && value.length !== this._arrayLength) { - throw new Error( - `Expected array of ${JSON.stringify( - this._arrayLength, - )} elements, but got array of length ${JSON.stringify(value.length)}`, - ); - } - - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( - this.getDataItem().name, - this.getSignature(), - parentName, - ); - - let members = this._members; - if (this._isArray && this._arrayLength === undefined) { - [members] = this._createMembersWithLength(this.getDataItem(), value.length); - - const lenBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), - Constants.EVM_WORD_WIDTH_IN_BYTES, - ); - methodBlock.setHeader(lenBuf); - } - - const memberBlocks: CalldataBlock[] = []; - _.each(members, (member: DataType, idx: number) => { - const block = member.generateCalldataBlock(value[idx], methodBlock); - memberBlocks.push(block); - }); - methodBlock.setMembers(memberBlocks); - return methodBlock; - } - - protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock { - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( - this.getDataItem().name, - this.getSignature(), - parentName, - ); - const memberBlocks: CalldataBlock[] = []; - const childMap = _.cloneDeep(this._memberMap); - _.forOwn(obj, (value: any, key: string) => { - if (!(key in childMap)) { - throw new Error( - `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, - ); - } - const block = this._members[this._memberMap[key]].generateCalldataBlock(value, methodBlock); - memberBlocks.push(block); - delete childMap[key]; - }); - - if (Object.keys(childMap).length !== 0) { - throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); - } - - methodBlock.setMembers(memberBlocks); - return methodBlock; - } - - protected _computeSignatureOfMembers(): string { - // Compute signature of members - let signature = `(`; - _.each(this._members, (member: DataType, i: number) => { - signature += member.getSignature(); - if (i < this._members.length - 1) { - signature += ','; - } - }); - signature += ')'; - return signature; - } - - private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { - // Sanity check - if (dataItem.components === undefined) { - throw new Error(`Expected components`); - } - - const members: DataType[] = []; - const memberMap: MemberMap = {}; - _.each(dataItem.components, (memberItem: DataItem) => { - const childDataItem: DataItem = { - type: memberItem.type, - name: `${dataItem.name}.${memberItem.name}`, - }; - const components = memberItem.components; - if (components !== undefined) { - childDataItem.components = components; - } - const child = this.getFactory().create(childDataItem, this); - memberMap[memberItem.name] = members.length; - members.push(child); - }); - - return [members, memberMap]; - } - - private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { - const members: DataType[] = []; - const memberMap: MemberMap = {}; - const range = _.range(length); - _.each(range, (idx: number) => { - const childDataItem: DataItem = { - type: this._arrayElementType ? this._arrayElementType : '', - name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`, - }; - const components = dataItem.components; - if (components !== undefined) { - childDataItem.components = components; - } - const child = this.getFactory().create(childDataItem, this); - memberMap[idx.toString(Constants.DEC_BASE)] = members.length; - members.push(child); - }); - - return [members, memberMap]; - } -} diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index a3e1d404ee..5d37acad95 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -3,7 +3,7 @@ /* tslint:disable no-construct */ import { DataItem, MethodAbi } from 'ethereum-types'; -import { DataType, DataTypeFactory } from './data_type'; +import { DataType, DataTypeFactory } from './abstract_data_types'; import * as Impl from './evm_data_types'; export class Address extends Impl.Address { diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 3e2c9e78cc..707e265f8c 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -3,9 +3,9 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; -import { DataTypeFactory, PayloadDataType } from '../data_type'; export class Address extends PayloadDataType { public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; @@ -13,7 +13,7 @@ export class Address extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - - Address._ADDRESS_SIZE_IN_BYTES; + Address._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { return type === 'address'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 707af7c7eb..f334282b8d 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -1,7 +1,7 @@ import { DataItem } from 'ethereum-types'; +import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; import * as Constants from '../constants'; -import { DataTypeFactory, MemberDataType } from '../data_type'; export class Array extends MemberDataType { private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index aee2727c7a..4b9dd32b1f 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -4,9 +4,9 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; -import { DataTypeFactory, PayloadDataType } from '../data_type'; export class Bool extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index 51165881a8..5fca668e41 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -3,9 +3,9 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; -import { DataTypeFactory, PayloadDataType } from '../data_type'; export class DynamicBytes extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index ba5b4cac99..ec41b9cfcd 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -2,7 +2,7 @@ import { DataItem } from 'ethereum-types'; import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory } from '../data_type'; +import { DataTypeFactory } from '../abstract_data_types'; import { Number } from './number'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index c4e6ee93a8..ce33755f13 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -2,9 +2,9 @@ import { DataItem, MethodAbi } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { DataType, DataTypeFactory, MemberDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; -import { DataType, DataTypeFactory, MemberDataType } from '../data_type'; import { DecodingRules, EncodingRules } from '../utils/rules'; import { StaticBytes } from './static_bytes'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/number.ts b/packages/utils/src/abi_encoder/evm_data_types/number.ts index 17201362ee..8ff5e9a6a0 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/number.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/number.ts @@ -3,9 +3,9 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; -import { DataTypeFactory, PayloadDataType } from '../data_type'; export abstract class Number extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts index e0bd3509c4..d4411df9b5 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts @@ -1,6 +1,6 @@ import { DataItem } from 'ethereum-types'; -import { DataType, DataTypeFactory, DependentDataType } from '../data_type'; +import { DataType, DataTypeFactory, DependentDataType } from '../abstract_data_types'; export class Pointer extends DependentDataType { constructor(destDataType: DataType, parentDataType: DataType, dataTypeFactory: DataTypeFactory) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 309dca2348..90e872c78e 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -2,9 +2,9 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; -import { DataTypeFactory, PayloadDataType } from '../data_type'; export class StaticBytes extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; @@ -46,7 +46,7 @@ export class StaticBytes extends PayloadDataType { if (valueBuf.byteLength > this._width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 96b36e7350..c0bde86499 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -3,9 +3,9 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../constants'; -import { DataTypeFactory, PayloadDataType } from '../data_type'; export class String extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 0db29c1eb4..4a90e375a6 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -1,6 +1,6 @@ import { DataItem } from 'ethereum-types'; -import { DataTypeFactory, MemberDataType } from '../data_type'; +import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; export class Tuple extends MemberDataType { private readonly _tupleSignature: string; diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index 86b31ab4ca..ced3ef08be 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -2,7 +2,7 @@ import { DataItem } from 'ethereum-types'; import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory } from '../data_type'; +import { DataTypeFactory } from '../abstract_data_types'; import { Number } from './number'; From fee67326adbbf745966803c935d5663c3ca7f52c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 13:40:46 -0800 Subject: [PATCH 149/230] Merge above --- .../abstract_data_types/data_type.ts | 51 ++++ .../dependent_data_type.ts | 50 ++++ .../abi_encoder/abstract_data_types/index.ts | 5 + .../abstract_data_types/interfaces.ts | 16 ++ .../abstract_data_types/member_data_type.ts | 230 ++++++++++++++++++ .../abstract_data_types/payload_data_type.ts | 38 +++ 6 files changed, 390 insertions(+) create mode 100644 packages/utils/src/abi_encoder/abstract_data_types/data_type.ts create mode 100644 packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts create mode 100644 packages/utils/src/abi_encoder/abstract_data_types/index.ts create mode 100644 packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts create mode 100644 packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts create mode 100644 packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts new file mode 100644 index 0000000000..a6adeb23ba --- /dev/null +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -0,0 +1,51 @@ +import { DataItem } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { Calldata, CalldataBlock, RawCalldata } from '../calldata'; +import { DecodingRules, EncodingRules } from '../utils/rules'; + +import { DataTypeFactory } from './interfaces'; + +export abstract class DataType { + private static readonly _DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false }; + private static readonly _DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false }; + private readonly _dataItem: DataItem; + private readonly _factory: DataTypeFactory; + + constructor(dataItem: DataItem, factory: DataTypeFactory) { + this._dataItem = dataItem; + this._factory = factory; + } + + public getDataItem(): DataItem { + return this._dataItem; + } + + public getFactory(): DataTypeFactory { + return this._factory; + } + + public encode(value: any, rules?: EncodingRules, selector?: string): string { + const rules_ = rules ? rules : DataType._DEFAULT_ENCODING_RULES; + const calldata = new Calldata(rules_); + if (selector) { + calldata.setSelector(selector); + } + const block = this.generateCalldataBlock(value); + calldata.setRoot(block); // @TODO CHANGE + const calldataHex = calldata.toHexString(); + return calldataHex; + } + + public decode(calldata: string, rules?: DecodingRules, hasSelector: boolean = false): any { + const rawCalldata = new RawCalldata(calldata, hasSelector); + const rules_ = rules ? rules : DataType._DEFAULT_DECODING_RULES; + const value = this.generateValue(rawCalldata, rules_); + return value; + } + + public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; + public abstract generateValue(calldata: RawCalldata, rules: DecodingRules): any; + public abstract getSignature(): string; + public abstract isStatic(): boolean; +} diff --git a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts new file mode 100644 index 0000000000..e9f390b227 --- /dev/null +++ b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts @@ -0,0 +1,50 @@ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { CalldataBlock, DependentCalldataBlock, RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DecodingRules } from '../utils/rules'; + +import { DataType } from './data_type'; +import { DataTypeFactory } from './interfaces'; + +export abstract class DependentDataType extends DataType { + protected _dependency: DataType; + protected _parent: DataType; + private readonly _isStatic: boolean; + + public constructor(dataItem: DataItem, factory: DataTypeFactory, dependency: DataType, parent: DataType) { + super(dataItem, factory); + this._dependency = dependency; + this._parent = parent; + this._isStatic = true; + } + + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): DependentCalldataBlock { + if (parentBlock === undefined) { + throw new Error(`DependentDataType requires a parent block to generate its block`); + } + const dependencyBlock = this._dependency.generateCalldataBlock(value, parentBlock); + const name = this.getDataItem().name; + const signature = this.getSignature(); + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const block = new DependentCalldataBlock(name, signature, parentName, dependencyBlock, parentBlock); + return block; + } + + public generateValue(calldata: RawCalldata, rules: DecodingRules): any { + const destinationOffsetBuf = calldata.popWord(); + const currentOffset = calldata.getOffset(); + const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), Constants.HEX_BASE); + const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); + calldata.setOffset(destinationOffsetAbsolute); + const value = this._dependency.generateValue(calldata, rules); + calldata.setOffset(currentOffset); + return value; + } + + public isStatic(): boolean { + return this._isStatic; + } +} diff --git a/packages/utils/src/abi_encoder/abstract_data_types/index.ts b/packages/utils/src/abi_encoder/abstract_data_types/index.ts new file mode 100644 index 0000000000..9ad5681345 --- /dev/null +++ b/packages/utils/src/abi_encoder/abstract_data_types/index.ts @@ -0,0 +1,5 @@ +export * from './interfaces'; +export * from './data_type'; +export * from './dependent_data_type'; +export * from './member_data_type'; +export * from './payload_data_type'; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts new file mode 100644 index 0000000000..2ae92659c5 --- /dev/null +++ b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts @@ -0,0 +1,16 @@ +import { DataItem } from 'ethereum-types'; + +import { RawCalldata } from '../calldata'; + +import { DataType } from './data_type'; + +export interface DataTypeFactory { + create: (dataItem: DataItem, parentDataType?: DataType) => DataType; + mapDataItemToDataType: (dataItem: DataItem) => DataType; +} + +export interface DataTypeStaticInterface { + matchType: (type: string) => boolean; + encodeValue: (value: any) => Buffer; + decodeValue: (rawCalldata: RawCalldata) => any; +} diff --git a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts new file mode 100644 index 0000000000..f44a6dd30f --- /dev/null +++ b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts @@ -0,0 +1,230 @@ +import { DataItem } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { BigNumber } from '../../configured_bignumber'; +import { CalldataBlock, MemberCalldataBlock, RawCalldata } from '../calldata'; +import * as Constants from '../constants'; +import { DecodingRules } from '../utils/rules'; + +import { DataType } from './data_type'; +import { DependentDataType } from './dependent_data_type'; +import { DataTypeFactory } from './interfaces'; + +interface MemberMap { + [key: string]: number; +} + +export abstract class MemberDataType extends DataType { + protected readonly _arrayLength: number | undefined; + protected readonly _arrayElementType: string | undefined; + private readonly _memberMap: MemberMap; + private readonly _members: DataType[]; + private readonly _isArray: boolean; + + public constructor( + dataItem: DataItem, + factory: DataTypeFactory, + isArray: boolean = false, + arrayLength?: number, + arrayElementType?: string, + ) { + super(dataItem, factory); + this._memberMap = {}; + this._members = []; + this._isArray = isArray; + this._arrayLength = arrayLength; + this._arrayElementType = arrayElementType; + if (isArray && arrayLength !== undefined) { + [this._members, this._memberMap] = this._createMembersWithLength(dataItem, arrayLength); + } else if (!isArray) { + [this._members, this._memberMap] = this._createMembersWithKeys(dataItem); + } + } + + public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { + const block = + value instanceof Array + ? this._generateCalldataBlockFromArray(value, parentBlock) + : this._generateCalldataBlockFromObject(value, parentBlock); + return block; + } + + public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object { + let members = this._members; + if (this._isArray && this._arrayLength === undefined) { + const arrayLengthBuf = calldata.popWord(); + const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); + const hexBase = 16; + const arrayLength = new BigNumber(arrayLengthHex, hexBase); + + [members] = this._createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); + } + + calldata.startScope(); + let value: any[] | object; + if (rules.structsAsObjects && !this._isArray) { + value = {}; + _.each(this._memberMap, (idx: number, key: string) => { + const member = this._members[idx]; + const memberValue = member.generateValue(calldata, rules); + (value as { [key: string]: any })[key] = memberValue; + }); + } else { + value = []; + _.each(members, (member: DataType, idx: number) => { + const memberValue = member.generateValue(calldata, rules); + (value as any[]).push(memberValue); + }); + } + calldata.endScope(); + return value; + } + + public isStatic(): boolean { + /* For Tuple: + const isStaticTuple = this.children.length === 0; + return isStaticTuple; // @TODO: True in every case or only when dynamic data? + + For Array: + if isLengthDefined = false then this is false + + Otherwise if the first element is a Pointer then false + */ + + if (this._isArray && this._arrayLength === undefined) { + return false; + } + + // Search for dependent members + const dependentMember = _.find(this._members, (member: DataType) => { + return member instanceof DependentDataType; + }); + const isStatic = dependentMember === undefined; // static if we couldn't find a dependent member + return isStatic; + } + + protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): MemberCalldataBlock { + // Sanity check length + if (this._arrayLength !== undefined && value.length !== this._arrayLength) { + throw new Error( + `Expected array of ${JSON.stringify( + this._arrayLength, + )} elements, but got array of length ${JSON.stringify(value.length)}`, + ); + } + + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + this.getDataItem().name, + this.getSignature(), + parentName, + ); + + let members = this._members; + if (this._isArray && this._arrayLength === undefined) { + [members] = this._createMembersWithLength(this.getDataItem(), value.length); + + const lenBuf = ethUtil.setLengthLeft( + ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), + Constants.EVM_WORD_WIDTH_IN_BYTES, + ); + methodBlock.setHeader(lenBuf); + } + + const memberBlocks: CalldataBlock[] = []; + _.each(members, (member: DataType, idx: number) => { + const block = member.generateCalldataBlock(value[idx], methodBlock); + memberBlocks.push(block); + }); + methodBlock.setMembers(memberBlocks); + return methodBlock; + } + + protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock { + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + this.getDataItem().name, + this.getSignature(), + parentName, + ); + const memberBlocks: CalldataBlock[] = []; + const childMap = _.cloneDeep(this._memberMap); + _.forOwn(obj, (value: any, key: string) => { + if (!(key in childMap)) { + throw new Error( + `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, + ); + } + const block = this._members[this._memberMap[key]].generateCalldataBlock(value, methodBlock); + memberBlocks.push(block); + delete childMap[key]; + }); + + if (Object.keys(childMap).length !== 0) { + throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); + } + + methodBlock.setMembers(memberBlocks); + return methodBlock; + } + + protected _computeSignatureOfMembers(): string { + // Compute signature of members + let signature = `(`; + _.each(this._members, (member: DataType, i: number) => { + signature += member.getSignature(); + if (i < this._members.length - 1) { + signature += ','; + } + }); + signature += ')'; + return signature; + } + + private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { + // Sanity check + if (dataItem.components === undefined) { + throw new Error(`Expected components`); + } + + const members: DataType[] = []; + const memberMap: MemberMap = {}; + _.each(dataItem.components, (memberItem: DataItem) => { + const childDataItem: DataItem = { + type: memberItem.type, + name: `${dataItem.name}.${memberItem.name}`, + }; + const components = memberItem.components; + if (components !== undefined) { + childDataItem.components = components; + } + const child = this.getFactory().create(childDataItem, this); + memberMap[memberItem.name] = members.length; + members.push(child); + }); + + return [members, memberMap]; + } + + private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { + const members: DataType[] = []; + const memberMap: MemberMap = {}; + const range = _.range(length); + _.each(range, (idx: number) => { + const childDataItem: DataItem = { + type: this._arrayElementType ? this._arrayElementType : '', + name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`, + }; + const components = dataItem.components; + if (components !== undefined) { + childDataItem.components = components; + } + const child = this.getFactory().create(childDataItem, this); + memberMap[idx.toString(Constants.DEC_BASE)] = members.length; + members.push(child); + }); + + return [members, memberMap]; + } +} diff --git a/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts new file mode 100644 index 0000000000..767e64f514 --- /dev/null +++ b/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts @@ -0,0 +1,38 @@ +import { DataItem } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { CalldataBlock, PayloadCalldataBlock, RawCalldata } from '../calldata'; +import { DecodingRules } from '../utils/rules'; + +import { DataType } from './data_type'; +import { DataTypeFactory } from './interfaces'; + +export abstract class PayloadDataType extends DataType { + protected _hasConstantSize: boolean; + + public constructor(dataItem: DataItem, factory: DataTypeFactory, hasConstantSize: boolean) { + super(dataItem, factory); + this._hasConstantSize = hasConstantSize; + } + + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { + const encodedValue = this.encodeValue(value); + const name = this.getDataItem().name; + const signature = this.getSignature(); + const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const block = new PayloadCalldataBlock(name, signature, parentName, encodedValue); + return block; + } + + public generateValue(calldata: RawCalldata, rules: DecodingRules): any { + const value = this.decodeValue(calldata); + return value; + } + + public isStatic(): boolean { + return this._hasConstantSize; + } + + public abstract encodeValue(value: any): Buffer; + public abstract decodeValue(calldata: RawCalldata): any; +} From 5a748fb4e5ce9603cf100af5d46c323934ab17ad Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 13:56:37 -0800 Subject: [PATCH 150/230] Split ABI Encoder/Decoder tests into separate files --- .../src/abi_encoder/evm_data_types/address.ts | 2 +- .../evm_data_types/static_bytes.ts | 2 +- .../abi_samples/method_abis.ts} | 0 .../abi_samples}/optimizer_abis.ts | 0 .../abi_samples}/return_value_abis.ts | 0 .../test/abi_encoder/evm_data_types_test.ts | 1094 ++++++++++ .../utils/test/abi_encoder/methods_test.ts | 401 ++++ .../utils/test/abi_encoder/optimizer_test.ts | 293 +++ .../test/abi_encoder/return_values_test.ts | 78 + packages/utils/test/abi_encoder_test.ts | 1852 ----------------- 10 files changed, 1868 insertions(+), 1854 deletions(-) rename packages/utils/test/{abi_samples.ts => abi_encoder/abi_samples/method_abis.ts} (100%) rename packages/utils/test/{ => abi_encoder/abi_samples}/optimizer_abis.ts (100%) rename packages/utils/test/{ => abi_encoder/abi_samples}/return_value_abis.ts (100%) create mode 100644 packages/utils/test/abi_encoder/evm_data_types_test.ts create mode 100644 packages/utils/test/abi_encoder/methods_test.ts create mode 100644 packages/utils/test/abi_encoder/optimizer_test.ts create mode 100644 packages/utils/test/abi_encoder/return_values_test.ts delete mode 100644 packages/utils/test/abi_encoder_test.ts diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 707e265f8c..0107fdc50c 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -13,7 +13,7 @@ export class Address extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - - Address._ADDRESS_SIZE_IN_BYTES; + Address._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { return type === 'address'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 90e872c78e..4e49db6099 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -46,7 +46,7 @@ export class StaticBytes extends PayloadDataType { if (valueBuf.byteLength > this._width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { diff --git a/packages/utils/test/abi_samples.ts b/packages/utils/test/abi_encoder/abi_samples/method_abis.ts similarity index 100% rename from packages/utils/test/abi_samples.ts rename to packages/utils/test/abi_encoder/abi_samples/method_abis.ts diff --git a/packages/utils/test/optimizer_abis.ts b/packages/utils/test/abi_encoder/abi_samples/optimizer_abis.ts similarity index 100% rename from packages/utils/test/optimizer_abis.ts rename to packages/utils/test/abi_encoder/abi_samples/optimizer_abis.ts diff --git a/packages/utils/test/return_value_abis.ts b/packages/utils/test/abi_encoder/abi_samples/return_value_abis.ts similarity index 100% rename from packages/utils/test/return_value_abis.ts rename to packages/utils/test/abi_encoder/abi_samples/return_value_abis.ts diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts new file mode 100644 index 0000000000..9c3e3c0f9f --- /dev/null +++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts @@ -0,0 +1,1094 @@ +/* tslint:disable max-file-line-count */ +import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; +import 'mocha'; + +import { AbiEncoder, BigNumber } from '../../src/'; +import { chaiSetup } from '../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { + describe('Array', () => { + it('Fixed size; Static elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'int[2]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const args = [new BigNumber(5), new BigNumber(6)]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Dynamic size; Static elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'int[]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const args = [new BigNumber(5), new BigNumber(6)]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Fixed size; Dynamic elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'string[2]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const args = ['Hello', 'world']; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Dynamic size; Dynamic elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'string[]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const args = ['Hello', 'world']; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Dynamic Size; Multidimensional; Dynamic Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes[][]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617']; + const array3 = ['0x18192021']; + const args = [array1, array2, array3]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405060708000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000041011121300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414151617000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Dynamic Size; Multidimensional; Static Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes4[][]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617']; + const array3 = ['0x18192021']; + const args = [array1, array2, array3]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000301020304000000000000000000000000000000000000000000000000000000000506070800000000000000000000000000000000000000000000000000000000091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021011121300000000000000000000000000000000000000000000000000000000141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Static Size; Multidimensional; Static Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes4[3][2]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617', '0x18192021']; + const args = [array1, array2]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x010203040000000000000000000000000000000000000000000000000000000005060708000000000000000000000000000000000000000000000000000000000910111200000000000000000000000000000000000000000000000000000000101112130000000000000000000000000000000000000000000000000000000014151617000000000000000000000000000000000000000000000000000000001819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Static Size; Multidimensional; Dynamic Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'bytes[3][2]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708', '0x09101112']; + const array2 = ['0x10111213', '0x14151617', '0x18192021']; + const args = [array1, array2]; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000401020304000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004050607080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040910111200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000410111213000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Static size; Too Few Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'string[3]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const args = ['Hello', 'world']; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Expected array of 3 elements, but got array of length 2'); + }); + it('Static size; Too Many Elements', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'string[1]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const args = ['Hello', 'world']; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Expected array of 1 elements, but got array of length 2'); + }); + it('Element Type Mismatch', async () => { + // Create DataType object + const testDataItem = { name: 'testArray', type: 'uint[]' }; + const dataType = new AbiEncoder.Array(testDataItem); + // Construct args to be encoded + const args = [new BigNumber(1), 'Bad Argument']; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + }); + + describe('Tuple', () => { + it('Static elements only', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field_1: new BigNumber(-5), field_2: true }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Dynamic elements only', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'string' }, { name: 'field_2', type: 'bytes' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field_1: 'Hello, World!', field_2: '0xabcdef0123456789' }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Nested Static Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'uint[2]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field: [new BigNumber(1), new BigNumber(2)] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Nested Dynamic Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'uint[]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field: [new BigNumber(1), new BigNumber(2)] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Nested Static Multidimensional Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'bytes4[2][2]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708']; + const array2 = ['0x09101112', '0x13141516']; + const args = { field: [array1, array2] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Nested Dynamic Multidimensional Array', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field', type: 'bytes[2][2]' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const array1 = ['0x01020304', '0x05060708']; + const array2 = ['0x09101112', '0x13141516']; + const args = { field: [array1, array2] }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Static and dynamic elements mixed', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [ + { name: 'field_1', type: 'int32' }, + { name: 'field_2', type: 'string' }, + { name: 'field_3', type: 'bool' }, + { name: 'field_4', type: 'bytes' }, + ], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { + field_1: new BigNumber(-5), + field_2: 'Hello, World!', + field_3: true, + field_4: '0xabcdef0123456789', + }; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; + const decodedArgs = dataType.decode(encodedArgs, decodingRules); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Missing Key', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { field_1: new BigNumber(-5) }; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Could not assign tuple to object: missing keys field_2'); + }); + it('Bad Key', async () => { + // Create DataType object + const testDataItem = { + name: 'Tuple', + type: 'tuple', + components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], + }; + const dataType = new AbiEncoder.Tuple(testDataItem); + // Construct args to be encoded + const args = { unknown_field: new BigNumber(-5) }; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw("Could not assign tuple to object: unrecognized key 'unknown_field' in object Tuple"); + }); + }); + + describe('Address', () => { + it('Valid Address', async () => { + // Create DataType object + const testDataItem = { name: 'Address', type: 'address' }; + const dataType = new AbiEncoder.Address(testDataItem); + // Construct args to be encoded + const args = '0xe41d2489571d322189246dafa5ebde1f4699f498'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Invalid Address - input is not valid hex', async () => { + // Create DataType object + const testDataItem = { name: 'Address', type: 'address' }; + const dataType = new AbiEncoder.Address(testDataItem); + // Construct args to be encoded + const args = 'e4'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); + }); + it('Invalid Address - input is not 20 bytes', async () => { + // Create DataType object + const testDataItem = { name: 'Address', type: 'address' }; + const dataType = new AbiEncoder.Address(testDataItem); + // Construct args to be encoded + const args = '0xe4'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); + }); + }); + + describe('Bool', () => { + it('True', async () => { + // Create DataType object + const testDataItem = { name: 'Boolean', type: 'bool' }; + const dataType = new AbiEncoder.Bool(testDataItem); + // Construct args to be encoded + const args = true; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('False', async () => { + // Create DataType object + const testDataItem = { name: 'Boolean', type: 'bool' }; + const dataType = new AbiEncoder.Bool(testDataItem); + // Construct args to be encoded + const args = false; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + }); + + describe('Integer', () => { + /* tslint:disable custom-no-magic-numbers */ + const max256BitInteger = new BigNumber(2).pow(255).minus(1); + const min256BitInteger = new BigNumber(2).pow(255).times(-1); + const max32BitInteger = new BigNumber(2).pow(31).minus(1); + const min32BitInteger = new BigNumber(2).pow(31).times(-1); + /* tslint:enable custom-no-magic-numbers */ + + it('Int256 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int256 - Negative Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(-1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int256 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = max256BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int256 - Negative Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = min256BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0x8000000000000000000000000000000000000000000000000000000000000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int256 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = max256BitInteger.plus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + it('Int256 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (256)', type: 'int' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = min256BitInteger.minus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + it('Int32 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int32 - Negative Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = new BigNumber(-1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int32 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = max32BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000007fffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int32 - Negative Value', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = min32BitInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Int32 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = max32BitInteger.plus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + it('Int32 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Integer (32)', type: 'int32' }; + const dataType = new AbiEncoder.Int(testDataItem); + // Construct args to be encoded + const args = min32BitInteger.minus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + }); + + describe('Unsigned Integer', () => { + /* tslint:disable custom-no-magic-numbers */ + const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1); + const min256BitUnsignedInteger = new BigNumber(0); + const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1); + const min32BitUnsignedInteger = new BigNumber(0); + /* tslint:enable custom-no-magic-numbers */ + + it('UInt256 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('UInt256 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = max256BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('UInt256 - Zero Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = min256BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('UInt256 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = max256BitUnsignedInteger.plus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + it('UInt256 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = min256BitUnsignedInteger.minus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + it('UInt32 - Positive Base Case', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = new BigNumber(1); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('UInt32 - Positive Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = max32BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000ffffffff'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('UInt32 - Zero Value', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = min32BitUnsignedInteger; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('UInt32 - Value too large', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = max32BitUnsignedInteger.plus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + it('UInt32 - Value too small', async () => { + // Create DataType object + const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; + const dataType = new AbiEncoder.UInt(testDataItem); + // Construct args to be encoded + const args = min32BitUnsignedInteger.minus(1); + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw(); + }); + }); + + describe('Static Bytes', () => { + it('Single Byte (byte)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Byte', type: 'byte' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0x05'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Single Byte (bytes1)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes1', type: 'bytes1' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0x05'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('4 Bytes (bytes4)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0x00010203'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0001020300000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('4 Bytes (bytes4); Encoder must pad input', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x1a18000000000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const paddedArgs = '0x1a180000'; + const paddedArgsAsJson = JSON.stringify(paddedArgs); + expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); + }); + it('32 Bytes (bytes32)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0x0001020304050607080911121314151617181920212223242526272829303132'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x0001020304050607080911121314151617181920212223242526272829303132'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('32 Bytes (bytes32); Encoder must pad input', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18bf61'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const paddedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + const paddedArgsAsJson = JSON.stringify(paddedArgs); + expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); + }); + it('Should throw when pass in too many bytes (bytes4)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0x0102030405'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw( + 'Tried to assign 0x0102030405 (5 bytes), which exceeds max bytes that can be stored in a bytes4', + ); + }); + it('Should throw when pass in too many bytes (bytes32)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0x010203040506070809101112131415161718192021222324252627282930313233'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw( + 'Tried to assign 0x010203040506070809101112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32', + ); + }); + it('Should throw when pass in bad hex (no 0x prefix)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0102030405060708091011121314151617181920212223242526272829303132'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); + }); + it('Should throw when pass in bad hex (include a half-byte)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; + const dataType = new AbiEncoder.StaticBytes(testDataItem); + // Construct args to be encoded + const args = '0x010'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); + }); + }); + + describe('Dynamic Bytes', () => { + it('Fits into one EVM word', async () => { + // Create DataType object + const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.DynamicBytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18bf61'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Spans multiple EVM words', async () => { + // Create DataType object + const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.DynamicBytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const bytesLength = 40; + const args = '0x' + '61'.repeat(bytesLength); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Input as Buffer', async () => { + // Create DataType object + const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.DynamicBytes(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = '0x1a18bf61'; + const argsAsBuffer = ethUtil.toBuffer(args); + // Encode Args and validate result + const encodedArgs = dataType.encode(argsAsBuffer); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Should throw when pass in bad hex (no 0x prefix)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.DynamicBytes(testDataItem); + // Construct args to be encoded + const args = '01'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '01'"); + }); + it('Should throw when pass in bad hex (include a half-byte)', async () => { + // Create DataType object + const testDataItem = { name: 'Static Bytes', type: 'bytes' }; + const dataType = new AbiEncoder.DynamicBytes(testDataItem); + // Construct args to be encoded + const args = '0x010'; + // Encode Args and validate result + expect(() => { + dataType.encode(args); + }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); + }); + }); + + describe('String', () => { + it('Fits into one EVM word', async () => { + // Create DataType object + const testDataItem = { name: 'String', type: 'string' }; + const dataType = new AbiEncoder.String(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const args = 'five'; + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('Spans multiple EVM words', async () => { + // Create DataType object + const testDataItem = { name: 'String', type: 'string' }; + const dataType = new AbiEncoder.String(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const bytesLength = 40; + const args = 'a'.repeat(bytesLength); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + it('String that begins with 0x prefix', async () => { + // Create DataType object + const testDataItem = { name: 'String', type: 'string' }; + const dataType = new AbiEncoder.String(testDataItem); + // Construct args to be encoded + // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. + const strLength = 40; + const args = '0x' + 'a'.repeat(strLength); + // Encode Args and validate result + const encodedArgs = dataType.encode(args); + const expectedEncodedArgs = + '0x000000000000000000000000000000000000000000000000000000000000002a30786161616161616161616161616161616161616161616161616161616161616161616161616161616100000000000000000000000000000000000000000000'; + expect(encodedArgs).to.be.equal(expectedEncodedArgs); + // Decode Encoded Args and validate result + const decodedArgs = dataType.decode(encodedArgs); + const decodedArgsAsJson = JSON.stringify(decodedArgs); + const argsAsJson = JSON.stringify(args); + expect(decodedArgsAsJson).to.be.equal(argsAsJson); + }); + }); +}); diff --git a/packages/utils/test/abi_encoder/methods_test.ts b/packages/utils/test/abi_encoder/methods_test.ts new file mode 100644 index 0000000000..d158b9e5b4 --- /dev/null +++ b/packages/utils/test/abi_encoder/methods_test.ts @@ -0,0 +1,401 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { AbiEncoder, BigNumber } from '../../src/'; +import { chaiSetup } from '../utils/chai_setup'; + +import * as AbiSamples from './abi_samples/method_abis'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('ABI Encoder: Method Encoding / Decoding', () => { + it('Types with default widths', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); + const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Static Tuples (Array has defined length)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); + let value = 0; + const arrayOfTuples = []; + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Static Tuples (Array has dynamic length)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi); + let value = 0; + const arrayOfTuples = []; + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Dynamic Tuples (Array has defined length)', async () => { + // Generate Calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi); + let value = 0; + const arrayOfTuples = []; + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array of Dynamic Tuples (Array has dynamic length)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi); + let value = 0; + const arrayOfTuples = []; + const arrayOfTuplesLength = 8; + for (let i = 0; i < arrayOfTuplesLength; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Multidimensional Arrays / Static Members', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); + // Eight 3-dimensional arrays of uint8[2][2][2] + let value = 0; + const args = []; + const argsLength = 8; + for (let i = 0; i < argsLength; ++i) { + args.push([ + [[new BigNumber(++value), new BigNumber(++value)], [new BigNumber(++value), new BigNumber(++value)]], + [[new BigNumber(++value), new BigNumber(++value)], [new BigNumber(++value), new BigNumber(++value)]], + ]); + } + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; + expect(calldata).to.be.equal(expectedCalldata); + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Multidimensional Arrays / Dynamic Members', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi); + // Eight 3-dimensional arrays of string[2][2][2] + let value = 0; + const args = []; + const argsLength = 4; + for (let i = 0; i < argsLength; ++i) { + args.push([ + [ + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + ], + [ + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + [new BigNumber(++value).toString(), new BigNumber(++value).toString()], + ], + ]); + } + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Fixed Length Array / Dynamic Members', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [['Brave', 'New', 'World']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Fixed Length Array / Dynamic Members', async () => { + // Generaet calldata + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [['Brave', 'New', 'World']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Unfixed Length Array / Dynamic Members ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); + const args = [['Brave', 'New', 'World']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Unfixed Length Array / Static Members ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Fixed Length Array / Static Members ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Array ABI', async () => { + // Generate calldata + const method = new AbiEncoder.Method(AbiSamples.stringAbi); + const args = [['five', 'six', 'seven']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Static Tuple', async () => { + // Generate calldata + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); + const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Dynamic Tuple (Array input)', async () => { + // Generate calldata + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); + const args = [[new BigNumber(5), 'five']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Dynamic Tuple (Object input)', async () => { + // Generate Calldata + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); + const args = [[new BigNumber(5), 'five']]; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Large, Flat ABI', async () => { + // Construct calldata + const method = new AbiEncoder.Method(AbiSamples.largeFlatAbi); + const args = [ + new BigNumber(256745454), + new BigNumber(-256745454), + new BigNumber(434244), + '0x43', + '0x0001020304050607080911121314151617181920212223242526272829303132', + '0x0001020304050607080911121314151617181920212223242526272829303132080911121314151617181920212223242526272829303132', + 'Little peter piper piped a piping pepper pot', + '0xe41d2489571d322189246dafa5ebde1f4699f498', + true, + ]; + // Validate calldata + const calldata = method.encode(args); + const expectedCalldata = + '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + it('Large, Nested ABI', async () => { + // Construct Calldata + const method = new AbiEncoder.Method(AbiSamples.largeNestedAbi); + const someStaticArray = [new BigNumber(127), new BigNumber(14), new BigNumber(54)]; + const someStaticArrayWithDynamicMembers = [ + 'the little piping piper piped a piping pipper papper', + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + ]; + const someDynamicArrayWithDynamicMembers = [ + '0x38745637834987324827439287423897238947239847', + '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398', + '0x283473298473248923749238742398742398472894729843278942374982374892374892743982', + ]; + const some2DArray = [ + [ + 'some string', + 'some another string', + 'there are just too many stringsup in', + 'here', + 'yall ghonna make me lose my mind', + ], + [ + 'the little piping piper piped a piping pipper papper', + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + ], + [], + ]; + const someTuple = { + someUint32: new BigNumber(4037824789), + someStr: + 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', + }; + const someTupleWithDynamicTypes = { + someUint: new BigNumber(4024789), + someStr: 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', + someBytes: '0x29384723894723843743289742389472398473289472348927489274894738427428947389facdea', + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', + }; + const someTupleWithDynamicTypes2 = { + someUint: new BigNumber(9024789), + someStr: 'ksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjkakdhjasjkdhasjkldshdjahdkjsahdaj', + someBytes: '0x29384723894398473289472348927489272384374328974238947274894738427428947389facde1', + someAddress: '0x746dafa5ebde1f4699f4981d3221892e41d24895', + }; + const someTupleWithDynamicTypes3 = { + someUint: new BigNumber(1024789), + someStr: 'sdhsajkdhsajkdhadjkashdjakdhjasjkdhasjkldshdjahdkjsahdajkksadhajkdhsajkdhsadjk', + someBytes: '0x38947238437432829384729742389472398473289472348927489274894738427428947389facdef', + someAddress: '0x89571d322189e415ebde1f4699f498d24246dafa', + }; + const someArrayOfTuplesWithDynamicTypes = [someTupleWithDynamicTypes2, someTupleWithDynamicTypes3]; + const args = { + someStaticArray, + someStaticArrayWithDynamicMembers, + someDynamicArrayWithDynamicMembers, + some2DArray, + someTuple, + someTupleWithDynamicTypes, + someArrayOfTuplesWithDynamicTypes, + }; + const calldata = method.encode(args); + // Validate calldata + const expectedCalldata = + '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + // Validate decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata, { structsAsObjects: true }); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); +}); diff --git a/packages/utils/test/abi_encoder/optimizer_test.ts b/packages/utils/test/abi_encoder/optimizer_test.ts new file mode 100644 index 0000000000..304c9cbc2e --- /dev/null +++ b/packages/utils/test/abi_encoder/optimizer_test.ts @@ -0,0 +1,293 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { AbiEncoder, BigNumber } from '../../src/'; +import { chaiSetup } from '../utils/chai_setup'; + +import * as OptimizedAbis from './abi_samples/optimizer_abis'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { + it('Duplicate Dynamic Arrays with Static Elements', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithStaticElements); + const array1 = [new BigNumber(100), new BigNumber(150)]; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x7221063300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Dynamic Arrays with Dynamic Elements', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithDynamicElements); + const array1 = ['Hello', 'World']; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0xbb4f12e300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Static Arrays with Static Elements (should not optimize)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithStaticElements); + const array1 = [new BigNumber(100), new BigNumber(150)]; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x7f8130430000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + const unoptimizedCalldata = method.encode(args); + expect(optimizedCalldata).to.be.equal(unoptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Static Arrays with Dynamic Elements', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithDynamicElements); + const array1 = ['Hello', 'World']; + const array2 = array1; + const args = [array1, array2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x9fe31f8e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Array Elements (should optimize)', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateArrayElements); + const strings = ['Hello', 'World', 'Hello', 'World']; + const args = [strings]; + // Validate calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Tuple Fields', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTupleFields); + const tuple = ['Hello', 'Hello']; + const args = [tuple]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x16780a5e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Strings', async () => { + // Description: + // Two dynamic arrays with the same values. + // In the optimized calldata, only one set of elements should be included. + // Both arrays should point to this set. + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateStrings); + const args = ['Hello', 'Hello']; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x07370bfa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Bytes', async () => { + // Description: + // Two dynamic arrays with the same values. + // In the optimized calldata, only one set of elements should be included. + // Both arrays should point to this set. + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateBytes); + const value = '0x01020304050607080910111213141516171819202122232425262728293031323334353637383940'; + const args = [value, value]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x6045e42900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002801020304050607080910111213141516171819202122232425262728293031323334353637383940000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Tuples', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples); + const tuple1 = ['Hello, World!', new BigNumber(424234)]; + const tuple2 = tuple1; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000006792a000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Fields Across Two Tuples', async () => { + // Description: + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples); + const tuple1 = ['Hello, World!', new BigNumber(1)]; + const tuple2 = [tuple1[0], new BigNumber(2)]; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Arrays, Nested in Separate Tuples', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateArraysNestedInTuples); + const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200)]; + const tuple1 = [array]; + const tuple2 = [array, 'extra argument to prevent exactly matching the tuples']; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x18970a9e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c80000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Tuples, Nested in Separate Tuples', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuplesNestedInTuples); + const nestedTuple = ['Hello, World!']; + const tuple1 = [nestedTuple]; + const tuple2 = [nestedTuple, 'extra argument to prevent exactly matching the tuples']; + const args = [tuple1, tuple2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x0b4d2e6a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Two-Dimensional Arrays', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays); + const twoDimArray1 = [['Hello', 'World'], ['Foo', 'Bar', 'Zaa']]; + const twoDimArray2 = twoDimArray1; + const args = [twoDimArray1, twoDimArray2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: false }); + const expectedOptimizedCalldata = + '0x0d28c4f9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Duplicate Array, Nested within Separate Two-Dimensional Arrays', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays); + const twoDimArray1 = [['Hello', 'World'], ['Foo']]; + const twoDimArray2 = [['Hello', 'World'], ['Bar']]; + const args = [twoDimArray1, twoDimArray2]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x0d28c4f900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003466f6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034261720000000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Array Elements Duplicated as Tuple Fields', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsTupleFields); + const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)]; + const tuple = [[array[0]], [array[1]], [array[2]], [array[3]]]; + const args = [array, tuple]; + // Validata calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0x5b5c78fd0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000e1'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + it('Array Elements Duplicated as Separate Parameter', async () => { + // Generate calldata + const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsSeparateParameter); + const array = ['Hello', 'Hello', 'Hello', 'World']; + const str = 'Hello'; + const args = [array, str]; + // Validate calldata + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = + '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + // Validate decoding + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + }); +}); diff --git a/packages/utils/test/abi_encoder/return_values_test.ts b/packages/utils/test/abi_encoder/return_values_test.ts new file mode 100644 index 0000000000..850cb1746a --- /dev/null +++ b/packages/utils/test/abi_encoder/return_values_test.ts @@ -0,0 +1,78 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { AbiEncoder } from '../../src/'; +import { chaiSetup } from '../utils/chai_setup'; + +import * as ReturnValueAbis from './abi_samples/return_value_abis'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('ABI Encoder: Return Value Encoding/Decoding', () => { + it('No Return Value', async () => { + // Decode return value + const method = new AbiEncoder.Method(ReturnValueAbis.noReturnValues); + const returnValue = '0x'; + const decodedReturnValue = method.decodeReturnValues(returnValue); + const expectedDecodedReturnValue: any[] = []; + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(expectedDecodedReturnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Single static return value', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.singleStaticReturnValue); + const returnValue = ['0x01020304']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Multiple static return values', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.multipleStaticReturnValues); + const returnValue = ['0x01020304', '0x05060708']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Single dynamic return value', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.singleDynamicReturnValue); + const returnValue = ['0x01020304']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Multiple dynamic return values', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.multipleDynamicReturnValues); + const returnValue = ['0x01020304', '0x05060708']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); + it('Mixed static/dynamic return values', async () => { + // Generate Return Value + const method = new AbiEncoder.Method(ReturnValueAbis.mixedStaticAndDynamicReturnValues); + const returnValue = ['0x01020304', '0x05060708']; + const encodedReturnValue = method.encodeReturnValues(returnValue); + const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); + // Validate decoded return value + const decodedReturnValueJson = JSON.stringify(decodedReturnValue); + const expectedDecodedReturnValueJson = JSON.stringify(returnValue); + expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + }); +}); diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts deleted file mode 100644 index 35eb8d0a98..0000000000 --- a/packages/utils/test/abi_encoder_test.ts +++ /dev/null @@ -1,1852 +0,0 @@ -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import 'mocha'; - -import { AbiEncoder, BigNumber } from '../src/'; - -import * as AbiSamples from './abi_samples'; -import * as OptimizedAbis from './optimizer_abis'; -import * as ReturnValueAbis from './return_value_abis'; -import { chaiSetup } from './utils/chai_setup'; - -chaiSetup.configure(); -const expect = chai.expect; - -describe.only('ABI Encoder', () => { - describe('Decode Return Values', () => { - it('No Return Value', async () => { - // Decode return value - const method = new AbiEncoder.Method(ReturnValueAbis.noReturnValues); - const returnValue = '0x'; - const decodedReturnValue = method.decodeReturnValues(returnValue); - const expectedDecodedReturnValue: any[] = []; - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(expectedDecodedReturnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); - }); - it('Single static return value', async () => { - // Generate Return Value - const method = new AbiEncoder.Method(ReturnValueAbis.singleStaticReturnValue); - const returnValue = ['0x01020304']; - const encodedReturnValue = method.encodeReturnValues(returnValue); - const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); - // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); - }); - it('Multiple static return values', async () => { - // Generate Return Value - const method = new AbiEncoder.Method(ReturnValueAbis.multipleStaticReturnValues); - const returnValue = ['0x01020304', '0x05060708']; - const encodedReturnValue = method.encodeReturnValues(returnValue); - const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); - // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); - }); - it('Single dynamic return value', async () => { - // Generate Return Value - const method = new AbiEncoder.Method(ReturnValueAbis.singleDynamicReturnValue); - const returnValue = ['0x01020304']; - const encodedReturnValue = method.encodeReturnValues(returnValue); - const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); - // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); - }); - it('Multiple dynamic return values', async () => { - // Generate Return Value - const method = new AbiEncoder.Method(ReturnValueAbis.multipleDynamicReturnValues); - const returnValue = ['0x01020304', '0x05060708']; - const encodedReturnValue = method.encodeReturnValues(returnValue); - const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); - // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); - }); - it('Mixed static/dynamic return values', async () => { - // Generate Return Value - const method = new AbiEncoder.Method(ReturnValueAbis.mixedStaticAndDynamicReturnValues); - const returnValue = ['0x01020304', '0x05060708']; - const encodedReturnValue = method.encodeReturnValues(returnValue); - const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); - // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); - }); - }); - - describe('Optimizer', () => { - it('Duplicate Dynamic Arrays with Static Elements', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithStaticElements); - const array1 = [new BigNumber(100), new BigNumber(150)]; - const array2 = array1; - const args = [array1, array2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x7221063300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Dynamic Arrays with Dynamic Elements', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithDynamicElements); - const array1 = ['Hello', 'World']; - const array2 = array1; - const args = [array1, array2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0xbb4f12e300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Static Arrays with Static Elements (should not optimize)', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithStaticElements); - const array1 = [new BigNumber(100), new BigNumber(150)]; - const array2 = array1; - const args = [array1, array2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x7f8130430000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - const unoptimizedCalldata = method.encode(args); - expect(optimizedCalldata).to.be.equal(unoptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Static Arrays with Dynamic Elements', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithDynamicElements); - const array1 = ['Hello', 'World']; - const array2 = array1; - const args = [array1, array2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x9fe31f8e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Array Elements (should optimize)', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateArrayElements); - const strings = ['Hello', 'World', 'Hello', 'World']; - const args = [strings]; - // Validate calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Tuple Fields', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateTupleFields); - const tuple = ['Hello', 'Hello']; - const args = [tuple]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x16780a5e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Strings', async () => { - // Description: - // Two dynamic arrays with the same values. - // In the optimized calldata, only one set of elements should be included. - // Both arrays should point to this set. - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateStrings); - const args = ['Hello', 'Hello']; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x07370bfa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Bytes', async () => { - // Description: - // Two dynamic arrays with the same values. - // In the optimized calldata, only one set of elements should be included. - // Both arrays should point to this set. - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateBytes); - const value = '0x01020304050607080910111213141516171819202122232425262728293031323334353637383940'; - const args = [value, value]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x6045e42900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002801020304050607080910111213141516171819202122232425262728293031323334353637383940000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Tuples', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples); - const tuple1 = ['Hello, World!', new BigNumber(424234)]; - const tuple2 = tuple1; - const args = [tuple1, tuple2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000006792a000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Fields Across Two Tuples', async () => { - // Description: - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples); - const tuple1 = ['Hello, World!', new BigNumber(1)]; - const tuple2 = [tuple1[0], new BigNumber(2)]; - const args = [tuple1, tuple2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Arrays, Nested in Separate Tuples', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateArraysNestedInTuples); - const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200)]; - const tuple1 = [array]; - const tuple2 = [array, 'extra argument to prevent exactly matching the tuples']; - const args = [tuple1, tuple2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x18970a9e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c80000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Tuples, Nested in Separate Tuples', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuplesNestedInTuples); - const nestedTuple = ['Hello, World!']; - const tuple1 = [nestedTuple]; - const tuple2 = [nestedTuple, 'extra argument to prevent exactly matching the tuples']; - const args = [tuple1, tuple2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x0b4d2e6a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Two-Dimensional Arrays', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays); - const twoDimArray1 = [['Hello', 'World'], ['Foo', 'Bar', 'Zaa']]; - const twoDimArray2 = twoDimArray1; - const args = [twoDimArray1, twoDimArray2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: false }); - const expectedOptimizedCalldata = - '0x0d28c4f9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Duplicate Array, Nested within Separate Two-Dimensional Arrays', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays); - const twoDimArray1 = [['Hello', 'World'], ['Foo']]; - const twoDimArray2 = [['Hello', 'World'], ['Bar']]; - const args = [twoDimArray1, twoDimArray2]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x0d28c4f900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003466f6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034261720000000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Array Elements Duplicated as Tuple Fields', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsTupleFields); - const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)]; - const tuple = [[array[0]], [array[1]], [array[2]], [array[3]]]; - const args = [array, tuple]; - // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0x5b5c78fd0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000e1'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - it('Array Elements Duplicated as Separate Parameter', async () => { - // Generate calldata - const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsSeparateParameter); - const array = ['Hello', 'Hello', 'Hello', 'World']; - const str = 'Hello'; - const args = [array, str]; - // Validate calldata - const optimizedCalldata = method.encode(args, { optimize: true }); - const expectedOptimizedCalldata = - '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; - expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); - // Validate decoding - const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); - }); - }); - - describe('Method ABIs', () => { - it('Types with default widths', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); - const args = [ - new BigNumber(1), - new BigNumber(-1), - '0x56', - [new BigNumber(1)], - [new BigNumber(-1)], - ['0x56'], - ]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Array of Static Tuples (Array has defined length)', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); - let value = 0; - const arrayOfTuples = []; - const arrayOfTuplesLength = 8; - for (let i = 0; i < arrayOfTuplesLength; ++i) { - arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Array of Static Tuples (Array has dynamic length)', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi); - let value = 0; - const arrayOfTuples = []; - const arrayOfTuplesLength = 8; - for (let i = 0; i < arrayOfTuplesLength; ++i) { - arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Array of Dynamic Tuples (Array has defined length)', async () => { - // Generate Calldata - const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi); - let value = 0; - const arrayOfTuples = []; - const arrayOfTuplesLength = 8; - for (let i = 0; i < arrayOfTuplesLength; ++i) { - arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Array of Dynamic Tuples (Array has dynamic length)', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi); - let value = 0; - const arrayOfTuples = []; - const arrayOfTuplesLength = 8; - for (let i = 0; i < arrayOfTuplesLength; ++i) { - arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); - } - const args = [arrayOfTuples]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Multidimensional Arrays / Static Members', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); - // Eight 3-dimensional arrays of uint8[2][2][2] - let value = 0; - const args = []; - const argsLength = 8; - for (let i = 0; i < argsLength; ++i) { - args.push([ - [ - [new BigNumber(++value), new BigNumber(++value)], - [new BigNumber(++value), new BigNumber(++value)], - ], - [ - [new BigNumber(++value), new BigNumber(++value)], - [new BigNumber(++value), new BigNumber(++value)], - ], - ]); - } - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; - expect(calldata).to.be.equal(expectedCalldata); - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Multidimensional Arrays / Dynamic Members', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi); - // Eight 3-dimensional arrays of string[2][2][2] - let value = 0; - const args = []; - const argsLength = 4; - for (let i = 0; i < argsLength; ++i) { - args.push([ - [ - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - ], - [ - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - [new BigNumber(++value).toString(), new BigNumber(++value).toString()], - ], - ]); - } - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Fixed Length Array / Dynamic Members', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); - const args = [['Brave', 'New', 'World']]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Fixed Length Array / Dynamic Members', async () => { - // Generaet calldata - const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); - const args = [['Brave', 'New', 'World']]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Unfixed Length Array / Dynamic Members ABI', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); - const args = [['Brave', 'New', 'World']]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Unfixed Length Array / Static Members ABI', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Fixed Length Array / Static Members ABI', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Array ABI', async () => { - // Generate calldata - const method = new AbiEncoder.Method(AbiSamples.stringAbi); - const args = [['five', 'six', 'seven']]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Static Tuple', async () => { - // Generate calldata - // This is dynamic because it has dynamic members - const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); - const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Dynamic Tuple (Array input)', async () => { - // Generate calldata - // This is dynamic because it has dynamic members - const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); - const args = [[new BigNumber(5), 'five']]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Dynamic Tuple (Object input)', async () => { - // Generate Calldata - // This is dynamic because it has dynamic members - const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); - const args = [[new BigNumber(5), 'five']]; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Large, Flat ABI', async () => { - // Construct calldata - const method = new AbiEncoder.Method(AbiSamples.largeFlatAbi); - const args = [ - new BigNumber(256745454), - new BigNumber(-256745454), - new BigNumber(434244), - '0x43', - '0x0001020304050607080911121314151617181920212223242526272829303132', - '0x0001020304050607080911121314151617181920212223242526272829303132080911121314151617181920212223242526272829303132', - 'Little peter piper piped a piping pepper pot', - '0xe41d2489571d322189246dafa5ebde1f4699f498', - true, - ]; - // Validate calldata - const calldata = method.encode(args); - const expectedCalldata = - '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - it('Large, Nested ABI', async () => { - // Construct Calldata - const method = new AbiEncoder.Method(AbiSamples.largeNestedAbi); - const someStaticArray = [new BigNumber(127), new BigNumber(14), new BigNumber(54)]; - const someStaticArrayWithDynamicMembers = [ - 'the little piping piper piped a piping pipper papper', - 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', - ]; - const someDynamicArrayWithDynamicMembers = [ - '0x38745637834987324827439287423897238947239847', - '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398', - '0x283473298473248923749238742398742398472894729843278942374982374892374892743982', - ]; - const some2DArray = [ - [ - 'some string', - 'some another string', - 'there are just too many stringsup in', - 'here', - 'yall ghonna make me lose my mind', - ], - [ - 'the little piping piper piped a piping pipper papper', - 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', - ], - [], - ]; - const someTuple = { - someUint32: new BigNumber(4037824789), - someStr: - 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.', - }; - const someTupleWithDynamicTypes = { - someUint: new BigNumber(4024789), - someStr: 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk', - someBytes: '0x29384723894723843743289742389472398473289472348927489274894738427428947389facdea', - someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', - }; - const someTupleWithDynamicTypes2 = { - someUint: new BigNumber(9024789), - someStr: 'ksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjkakdhjasjkdhasjkldshdjahdkjsahdaj', - someBytes: '0x29384723894398473289472348927489272384374328974238947274894738427428947389facde1', - someAddress: '0x746dafa5ebde1f4699f4981d3221892e41d24895', - }; - const someTupleWithDynamicTypes3 = { - someUint: new BigNumber(1024789), - someStr: 'sdhsajkdhsajkdhadjkashdjakdhjasjkdhasjkldshdjahdkjsahdajkksadhajkdhsajkdhsadjk', - someBytes: '0x38947238437432829384729742389472398473289472348927489274894738427428947389facdef', - someAddress: '0x89571d322189e415ebde1f4699f498d24246dafa', - }; - const someArrayOfTuplesWithDynamicTypes = [someTupleWithDynamicTypes2, someTupleWithDynamicTypes3]; - const args = { - someStaticArray, - someStaticArrayWithDynamicMembers, - someDynamicArrayWithDynamicMembers, - some2DArray, - someTuple, - someTupleWithDynamicTypes, - someArrayOfTuplesWithDynamicTypes, - }; - const calldata = method.encode(args); - // Validate calldata - const expectedCalldata = - '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; - expect(calldata).to.be.equal(expectedCalldata); - // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); - const decodedValue = method.decode(calldata, { structsAsObjects: true }); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); - }); - }); - - describe('Array', () => { - it('Fixed size; Static elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'int[2]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const args = [new BigNumber(5), new BigNumber(6)]; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Dynamic size; Static elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'int[]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const args = [new BigNumber(5), new BigNumber(6)]; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Fixed size; Dynamic elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'string[2]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const args = ['Hello', 'world']; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Dynamic size; Dynamic elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'string[]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const args = ['Hello', 'world']; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Dynamic Size; Multidimensional; Dynamic Elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'bytes[][]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const array1 = ['0x01020304', '0x05060708', '0x09101112']; - const array2 = ['0x10111213', '0x14151617']; - const array3 = ['0x18192021']; - const args = [array1, array2, array3]; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405060708000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000041011121300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414151617000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Dynamic Size; Multidimensional; Static Elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'bytes4[][]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const array1 = ['0x01020304', '0x05060708', '0x09101112']; - const array2 = ['0x10111213', '0x14151617']; - const array3 = ['0x18192021']; - const args = [array1, array2, array3]; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000301020304000000000000000000000000000000000000000000000000000000000506070800000000000000000000000000000000000000000000000000000000091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021011121300000000000000000000000000000000000000000000000000000000141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011819202100000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Static Size; Multidimensional; Static Elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'bytes4[3][2]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const array1 = ['0x01020304', '0x05060708', '0x09101112']; - const array2 = ['0x10111213', '0x14151617', '0x18192021']; - const args = [array1, array2]; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x010203040000000000000000000000000000000000000000000000000000000005060708000000000000000000000000000000000000000000000000000000000910111200000000000000000000000000000000000000000000000000000000101112130000000000000000000000000000000000000000000000000000000014151617000000000000000000000000000000000000000000000000000000001819202100000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Static Size; Multidimensional; Dynamic Elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'bytes[3][2]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const array1 = ['0x01020304', '0x05060708', '0x09101112']; - const array2 = ['0x10111213', '0x14151617', '0x18192021']; - const args = [array1, array2]; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000401020304000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004050607080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040910111200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000410111213000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Static size; Too Few Elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'string[3]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const args = ['Hello', 'world']; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw('Expected array of 3 elements, but got array of length 2'); - }); - it('Static size; Too Many Elements', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'string[1]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const args = ['Hello', 'world']; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw('Expected array of 1 elements, but got array of length 2'); - }); - it('Element Type Mismatch', async () => { - // Create DataType object - const testDataItem = { name: 'testArray', type: 'uint[]' }; - const dataType = new AbiEncoder.Array(testDataItem); - // Construct args to be encoded - const args = [new BigNumber(1), 'Bad Argument']; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - }); - - describe('Tuple', () => { - it('Static elements only', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const args = { field_1: new BigNumber(-5), field_2: true }; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; - const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Dynamic elements only', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field_1', type: 'string' }, { name: 'field_2', type: 'bytes' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const args = { field_1: 'Hello, World!', field_2: '0xabcdef0123456789' }; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; - const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Nested Static Array', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field', type: 'uint[2]' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const args = { field: [new BigNumber(1), new BigNumber(2)] }; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; - const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Nested Dynamic Array', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field', type: 'uint[]' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const args = { field: [new BigNumber(1), new BigNumber(2)] }; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; - const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Nested Static Multidimensional Array', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field', type: 'bytes4[2][2]' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const array1 = ['0x01020304', '0x05060708']; - const array2 = ['0x09101112', '0x13141516']; - const args = { field: [array1, array2] }; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; - const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Nested Dynamic Multidimensional Array', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field', type: 'bytes[2][2]' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const array1 = ['0x01020304', '0x05060708']; - const array2 = ['0x09101112', '0x13141516']; - const args = { field: [array1, array2] }; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; - const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Static and dynamic elements mixed', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [ - { name: 'field_1', type: 'int32' }, - { name: 'field_2', type: 'string' }, - { name: 'field_3', type: 'bool' }, - { name: 'field_4', type: 'bytes' }, - ], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const args = { - field_1: new BigNumber(-5), - field_2: 'Hello, World!', - field_3: true, - field_4: '0xabcdef0123456789', - }; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; - const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Missing Key', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const args = { field_1: new BigNumber(-5) }; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw('Could not assign tuple to object: missing keys field_2'); - }); - it('Bad Key', async () => { - // Create DataType object - const testDataItem = { - name: 'Tuple', - type: 'tuple', - components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }], - }; - const dataType = new AbiEncoder.Tuple(testDataItem); - // Construct args to be encoded - const args = { unknown_field: new BigNumber(-5) }; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw("Could not assign tuple to object: unrecognized key 'unknown_field' in object Tuple"); - }); - }); - - describe('Address', () => { - it('Valid Address', async () => { - // Create DataType object - const testDataItem = { name: 'Address', type: 'address' }; - const dataType = new AbiEncoder.Address(testDataItem); - // Construct args to be encoded - const args = '0xe41d2489571d322189246dafa5ebde1f4699f498'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Invalid Address - input is not valid hex', async () => { - // Create DataType object - const testDataItem = { name: 'Address', type: 'address' }; - const dataType = new AbiEncoder.Address(testDataItem); - // Construct args to be encoded - const args = 'e4'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); - }); - it('Invalid Address - input is not 20 bytes', async () => { - // Create DataType object - const testDataItem = { name: 'Address', type: 'address' }; - const dataType = new AbiEncoder.Address(testDataItem); - // Construct args to be encoded - const args = '0xe4'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); - }); - }); - - describe('Bool', () => { - it('True', async () => { - // Create DataType object - const testDataItem = { name: 'Boolean', type: 'bool' }; - const dataType = new AbiEncoder.Bool(testDataItem); - // Construct args to be encoded - const args = true; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('False', async () => { - // Create DataType object - const testDataItem = { name: 'Boolean', type: 'bool' }; - const dataType = new AbiEncoder.Bool(testDataItem); - // Construct args to be encoded - const args = false; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - }); - - describe('Integer', () => { - /* tslint:disable custom-no-magic-numbers */ - const max256BitInteger = new BigNumber(2).pow(255).minus(1); - const min256BitInteger = new BigNumber(2).pow(255).times(-1); - const max32BitInteger = new BigNumber(2).pow(31).minus(1); - const min32BitInteger = new BigNumber(2).pow(31).times(-1); - /* tslint:enable custom-no-magic-numbers */ - - it('Int256 - Positive Base Case', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (256)', type: 'int' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = new BigNumber(1); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int256 - Negative Base Case', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (256)', type: 'int' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = new BigNumber(-1); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int256 - Positive Value', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (256)', type: 'int' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = max256BitInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int256 - Negative Value', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (256)', type: 'int' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = min256BitInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = `0x8000000000000000000000000000000000000000000000000000000000000000`; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int256 - Value too large', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (256)', type: 'int' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = max256BitInteger.plus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - it('Int256 - Value too small', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (256)', type: 'int' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = min256BitInteger.minus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - it('Int32 - Positive Base Case', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (32)', type: 'int32' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = new BigNumber(1); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int32 - Negative Base Case', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (32)', type: 'int32' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = new BigNumber(-1); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int32 - Positive Value', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (32)', type: 'int32' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = max32BitInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000007fffffff'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int32 - Negative Value', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (32)', type: 'int32' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = min32BitInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000`; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Int32 - Value too large', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (32)', type: 'int32' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = max32BitInteger.plus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - it('Int32 - Value too small', async () => { - // Create DataType object - const testDataItem = { name: 'Integer (32)', type: 'int32' }; - const dataType = new AbiEncoder.Int(testDataItem); - // Construct args to be encoded - const args = min32BitInteger.minus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - }); - - describe('Unsigned Integer', () => { - /* tslint:disable custom-no-magic-numbers */ - const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1); - const min256BitUnsignedInteger = new BigNumber(0); - const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1); - const min32BitUnsignedInteger = new BigNumber(0); - /* tslint:enable custom-no-magic-numbers */ - - it('UInt256 - Positive Base Case', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = new BigNumber(1); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('UInt256 - Positive Value', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = max256BitUnsignedInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('UInt256 - Zero Value', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = min256BitUnsignedInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('UInt256 - Value too large', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = max256BitUnsignedInteger.plus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - it('UInt256 - Value too small', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = min256BitUnsignedInteger.minus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - it('UInt32 - Positive Base Case', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = new BigNumber(1); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('UInt32 - Positive Value', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = max32BitUnsignedInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000ffffffff'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('UInt32 - Zero Value', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = min32BitUnsignedInteger; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('UInt32 - Value too large', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = max32BitUnsignedInteger.plus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - it('UInt32 - Value too small', async () => { - // Create DataType object - const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' }; - const dataType = new AbiEncoder.UInt(testDataItem); - // Construct args to be encoded - const args = min32BitUnsignedInteger.minus(1); - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw(); - }); - }); - - describe('Static Bytes', () => { - it('Single Byte (byte)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Byte', type: 'byte' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0x05'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Single Byte (bytes1)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes1', type: 'bytes1' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0x05'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('4 Bytes (bytes4)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0x00010203'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0001020300000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('4 Bytes (bytes4); Encoder must pad input', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = '0x1a18'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x1a18000000000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const paddedArgs = '0x1a180000'; - const paddedArgsAsJson = JSON.stringify(paddedArgs); - expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); - }); - it('32 Bytes (bytes32)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0x0001020304050607080911121314151617181920212223242526272829303132'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x0001020304050607080911121314151617181920212223242526272829303132'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('32 Bytes (bytes32); Encoder must pad input', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = '0x1a18bf61'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const paddedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; - const paddedArgsAsJson = JSON.stringify(paddedArgs); - expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); - }); - it('Should throw when pass in too many bytes (bytes4)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes4', type: 'bytes4' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0x0102030405'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw( - 'Tried to assign 0x0102030405 (5 bytes), which exceeds max bytes that can be stored in a bytes4', - ); - }); - it('Should throw when pass in too many bytes (bytes32)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0x010203040506070809101112131415161718192021222324252627282930313233'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw( - 'Tried to assign 0x010203040506070809101112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32', - ); - }); - it('Should throw when pass in bad hex (no 0x prefix)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0102030405060708091011121314151617181920212223242526272829303132'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); - }); - it('Should throw when pass in bad hex (include a half-byte)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes32', type: 'bytes32' }; - const dataType = new AbiEncoder.StaticBytes(testDataItem); - // Construct args to be encoded - const args = '0x010'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); - }); - }); - - describe('Dynamic Bytes', () => { - it('Fits into one EVM word', async () => { - // Create DataType object - const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.DynamicBytes(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = '0x1a18bf61'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Spans multiple EVM words', async () => { - // Create DataType object - const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.DynamicBytes(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const bytesLength = 40; - const args = '0x' + '61'.repeat(bytesLength); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Input as Buffer', async () => { - // Create DataType object - const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.DynamicBytes(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = '0x1a18bf61'; - const argsAsBuffer = ethUtil.toBuffer(args); - // Encode Args and validate result - const encodedArgs = dataType.encode(argsAsBuffer); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Should throw when pass in bad hex (no 0x prefix)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.DynamicBytes(testDataItem); - // Construct args to be encoded - const args = '01'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '01'"); - }); - it('Should throw when pass in bad hex (include a half-byte)', async () => { - // Create DataType object - const testDataItem = { name: 'Static Bytes', type: 'bytes' }; - const dataType = new AbiEncoder.DynamicBytes(testDataItem); - // Construct args to be encoded - const args = '0x010'; - // Encode Args and validate result - expect(() => { - dataType.encode(args); - }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); - }); - }); - - describe('String', () => { - it('Fits into one EVM word', async () => { - // Create DataType object - const testDataItem = { name: 'String', type: 'string' }; - const dataType = new AbiEncoder.String(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const args = 'five'; - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('Spans multiple EVM words', async () => { - // Create DataType object - const testDataItem = { name: 'String', type: 'string' }; - const dataType = new AbiEncoder.String(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const bytesLength = 40; - const args = 'a'.repeat(bytesLength); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - it('String that begins with 0x prefix', async () => { - // Create DataType object - const testDataItem = { name: 'String', type: 'string' }; - const dataType = new AbiEncoder.String(testDataItem); - // Construct args to be encoded - // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. - const strLength = 40; - const args = '0x' + 'a'.repeat(strLength); - // Encode Args and validate result - const encodedArgs = dataType.encode(args); - const expectedEncodedArgs = - '0x000000000000000000000000000000000000000000000000000000000000002a30786161616161616161616161616161616161616161616161616161616161616161616161616161616100000000000000000000000000000000000000000000'; - expect(encodedArgs).to.be.equal(expectedEncodedArgs); - // Decode Encoded Args and validate result - const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); - }); - }); -}); From a895dacd4e20238087245c274564f694c71f7f6e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 14:18:01 -0800 Subject: [PATCH 151/230] moved abi encoder constants into utils dir --- .../abi_encoder/abstract_data_types/dependent_data_type.ts | 2 +- .../src/abi_encoder/abstract_data_types/member_data_type.ts | 2 +- packages/utils/src/abi_encoder/calldata/calldata.ts | 2 +- .../src/abi_encoder/calldata/dependent_calldata_block.ts | 2 +- packages/utils/src/abi_encoder/calldata/raw_calldata.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/address.ts | 4 ++-- packages/utils/src/abi_encoder/evm_data_types/array.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/bool.ts | 2 +- .../utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/method.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/number.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts | 4 ++-- packages/utils/src/abi_encoder/evm_data_types/string.ts | 2 +- packages/utils/src/abi_encoder/{ => utils}/constants.ts | 0 14 files changed, 15 insertions(+), 15 deletions(-) rename packages/utils/src/abi_encoder/{ => utils}/constants.ts (100%) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts index e9f390b227..f4992dd0f3 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts @@ -3,7 +3,7 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { CalldataBlock, DependentCalldataBlock, RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; import { DecodingRules } from '../utils/rules'; import { DataType } from './data_type'; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts index f44a6dd30f..318564e21f 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { CalldataBlock, MemberCalldataBlock, RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; import { DecodingRules } from '../utils/rules'; import { DataType } from './data_type'; diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index 1abf1b6816..154a81b7f2 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -1,7 +1,7 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; import { Queue } from '../utils/queue'; import { EncodingRules } from '../utils/rules'; diff --git a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts index 4aec5eabcd..16b9a6fe60 100644 --- a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts +++ b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts @@ -1,6 +1,6 @@ import * as ethUtil from 'ethereumjs-util'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; import { CalldataBlock } from './calldata_block'; diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts index b7bd357374..27a59c6a31 100644 --- a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -1,6 +1,6 @@ import * as ethUtil from 'ethereumjs-util'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; import { Queue } from '../utils/queue'; export class RawCalldata { diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 0107fdc50c..aab5a06051 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -5,7 +5,7 @@ import * as _ from 'lodash'; import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; export class Address extends PayloadDataType { public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; @@ -13,7 +13,7 @@ export class Address extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - - Address._ADDRESS_SIZE_IN_BYTES; + Address._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { return type === 'address'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index f334282b8d..9963b6f321 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -1,7 +1,7 @@ import { DataItem } from 'ethereum-types'; import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; export class Array extends MemberDataType { private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 4b9dd32b1f..6bc2995443 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -6,7 +6,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; export class Bool extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index 5fca668e41..626e266c96 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -5,7 +5,7 @@ import * as _ from 'lodash'; import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; export class DynamicBytes extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index ce33755f13..50d676b4a8 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { DataType, DataTypeFactory, MemberDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; import { StaticBytes } from './static_bytes'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/number.ts b/packages/utils/src/abi_encoder/evm_data_types/number.ts index 8ff5e9a6a0..86acdce076 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/number.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/number.ts @@ -5,7 +5,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; export abstract class Number extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 4e49db6099..77dde1333a 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; export class StaticBytes extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; @@ -46,7 +46,7 @@ export class StaticBytes extends PayloadDataType { if (valueBuf.byteLength > this._width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index c0bde86499..47ad7cb971 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -5,7 +5,7 @@ import * as _ from 'lodash'; import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../constants'; +import * as Constants from '../utils/constants'; export class String extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; diff --git a/packages/utils/src/abi_encoder/constants.ts b/packages/utils/src/abi_encoder/utils/constants.ts similarity index 100% rename from packages/utils/src/abi_encoder/constants.ts rename to packages/utils/src/abi_encoder/utils/constants.ts From dcc439c2e3a756af75889ddf3b22146322d1d97d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 16:28:37 -0800 Subject: [PATCH 152/230] Ran prettier --- packages/utils/src/abi_encoder/evm_data_types/address.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index aab5a06051..9ae22bd9ce 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -13,7 +13,7 @@ export class Address extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - - Address._ADDRESS_SIZE_IN_BYTES; + Address._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { return type === 'address'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 77dde1333a..9a2a99ec7b 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -46,7 +46,7 @@ export class StaticBytes extends PayloadDataType { if (valueBuf.byteLength > this._width) { throw new Error( `Tried to assign ${value} (${ - valueBuf.byteLength + valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); } else if (value.length % 2 !== 0) { From 978a58105cd2d2f5d6ee3bcd870218fd357fb010 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 16:34:41 -0800 Subject: [PATCH 153/230] Prepended `front` to function names in Queue --- .../utils/src/abi_encoder/calldata/calldata.ts | 14 +++++++------- .../utils/src/abi_encoder/calldata/raw_calldata.ts | 6 +++--- packages/utils/src/abi_encoder/utils/queue.ts | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index 154a81b7f2..3b85f821b2 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -19,7 +19,7 @@ export class Calldata { // Base Case if (!(block instanceof CalldataBlocks.MemberCalldataBlock)) { - blockQueue.push(block); + blockQueue.pushBack(block); return blockQueue; } @@ -38,9 +38,9 @@ export class Calldata { if (member instanceof CalldataBlocks.DependentCalldataBlock && member.getAlias() === undefined) { const dependency = member.getDependency(); if (dependency instanceof CalldataBlocks.MemberCalldataBlock) { - blockQueue.merge(Calldata._createQueue(dependency)); + blockQueue.mergeBack(Calldata._createQueue(dependency)); } else { - blockQueue.push(dependency); + blockQueue.pushBack(dependency); } } }); @@ -100,7 +100,7 @@ export class Calldata { const offsetQueue = Calldata._createQueue(this._root); let block: CalldataBlock | undefined; let offset = 0; - for (block = offsetQueue.pop(); block !== undefined; block = offsetQueue.pop()) { + for (block = offsetQueue.popFront(); block !== undefined; block = offsetQueue.popFront()) { block.setOffset(offset); offset += block.getSizeInBytes(); } @@ -140,9 +140,9 @@ export class Calldata { let block: CalldataBlock | undefined; let offset = 0; - const functionBlock = valueQueue.peek(); + const functionBlock = valueQueue.peekFront(); const functionName: string = functionBlock === undefined ? '' : functionBlock.getName(); - for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { + for (block = valueQueue.popFront(); block !== undefined; block = valueQueue.popFront()) { // Process each block 1 word at a time const size = block.getSizeInBytes(); const name = block.getName(); @@ -212,7 +212,7 @@ export class Calldata { const valueQueue = Calldata._createQueue(this._root); const valueBufs: Buffer[] = [selectorBuffer]; let block: CalldataBlock | undefined; - for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) { + for (block = valueQueue.popFront(); block !== undefined; block = valueQueue.popFront()) { valueBufs.push(block.toBuffer()); } diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts index 27a59c6a31..9e72bbd62b 100644 --- a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -26,7 +26,7 @@ export class RawCalldata { } this._scopes = new Queue(); - this._scopes.push(RawCalldata._INITIAL_OFFSET); + this._scopes.pushBack(RawCalldata._INITIAL_OFFSET); this._offset = RawCalldata._INITIAL_OFFSET; } @@ -60,7 +60,7 @@ export class RawCalldata { } public endScope(): void { - this._scopes.pop(); + this._scopes.popFront(); } public getOffset(): number { @@ -68,7 +68,7 @@ export class RawCalldata { } public toAbsoluteOffset(relativeOffset: number): number { - const scopeOffset = this._scopes.peek(); + const scopeOffset = this._scopes.peekFront(); if (scopeOffset === undefined) { throw new Error(`Tried to access undefined scope.`); } diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts index 3309d8ba2a..53afb7e112 100644 --- a/packages/utils/src/abi_encoder/utils/queue.ts +++ b/packages/utils/src/abi_encoder/utils/queue.ts @@ -1,7 +1,7 @@ export class Queue { private _store: T[] = []; - public push(val: T): void { + public pushBack(val: T): void { this._store.push(val); } @@ -9,7 +9,7 @@ export class Queue { this._store.unshift(val); } - public pop(): T | undefined { + public popFront(): T | undefined { return this._store.shift(); } @@ -21,7 +21,7 @@ export class Queue { return backElement; } - public merge(q: Queue): void { + public mergeBack(q: Queue): void { this._store = this._store.concat(q._store); } @@ -33,7 +33,7 @@ export class Queue { return this._store; } - public peek(): T | undefined { + public peekFront(): T | undefined { return this._store.length >= 0 ? this._store[0] : undefined; } } From c638151b73289fc936bb7d4323711d1954cc4fcb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 20 Nov 2018 16:51:56 -0800 Subject: [PATCH 154/230] Some minor cleanup in ABI Encoder --- .../abstract_data_types/data_type.ts | 2 +- .../dependent_data_type.ts | 7 ++-- .../abstract_data_types/interfaces.ts | 4 +++ .../abstract_data_types/member_data_type.ts | 36 +++++++++---------- .../abstract_data_types/payload_data_type.ts | 2 +- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index a6adeb23ba..c83f2085e6 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -32,7 +32,7 @@ export abstract class DataType { calldata.setSelector(selector); } const block = this.generateCalldataBlock(value); - calldata.setRoot(block); // @TODO CHANGE + calldata.setRoot(block); const calldataHex = calldata.toHexString(); return calldataHex; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts index f4992dd0f3..7649b18363 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts @@ -28,16 +28,17 @@ export abstract class DependentDataType extends DataType { const dependencyBlock = this._dependency.generateCalldataBlock(value, parentBlock); const name = this.getDataItem().name; const signature = this.getSignature(); - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const parentName = parentBlock ? parentBlock.getName() : ''; const block = new DependentCalldataBlock(name, signature, parentName, dependencyBlock, parentBlock); return block; } public generateValue(calldata: RawCalldata, rules: DecodingRules): any { const destinationOffsetBuf = calldata.popWord(); - const currentOffset = calldata.getOffset(); - const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), Constants.HEX_BASE); + const destinationOffsetHex = ethUtil.bufferToHex(destinationOffsetBuf); + const destinationOffsetRelative = parseInt(destinationOffsetHex, Constants.HEX_BASE); const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); + const currentOffset = calldata.getOffset(); calldata.setOffset(destinationOffsetAbsolute); const value = this._dependency.generateValue(calldata, rules); calldata.setOffset(currentOffset); diff --git a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts index 2ae92659c5..9e2a945223 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts @@ -14,3 +14,7 @@ export interface DataTypeStaticInterface { encodeValue: (value: any) => Buffer; decodeValue: (rawCalldata: RawCalldata) => any; } + +export interface MemberIndexByName { + [key: string]: number; +} diff --git a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts index 318564e21f..dacdbf8af6 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts @@ -9,16 +9,12 @@ import { DecodingRules } from '../utils/rules'; import { DataType } from './data_type'; import { DependentDataType } from './dependent_data_type'; -import { DataTypeFactory } from './interfaces'; - -interface MemberMap { - [key: string]: number; -} +import { DataTypeFactory, MemberIndexByName } from './interfaces'; export abstract class MemberDataType extends DataType { protected readonly _arrayLength: number | undefined; protected readonly _arrayElementType: string | undefined; - private readonly _memberMap: MemberMap; + private readonly _memberIndexByName: MemberIndexByName; private readonly _members: DataType[]; private readonly _isArray: boolean; @@ -30,15 +26,15 @@ export abstract class MemberDataType extends DataType { arrayElementType?: string, ) { super(dataItem, factory); - this._memberMap = {}; + this._memberIndexByName = {}; this._members = []; this._isArray = isArray; this._arrayLength = arrayLength; this._arrayElementType = arrayElementType; if (isArray && arrayLength !== undefined) { - [this._members, this._memberMap] = this._createMembersWithLength(dataItem, arrayLength); + [this._members, this._memberIndexByName] = this._createMembersWithLength(dataItem, arrayLength); } else if (!isArray) { - [this._members, this._memberMap] = this._createMembersWithKeys(dataItem); + [this._members, this._memberIndexByName] = this._createMembersWithKeys(dataItem); } } @@ -65,7 +61,7 @@ export abstract class MemberDataType extends DataType { let value: any[] | object; if (rules.structsAsObjects && !this._isArray) { value = {}; - _.each(this._memberMap, (idx: number, key: string) => { + _.each(this._memberIndexByName, (idx: number, key: string) => { const member = this._members[idx]; const memberValue = member.generateValue(calldata, rules); (value as { [key: string]: any })[key] = memberValue; @@ -149,14 +145,14 @@ export abstract class MemberDataType extends DataType { parentName, ); const memberBlocks: CalldataBlock[] = []; - const childMap = _.cloneDeep(this._memberMap); + const childMap = _.cloneDeep(this._memberIndexByName); _.forOwn(obj, (value: any, key: string) => { if (!(key in childMap)) { throw new Error( `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, ); } - const block = this._members[this._memberMap[key]].generateCalldataBlock(value, methodBlock); + const block = this._members[this._memberIndexByName[key]].generateCalldataBlock(value, methodBlock); memberBlocks.push(block); delete childMap[key]; }); @@ -182,14 +178,14 @@ export abstract class MemberDataType extends DataType { return signature; } - private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] { + private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberIndexByName] { // Sanity check if (dataItem.components === undefined) { throw new Error(`Expected components`); } const members: DataType[] = []; - const memberMap: MemberMap = {}; + const memberIndexByName: MemberIndexByName = {}; _.each(dataItem.components, (memberItem: DataItem) => { const childDataItem: DataItem = { type: memberItem.type, @@ -200,16 +196,16 @@ export abstract class MemberDataType extends DataType { childDataItem.components = components; } const child = this.getFactory().create(childDataItem, this); - memberMap[memberItem.name] = members.length; + memberIndexByName[memberItem.name] = members.length; members.push(child); }); - return [members, memberMap]; + return [members, memberIndexByName]; } - private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] { + private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberIndexByName] { const members: DataType[] = []; - const memberMap: MemberMap = {}; + const memberIndexByName: MemberIndexByName = {}; const range = _.range(length); _.each(range, (idx: number) => { const childDataItem: DataItem = { @@ -221,10 +217,10 @@ export abstract class MemberDataType extends DataType { childDataItem.components = components; } const child = this.getFactory().create(childDataItem, this); - memberMap[idx.toString(Constants.DEC_BASE)] = members.length; + memberIndexByName[idx.toString(Constants.DEC_BASE)] = members.length; members.push(child); }); - return [members, memberMap]; + return [members, memberIndexByName]; } } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts index 767e64f514..fa420bc745 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts @@ -19,7 +19,7 @@ export abstract class PayloadDataType extends DataType { const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const parentName = parentBlock ? parentBlock.getName() : ''; const block = new PayloadCalldataBlock(name, signature, parentName, encodedValue); return block; } From dc7092e1eb11ff9844efe02e367ef37592f38c55 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 15:32:13 -0800 Subject: [PATCH 155/230] Removed mapDataItemToDataType from Factory. Now its just ::create() --- .../abstract_data_types/interfaces.ts | 1 - .../src/abi_encoder/evm_data_type_factory.ts | 42 ++++++++----------- .../src/abi_encoder/evm_data_types/array.ts | 2 +- .../src/abi_encoder/evm_data_types/method.ts | 22 +++------- 4 files changed, 25 insertions(+), 42 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts index 9e2a945223..bd4d2effd5 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts @@ -6,7 +6,6 @@ import { DataType } from './data_type'; export interface DataTypeFactory { create: (dataItem: DataItem, parentDataType?: DataType) => DataType; - mapDataItemToDataType: (dataItem: DataItem) => DataType; } export interface DataTypeStaticInterface { diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index 5d37acad95..bfe457367d 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -82,42 +82,36 @@ export class EvmDataTypeFactory implements DataTypeFactory { return EvmDataTypeFactory._instance; } - public mapDataItemToDataType(dataItem: DataItem): DataType { + public create(dataItem: DataItem, parentDataType?: DataType): DataType { + // Create data type + let dataType: undefined | DataType; if (Array.matchType(dataItem.type)) { - return new Array(dataItem); + dataType = new Array(dataItem); } else if (Address.matchType(dataItem.type)) { - return new Address(dataItem); + dataType = new Address(dataItem); } else if (Bool.matchType(dataItem.type)) { - return new Bool(dataItem); + dataType = new Bool(dataItem); } else if (Int.matchType(dataItem.type)) { - return new Int(dataItem); + dataType = new Int(dataItem); } else if (UInt.matchType(dataItem.type)) { - return new UInt(dataItem); + dataType = new UInt(dataItem); } else if (StaticBytes.matchType(dataItem.type)) { - return new StaticBytes(dataItem); + dataType = new StaticBytes(dataItem); } else if (Tuple.matchType(dataItem.type)) { - return new Tuple(dataItem); + dataType = new Tuple(dataItem); } else if (DynamicBytes.matchType(dataItem.type)) { - return new DynamicBytes(dataItem); + dataType = new DynamicBytes(dataItem); } else if (String.matchType(dataItem.type)) { - return new String(dataItem); + dataType = new String(dataItem); } // @TODO: Implement Fixed/UFixed types - throw new Error(`Unrecognized data type: '${dataItem.type}'`); - } - - public create(dataItem: DataItem, parentDataType?: DataType): DataType { - const dataType = this.mapDataItemToDataType(dataItem); - if (dataType.isStatic()) { - return dataType; + if (!dataType) { + throw new Error(`Unrecognized data type: '${dataItem.type}'`); + } else if (parentDataType && !dataType.isStatic()) { + const pointerToDataType = new Pointer(dataType, parentDataType); + return pointerToDataType; } - - if (parentDataType === undefined) { - // @Todo -- will this work for return values? - throw new Error(`Trying to create a pointer`); - } - const pointer = new Pointer(dataType, parentDataType); - return pointer; + return dataType; } private constructor() {} diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 9963b6f321..dd8184fd02 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -44,7 +44,7 @@ export class Array extends MemberDataType { if (components !== undefined) { dataItem.components = components; } - const elementDataType = this.getFactory().mapDataItemToDataType(dataItem); + const elementDataType = this.getFactory().create(dataItem); const type = elementDataType.getSignature(); if (this._arrayLength === undefined) { return `${type}[]`; diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index 50d676b4a8..671b80890f 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -7,7 +7,6 @@ import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; -import { StaticBytes } from './static_bytes'; import { Tuple } from './tuple'; export class Method extends MemberDataType { @@ -16,19 +15,14 @@ export class Method extends MemberDataType { private readonly _methodSignature: string; private readonly _methodSelector: string; - private readonly _returnDataTypes: DataType[]; - private readonly _returnDataItem: DataItem; + private readonly _returnDataType: DataType; public constructor(abi: MethodAbi, dataTypeFactory: DataTypeFactory) { super({ type: 'method', name: abi.name, components: abi.inputs }, dataTypeFactory); this._methodSignature = this._computeSignature(); this.selector = this._methodSelector = this._computeSelector(); - this._returnDataTypes = []; - this._returnDataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; - const dummy = new StaticBytes({ type: 'byte', name: 'DUMMY' }, dataTypeFactory); // @TODO TMP - _.each(abi.outputs, (dataItem: DataItem) => { - this._returnDataTypes.push(this.getFactory().create(dataItem, dummy)); - }); + const returnDataItem: DataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; + this._returnDataType = new Tuple(returnDataItem, this.getFactory()); } public encode(value: any, rules?: EncodingRules): string { @@ -48,18 +42,14 @@ export class Method extends MemberDataType { } public encodeReturnValues(value: any, rules?: EncodingRules): string { - const returnDataType = new Tuple(this._returnDataItem, this.getFactory()); - const returndata = returnDataType.encode(value, rules); - return returndata; + const returnData = this._returnDataType.encode(value, rules); + return returnData; } public decodeReturnValues(returndata: string, rules?: DecodingRules): any { - const returnValues: any[] = []; const rules_: DecodingRules = rules ? rules : { structsAsObjects: false }; const rawReturnData = new RawCalldata(returndata, false); - _.each(this._returnDataTypes, (dataType: DataType) => { - returnValues.push(dataType.generateValue(rawReturnData, rules_)); - }); + const returnValues = this._returnDataType.generateValue(rawReturnData, rules_); return returnValues; } From dd8bb6d08b6e837304a76e9707b79e070f951e4e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 15:38:29 -0800 Subject: [PATCH 156/230] Made default encoding/decoding rules global to all modules in encoder --- .../abstract_data_types/data_type.ts | 7 +++--- .../src/abi_encoder/evm_data_types/method.ts | 23 +++++++++---------- .../utils/src/abi_encoder/utils/constants.ts | 4 ++++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index c83f2085e6..dd166b19cc 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -2,13 +2,12 @@ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; import { Calldata, CalldataBlock, RawCalldata } from '../calldata'; +import * as Constants from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; import { DataTypeFactory } from './interfaces'; export abstract class DataType { - private static readonly _DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false }; - private static readonly _DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false }; private readonly _dataItem: DataItem; private readonly _factory: DataTypeFactory; @@ -26,7 +25,7 @@ export abstract class DataType { } public encode(value: any, rules?: EncodingRules, selector?: string): string { - const rules_ = rules ? rules : DataType._DEFAULT_ENCODING_RULES; + const rules_ = rules ? rules : Constants.DEFAULT_ENCODING_RULES; const calldata = new Calldata(rules_); if (selector) { calldata.setSelector(selector); @@ -39,7 +38,7 @@ export abstract class DataType { public decode(calldata: string, rules?: DecodingRules, hasSelector: boolean = false): any { const rawCalldata = new RawCalldata(calldata, hasSelector); - const rules_ = rules ? rules : DataType._DEFAULT_DECODING_RULES; + const rules_ = rules ? rules : Constants.DEFAULT_DECODING_RULES; const value = this.generateValue(rawCalldata, rules_); return value; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index 671b80890f..2faffd44ec 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -10,30 +10,28 @@ import { DecodingRules, EncodingRules } from '../utils/rules'; import { Tuple } from './tuple'; export class Method extends MemberDataType { - // TMP - public selector: string; - private readonly _methodSignature: string; private readonly _methodSelector: string; private readonly _returnDataType: DataType; public constructor(abi: MethodAbi, dataTypeFactory: DataTypeFactory) { - super({ type: 'method', name: abi.name, components: abi.inputs }, dataTypeFactory); + const methodDataItem = { type: 'method', name: abi.name, components: abi.inputs }; + super(methodDataItem, dataTypeFactory); this._methodSignature = this._computeSignature(); - this.selector = this._methodSelector = this._computeSelector(); + this._methodSelector = this._computeSelector(); const returnDataItem: DataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; this._returnDataType = new Tuple(returnDataItem, this.getFactory()); } public encode(value: any, rules?: EncodingRules): string { - const calldata = super.encode(value, rules, this.selector); + const calldata = super.encode(value, rules, this._methodSelector); return calldata; } public decode(calldata: string, rules?: DecodingRules): any[] | object { - if (!calldata.startsWith(this.selector)) { + if (!calldata.startsWith(this._methodSelector)) { throw new Error( - `Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`, + `Tried to decode calldata, but it was missing the function selector. Expected '${this._methodSelector}'.`, ); } const hasSelector = true; @@ -46,10 +44,11 @@ export class Method extends MemberDataType { return returnData; } - public decodeReturnValues(returndata: string, rules?: DecodingRules): any { - const rules_: DecodingRules = rules ? rules : { structsAsObjects: false }; - const rawReturnData = new RawCalldata(returndata, false); - const returnValues = this._returnDataType.generateValue(rawReturnData, rules_); + public decodeReturnValues(returndata: string, rules_?: DecodingRules): any { + const rules: DecodingRules = rules_ ? rules_ : Constants.DEFAULT_DECODING_RULES; + const returnDataHasSelector = false; + const rawReturnData = new RawCalldata(returndata, returnDataHasSelector); + const returnValues = this._returnDataType.generateValue(rawReturnData, rules); return returnValues; } diff --git a/packages/utils/src/abi_encoder/utils/constants.ts b/packages/utils/src/abi_encoder/utils/constants.ts index 3d85fbdb82..05c6783e7a 100644 --- a/packages/utils/src/abi_encoder/utils/constants.ts +++ b/packages/utils/src/abi_encoder/utils/constants.ts @@ -1,3 +1,5 @@ +import { DecodingRules, EncodingRules } from './rules'; + export const EVM_WORD_WIDTH_IN_BYTES = 32; export const EVM_WORD_WIDTH_IN_BITS = 256; export const HEX_BASE = 16; @@ -6,3 +8,5 @@ export const BIN_BASE = 2; export const HEX_SELECTOR_LENGTH_IN_CHARS = 10; export const HEX_SELECTOR_LENGTH_IN_BYTES = 4; export const HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA = 0; +export const DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false }; +export const DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false }; From 173fc1dcefa266704dd80de6335c03b73b7d8702 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 15:50:49 -0800 Subject: [PATCH 157/230] Moved encoder selector check into DataType --- .../abi_encoder/abstract_data_types/data_type.ts | 8 +++++++- .../src/abi_encoder/evm_data_types/method.ts | 16 +++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index dd166b19cc..4500803539 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -36,7 +36,13 @@ export abstract class DataType { return calldataHex; } - public decode(calldata: string, rules?: DecodingRules, hasSelector: boolean = false): any { + public decode(calldata: string, rules?: DecodingRules, selector?: string): any { + if (selector && !calldata.startsWith(selector)) { + throw new Error( + `Tried to decode calldata, but it was missing the function selector. Expected '${selector}'.`, + ); + } + const hasSelector = selector ? true : false; const rawCalldata = new RawCalldata(calldata, hasSelector); const rules_ = rules ? rules : Constants.DEFAULT_DECODING_RULES; const value = this.generateValue(rawCalldata, rules_); diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index 2faffd44ec..f2e4174859 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -3,7 +3,6 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { DataType, DataTypeFactory, MemberDataType } from '../abstract_data_types'; -import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; @@ -29,13 +28,7 @@ export class Method extends MemberDataType { } public decode(calldata: string, rules?: DecodingRules): any[] | object { - if (!calldata.startsWith(this._methodSelector)) { - throw new Error( - `Tried to decode calldata, but it was missing the function selector. Expected '${this._methodSelector}'.`, - ); - } - const hasSelector = true; - const value = super.decode(calldata, rules, hasSelector); + const value = super.decode(calldata, rules, this._methodSelector); return value; } @@ -44,11 +37,8 @@ export class Method extends MemberDataType { return returnData; } - public decodeReturnValues(returndata: string, rules_?: DecodingRules): any { - const rules: DecodingRules = rules_ ? rules_ : Constants.DEFAULT_DECODING_RULES; - const returnDataHasSelector = false; - const rawReturnData = new RawCalldata(returndata, returnDataHasSelector); - const returnValues = this._returnDataType.generateValue(rawReturnData, rules); + public decodeReturnValues(returndata: string, rules?: DecodingRules): any { + const returnValues = this._returnDataType.decode(returndata, rules); return returnValues; } From 58a2dfbc4d191ea21e6a749371e586dcff3b3239 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 17:04:50 -0800 Subject: [PATCH 158/230] Final rounds on evm data types --- .../evm_data_types/static_bytes.ts | 58 +++++++++++-------- .../src/abi_encoder/evm_data_types/string.ts | 33 +++++++---- .../src/abi_encoder/evm_data_types/tuple.ts | 8 +-- packages/utils/src/abi_encoder/utils/queue.ts | 2 +- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 9a2a99ec7b..afa9afdf2a 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -11,7 +11,6 @@ export class StaticBytes extends PayloadDataType { private static readonly _matcher = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', ); - private static readonly _DEFAULT_WIDTH = 1; private readonly _width: number; @@ -19,16 +18,20 @@ export class StaticBytes extends PayloadDataType { return StaticBytes._matcher.test(type); } + private static _decodeWidthFromType(type: string): number { + const matches = StaticBytes._matcher.exec(type); + const width = (matches !== null && matches.length === 3 && matches[2] !== undefined) + ? parseInt(matches[2], Constants.DEC_BASE) + : StaticBytes._DEFAULT_WIDTH; + return width; + } + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { super(dataItem, dataTypeFactory, StaticBytes._SIZE_KNOWN_AT_COMPILE_TIME); - const matches = StaticBytes._matcher.exec(dataItem.type); if (!StaticBytes.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`); + throw new Error(`Tried to instantiate Static Bytes with bad input: ${dataItem}`); } - this._width = - matches !== null && matches.length === 3 && matches[2] !== undefined - ? parseInt(matches[2], Constants.DEC_BASE) - : StaticBytes._DEFAULT_WIDTH; + this._width = StaticBytes._decodeWidthFromType(dataItem.type); } public getSignature(): string { @@ -37,11 +40,30 @@ export class StaticBytes extends PayloadDataType { } public encodeValue(value: string | Buffer): Buffer { - // Sanity check if string - if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); + // 1/2 Convert value into a buffer and do bounds checking + this._sanityCheckValue(value); + const valueBuf = ethUtil.toBuffer(value); + // 2/2 Store value as hex + const valuePadded = ethUtil.setLengthRight(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + return valuePadded; + } + + public decodeValue(calldata: RawCalldata): string { + const valueBufPadded = calldata.popWord(); + const valueBuf = valueBufPadded.slice(0, this._width); + const value = ethUtil.bufferToHex(valueBuf); + this._sanityCheckValue(value); + return value; + } + + private _sanityCheckValue(value: string | Buffer): void { + if (typeof value === 'string') { + if (!value.startsWith('0x')) { + throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); + } else if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } } - // Convert value into a buffer and do bounds checking const valueBuf = ethUtil.toBuffer(value); if (valueBuf.byteLength > this._width) { throw new Error( @@ -49,20 +71,6 @@ export class StaticBytes extends PayloadDataType { valueBuf.byteLength } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`, ); - } else if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); } - - // Store value as hex - const evmWordWidth = 32; - const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth); - return paddedValue; - } - - public decodeValue(calldata: RawCalldata): string { - const paddedValueBuf = calldata.popWord(); - const valueBuf = paddedValueBuf.slice(0, this._width); - const value = ethUtil.bufferToHex(valueBuf); - return value; } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 47ad7cb971..15b93e4478 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -13,7 +13,7 @@ export class String extends PayloadDataType { public static matchType(type: string): boolean { return type === 'string'; } - + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { super(dataItem, dataTypeFactory, String._SIZE_KNOWN_AT_COMPILE_TIME); if (!String.matchType(dataItem.type)) { @@ -22,21 +22,30 @@ export class String extends PayloadDataType { } public encodeValue(value: string): Buffer { - const wordsForValue = Math.ceil(value.length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; - const valueBuf = ethUtil.setLengthRight(new Buffer(value), paddedDynamicBytesForValue); - const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), Constants.EVM_WORD_WIDTH_IN_BYTES); - const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]); + // Encoded value is of the form: , with each field padded to be word-aligned. + // 1/3 Construct the length + const wordsToStoreValuePadded = Math.ceil(value.length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const bytesToStoreValuePadded = wordsToStoreValuePadded * Constants.EVM_WORD_WIDTH_IN_BYTES; + const lengthBuf = ethUtil.toBuffer(value.length); + const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + // 2/3 Construct the value + const valueBuf = new Buffer(value); + const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); + // 3/3 Combine length and value + const encodedValueBuf = Buffer.concat([lengthBufPadded, valueBufPadded]); return encodedValueBuf; } public decodeValue(calldata: RawCalldata): string { - const lengthBuf = calldata.popWord(); - const lengthHex = ethUtil.bufferToHex(lengthBuf); - const length = parseInt(lengthHex, Constants.HEX_BASE); - const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedValueBuf = calldata.popWords(wordsForValue); - const valueBuf = paddedValueBuf.slice(0, length); + // Encoded value is of the form: , with each field padded to be word-aligned. + // 1/2 Decode length + const lengthBufPadded = calldata.popWord(); + const lengthHexPadded = ethUtil.bufferToHex(lengthBufPadded); + const length = parseInt(lengthHexPadded, Constants.HEX_BASE); + // 2/2 Decode value + const wordsToStoreValuePadded = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const valueBufPadded = calldata.popWords(wordsToStoreValuePadded); + const valueBuf = valueBufPadded.slice(0, length); const value = valueBuf.toString('ascii'); return value; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 4a90e375a6..89dd6604de 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -3,8 +3,8 @@ import { DataItem } from 'ethereum-types'; import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; export class Tuple extends MemberDataType { - private readonly _tupleSignature: string; - + private readonly _signature: string; + public static matchType(type: string): boolean { return type === 'tuple'; } @@ -14,10 +14,10 @@ export class Tuple extends MemberDataType { if (!Tuple.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); } - this._tupleSignature = this._computeSignatureOfMembers(); + this._signature = this._computeSignatureOfMembers(); } public getSignature(): string { - return this._tupleSignature; + return this._signature; } } diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts index 53afb7e112..506a0b56e2 100644 --- a/packages/utils/src/abi_encoder/utils/queue.ts +++ b/packages/utils/src/abi_encoder/utils/queue.ts @@ -32,7 +32,7 @@ export class Queue { public getStore(): T[] { return this._store; } - + public peekFront(): T | undefined { return this._store.length >= 0 ? this._store[0] : undefined; } From d2d89adbddaec435ddb65545a86fc4dc981de521 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 17:12:21 -0800 Subject: [PATCH 159/230] Abstracted out encoding/decoding of numeric values into its own utility. Could be useful elsewhere. --- .../src/abi_encoder/evm_data_types/array.ts | 6 +- .../src/abi_encoder/evm_data_types/int.ts | 6 +- .../src/abi_encoder/evm_data_types/number.ts | 71 +----------- .../evm_data_types/static_bytes.ts | 8 +- .../src/abi_encoder/evm_data_types/string.ts | 2 +- .../src/abi_encoder/evm_data_types/tuple.ts | 2 +- .../src/abi_encoder/evm_data_types/uint.ts | 6 +- packages/utils/src/abi_encoder/utils/math.ts | 103 ++++++++++++++++++ packages/utils/src/abi_encoder/utils/queue.ts | 2 +- 9 files changed, 125 insertions(+), 81 deletions(-) create mode 100644 packages/utils/src/abi_encoder/utils/math.ts diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index dd8184fd02..527cdadfea 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -4,17 +4,17 @@ import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; import * as Constants from '../utils/constants'; export class Array extends MemberDataType { - private static readonly _matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); + private static readonly _MATCHER = RegExp('^(.+)\\[([0-9]*)\\]$'); private readonly _arraySignature: string; private readonly _elementType: string; public static matchType(type: string): boolean { - return Array._matcher.test(type); + return Array._MATCHER.test(type); } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { // Sanity check - const matches = Array._matcher.exec(dataItem.type); + const matches = Array._MATCHER.exec(dataItem.type); if (matches === null || matches.length !== 3) { throw new Error(`Could not parse array: ${dataItem.type}`); } else if (matches[1] === undefined) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index ec41b9cfcd..457c41b282 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -7,16 +7,16 @@ import { DataTypeFactory } from '../abstract_data_types'; import { Number } from './number'; export class Int extends Number { - private static readonly _matcher = RegExp( + private static readonly _MATCHER = RegExp( '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); public static matchType(type: string): boolean { - return Int._matcher.test(type); + return Int._MATCHER.test(type); } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, Int._matcher, dataTypeFactory); + super(dataItem, Int._MATCHER, dataTypeFactory); } public getMaxValue(): BigNumber { diff --git a/packages/utils/src/abi_encoder/evm_data_types/number.ts b/packages/utils/src/abi_encoder/evm_data_types/number.ts index 86acdce076..053a574e38 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/number.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/number.ts @@ -1,11 +1,11 @@ import { DataItem } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; +import * as EncoderMath from '../utils/math'; export abstract class Number extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; @@ -25,73 +25,14 @@ export abstract class Number extends PayloadDataType { : (this._width = Number._DEFAULT_WIDTH); } - public encodeValue(value_: BigNumber | string | number): Buffer { - const value = new BigNumber(value_, 10); - if (value.greaterThan(this.getMaxValue())) { - throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`); - } else if (value.lessThan(this.getMinValue())) { - throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`); - } - - let valueBuf: Buffer; - if (value.greaterThanOrEqualTo(0)) { - valueBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(`0x${value.toString(Constants.HEX_BASE)}`), - Constants.EVM_WORD_WIDTH_IN_BYTES, - ); - } else { - // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves. - // Step 1/3: Convert value to positive binary string - const binBase = 2; - const valueBin = value.times(-1).toString(binBase); - - // Step 2/3: Invert binary value - let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length); - _.each(valueBin, (bit: string) => { - invertedValueBin += bit === '1' ? '0' : '1'; - }); - const invertedValue = new BigNumber(invertedValueBin, binBase); - - // Step 3/3: Add 1 to inverted value - // The result is the two's-complement represent of the input value. - const negativeValue = invertedValue.plus(1); - - // Convert the negated value to a hex string - valueBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(`0x${negativeValue.toString(Constants.HEX_BASE)}`), - Constants.EVM_WORD_WIDTH_IN_BYTES, - ); - } - - return valueBuf; + public encodeValue(value: BigNumber | string | number): Buffer { + const encodedValue = EncoderMath.safeEncodeNumericValue(value, this.getMinValue(), this.getMaxValue()); + return encodedValue; } public decodeValue(calldata: RawCalldata): BigNumber { - const paddedValueBuf = calldata.popWord(); - const paddedValueHex = ethUtil.bufferToHex(paddedValueBuf); - let value = new BigNumber(paddedValueHex, 16); - if (this.getMinValue().lessThan(0)) { - // Check if we're negative - const valueBin = value.toString(Constants.BIN_BASE); - if (valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1')) { - // Negative - // Step 1/3: Invert binary value - let invertedValueBin = ''; - _.each(valueBin, (bit: string) => { - invertedValueBin += bit === '1' ? '0' : '1'; - }); - const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); - - // Step 2/3: Add 1 to inverted value - // The result is the two's-complement represent of the input value. - const positiveValue = invertedValue.plus(1); - - // Step 3/3: Invert positive value - const negativeValue = positiveValue.times(-1); - value = negativeValue; - } - } - + const valueBuf = calldata.popWord(); + const value = EncoderMath.safeDecodeNumericValue(valueBuf, this.getMinValue(), this.getMaxValue()); return value; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index afa9afdf2a..0d01e61059 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -8,18 +8,18 @@ import * as Constants from '../utils/constants'; export class StaticBytes extends PayloadDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - private static readonly _matcher = RegExp( + private static readonly _MATCHER = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', ); private static readonly _DEFAULT_WIDTH = 1; private readonly _width: number; public static matchType(type: string): boolean { - return StaticBytes._matcher.test(type); + return StaticBytes._MATCHER.test(type); } private static _decodeWidthFromType(type: string): number { - const matches = StaticBytes._matcher.exec(type); + const matches = StaticBytes._MATCHER.exec(type); const width = (matches !== null && matches.length === 3 && matches[2] !== undefined) ? parseInt(matches[2], Constants.DEC_BASE) : StaticBytes._DEFAULT_WIDTH; @@ -55,7 +55,7 @@ export class StaticBytes extends PayloadDataType { this._sanityCheckValue(value); return value; } - + private _sanityCheckValue(value: string | Buffer): void { if (typeof value === 'string') { if (!value.startsWith('0x')) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 15b93e4478..428ea21db8 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -13,7 +13,7 @@ export class String extends PayloadDataType { public static matchType(type: string): boolean { return type === 'string'; } - + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { super(dataItem, dataTypeFactory, String._SIZE_KNOWN_AT_COMPILE_TIME); if (!String.matchType(dataItem.type)) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 89dd6604de..63d9dfa9e7 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -4,7 +4,7 @@ import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; export class Tuple extends MemberDataType { private readonly _signature: string; - + public static matchType(type: string): boolean { return type === 'tuple'; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index ced3ef08be..c2b6e214ac 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -7,16 +7,16 @@ import { DataTypeFactory } from '../abstract_data_types'; import { Number } from './number'; export class UInt extends Number { - private static readonly _matcher = RegExp( + private static readonly _MATCHER = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); public static matchType(type: string): boolean { - return UInt._matcher.test(type); + return UInt._MATCHER.test(type); } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, UInt._matcher, dataTypeFactory); + super(dataItem, UInt._MATCHER, dataTypeFactory); } public getMaxValue(): BigNumber { diff --git a/packages/utils/src/abi_encoder/utils/math.ts b/packages/utils/src/abi_encoder/utils/math.ts new file mode 100644 index 0000000000..8d21ada0a9 --- /dev/null +++ b/packages/utils/src/abi_encoder/utils/math.ts @@ -0,0 +1,103 @@ +import BigNumber from 'bignumber.js'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import * as Constants from '../utils/constants'; + +function sanityCheckBigNumberRange(value_: BigNumber | string | number, minValue: BigNumber, maxValue: BigNumber): void { + const value = new BigNumber(value_, 10); + if (value.greaterThan(maxValue)) { + throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${maxValue}`); + } else if (value.lessThan(minValue)) { + throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${minValue}`); + } +} +function bigNumberToPaddedBuffer(value: BigNumber): Buffer { + const valueHex = `0x${value.toString(Constants.HEX_BASE)}`; + const valueBuf = ethUtil.toBuffer(valueHex); + const valueBufPadded = ethUtil.setLengthLeft(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + return valueBufPadded; +} +/** + * Takes a numeric value and returns its ABI-encoded value + * @param value_ The value to encode. + * @return ABI Encoded value + */ +export function encodeNumericValue(value_: BigNumber | string | number): Buffer { + const value = new BigNumber(value_, 10); + // Case 1/2: value is non-negative + if (value.greaterThanOrEqualTo(0)) { + const encodedPositiveValue = bigNumberToPaddedBuffer(value); + return encodedPositiveValue; + } + // Case 2/2: Value is negative + // Use two's-complement to encode the value + // Step 1/3: Convert negative value to positive binary string + const valueBin = value.times(-1).toString(Constants.BIN_BASE); + // Step 2/3: Invert binary value + let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length); + _.each(valueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); + // Step 3/3: Add 1 to inverted value + const negativeValue = invertedValue.plus(1); + const encodedValue = bigNumberToPaddedBuffer(negativeValue); + return encodedValue; +} +/** + * Takes a numeric value and returns its ABI-encoded value. + * Performs an additional sanity check, given the min/max allowed value. + * @param value_ The value to encode. + * @return ABI Encoded value + */ +export function safeEncodeNumericValue(value: BigNumber | string | number, minValue: BigNumber, maxValue: BigNumber): Buffer { + sanityCheckBigNumberRange(value, minValue, maxValue); + const encodedValue = encodeNumericValue(value); + return encodedValue; +} +/** + * Takes an ABI-encoded numeric value and returns its decoded value as a BigNumber. + * @param encodedValue The encoded numeric value. + * @param minValue The minimum possible decoded value. + * @return ABI Decoded value + */ +export function decodeNumericValue(encodedValue: Buffer, minValue: BigNumber): BigNumber { + const valueHex = ethUtil.bufferToHex(encodedValue); + // Case 1/3: value is definitely non-negative because of numeric boundaries + const value = new BigNumber(valueHex, Constants.HEX_BASE); + if (!minValue.lessThan(0)) { + return value; + } + // Case 2/3: value is non-negative because there is no leading 1 (encoded as two's-complement) + const valueBin = value.toString(Constants.BIN_BASE); + const valueIsNegative = valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1'); + if (!valueIsNegative) { + return value; + } + // Case 3/3: value is negative + // Step 1/3: Invert b inary value + let invertedValueBin = ''; + _.each(valueBin, (bit: string) => { + invertedValueBin += bit === '1' ? '0' : '1'; + }); + const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); + // Step 2/3: Add 1 to inverted value + // The result is the two's-complement representation of the input value. + const positiveValue = invertedValue.plus(1); + // Step 3/3: Invert positive value to get the negative value + const negativeValue = positiveValue.times(-1); + return negativeValue; +} +/** + * Takes an ABI-encoded numeric value and returns its decoded value as a BigNumber. + * Performs an additional sanity check, given the min/max allowed value. + * @param encodedValue The encoded numeric value. + * @param minValue The minimum possible decoded value. + * @return ABI Decoded value + */ +export function safeDecodeNumericValue(encodedValue: Buffer, minValue: BigNumber, maxValue: BigNumber): BigNumber { + const value = decodeNumericValue(encodedValue, minValue); + sanityCheckBigNumberRange(value, minValue, maxValue); + return value; +} diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts index 506a0b56e2..53afb7e112 100644 --- a/packages/utils/src/abi_encoder/utils/queue.ts +++ b/packages/utils/src/abi_encoder/utils/queue.ts @@ -32,7 +32,7 @@ export class Queue { public getStore(): T[] { return this._store; } - + public peekFront(): T | undefined { return this._store.length >= 0 ? this._store[0] : undefined; } From ebaf9dd275403cdecfb3364876737fcbcd0eab82 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 17:25:04 -0800 Subject: [PATCH 160/230] Removed abstract Number class. --- .../src/abi_encoder/evm_data_types/int.ts | 42 ++++++++++++++---- .../src/abi_encoder/evm_data_types/number.ts | 41 ------------------ .../src/abi_encoder/evm_data_types/uint.ts | 43 +++++++++++++++---- 3 files changed, 67 insertions(+), 59 deletions(-) delete mode 100644 packages/utils/src/abi_encoder/evm_data_types/number.ts diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 457c41b282..83aeacd66e 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -2,29 +2,53 @@ import { DataItem } from 'ethereum-types'; import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { RawCalldata } from '../calldata'; +import * as Constants from '../utils/constants'; +import * as EncoderMath from '../utils/math'; -import { Number } from './number'; - -export class Int extends Number { +export class Int extends PayloadDataType { private static readonly _MATCHER = RegExp( '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _MAX_WIDTH: number = 256; + private static readonly _DEFAULT_WIDTH: number = Int._MAX_WIDTH; + private _width: number; + private _minValue: BigNumber; + private _maxValue: BigNumber; public static matchType(type: string): boolean { return Int._MATCHER.test(type); } + private static _decodeWidthFromType(type: string): number { + const matches = Int._MATCHER.exec(type); + const width = (matches !== null && matches.length === 2 && matches[1] !== undefined) + ? parseInt(matches[1], Constants.DEC_BASE) + : Int._DEFAULT_WIDTH; + return width; + } + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, Int._MATCHER, dataTypeFactory); + super(dataItem, dataTypeFactory, Int._SIZE_KNOWN_AT_COMPILE_TIME); + if (!Int.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate Int with bad input: ${dataItem}`); + } + this._width = Int._decodeWidthFromType(dataItem.type); + this._minValue = new BigNumber(2).toPower(this._width - 1).times(-1); + this._maxValue = new BigNumber(2).toPower(this._width - 1).sub(1); } - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this._width - 1).sub(1); + public encodeValue(value: BigNumber | string | number): Buffer { + const encodedValue = EncoderMath.safeEncodeNumericValue(value, this._minValue, this._maxValue); + return encodedValue; } - public getMinValue(): BigNumber { - return new BigNumber(2).toPower(this._width - 1).times(-1); + public decodeValue(calldata: RawCalldata): BigNumber { + const valueBuf = calldata.popWord(); + const value = EncoderMath.safeDecodeNumericValue(valueBuf, this._minValue, this._maxValue); + return value; } public getSignature(): string { diff --git a/packages/utils/src/abi_encoder/evm_data_types/number.ts b/packages/utils/src/abi_encoder/evm_data_types/number.ts deleted file mode 100644 index 053a574e38..0000000000 --- a/packages/utils/src/abi_encoder/evm_data_types/number.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { DataItem } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; -import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; -import * as EncoderMath from '../utils/math'; - -export abstract class Number extends PayloadDataType { - private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; - private static readonly _MAX_WIDTH: number = 256; - private static readonly _DEFAULT_WIDTH: number = Number._MAX_WIDTH; - protected _width: number; - - constructor(dataItem: DataItem, matcher: RegExp, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, Number._SIZE_KNOWN_AT_COMPILE_TIME); - const matches = matcher.exec(dataItem.type); - if (matches === null) { - throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`); - } - this._width = - matches !== null && matches.length === 2 && matches[1] !== undefined - ? parseInt(matches[1], Constants.DEC_BASE) - : (this._width = Number._DEFAULT_WIDTH); - } - - public encodeValue(value: BigNumber | string | number): Buffer { - const encodedValue = EncoderMath.safeEncodeNumericValue(value, this.getMinValue(), this.getMaxValue()); - return encodedValue; - } - - public decodeValue(calldata: RawCalldata): BigNumber { - const valueBuf = calldata.popWord(); - const value = EncoderMath.safeDecodeNumericValue(valueBuf, this.getMinValue(), this.getMaxValue()); - return value; - } - - public abstract getMaxValue(): BigNumber; - public abstract getMinValue(): BigNumber; -} diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index c2b6e214ac..832ab075c0 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -2,32 +2,57 @@ import { DataItem } from 'ethereum-types'; import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { RawCalldata } from '../calldata'; +import * as Constants from '../utils/constants'; +import * as EncoderMath from '../utils/math'; -import { Number } from './number'; - -export class UInt extends Number { +export class UInt extends PayloadDataType { private static readonly _MATCHER = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); + private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; + private static readonly _MAX_WIDTH: number = 256; + private static readonly _DEFAULT_WIDTH: number = UInt._MAX_WIDTH; + private _width: number; + private _minValue: BigNumber; + private _maxValue: BigNumber; public static matchType(type: string): boolean { return UInt._MATCHER.test(type); } + private static _decodeWidthFromType(type: string): number { + const matches = UInt._MATCHER.exec(type); + const width = (matches !== null && matches.length === 2 && matches[1] !== undefined) + ? parseInt(matches[1], Constants.DEC_BASE) + : UInt._DEFAULT_WIDTH; + return width; + } + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, UInt._MATCHER, dataTypeFactory); + super(dataItem, dataTypeFactory, UInt._SIZE_KNOWN_AT_COMPILE_TIME); + if (!UInt.matchType(dataItem.type)) { + throw new Error(`Tried to instantiate UInt with bad input: ${dataItem}`); + } + this._width = UInt._decodeWidthFromType(dataItem.type); + this._minValue = new BigNumber(0); + this._maxValue = new BigNumber(2).toPower(this._width).sub(1); } - public getMaxValue(): BigNumber { - return new BigNumber(2).toPower(this._width).sub(1); + public encodeValue(value: BigNumber | string | number): Buffer { + const encodedValue = EncoderMath.safeEncodeNumericValue(value, this._minValue, this._maxValue); + return encodedValue; } - public getMinValue(): BigNumber { - return new BigNumber(0); + public decodeValue(calldata: RawCalldata): BigNumber { + const valueBuf = calldata.popWord(); + const value = EncoderMath.safeDecodeNumericValue(valueBuf, this._minValue, this._maxValue); + return value; } public getSignature(): string { return `uint${this._width}`; } } + From acd364b71c8b3ddb6d4d75d8667cc7f50b18694d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 17:37:14 -0800 Subject: [PATCH 161/230] Comments and inline documentation for dynamic bytes --- .../src/abi_encoder/evm_data_types/bool.ts | 8 +-- .../evm_data_types/dynamic_bytes.ts | 55 +++++++++++-------- .../src/abi_encoder/evm_data_types/string.ts | 4 +- .../test/abi_encoder/evm_data_types_test.ts | 2 +- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 6bc2995443..82a519aae2 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -22,10 +22,6 @@ export class Bool extends PayloadDataType { } } - public getSignature(): string { - return 'bool'; - } - public encodeValue(value: boolean): Buffer { const encodedValue = value ? '0x1' : '0x0'; const encodedValueBuf = ethUtil.setLengthLeft( @@ -47,4 +43,8 @@ export class Bool extends PayloadDataType { /* tslint:enable boolean-naming */ return value; } + + public getSignature(): string { + return 'bool'; + } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index 626e266c96..ce6ace627e 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -17,42 +17,53 @@ export class DynamicBytes extends PayloadDataType { public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { super(dataItem, dataTypeFactory, DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME); if (!DynamicBytes.matchType(dataItem.type)) { - throw new Error(`Tried to instantiate DynamicBytes with bad input: ${dataItem}`); + throw new Error(`Tried to instantiate Dynamic Bytes with bad input: ${dataItem}`); } } public encodeValue(value: string | Buffer): Buffer { - if (typeof value === 'string' && !value.startsWith('0x')) { - throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '${value}'`); - } + // Encoded value is of the form: , with each field padded to be word-aligned. + // 1/3 Construct the length const valueBuf = ethUtil.toBuffer(value); - if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - - const wordsForValue = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; - const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedDynamicBytesForValue); - const paddedLengthBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(valueBuf.byteLength), - Constants.EVM_WORD_WIDTH_IN_BYTES, - ); - const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); - return encodedValueBuf; + const wordsToStoreValuePadded = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); + const bytesToStoreValuePadded = wordsToStoreValuePadded * Constants.EVM_WORD_WIDTH_IN_BYTES; + const lengthBuf = ethUtil.toBuffer(valueBuf.byteLength); + const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + // 2/3 Construct the value + this._sanityCheckValue(value); + const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); + // 3/3 Combine length and value + const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]); + return encodedValue; } public decodeValue(calldata: RawCalldata): string { + // Encoded value is of the form: , with each field padded to be word-aligned. + // 1/2 Decode length const lengthBuf = calldata.popWord(); const lengthHex = ethUtil.bufferToHex(lengthBuf); const length = parseInt(lengthHex, Constants.HEX_BASE); - const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const paddedValueBuf = calldata.popWords(wordsForValue); - const valueBuf = paddedValueBuf.slice(0, length); - const decodedValue = ethUtil.bufferToHex(valueBuf); - return decodedValue; + // 2/2 Decode value + const wordsToStoreValuePadded = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const valueBufPadded = calldata.popWords(wordsToStoreValuePadded); + const valueBuf = valueBufPadded.slice(0, length); + const value = ethUtil.bufferToHex(valueBuf); + this._sanityCheckValue(value); + return value; } public getSignature(): string { return 'bytes'; } + + private _sanityCheckValue(value: string | Buffer): void { + if (typeof value !== 'string') { + return; + } + if (!value.startsWith('0x')) { + throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); + } else if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 428ea21db8..2bb6541a35 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -32,8 +32,8 @@ export class String extends PayloadDataType { const valueBuf = new Buffer(value); const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); // 3/3 Combine length and value - const encodedValueBuf = Buffer.concat([lengthBufPadded, valueBufPadded]); - return encodedValueBuf; + const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]); + return encodedValue; } public decodeValue(calldata: RawCalldata): string { diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts index 9c3e3c0f9f..7cea865293 100644 --- a/packages/utils/test/abi_encoder/evm_data_types_test.ts +++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts @@ -1018,7 +1018,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Encode Args and validate result expect(() => { dataType.encode(args); - }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '01'"); + }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); }); it('Should throw when pass in bad hex (include a half-byte)', async () => { // Create DataType object From 22ce3e2e29fb50d9b9244c9ee567c124586be9ae Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 17:46:45 -0800 Subject: [PATCH 162/230] Comments for Array --- .../src/abi_encoder/evm_data_types/array.ts | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 527cdadfea..77e38ebd73 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -12,21 +12,26 @@ export class Array extends MemberDataType { return Array._MATCHER.test(type); } - public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - // Sanity check - const matches = Array._MATCHER.exec(dataItem.type); + private static _decodeElementTypeAndLengthFromType(type: string): [string, undefined|number] { + const matches = Array._MATCHER.exec(type); if (matches === null || matches.length !== 3) { - throw new Error(`Could not parse array: ${dataItem.type}`); + throw new Error(`Could not parse array: ${type}`); } else if (matches[1] === undefined) { - throw new Error(`Could not parse array type: ${dataItem.type}`); + throw new Error(`Could not parse array type: ${type}`); } else if (matches[2] === undefined) { - throw new Error(`Could not parse array length: ${dataItem.type}`); + throw new Error(`Could not parse array length: ${type}`); } - - const isArray = true; const arrayElementType = matches[1]; const arrayLength = matches[2] === '' ? undefined : parseInt(matches[2], Constants.DEC_BASE); + return [arrayElementType, arrayLength]; + } + + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { + // Construct parent + const isArray = true; + const [arrayElementType, arrayLength] = Array._decodeElementTypeAndLengthFromType(dataItem.type); super(dataItem, dataTypeFactory, isArray, arrayLength, arrayElementType); + // Set array properties this._elementType = arrayElementType; this._arraySignature = this._computeSignature(); } @@ -36,20 +41,22 @@ export class Array extends MemberDataType { } private _computeSignature(): string { - const dataItem: DataItem = { + // Compute signature for a single array element + const elementDataItem: DataItem = { type: this._elementType, name: 'N/A', }; - const components = this.getDataItem().components; - if (components !== undefined) { - dataItem.components = components; + const elementComponents = this.getDataItem().components; + if (elementComponents !== undefined) { + elementDataItem.components = elementComponents; } - const elementDataType = this.getFactory().create(dataItem); - const type = elementDataType.getSignature(); + const elementDataType = this.getFactory().create(elementDataItem); + const elementSignature = elementDataType.getSignature(); + // Construct signature for array of type `element` if (this._arrayLength === undefined) { - return `${type}[]`; + return `${elementSignature}[]`; } else { - return `${type}[${this._arrayLength}]`; + return `${elementSignature}[${this._arrayLength}]`; } } } From bb4d02e413119132f283ee17549cf5e1732d75b5 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 17:47:33 -0800 Subject: [PATCH 163/230] Comments for Address --- .../utils/src/abi_encoder/evm_data_types/address.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 9ae22bd9ce..84f6665cbf 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -34,17 +34,17 @@ export class Address extends PayloadDataType { if (!value.startsWith('0x')) { throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); } - const valueAsBuffer = ethUtil.toBuffer(value); - if (valueAsBuffer.byteLength !== Address._ADDRESS_SIZE_IN_BYTES) { + const valueBuf = ethUtil.toBuffer(value); + if (valueBuf.byteLength !== Address._ADDRESS_SIZE_IN_BYTES) { throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); } - const encodedValueBuf = ethUtil.setLengthLeft(valueAsBuffer, Constants.EVM_WORD_WIDTH_IN_BYTES); + const encodedValueBuf = ethUtil.setLengthLeft(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); return encodedValueBuf; } public decodeValue(calldata: RawCalldata): string { - const paddedValueBuf = calldata.popWord(); - const valueBuf = paddedValueBuf.slice(Address._DECODED_ADDRESS_OFFSET_IN_BYTES); + const valueBufPadded = calldata.popWord(); + const valueBuf = valueBufPadded.slice(Address._DECODED_ADDRESS_OFFSET_IN_BYTES); const value = ethUtil.bufferToHex(valueBuf); return value; } From 9a51af46ee4a35b3d1ce2fcdc6f561aa68307cf0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 18:24:46 -0800 Subject: [PATCH 164/230] Payload -> Blob, Dependent -> Pointer, Member -> Set --- .../abi_encoder/abstract_data_types/index.ts | 5 +-- .../{payload_data_type.ts => types/blob.ts} | 14 +++---- .../abstract_data_types/types/index.ts | 3 ++ .../pointer.ts} | 16 ++++---- .../{member_data_type.ts => types/set.ts} | 41 ++++++++++--------- .../src/abi_encoder/calldata/blocks/blob.ts | 20 +++++++++ .../src/abi_encoder/calldata/blocks/index.ts | 3 ++ .../pointer.ts} | 20 ++++----- .../set.ts} | 9 ++-- .../src/abi_encoder/calldata/calldata.ts | 14 +++---- .../abi_encoder/calldata/calldata_blocks.ts | 3 -- .../utils/src/abi_encoder/calldata/index.ts | 7 ++-- .../calldata/payload_calldata_block.ts | 20 --------- .../src/abi_encoder/calldata/raw_calldata.ts | 25 ++++++----- .../src/abi_encoder/evm_data_types/address.ts | 4 +- .../src/abi_encoder/evm_data_types/array.ts | 4 +- .../src/abi_encoder/evm_data_types/bool.ts | 4 +- .../evm_data_types/dynamic_bytes.ts | 4 +- .../src/abi_encoder/evm_data_types/int.ts | 10 ++--- .../src/abi_encoder/evm_data_types/method.ts | 4 +- .../src/abi_encoder/evm_data_types/pointer.ts | 4 +- .../evm_data_types/static_bytes.ts | 4 +- .../src/abi_encoder/evm_data_types/string.ts | 4 +- .../src/abi_encoder/evm_data_types/tuple.ts | 4 +- .../src/abi_encoder/evm_data_types/uint.ts | 16 ++++---- 25 files changed, 130 insertions(+), 132 deletions(-) rename packages/utils/src/abi_encoder/abstract_data_types/{payload_data_type.ts => types/blob.ts} (70%) create mode 100644 packages/utils/src/abi_encoder/abstract_data_types/types/index.ts rename packages/utils/src/abi_encoder/abstract_data_types/{dependent_data_type.ts => types/pointer.ts} (80%) rename packages/utils/src/abi_encoder/abstract_data_types/{member_data_type.ts => types/set.ts} (88%) create mode 100644 packages/utils/src/abi_encoder/calldata/blocks/blob.ts create mode 100644 packages/utils/src/abi_encoder/calldata/blocks/index.ts rename packages/utils/src/abi_encoder/calldata/{dependent_calldata_block.ts => blocks/pointer.ts} (75%) rename packages/utils/src/abi_encoder/calldata/{member_calldata_block.ts => blocks/set.ts} (85%) delete mode 100644 packages/utils/src/abi_encoder/calldata/calldata_blocks.ts delete mode 100644 packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts diff --git a/packages/utils/src/abi_encoder/abstract_data_types/index.ts b/packages/utils/src/abi_encoder/abstract_data_types/index.ts index 9ad5681345..d1c7d93e4d 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/index.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/index.ts @@ -1,5 +1,4 @@ export * from './interfaces'; export * from './data_type'; -export * from './dependent_data_type'; -export * from './member_data_type'; -export * from './payload_data_type'; +import * as AbstractDataTypes from './types'; +export { AbstractDataTypes }; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts similarity index 70% rename from packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts rename to packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts index fa420bc745..f4246c8933 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts @@ -1,13 +1,13 @@ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; -import { CalldataBlock, PayloadCalldataBlock, RawCalldata } from '../calldata'; -import { DecodingRules } from '../utils/rules'; +import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; +import { DecodingRules } from '../../utils/rules'; -import { DataType } from './data_type'; -import { DataTypeFactory } from './interfaces'; +import { DataType } from '../data_type'; +import { DataTypeFactory } from '../interfaces'; -export abstract class PayloadDataType extends DataType { +export abstract class Blob extends DataType { protected _hasConstantSize: boolean; public constructor(dataItem: DataItem, factory: DataTypeFactory, hasConstantSize: boolean) { @@ -15,12 +15,12 @@ export abstract class PayloadDataType extends DataType { this._hasConstantSize = hasConstantSize; } - public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlocks.Blob { const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = parentBlock ? parentBlock.getName() : ''; - const block = new PayloadCalldataBlock(name, signature, parentName, encodedValue); + const block = new CalldataBlocks.Blob(name, signature, parentName, encodedValue); return block; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/index.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/index.ts new file mode 100644 index 0000000000..958582dae4 --- /dev/null +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/index.ts @@ -0,0 +1,3 @@ +export * from './blob'; +export * from './pointer'; +export * from './set'; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts similarity index 80% rename from packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts rename to packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts index 7649b18363..47efac5217 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts @@ -2,14 +2,14 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { CalldataBlock, DependentCalldataBlock, RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; -import { DecodingRules } from '../utils/rules'; +import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; +import * as Constants from '../../utils/constants'; +import { DecodingRules } from '../../utils/rules'; -import { DataType } from './data_type'; -import { DataTypeFactory } from './interfaces'; +import { DataType } from '../data_type'; +import { DataTypeFactory } from '../interfaces'; -export abstract class DependentDataType extends DataType { +export abstract class Pointer extends DataType { protected _dependency: DataType; protected _parent: DataType; private readonly _isStatic: boolean; @@ -21,7 +21,7 @@ export abstract class DependentDataType extends DataType { this._isStatic = true; } - public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): DependentCalldataBlock { + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlocks.Pointer { if (parentBlock === undefined) { throw new Error(`DependentDataType requires a parent block to generate its block`); } @@ -29,7 +29,7 @@ export abstract class DependentDataType extends DataType { const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = parentBlock ? parentBlock.getName() : ''; - const block = new DependentCalldataBlock(name, signature, parentName, dependencyBlock, parentBlock); + const block = new CalldataBlocks.Pointer(name, signature, parentName, dependencyBlock, parentBlock); return block; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts similarity index 88% rename from packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts rename to packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index dacdbf8af6..77fd7b3ea2 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -2,16 +2,17 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { BigNumber } from '../../configured_bignumber'; -import { CalldataBlock, MemberCalldataBlock, RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; -import { DecodingRules } from '../utils/rules'; +import { BigNumber } from '../../../configured_bignumber'; +import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; +import * as Constants from '../../utils/constants'; +import { DecodingRules } from '../../utils/rules'; -import { DataType } from './data_type'; -import { DependentDataType } from './dependent_data_type'; -import { DataTypeFactory, MemberIndexByName } from './interfaces'; +import { DataType } from '../data_type'; +import { DataTypeFactory, MemberIndexByName } from '../interfaces'; -export abstract class MemberDataType extends DataType { +import { Pointer } from './pointer'; + +export abstract class Set extends DataType { protected readonly _arrayLength: number | undefined; protected readonly _arrayElementType: string | undefined; private readonly _memberIndexByName: MemberIndexByName; @@ -38,7 +39,7 @@ export abstract class MemberDataType extends DataType { } } - public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { + public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): CalldataBlocks.Set { const block = value instanceof Array ? this._generateCalldataBlockFromArray(value, parentBlock) @@ -94,13 +95,13 @@ export abstract class MemberDataType extends DataType { // Search for dependent members const dependentMember = _.find(this._members, (member: DataType) => { - return member instanceof DependentDataType; + return member instanceof Pointer; }); const isStatic = dependentMember === undefined; // static if we couldn't find a dependent member return isStatic; } - protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): MemberCalldataBlock { + protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): CalldataBlocks.Set { // Sanity check length if (this._arrayLength !== undefined && value.length !== this._arrayLength) { throw new Error( @@ -111,7 +112,7 @@ export abstract class MemberDataType extends DataType { } const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + const methodBlock: CalldataBlocks.Set = new CalldataBlocks.Set( this.getDataItem().name, this.getSignature(), parentName, @@ -128,23 +129,23 @@ export abstract class MemberDataType extends DataType { methodBlock.setHeader(lenBuf); } - const memberBlocks: CalldataBlock[] = []; + const memberCalldataBlocks: CalldataBlock[] = []; _.each(members, (member: DataType, idx: number) => { const block = member.generateCalldataBlock(value[idx], methodBlock); - memberBlocks.push(block); + memberCalldataBlocks.push(block); }); - methodBlock.setMembers(memberBlocks); + methodBlock.setMembers(memberCalldataBlocks); return methodBlock; } - protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock { + protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): CalldataBlocks.Set { const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: MemberCalldataBlock = new MemberCalldataBlock( + const methodBlock: CalldataBlocks.Set = new CalldataBlocks.Set( this.getDataItem().name, this.getSignature(), parentName, ); - const memberBlocks: CalldataBlock[] = []; + const memberCalldataBlocks: CalldataBlock[] = []; const childMap = _.cloneDeep(this._memberIndexByName); _.forOwn(obj, (value: any, key: string) => { if (!(key in childMap)) { @@ -153,7 +154,7 @@ export abstract class MemberDataType extends DataType { ); } const block = this._members[this._memberIndexByName[key]].generateCalldataBlock(value, methodBlock); - memberBlocks.push(block); + memberCalldataBlocks.push(block); delete childMap[key]; }); @@ -161,7 +162,7 @@ export abstract class MemberDataType extends DataType { throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); } - methodBlock.setMembers(memberBlocks); + methodBlock.setMembers(memberCalldataBlocks); return methodBlock; } diff --git a/packages/utils/src/abi_encoder/calldata/blocks/blob.ts b/packages/utils/src/abi_encoder/calldata/blocks/blob.ts new file mode 100644 index 0000000000..210ef64202 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/blocks/blob.ts @@ -0,0 +1,20 @@ +import { CalldataBlock } from '../calldata_block'; + +export class Blob extends CalldataBlock { + private readonly _blob: Buffer; + + constructor(name: string, signature: string, parentName: string, blob: Buffer) { + const headerSizeInBytes = 0; + const bodySizeInBytes = blob.byteLength; + super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); + this._blob = blob; + } + + public toBuffer(): Buffer { + return this._blob; + } + + public getRawData(): Buffer { + return this._blob; + } +} diff --git a/packages/utils/src/abi_encoder/calldata/blocks/index.ts b/packages/utils/src/abi_encoder/calldata/blocks/index.ts new file mode 100644 index 0000000000..958582dae4 --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/blocks/index.ts @@ -0,0 +1,3 @@ +export * from './blob'; +export * from './pointer'; +export * from './set'; diff --git a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts similarity index 75% rename from packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts rename to packages/utils/src/abi_encoder/calldata/blocks/pointer.ts index 16b9a6fe60..1c49a8c6c9 100644 --- a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts @@ -1,10 +1,10 @@ import * as ethUtil from 'ethereumjs-util'; -import * as Constants from '../utils/constants'; +import * as Constants from '../../utils/constants'; -import { CalldataBlock } from './calldata_block'; +import { CalldataBlock } from '../calldata_block'; -export class DependentCalldataBlock extends CalldataBlock { +export class Pointer extends CalldataBlock { public static readonly RAW_DATA_START = new Buffer('<'); public static readonly RAW_DATA_END = new Buffer('>'); private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; @@ -14,8 +14,8 @@ export class DependentCalldataBlock extends CalldataBlock { private _aliasFor: CalldataBlock | undefined; constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) { - const headerSizeInBytes = DependentCalldataBlock._EMPTY_HEADER_SIZE; - const bodySizeInBytes = DependentCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; + const headerSizeInBytes = Pointer._EMPTY_HEADER_SIZE; + const bodySizeInBytes = Pointer._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); this._parent = parent; this._dependency = dependency; @@ -28,9 +28,9 @@ export class DependentCalldataBlock extends CalldataBlock { const parentOffset = this._parent.getOffsetInBytes(); const parentHeaderSize = this._parent.getHeaderSizeInBytes(); const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); - const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(Constants.HEX_BASE)}`); - const evmWordWidthInBytes = 32; - const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); + const pointerHex = `0x${pointer.toString(Constants.HEX_BASE)}`; + const pointerBuf = ethUtil.toBuffer(pointerHex); + const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); return pointerBufPadded; } @@ -50,9 +50,9 @@ export class DependentCalldataBlock extends CalldataBlock { public getRawData(): Buffer { const dependencyRawData = this._dependency.getRawData(); const rawDataComponents: Buffer[] = []; - rawDataComponents.push(DependentCalldataBlock.RAW_DATA_START); + rawDataComponents.push(Pointer.RAW_DATA_START); rawDataComponents.push(dependencyRawData); - rawDataComponents.push(DependentCalldataBlock.RAW_DATA_END); + rawDataComponents.push(Pointer.RAW_DATA_END); const rawData = Buffer.concat(rawDataComponents); return rawData; } diff --git a/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/blocks/set.ts similarity index 85% rename from packages/utils/src/abi_encoder/calldata/member_calldata_block.ts rename to packages/utils/src/abi_encoder/calldata/blocks/set.ts index c35beb8de3..e4de22c5cc 100644 --- a/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/set.ts @@ -1,8 +1,8 @@ import * as _ from 'lodash'; -import { CalldataBlock } from './calldata_block'; +import { CalldataBlock } from '../calldata_block'; -export class MemberCalldataBlock extends CalldataBlock { +export class Set extends CalldataBlock { private _header: Buffer | undefined; private _members: CalldataBlock[]; @@ -14,14 +14,13 @@ export class MemberCalldataBlock extends CalldataBlock { public getRawData(): Buffer { const rawDataComponents: Buffer[] = []; - if (this._header !== undefined) { + if (this._header) { rawDataComponents.push(this._header); } _.each(this._members, (member: CalldataBlock) => { const memberBuffer = member.getRawData(); rawDataComponents.push(memberBuffer); }); - const rawData = Buffer.concat(rawDataComponents); return rawData; } @@ -36,7 +35,7 @@ export class MemberCalldataBlock extends CalldataBlock { } public toBuffer(): Buffer { - if (this._header !== undefined) { + if (this._header) { return this._header; } return new Buffer(''); diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index 3b85f821b2..b2396ee8f1 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -5,8 +5,8 @@ import * as Constants from '../utils/constants'; import { Queue } from '../utils/queue'; import { EncodingRules } from '../utils/rules'; +import * as CalldataBlocks from './blocks'; import { CalldataBlock } from './calldata_block'; -import * as CalldataBlocks from './calldata_blocks'; export class Calldata { private readonly _rules: EncodingRules; @@ -18,7 +18,7 @@ export class Calldata { const blockQueue = new Queue(); // Base Case - if (!(block instanceof CalldataBlocks.MemberCalldataBlock)) { + if (!(block instanceof CalldataBlocks.Set)) { blockQueue.pushBack(block); return blockQueue; } @@ -26,7 +26,7 @@ export class Calldata { // This is a Member Block const memberBlock = block; _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => { - if (member instanceof CalldataBlocks.MemberCalldataBlock) { + if (member instanceof CalldataBlocks.Set) { blockQueue.mergeFront(Calldata._createQueue(member)); } else { blockQueue.pushFront(member); @@ -35,9 +35,9 @@ export class Calldata { // Children _.each(memberBlock.getMembers(), (member: CalldataBlock) => { - if (member instanceof CalldataBlocks.DependentCalldataBlock && member.getAlias() === undefined) { + if (member instanceof CalldataBlocks.Pointer && member.getAlias() === undefined) { const dependency = member.getDependency(); - if (dependency instanceof CalldataBlocks.MemberCalldataBlock) { + if (dependency instanceof CalldataBlocks.Set) { blockQueue.mergeBack(Calldata._createQueue(dependency)); } else { blockQueue.pushBack(dependency); @@ -68,7 +68,7 @@ export class Calldata { const subtreeQueue = Calldata._createQueue(this._root); let block: CalldataBlock | undefined; for (block = subtreeQueue.popBack(); block !== undefined; block = subtreeQueue.popBack()) { - if (block instanceof CalldataBlocks.DependentCalldataBlock) { + if (block instanceof CalldataBlocks.Pointer) { const dependencyBlockHashBuf = block.getDependency().computeHash(); const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); if (dependencyBlockHash in blocksByHash) { @@ -175,7 +175,7 @@ export class Calldata { ), ) .padEnd(valuePadding); - if (block instanceof CalldataBlocks.MemberCalldataBlock) { + if (block instanceof CalldataBlocks.Set) { nameStr = `### ${prettyName.padEnd(namePadding)}`; line = `\n${offsetStr}${value}${nameStr}`; } else { diff --git a/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts b/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts deleted file mode 100644 index 8d4e7d7ca8..0000000000 --- a/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './dependent_calldata_block'; -export * from './member_calldata_block'; -export * from './payload_calldata_block'; diff --git a/packages/utils/src/abi_encoder/calldata/index.ts b/packages/utils/src/abi_encoder/calldata/index.ts index 2c786cd8dc..2ef75e8d00 100644 --- a/packages/utils/src/abi_encoder/calldata/index.ts +++ b/packages/utils/src/abi_encoder/calldata/index.ts @@ -1,6 +1,5 @@ -export * from './calldata_block'; -export * from './dependent_calldata_block'; -export * from './payload_calldata_block'; -export * from './member_calldata_block'; export * from './calldata'; +export * from './calldata_block'; export * from './raw_calldata'; +import * as CalldataBlocks from './blocks'; +export { CalldataBlocks }; diff --git a/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts deleted file mode 100644 index 0420b01d85..0000000000 --- a/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CalldataBlock } from './calldata_block'; - -export class PayloadCalldataBlock extends CalldataBlock { - private readonly _payload: Buffer; - - constructor(name: string, signature: string, parentName: string, payload: Buffer) { - const headerSizeInBytes = 0; - const bodySizeInBytes = payload.byteLength; - super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); - this._payload = payload; - } - - public toBuffer(): Buffer { - return this._payload; - } - - public getRawData(): Buffer { - return this._payload; - } -} diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts index 9e72bbd62b..b13cbdfd95 100644 --- a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -8,26 +8,25 @@ export class RawCalldata { private readonly _value: Buffer; private readonly _selector: string; private readonly _scopes: Queue; - private _offset: number; // tracks current offset into raw calldata; used for parsing + private _offset: number; - constructor(value: string | Buffer, hasSelectorPrefix: boolean = true) { + public constructor(value: string | Buffer, hasSelector: boolean = true) { + // Sanity check if (typeof value === 'string' && !value.startsWith('0x')) { throw new Error(`Expected raw calldata to start with '0x'`); } - const valueBuf = ethUtil.toBuffer(value); - if (hasSelectorPrefix) { - this._selector = ethUtil.bufferToHex( - valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES), - ); - this._value = valueBuf.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); // disregard selector - } else { - this._selector = '0x'; - this._value = valueBuf; - } - + // Construct initial values + this._value = ethUtil.toBuffer(value); + this._selector = '0x'; this._scopes = new Queue(); this._scopes.pushBack(RawCalldata._INITIAL_OFFSET); this._offset = RawCalldata._INITIAL_OFFSET; + // If there's a selector then slice it + if (hasSelector) { + const selectorBuf = this._value.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); + this._value = this._value.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); + this._selector = ethUtil.bufferToHex(selectorBuf); + } } public popBytes(lengthInBytes: number): Buffer { diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 84f6665cbf..71aa293b53 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -3,11 +3,11 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; -export class Address extends PayloadDataType { +export class Address extends AbstractDataTypes.Blob { public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = 'Address must be 20 bytes'; private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 77e38ebd73..54f7ba9fa1 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -1,9 +1,9 @@ import { DataItem } from 'ethereum-types'; -import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import * as Constants from '../utils/constants'; -export class Array extends MemberDataType { +export class Array extends AbstractDataTypes.Set { private static readonly _MATCHER = RegExp('^(.+)\\[([0-9]*)\\]$'); private readonly _arraySignature: string; private readonly _elementType: string; diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 82a519aae2..031acc88ae 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -4,11 +4,11 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; -export class Bool extends PayloadDataType { +export class Bool extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index ce6ace627e..01d83d11a7 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -3,11 +3,11 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; -export class DynamicBytes extends PayloadDataType { +export class DynamicBytes extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 83aeacd66e..8a82ed4cc9 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -2,21 +2,21 @@ import { DataItem } from 'ethereum-types'; import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; import * as EncoderMath from '../utils/math'; -export class Int extends PayloadDataType { +export class Int extends AbstractDataTypes.Blob { private static readonly _MATCHER = RegExp( '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _MAX_WIDTH: number = 256; private static readonly _DEFAULT_WIDTH: number = Int._MAX_WIDTH; - private _width: number; - private _minValue: BigNumber; - private _maxValue: BigNumber; + private readonly _width: number; + private readonly _minValue: BigNumber; + private readonly _maxValue: BigNumber; public static matchType(type: string): boolean { return Int._MATCHER.test(type); diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index f2e4174859..bd4732097f 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -2,13 +2,13 @@ import { DataItem, MethodAbi } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { DataType, DataTypeFactory, MemberDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataType, DataTypeFactory } from '../abstract_data_types'; import * as Constants from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; import { Tuple } from './tuple'; -export class Method extends MemberDataType { +export class Method extends AbstractDataTypes.Set { private readonly _methodSignature: string; private readonly _methodSelector: string; private readonly _returnDataType: DataType; diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts index d4411df9b5..00c743d2b9 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts @@ -1,8 +1,8 @@ import { DataItem } from 'ethereum-types'; -import { DataType, DataTypeFactory, DependentDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataType, DataTypeFactory } from '../abstract_data_types'; -export class Pointer extends DependentDataType { +export class Pointer extends AbstractDataTypes.Pointer { constructor(destDataType: DataType, parentDataType: DataType, dataTypeFactory: DataTypeFactory) { const destDataItem = destDataType.getDataItem(); const dataItem: DataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` }; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 0d01e61059..d0b41194e6 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -2,11 +2,11 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; -export class StaticBytes extends PayloadDataType { +export class StaticBytes extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _MATCHER = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 2bb6541a35..6ab3513c97 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -3,11 +3,11 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; -export class String extends PayloadDataType { +export class String extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 63d9dfa9e7..3802f96c00 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -1,8 +1,8 @@ import { DataItem } from 'ethereum-types'; -import { DataTypeFactory, MemberDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; -export class Tuple extends MemberDataType { +export class Tuple extends AbstractDataTypes.Set { private readonly _signature: string; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index 832ab075c0..b1bc690d8f 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -2,21 +2,21 @@ import { DataItem } from 'ethereum-types'; import { BigNumber } from '../../configured_bignumber'; -import { DataTypeFactory, PayloadDataType } from '../abstract_data_types'; +import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; import * as EncoderMath from '../utils/math'; -export class UInt extends PayloadDataType { +export class UInt extends AbstractDataTypes.Blob { private static readonly _MATCHER = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _MAX_WIDTH: number = 256; private static readonly _DEFAULT_WIDTH: number = UInt._MAX_WIDTH; - private _width: number; - private _minValue: BigNumber; - private _maxValue: BigNumber; + private static readonly _MIN_VALUE = new BigNumber(0); + private readonly _width: number; + private readonly _maxValue: BigNumber; public static matchType(type: string): boolean { return UInt._MATCHER.test(type); @@ -36,18 +36,17 @@ export class UInt extends PayloadDataType { throw new Error(`Tried to instantiate UInt with bad input: ${dataItem}`); } this._width = UInt._decodeWidthFromType(dataItem.type); - this._minValue = new BigNumber(0); this._maxValue = new BigNumber(2).toPower(this._width).sub(1); } public encodeValue(value: BigNumber | string | number): Buffer { - const encodedValue = EncoderMath.safeEncodeNumericValue(value, this._minValue, this._maxValue); + const encodedValue = EncoderMath.safeEncodeNumericValue(value, UInt._MIN_VALUE, this._maxValue); return encodedValue; } public decodeValue(calldata: RawCalldata): BigNumber { const valueBuf = calldata.popWord(); - const value = EncoderMath.safeDecodeNumericValue(valueBuf, this._minValue, this._maxValue); + const value = EncoderMath.safeDecodeNumericValue(valueBuf, UInt._MIN_VALUE, this._maxValue); return value; } @@ -55,4 +54,3 @@ export class UInt extends PayloadDataType { return `uint${this._width}`; } } - From 8f73f53c95d8ba887558863b8b726a2b3f5b7e2b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 20:05:41 -0800 Subject: [PATCH 165/230] Moved calldata iterator logic into its own iterator clas --- .../abstract_data_types/types/blob.ts | 8 +- .../abstract_data_types/types/pointer.ts | 21 ++--- .../src/abi_encoder/calldata/calldata.ts | 56 ++--------- .../src/abi_encoder/calldata/iterator.ts | 94 +++++++++++++++++++ .../src/abi_encoder/evm_data_types/pointer.ts | 2 +- 5 files changed, 119 insertions(+), 62 deletions(-) create mode 100644 packages/utils/src/abi_encoder/calldata/iterator.ts diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts index f4246c8933..35ccc05860 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts @@ -8,11 +8,11 @@ import { DataType } from '../data_type'; import { DataTypeFactory } from '../interfaces'; export abstract class Blob extends DataType { - protected _hasConstantSize: boolean; + protected _sizeKnownAtCompileTime: boolean; - public constructor(dataItem: DataItem, factory: DataTypeFactory, hasConstantSize: boolean) { + public constructor(dataItem: DataItem, factory: DataTypeFactory, sizeKnownAtCompileTime: boolean) { super(dataItem, factory); - this._hasConstantSize = hasConstantSize; + this._sizeKnownAtCompileTime = sizeKnownAtCompileTime; } public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlocks.Blob { @@ -30,7 +30,7 @@ export abstract class Blob extends DataType { } public isStatic(): boolean { - return this._hasConstantSize; + return this._sizeKnownAtCompileTime; } public abstract encodeValue(value: any): Buffer; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts index 47efac5217..46e60979ae 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts @@ -1,3 +1,4 @@ +/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -10,26 +11,24 @@ import { DataType } from '../data_type'; import { DataTypeFactory } from '../interfaces'; export abstract class Pointer extends DataType { - protected _dependency: DataType; + protected _destination: DataType; protected _parent: DataType; - private readonly _isStatic: boolean; - public constructor(dataItem: DataItem, factory: DataTypeFactory, dependency: DataType, parent: DataType) { + public constructor(dataItem: DataItem, factory: DataTypeFactory, destination: DataType, parent: DataType) { super(dataItem, factory); - this._dependency = dependency; + this._destination = destination; this._parent = parent; - this._isStatic = true; } public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlocks.Pointer { - if (parentBlock === undefined) { + if (!parentBlock) { throw new Error(`DependentDataType requires a parent block to generate its block`); } - const dependencyBlock = this._dependency.generateCalldataBlock(value, parentBlock); + const destinationBlock = this._destination.generateCalldataBlock(value, parentBlock); const name = this.getDataItem().name; const signature = this.getSignature(); - const parentName = parentBlock ? parentBlock.getName() : ''; - const block = new CalldataBlocks.Pointer(name, signature, parentName, dependencyBlock, parentBlock); + const parentName = parentBlock.getName(); + const block = new CalldataBlocks.Pointer(name, signature, parentName, destinationBlock, parentBlock); return block; } @@ -40,12 +39,12 @@ export abstract class Pointer extends DataType { const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); const currentOffset = calldata.getOffset(); calldata.setOffset(destinationOffsetAbsolute); - const value = this._dependency.generateValue(calldata, rules); + const value = this._destination.generateValue(calldata, rules); calldata.setOffset(currentOffset); return value; } public isStatic(): boolean { - return this._isStatic; + return true; } } diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index b2396ee8f1..dd9d47def8 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -2,11 +2,11 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import * as Constants from '../utils/constants'; -import { Queue } from '../utils/queue'; import { EncodingRules } from '../utils/rules'; import * as CalldataBlocks from './blocks'; import { CalldataBlock } from './calldata_block'; +import { CalldataIterator, ReverseCalldataIterator } from './iterator'; export class Calldata { private readonly _rules: EncodingRules; @@ -14,41 +14,6 @@ export class Calldata { private _sizeInBytes: number; private _root: CalldataBlock | undefined; - private static _createQueue(block: CalldataBlock): Queue { - const blockQueue = new Queue(); - - // Base Case - if (!(block instanceof CalldataBlocks.Set)) { - blockQueue.pushBack(block); - return blockQueue; - } - - // This is a Member Block - const memberBlock = block; - _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => { - if (member instanceof CalldataBlocks.Set) { - blockQueue.mergeFront(Calldata._createQueue(member)); - } else { - blockQueue.pushFront(member); - } - }); - - // Children - _.each(memberBlock.getMembers(), (member: CalldataBlock) => { - if (member instanceof CalldataBlocks.Pointer && member.getAlias() === undefined) { - const dependency = member.getDependency(); - if (dependency instanceof CalldataBlocks.Set) { - blockQueue.mergeBack(Calldata._createQueue(dependency)); - } else { - blockQueue.pushBack(dependency); - } - } - }); - - blockQueue.pushFront(memberBlock); - return blockQueue; - } - public constructor(rules: EncodingRules) { this._rules = rules; this._selector = ''; @@ -65,9 +30,9 @@ export class Calldata { // 1. Create a queue of subtrees by hash // Note that they are ordered the same as - const subtreeQueue = Calldata._createQueue(this._root); + const iterator = new ReverseCalldataIterator(this._root); let block: CalldataBlock | undefined; - for (block = subtreeQueue.popBack(); block !== undefined; block = subtreeQueue.popBack()) { + while (block = iterator.next()) { if (block instanceof CalldataBlocks.Pointer) { const dependencyBlockHashBuf = block.getDependency().computeHash(); const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); @@ -97,10 +62,10 @@ export class Calldata { this.optimize(); } - const offsetQueue = Calldata._createQueue(this._root); + const iterator = new CalldataIterator(this._root); let block: CalldataBlock | undefined; let offset = 0; - for (block = offsetQueue.popFront(); block !== undefined; block = offsetQueue.popFront()) { + while (block = iterator.next()) { block.setOffset(offset); offset += block.getSizeInBytes(); } @@ -136,13 +101,12 @@ export class Calldata { throw new Error('expected root'); } - const valueQueue = Calldata._createQueue(this._root); + const iterator = new CalldataIterator(this._root); let block: CalldataBlock | undefined; let offset = 0; - const functionBlock = valueQueue.peekFront(); - const functionName: string = functionBlock === undefined ? '' : functionBlock.getName(); - for (block = valueQueue.popFront(); block !== undefined; block = valueQueue.popFront()) { + const functionName: string = this._root.getName(); + while (block = iterator.next()) { // Process each block 1 word at a time const size = block.getSizeInBytes(); const name = block.getName(); @@ -209,10 +173,10 @@ export class Calldata { throw new Error('expected root'); } - const valueQueue = Calldata._createQueue(this._root); + const iterator = new CalldataIterator(this._root); const valueBufs: Buffer[] = [selectorBuffer]; let block: CalldataBlock | undefined; - for (block = valueQueue.popFront(); block !== undefined; block = valueQueue.popFront()) { + while (block = iterator.next()) { valueBufs.push(block.toBuffer()); } diff --git a/packages/utils/src/abi_encoder/calldata/iterator.ts b/packages/utils/src/abi_encoder/calldata/iterator.ts new file mode 100644 index 0000000000..3e3367e10b --- /dev/null +++ b/packages/utils/src/abi_encoder/calldata/iterator.ts @@ -0,0 +1,94 @@ +/* tslint:disable max-classes-per-file */ +import * as _ from 'lodash'; + +import { Queue } from '../utils/queue'; + +import * as CalldataBlocks from './blocks'; +import { CalldataBlock } from './calldata_block'; + +/** + * Iterator class for Calldata Blocks. Blocks follows the order + * they should be put into calldata that is passed to he EVM. + * + * Example #1: + * Let root = Set { + * Blob{} A, + * Pointer { + * Blob{} a + * } B, + * Blob{} C + * } + * It will iterate as follows: [A, B, C, B.a] + * + * Example #2: + * Let root = Set { + * Blob{} A, + * Pointer { + * Blob{} a + * Pointer { + * Blob{} b + * } + * } B, + * Pointer { + * Blob{} c + * } C + * } + * It will iterate as follows: [A, B, C, B.a, B.b, C.c] + */ +abstract class BaseIterator { + protected readonly _root: CalldataBlock; + protected readonly _queue: Queue; + + private static _createQueue(block: CalldataBlock): Queue { + const queue = new Queue(); + // Base case + if (!(block instanceof CalldataBlocks.Set)) { + queue.pushBack(block); + return queue; + } + // This is a set; add members + const set = block; + _.eachRight(set.getMembers(), (member: CalldataBlock) => { + queue.mergeFront(BaseIterator._createQueue(member)); + }); + // Add children + _.each(set.getMembers(), (member: CalldataBlock) => { + // Traverse child if it is a unique pointer. + // A pointer that is an alias for another pointer is ignored. + if (member instanceof CalldataBlocks.Pointer && member.getAlias() === undefined) { + const dependency = member.getDependency(); + queue.mergeBack(BaseIterator._createQueue(dependency)); + } + }); + // Put set block at the front of the queue + queue.pushFront(set); + return queue; + } + + public constructor(root: CalldataBlock) { + this._root = root; + this._queue = BaseIterator._createQueue(root); + } + + public abstract next(): CalldataBlock | undefined; +} + +export class CalldataIterator extends BaseIterator { + public constructor(root: CalldataBlock) { + super(root); + } + + public next(): CalldataBlock | undefined { + return this._queue.popFront(); + } +} + +export class ReverseCalldataIterator extends BaseIterator { + public constructor(root: CalldataBlock) { + super(root); + } + + public next(): CalldataBlock | undefined { + return this._queue.popBack(); + } +} diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts index 00c743d2b9..e7c172afb1 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts @@ -10,6 +10,6 @@ export class Pointer extends AbstractDataTypes.Pointer { } public getSignature(): string { - return this._dependency.getSignature(); + return this._destination.getSignature(); } } From bab1c92c703ee53e77a56a7f7a5e3bba5b4a2306 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 20:11:10 -0800 Subject: [PATCH 166/230] Linter for Calldata Block Iterator --- .../src/abi_encoder/calldata/calldata.ts | 13 +++------- .../src/abi_encoder/calldata/iterator.ts | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index dd9d47def8..50f0f0fadd 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -31,8 +31,7 @@ export class Calldata { // 1. Create a queue of subtrees by hash // Note that they are ordered the same as const iterator = new ReverseCalldataIterator(this._root); - let block: CalldataBlock | undefined; - while (block = iterator.next()) { + for (const block of iterator) { if (block instanceof CalldataBlocks.Pointer) { const dependencyBlockHashBuf = block.getDependency().computeHash(); const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); @@ -63,9 +62,8 @@ export class Calldata { } const iterator = new CalldataIterator(this._root); - let block: CalldataBlock | undefined; let offset = 0; - while (block = iterator.next()) { + for (const block of iterator) { block.setOffset(offset); offset += block.getSizeInBytes(); } @@ -102,11 +100,9 @@ export class Calldata { } const iterator = new CalldataIterator(this._root); - - let block: CalldataBlock | undefined; let offset = 0; const functionName: string = this._root.getName(); - while (block = iterator.next()) { + for (const block of iterator) { // Process each block 1 word at a time const size = block.getSizeInBytes(); const name = block.getName(); @@ -175,8 +171,7 @@ export class Calldata { const iterator = new CalldataIterator(this._root); const valueBufs: Buffer[] = [selectorBuffer]; - let block: CalldataBlock | undefined; - while (block = iterator.next()) { + for (const block of iterator) { valueBufs.push(block.toBuffer()); } diff --git a/packages/utils/src/abi_encoder/calldata/iterator.ts b/packages/utils/src/abi_encoder/calldata/iterator.ts index 3e3367e10b..8e2b16a5ae 100644 --- a/packages/utils/src/abi_encoder/calldata/iterator.ts +++ b/packages/utils/src/abi_encoder/calldata/iterator.ts @@ -35,7 +35,7 @@ import { CalldataBlock } from './calldata_block'; * } * It will iterate as follows: [A, B, C, B.a, B.b, C.c] */ -abstract class BaseIterator { +abstract class BaseIterator implements Iterable { protected readonly _root: CalldataBlock; protected readonly _queue: Queue; @@ -70,7 +70,25 @@ abstract class BaseIterator { this._queue = BaseIterator._createQueue(root); } - public abstract next(): CalldataBlock | undefined; + public [Symbol.iterator](): { next: () => IteratorResult } { + return { + next: () => { + const nextBlock = this.nextBlock(); + if (nextBlock !== undefined) { + return { + value: nextBlock, + done: false, + }; + } + return { + done: true, + value: new CalldataBlocks.Blob('', '', '', new Buffer('')), + }; + }, + }; + } + + public abstract nextBlock(): CalldataBlock | undefined; } export class CalldataIterator extends BaseIterator { @@ -78,7 +96,7 @@ export class CalldataIterator extends BaseIterator { super(root); } - public next(): CalldataBlock | undefined { + public nextBlock(): CalldataBlock | undefined { return this._queue.popFront(); } } @@ -88,7 +106,7 @@ export class ReverseCalldataIterator extends BaseIterator { super(root); } - public next(): CalldataBlock | undefined { + public nextBlock(): CalldataBlock | undefined { return this._queue.popBack(); } } From 50344fa24a4a708ae90030af107afb15366f883f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 21:19:11 -0800 Subject: [PATCH 167/230] Added inline documentation for Calldata class --- .../abstract_data_types/data_type.ts | 4 +- .../src/abi_encoder/calldata/calldata.ts | 229 +++++++++++------- 2 files changed, 148 insertions(+), 85 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index 4500803539..ab7df6eccb 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -32,8 +32,8 @@ export abstract class DataType { } const block = this.generateCalldataBlock(value); calldata.setRoot(block); - const calldataHex = calldata.toHexString(); - return calldataHex; + const encodedCalldata = calldata.toString(); + return encodedCalldata; } public decode(calldata: string, rules?: DecodingRules, selector?: string): any { diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index 50f0f0fadd..da61b22561 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -11,27 +11,107 @@ import { CalldataIterator, ReverseCalldataIterator } from './iterator'; export class Calldata { private readonly _rules: EncodingRules; private _selector: string; - private _sizeInBytes: number; private _root: CalldataBlock | undefined; public constructor(rules: EncodingRules) { this._rules = rules; this._selector = ''; - this._sizeInBytes = 0; this._root = undefined; } - - public optimize(): void { + /** + * Sets the root calldata block. This block usually corresponds to a Method. + */ + public setRoot(block: CalldataBlock): void { + this._root = block; + } + /** + * Sets the selector to be prepended onto the calldata. + * If the root block was created by a Method then a selector will likely be set. + */ + public setSelector(selector: string): void { + if (!selector.startsWith('0x')) { + throw new Error(`Expected selector to be hex. Missing prefix '0x'`); + } else if (selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) { + throw new Error(`Invalid selector '${selector}'`); + } + this._selector = selector; + } + /** + * Iterates through the calldata blocks, starting from the root block, to construct calldata as a hex string. + * If the `optimize` flag is set then this calldata will be condensed, to save gas. + * If the `annotate` flag is set then this will return human-readable calldata. + * If the `annotate` flag is *not* set then this will return EVM-compatible calldata. + */ + public toString(): string { + // Sanity check: root block must be set if (this._root === undefined) { throw new Error('expected root'); } - - const blocksByHash: { [key: string]: CalldataBlock } = {}; - - // 1. Create a queue of subtrees by hash - // Note that they are ordered the same as - const iterator = new ReverseCalldataIterator(this._root); + // Optimize, if flag set + if (this._rules.optimize) { + this._optimize(); + } + // Set offsets + const iterator = new CalldataIterator(this._root); + let offset = 0; for (const block of iterator) { + block.setOffset(offset); + offset += block.getSizeInBytes(); + } + // Generate hex string + const hexString = this._rules.annotate ? this._toAnnotatedString() : this._toCondensedString(); + return hexString; + } + /** + * There are three types of calldata blocks: Blob, Set and Pointer. + * Scenarios arise where distinct pointers resolve to identical values. + * We optimize by keeping only one such instance of the identical value, and redirecting all pointers here. + * We keep the last such duplicate value because pointers can only be positive (they cannot point backwards). + * + * Example #1: + * function f(string[], string[]) + * f(["foo", "bar", "blitz"], ["foo", "bar", "blitz"]) + * The array ["foo", "bar", "blitz"] will only be included in the calldata once. + * + * Example #2: + * function f(string[], string) + * f(["foo", "bar", "blitz"], "foo") + * The string "foo" will only be included in the calldata once. + * + * Example #3: + * function f((string, uint, bytes), string, uint, bytes) + * f(("foo", 5, "0x05"), "foo", 5, "0x05") + * The string "foo" and bytes "0x05" will only be included in the calldata once. + * The duplicate `uint 5` values cannot be optimized out because they are static values (no pointer points to them). + * + * @TODO #1: + * This optimization strategy handles blocks that are exact duplicates of one another. + * But what if some block is a combination of two other blocks? Or a subset of another block? + * This optimization problem is not much different from the current implemetation. + * Instead of tracking "observed" hashes, at each node we would simply do pattern-matching on the calldata. + * This strategy would be applied after assigning offsets to the tree, rather than before (as in this strategy). + * Note that one consequence of this strategy is pointers may resolve to offsets that are not word-aligned. + * This shouldn't be a problem but further investigation should be done. + * + * @TODO #2: + * To be done as a follow-up to @TODO #1. + * Since we optimize from the bottom-up, we could be affecting the outcome of a later potential optimization. + * For example, what if by removing one duplicate value we miss out on optimizing another block higher in the tree. + * To handle this case, at each node we can store a candidate optimization in a priority queue (sorted by calldata size). + * At the end of traversing the tree, the candidate at the front of the queue will be the most optimal output. + * + */ + private _optimize(): void { + // Step 1/1 Create a reverse iterator (starts from the end of the calldata to the beginning) + if (this._root === undefined) { + throw new Error('expected root'); + } + const iterator = new ReverseCalldataIterator(this._root); + // Step 2/2 Iterate over each block, keeping track of which blocks have been seen and pruning redundant blocks. + const blocksByHash: { [key: string]: CalldataBlock } = {}; + for (const block of iterator) { + // If a block is a pointer and its value has already been observed, then update + // the pointer to resolve to the existing value. if (block instanceof CalldataBlocks.Pointer) { const dependencyBlockHashBuf = block.getDependency().computeHash(); const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); @@ -43,7 +123,7 @@ export class Calldata { } continue; } - + // This block has not been seen. Record its hash. const blockHashBuf = block.computeHash(); const blockHash = ethUtil.bufferToHex(blockHashBuf); if (!(blockHash in blocksByHash)) { @@ -51,84 +131,85 @@ export class Calldata { } } } - - public toHexString(): string { + /** + * Returns EVM-compatible calldata as a Hex string. + */ + private _toCondensedString(): string { + // Sanity check: must have a root block. if (this._root === undefined) { throw new Error('expected root'); } - - if (this._rules.optimize) { - this.optimize(); - } - + // Construct an array of buffers (one buffer for each block). + const selectorBuffer = ethUtil.toBuffer(this._selector); + const valueBufs: Buffer[] = [selectorBuffer]; const iterator = new CalldataIterator(this._root); - let offset = 0; for (const block of iterator) { - block.setOffset(offset); - offset += block.getSizeInBytes(); + valueBufs.push(block.toBuffer()); } - - const hexValue = this._rules.annotate ? this._generateAnnotatedHexString() : this._generateCondensedHexString(); + // Create hex from buffer array. + const combinedBuffers = Buffer.concat(valueBufs); + const hexValue = ethUtil.bufferToHex(combinedBuffers); return hexValue; } - - public getSelectorHex(): string { - return this._selector; - } - - public getSizeInBytes(): number { - return this._sizeInBytes; - } - - public setRoot(block: CalldataBlock): void { - this._root = block; - this._sizeInBytes += block.getSizeInBytes(); - } - - public setSelector(selector: string): void { - this._selector = selector.startsWith('0x') ? selector : `$0x${selector}`; - if (this._selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) { - throw new Error(`Invalid selector '${this._selector}'`); - } - this._sizeInBytes += Constants.HEX_SELECTOR_LENGTH_IN_BYTES; // @TODO: Used to be += 8. Bad? - } - - private _generateAnnotatedHexString(): string { - let hexValue = `${this._selector}`; + /** + * Returns human-redable calldata. + * + * Example: + * simpleFunction(string[], string[]) + * strings = ["Hello", "World"] + * simpleFunction(strings, strings) + * + * Output: + * 0xbb4f12e3 + * ### simpleFunction + * 0x0 0000000000000000000000000000000000000000000000000000000000000040 ptr (alias for array2) + * 0x20 0000000000000000000000000000000000000000000000000000000000000040 ptr + * + * 0x40 0000000000000000000000000000000000000000000000000000000000000002 ### array2 + * 0x60 0000000000000000000000000000000000000000000000000000000000000040 ptr + * 0x80 0000000000000000000000000000000000000000000000000000000000000080 ptr + * 0xa0 0000000000000000000000000000000000000000000000000000000000000005 array2[0] + * 0xc0 48656c6c6f000000000000000000000000000000000000000000000000000000 + * 0xe0 0000000000000000000000000000000000000000000000000000000000000005 array2[1] + * 0x100 576f726c64000000000000000000000000000000000000000000000000000000 + */ + private _toAnnotatedString(): string { + // Sanity check: must have a root block. if (this._root === undefined) { throw new Error('expected root'); } - - const iterator = new CalldataIterator(this._root); + // Construct annotated calldata + let hexValue = `${this._selector}`; let offset = 0; const functionName: string = this._root.getName(); + const iterator = new CalldataIterator(this._root); for (const block of iterator) { // Process each block 1 word at a time const size = block.getSizeInBytes(); const name = block.getName(); const parentName = block.getParentName(); const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, ''); - - // Current offset - let offsetStr = ''; - - // If this block is empty then it's a newline const offsetPadding = 10; const valuePadding = 74; const namePadding = 80; const evmWordStartIndex = 0; const emptySize = 0; - let value = ''; + // Resulting line will be + let offsetStr = ''; + let valueStr = ''; let nameStr = ''; - let line = ''; + let lineStr = ''; if (size === emptySize) { + // This is a Set block with no header. + // For example, a tuple or an array with a defined length. offsetStr = ' '.repeat(offsetPadding); - value = ' '.repeat(valuePadding); + valueStr = ' '.repeat(valuePadding); nameStr = `### ${prettyName.padEnd(namePadding)}`; - line = `\n${offsetStr}${value}${nameStr}`; + lineStr = `\n${offsetStr}${valueStr}${nameStr}`; } else { + // This block has at least one word of value. offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); - value = ethUtil + valueStr = ethUtil .stripHexPrefix( ethUtil.bufferToHex( block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES), @@ -137,46 +218,28 @@ export class Calldata { .padEnd(valuePadding); if (block instanceof CalldataBlocks.Set) { nameStr = `### ${prettyName.padEnd(namePadding)}`; - line = `\n${offsetStr}${value}${nameStr}`; + lineStr = `\n${offsetStr}${valueStr}${nameStr}`; } else { nameStr = ` ${prettyName.padEnd(namePadding)}`; - line = `${offsetStr}${value}${nameStr}`; + lineStr = `${offsetStr}${valueStr}${nameStr}`; } } - + // This block has a value that is more than 1 word. for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) { offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); - value = ethUtil + valueStr = ethUtil .stripHexPrefix( ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES)), ) .padEnd(valuePadding); nameStr = ' '.repeat(namePadding); - line = `${line}\n${offsetStr}${value}${nameStr}`; + lineStr = `${lineStr}\n${offsetStr}${valueStr}${nameStr}`; } - // Append to hex value - hexValue = `${hexValue}\n${line}`; + hexValue = `${hexValue}\n${lineStr}`; offset += size; } return hexValue; } - - private _generateCondensedHexString(): string { - const selectorBuffer = ethUtil.toBuffer(this._selector); - if (this._root === undefined) { - throw new Error('expected root'); - } - - const iterator = new CalldataIterator(this._root); - const valueBufs: Buffer[] = [selectorBuffer]; - for (const block of iterator) { - valueBufs.push(block.toBuffer()); - } - - const combinedBuffers = Buffer.concat(valueBufs); - const hexValue = ethUtil.bufferToHex(combinedBuffers); - return hexValue; - } } From 3bf5a4e83f57f82090a64fc76fcc7bf3f7c68607 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 21:20:46 -0800 Subject: [PATCH 168/230] Moved some consts outside of a loop --- packages/utils/src/abi_encoder/calldata/calldata.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index da61b22561..668b92f067 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -178,6 +178,12 @@ export class Calldata { if (this._root === undefined) { throw new Error('expected root'); } + // Constants for constructing annotated string + const offsetPadding = 10; + const valuePadding = 74; + const namePadding = 80; + const evmWordStartIndex = 0; + const emptySize = 0; // Construct annotated calldata let hexValue = `${this._selector}`; let offset = 0; @@ -189,11 +195,6 @@ export class Calldata { const name = block.getName(); const parentName = block.getParentName(); const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, ''); - const offsetPadding = 10; - const valuePadding = 74; - const namePadding = 80; - const evmWordStartIndex = 0; - const emptySize = 0; // Resulting line will be let offsetStr = ''; let valueStr = ''; From a8d707b4627b8997b02c53e670bd1f5636eced4c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 21:23:37 -0800 Subject: [PATCH 169/230] Linter on Calldata --- .../src/abi_encoder/calldata/calldata.ts | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index 668b92f067..a662f30b97 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -67,23 +67,23 @@ export class Calldata { * Scenarios arise where distinct pointers resolve to identical values. * We optimize by keeping only one such instance of the identical value, and redirecting all pointers here. * We keep the last such duplicate value because pointers can only be positive (they cannot point backwards). - * + * * Example #1: * function f(string[], string[]) * f(["foo", "bar", "blitz"], ["foo", "bar", "blitz"]) * The array ["foo", "bar", "blitz"] will only be included in the calldata once. - * + * * Example #2: * function f(string[], string) * f(["foo", "bar", "blitz"], "foo") * The string "foo" will only be included in the calldata once. - * + * * Example #3: * function f((string, uint, bytes), string, uint, bytes) * f(("foo", 5, "0x05"), "foo", 5, "0x05") * The string "foo" and bytes "0x05" will only be included in the calldata once. * The duplicate `uint 5` values cannot be optimized out because they are static values (no pointer points to them). - * + * * @TODO #1: * This optimization strategy handles blocks that are exact duplicates of one another. * But what if some block is a combination of two other blocks? Or a subset of another block? @@ -92,14 +92,14 @@ export class Calldata { * This strategy would be applied after assigning offsets to the tree, rather than before (as in this strategy). * Note that one consequence of this strategy is pointers may resolve to offsets that are not word-aligned. * This shouldn't be a problem but further investigation should be done. - * + * * @TODO #2: * To be done as a follow-up to @TODO #1. * Since we optimize from the bottom-up, we could be affecting the outcome of a later potential optimization. - * For example, what if by removing one duplicate value we miss out on optimizing another block higher in the tree. + * For example, what if by removing one duplicate value we miss out on optimizing another block higher in the tree. * To handle this case, at each node we can store a candidate optimization in a priority queue (sorted by calldata size). * At the end of traversing the tree, the candidate at the front of the queue will be the most optimal output. - * + * */ private _optimize(): void { // Step 1/1 Create a reverse iterator (starts from the end of the calldata to the beginning) @@ -153,25 +153,25 @@ export class Calldata { } /** * Returns human-redable calldata. - * + * * Example: * simpleFunction(string[], string[]) * strings = ["Hello", "World"] * simpleFunction(strings, strings) - * + * * Output: * 0xbb4f12e3 - * ### simpleFunction - * 0x0 0000000000000000000000000000000000000000000000000000000000000040 ptr (alias for array2) - * 0x20 0000000000000000000000000000000000000000000000000000000000000040 ptr + * ### simpleFunction + * 0x0 0000000000000000000000000000000000000000000000000000000000000040 ptr (alias for array2) + * 0x20 0000000000000000000000000000000000000000000000000000000000000040 ptr * - * 0x40 0000000000000000000000000000000000000000000000000000000000000002 ### array2 - * 0x60 0000000000000000000000000000000000000000000000000000000000000040 ptr - * 0x80 0000000000000000000000000000000000000000000000000000000000000080 ptr - * 0xa0 0000000000000000000000000000000000000000000000000000000000000005 array2[0] - * 0xc0 48656c6c6f000000000000000000000000000000000000000000000000000000 - * 0xe0 0000000000000000000000000000000000000000000000000000000000000005 array2[1] - * 0x100 576f726c64000000000000000000000000000000000000000000000000000000 + * 0x40 0000000000000000000000000000000000000000000000000000000000000002 ### array2 + * 0x60 0000000000000000000000000000000000000000000000000000000000000040 ptr + * 0x80 0000000000000000000000000000000000000000000000000000000000000080 ptr + * 0xa0 0000000000000000000000000000000000000000000000000000000000000005 array2[0] + * 0xc0 48656c6c6f000000000000000000000000000000000000000000000000000000 + * 0xe0 0000000000000000000000000000000000000000000000000000000000000005 array2[1] + * 0x100 576f726c64000000000000000000000000000000000000000000000000000000 */ private _toAnnotatedString(): string { // Sanity check: must have a root block. @@ -240,7 +240,6 @@ export class Calldata { hexValue = `${hexValue}\n${lineStr}`; offset += size; } - return hexValue; } } From feb551a02efe144b0aca4696f01dcdd42383fb36 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 21:39:47 -0800 Subject: [PATCH 170/230] Comments to Set datatype --- .../abstract_data_types/types/set.ts | 77 +++++++++---------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index 77fd7b3ea2..427122ad6b 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -49,18 +49,19 @@ export abstract class Set extends DataType { public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object { let members = this._members; + // Case 1: This is an array of undefined length, which means that `this._members` was not + // populated in the constructor. So, construct the set of members it now. if (this._isArray && this._arrayLength === undefined) { const arrayLengthBuf = calldata.popWord(); const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); - const hexBase = 16; - const arrayLength = new BigNumber(arrayLengthHex, hexBase); - + const arrayLength = new BigNumber(arrayLengthHex, Constants.HEX_BASE); [members] = this._createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); } - + // Create a new scope in the calldata, before descending into the members of this set. calldata.startScope(); let value: any[] | object; if (rules.structsAsObjects && !this._isArray) { + // Construct an object with values for each member of the set. value = {}; _.each(this._memberIndexByName, (idx: number, key: string) => { const member = this._members[idx]; @@ -68,41 +69,33 @@ export abstract class Set extends DataType { (value as { [key: string]: any })[key] = memberValue; }); } else { + // Construct an array with values for each member of the set. value = []; _.each(members, (member: DataType, idx: number) => { const memberValue = member.generateValue(calldata, rules); (value as any[]).push(memberValue); }); } + // Close this scope and return tetheh value. calldata.endScope(); return value; } public isStatic(): boolean { - /* For Tuple: - const isStaticTuple = this.children.length === 0; - return isStaticTuple; // @TODO: True in every case or only when dynamic data? - - For Array: - if isLengthDefined = false then this is false - - Otherwise if the first element is a Pointer then false - */ - + // An array with an undefined length is never static. if (this._isArray && this._arrayLength === undefined) { return false; } - - // Search for dependent members + // If any member of the set is a pointer then the set is not static. const dependentMember = _.find(this._members, (member: DataType) => { return member instanceof Pointer; }); - const isStatic = dependentMember === undefined; // static if we couldn't find a dependent member + const isStatic = dependentMember === undefined; return isStatic; } protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): CalldataBlocks.Set { - // Sanity check length + // Sanity check: if the set has a defined length then `value` must have the same length. if (this._arrayLength !== undefined && value.length !== this._arrayLength) { throw new Error( `Expected array of ${JSON.stringify( @@ -110,41 +103,42 @@ export abstract class Set extends DataType { )} elements, but got array of length ${JSON.stringify(value.length)}`, ); } - + // Create a new calldata block for this set. const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: CalldataBlocks.Set = new CalldataBlocks.Set( + const block: CalldataBlocks.Set = new CalldataBlocks.Set( this.getDataItem().name, this.getSignature(), parentName, ); - + // If this set has an undefined length then set its header to be the number of elements. let members = this._members; if (this._isArray && this._arrayLength === undefined) { [members] = this._createMembersWithLength(this.getDataItem(), value.length); - const lenBuf = ethUtil.setLengthLeft( ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), Constants.EVM_WORD_WIDTH_IN_BYTES, ); - methodBlock.setHeader(lenBuf); + block.setHeader(lenBuf); } - + // Create blocks for members of set. const memberCalldataBlocks: CalldataBlock[] = []; _.each(members, (member: DataType, idx: number) => { - const block = member.generateCalldataBlock(value[idx], methodBlock); - memberCalldataBlocks.push(block); + const memberBlock = member.generateCalldataBlock(value[idx], block); + memberCalldataBlocks.push(memberBlock); }); - methodBlock.setMembers(memberCalldataBlocks); - return methodBlock; + block.setMembers(memberCalldataBlocks); + return block; } protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): CalldataBlocks.Set { + // Create a new calldata block for this set. const parentName = parentBlock === undefined ? '' : parentBlock.getName(); - const methodBlock: CalldataBlocks.Set = new CalldataBlocks.Set( + const block: CalldataBlocks.Set = new CalldataBlocks.Set( this.getDataItem().name, this.getSignature(), parentName, ); + // Create blocks for members of set. const memberCalldataBlocks: CalldataBlock[] = []; const childMap = _.cloneDeep(this._memberIndexByName); _.forOwn(obj, (value: any, key: string) => { @@ -153,17 +147,17 @@ export abstract class Set extends DataType { `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, ); } - const block = this._members[this._memberIndexByName[key]].generateCalldataBlock(value, methodBlock); - memberCalldataBlocks.push(block); + const memberBlock = this._members[this._memberIndexByName[key]].generateCalldataBlock(value, block); + memberCalldataBlocks.push(memberBlock); delete childMap[key]; }); - + // Sanity check that all members have been included. if (Object.keys(childMap).length !== 0) { throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); } - - methodBlock.setMembers(memberCalldataBlocks); - return methodBlock; + // Associate member blocks with Set block. + block.setMembers(memberCalldataBlocks); + return block; } protected _computeSignatureOfMembers(): string { @@ -184,7 +178,7 @@ export abstract class Set extends DataType { if (dataItem.components === undefined) { throw new Error(`Expected components`); } - + // Create one member for each component of `dataItem` const members: DataType[] = []; const memberIndexByName: MemberIndexByName = {}; _.each(dataItem.components, (memberItem: DataItem) => { @@ -200,28 +194,27 @@ export abstract class Set extends DataType { memberIndexByName[memberItem.name] = members.length; members.push(child); }); - return [members, memberIndexByName]; } private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberIndexByName] { + // Create `length` members, deriving the type from `dataItem` const members: DataType[] = []; const memberIndexByName: MemberIndexByName = {}; const range = _.range(length); _.each(range, (idx: number) => { - const childDataItem: DataItem = { + const memberDataItem: DataItem = { type: this._arrayElementType ? this._arrayElementType : '', name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`, }; const components = dataItem.components; if (components !== undefined) { - childDataItem.components = components; + memberDataItem.components = components; } - const child = this.getFactory().create(childDataItem, this); + const memberType = this.getFactory().create(memberDataItem, this); memberIndexByName[idx.toString(Constants.DEC_BASE)] = members.length; - members.push(child); + members.push(memberType); }); - return [members, memberIndexByName]; } } From ad1b5af4e59ba750c019cab1f5ec9584b8645101 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sun, 25 Nov 2018 21:40:37 -0800 Subject: [PATCH 171/230] Ran prettier --- .../utils/src/abi_encoder/evm_data_types/array.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/int.ts | 7 ++++--- .../src/abi_encoder/evm_data_types/static_bytes.ts | 7 ++++--- .../utils/src/abi_encoder/evm_data_types/uint.ts | 7 ++++--- packages/utils/src/abi_encoder/utils/math.ts | 12 ++++++++++-- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 54f7ba9fa1..8cf2cf7cf1 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -12,7 +12,7 @@ export class Array extends AbstractDataTypes.Set { return Array._MATCHER.test(type); } - private static _decodeElementTypeAndLengthFromType(type: string): [string, undefined|number] { + private static _decodeElementTypeAndLengthFromType(type: string): [string, undefined | number] { const matches = Array._MATCHER.exec(type); if (matches === null || matches.length !== 3) { throw new Error(`Could not parse array: ${type}`); diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 8a82ed4cc9..032cd045a4 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -24,9 +24,10 @@ export class Int extends AbstractDataTypes.Blob { private static _decodeWidthFromType(type: string): number { const matches = Int._MATCHER.exec(type); - const width = (matches !== null && matches.length === 2 && matches[1] !== undefined) - ? parseInt(matches[1], Constants.DEC_BASE) - : Int._DEFAULT_WIDTH; + const width = + matches !== null && matches.length === 2 && matches[1] !== undefined + ? parseInt(matches[1], Constants.DEC_BASE) + : Int._DEFAULT_WIDTH; return width; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index d0b41194e6..2c649cb33b 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -20,9 +20,10 @@ export class StaticBytes extends AbstractDataTypes.Blob { private static _decodeWidthFromType(type: string): number { const matches = StaticBytes._MATCHER.exec(type); - const width = (matches !== null && matches.length === 3 && matches[2] !== undefined) - ? parseInt(matches[2], Constants.DEC_BASE) - : StaticBytes._DEFAULT_WIDTH; + const width = + matches !== null && matches.length === 3 && matches[2] !== undefined + ? parseInt(matches[2], Constants.DEC_BASE) + : StaticBytes._DEFAULT_WIDTH; return width; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index b1bc690d8f..b5b7683a2a 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -24,9 +24,10 @@ export class UInt extends AbstractDataTypes.Blob { private static _decodeWidthFromType(type: string): number { const matches = UInt._MATCHER.exec(type); - const width = (matches !== null && matches.length === 2 && matches[1] !== undefined) - ? parseInt(matches[1], Constants.DEC_BASE) - : UInt._DEFAULT_WIDTH; + const width = + matches !== null && matches.length === 2 && matches[1] !== undefined + ? parseInt(matches[1], Constants.DEC_BASE) + : UInt._DEFAULT_WIDTH; return width; } diff --git a/packages/utils/src/abi_encoder/utils/math.ts b/packages/utils/src/abi_encoder/utils/math.ts index 8d21ada0a9..c5fa10e73f 100644 --- a/packages/utils/src/abi_encoder/utils/math.ts +++ b/packages/utils/src/abi_encoder/utils/math.ts @@ -4,7 +4,11 @@ import * as _ from 'lodash'; import * as Constants from '../utils/constants'; -function sanityCheckBigNumberRange(value_: BigNumber | string | number, minValue: BigNumber, maxValue: BigNumber): void { +function sanityCheckBigNumberRange( + value_: BigNumber | string | number, + minValue: BigNumber, + maxValue: BigNumber, +): void { const value = new BigNumber(value_, 10); if (value.greaterThan(maxValue)) { throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${maxValue}`); @@ -51,7 +55,11 @@ export function encodeNumericValue(value_: BigNumber | string | number): Buffer * @param value_ The value to encode. * @return ABI Encoded value */ -export function safeEncodeNumericValue(value: BigNumber | string | number, minValue: BigNumber, maxValue: BigNumber): Buffer { +export function safeEncodeNumericValue( + value: BigNumber | string | number, + minValue: BigNumber, + maxValue: BigNumber, +): Buffer { sanityCheckBigNumberRange(value, minValue, maxValue); const encodedValue = encodeNumericValue(value); return encodedValue; From 2e79ce26cbeacdeb0ea52b7c2e3da68e8055e7c6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 26 Nov 2018 18:00:05 -0800 Subject: [PATCH 172/230] Tests use to.be.deep.equal instead of JSON --- .../test/abi_encoder/evm_data_types_test.ts | 176 +++++------------- .../utils/test/abi_encoder/methods_test.ts | 72 ++----- .../utils/test/abi_encoder/optimizer_test.ts | 64 ++----- .../test/abi_encoder/return_values_test.ts | 24 +-- 4 files changed, 84 insertions(+), 252 deletions(-) diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts index 7cea865293..b995002654 100644 --- a/packages/utils/test/abi_encoder/evm_data_types_test.ts +++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts @@ -24,9 +24,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Dynamic size; Static elements', async () => { // Create DataType object @@ -41,9 +39,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Fixed size; Dynamic elements', async () => { // Create DataType object @@ -58,9 +54,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Dynamic size; Dynamic elements', async () => { // Create DataType object @@ -75,9 +69,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Dynamic Size; Multidimensional; Dynamic Elements', async () => { // Create DataType object @@ -95,9 +87,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Dynamic Size; Multidimensional; Static Elements', async () => { // Create DataType object @@ -115,9 +105,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Static Size; Multidimensional; Static Elements', async () => { // Create DataType object @@ -134,9 +122,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Static Size; Multidimensional; Dynamic Elements', async () => { // Create DataType object @@ -153,9 +139,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Static size; Too Few Elements', async () => { // Create DataType object @@ -211,9 +195,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Decode Encoded Args and validate result const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Dynamic elements only', async () => { // Create DataType object @@ -233,9 +215,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Decode Encoded Args and validate result const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Nested Static Array', async () => { // Create DataType object @@ -255,9 +235,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Decode Encoded Args and validate result const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Nested Dynamic Array', async () => { // Create DataType object @@ -277,9 +255,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Decode Encoded Args and validate result const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Nested Static Multidimensional Array', async () => { // Create DataType object @@ -301,9 +277,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Decode Encoded Args and validate result const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Nested Dynamic Multidimensional Array', async () => { // Create DataType object @@ -325,9 +299,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Decode Encoded Args and validate result const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Static and dynamic elements mixed', async () => { // Create DataType object @@ -357,9 +329,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Decode Encoded Args and validate result const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodedArgs = dataType.decode(encodedArgs, decodingRules); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Missing Key', async () => { // Create DataType object @@ -406,9 +376,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Invalid Address - input is not valid hex', async () => { // Create DataType object @@ -447,9 +415,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('False', async () => { // Create DataType object @@ -463,9 +429,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); }); @@ -489,9 +453,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int256 - Negative Base Case', async () => { // Create DataType object @@ -505,9 +467,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int256 - Positive Value', async () => { // Create DataType object @@ -521,9 +481,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int256 - Negative Value', async () => { // Create DataType object @@ -537,9 +495,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int256 - Value too large', async () => { // Create DataType object @@ -575,9 +531,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int32 - Negative Base Case', async () => { // Create DataType object @@ -591,9 +545,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int32 - Positive Value', async () => { // Create DataType object @@ -607,9 +559,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int32 - Negative Value', async () => { // Create DataType object @@ -623,9 +573,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Int32 - Value too large', async () => { // Create DataType object @@ -671,9 +619,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('UInt256 - Positive Value', async () => { // Create DataType object @@ -687,9 +633,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('UInt256 - Zero Value', async () => { // Create DataType object @@ -703,9 +647,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('UInt256 - Value too large', async () => { // Create DataType object @@ -741,9 +683,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('UInt32 - Positive Value', async () => { // Create DataType object @@ -757,9 +697,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('UInt32 - Zero Value', async () => { // Create DataType object @@ -773,9 +711,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('UInt32 - Value too large', async () => { // Create DataType object @@ -814,9 +750,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Single Byte (bytes1)', async () => { // Create DataType object @@ -830,9 +764,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('4 Bytes (bytes4)', async () => { // Create DataType object @@ -846,9 +778,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('4 Bytes (bytes4); Encoder must pad input', async () => { // Create DataType object @@ -863,10 +793,8 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); const paddedArgs = '0x1a180000'; - const paddedArgsAsJson = JSON.stringify(paddedArgs); - expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); + expect(decodedArgs).to.be.deep.equal(paddedArgs); }); it('32 Bytes (bytes32)', async () => { // Create DataType object @@ -880,9 +808,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('32 Bytes (bytes32); Encoder must pad input', async () => { // Create DataType object @@ -897,10 +823,8 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); const paddedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; - const paddedArgsAsJson = JSON.stringify(paddedArgs); - expect(decodedArgsAsJson).to.be.equal(paddedArgsAsJson); + expect(decodedArgs).to.be.deep.equal(paddedArgs); }); it('Should throw when pass in too many bytes (bytes4)', async () => { // Create DataType object @@ -967,9 +891,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Spans multiple EVM words', async () => { // Create DataType object @@ -986,9 +908,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Input as Buffer', async () => { // Create DataType object @@ -1005,9 +925,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Should throw when pass in bad hex (no 0x prefix)', async () => { // Create DataType object @@ -1048,9 +966,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Spans multiple EVM words', async () => { // Create DataType object @@ -1067,9 +983,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('String that begins with 0x prefix', async () => { // Create DataType object @@ -1086,9 +1000,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result const decodedArgs = dataType.decode(encodedArgs); - const decodedArgsAsJson = JSON.stringify(decodedArgs); - const argsAsJson = JSON.stringify(args); - expect(decodedArgsAsJson).to.be.equal(argsAsJson); + expect(decodedArgs).to.be.deep.equal(args); }); }); }); diff --git a/packages/utils/test/abi_encoder/methods_test.ts b/packages/utils/test/abi_encoder/methods_test.ts index d158b9e5b4..c870f4f048 100644 --- a/packages/utils/test/abi_encoder/methods_test.ts +++ b/packages/utils/test/abi_encoder/methods_test.ts @@ -20,10 +20,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Array of Static Tuples (Array has defined length)', async () => { // Generate calldata @@ -41,10 +39,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Array of Static Tuples (Array has dynamic length)', async () => { // Generate calldata @@ -62,10 +58,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Array of Dynamic Tuples (Array has defined length)', async () => { // Generate Calldata @@ -83,10 +77,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Array of Dynamic Tuples (Array has dynamic length)', async () => { // Generate calldata @@ -104,10 +96,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Multidimensional Arrays / Static Members', async () => { // Generate calldata @@ -129,10 +119,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { expect(calldata).to.be.equal(expectedCalldata); expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Multidimensional Arrays / Dynamic Members', async () => { // Generate calldata @@ -159,10 +147,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Fixed Length Array / Dynamic Members', async () => { // Generate calldata @@ -174,10 +160,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Fixed Length Array / Dynamic Members', async () => { // Generaet calldata @@ -189,10 +173,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Unfixed Length Array / Dynamic Members ABI', async () => { // Generate calldata @@ -204,10 +186,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Unfixed Length Array / Static Members ABI', async () => { // Generate calldata @@ -219,10 +199,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Fixed Length Array / Static Members ABI', async () => { // Generate calldata @@ -234,10 +212,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Array ABI', async () => { // Generate calldata @@ -249,10 +225,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Static Tuple', async () => { // Generate calldata @@ -265,10 +239,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Dynamic Tuple (Array input)', async () => { // Generate calldata @@ -281,10 +253,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Dynamic Tuple (Object input)', async () => { // Generate Calldata @@ -297,10 +267,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Large, Flat ABI', async () => { // Construct calldata @@ -322,10 +290,8 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); it('Large, Nested ABI', async () => { // Construct Calldata @@ -393,9 +359,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); // Validate decoding - const expectedDecodedValueJson = JSON.stringify(args); const decodedValue = method.decode(calldata, { structsAsObjects: true }); - const decodedValueJson = JSON.stringify(decodedValue); - expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + expect(decodedValue).to.be.deep.equal(args); }); }); diff --git a/packages/utils/test/abi_encoder/optimizer_test.ts b/packages/utils/test/abi_encoder/optimizer_test.ts index 304c9cbc2e..27443fe488 100644 --- a/packages/utils/test/abi_encoder/optimizer_test.ts +++ b/packages/utils/test/abi_encoder/optimizer_test.ts @@ -23,9 +23,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Dynamic Arrays with Dynamic Elements', async () => { // Generate calldata @@ -40,9 +38,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Static Arrays with Static Elements (should not optimize)', async () => { // Generate calldata @@ -59,9 +55,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(unoptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Static Arrays with Dynamic Elements', async () => { // Generate calldata @@ -76,9 +70,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Array Elements (should optimize)', async () => { // Generate calldata @@ -92,9 +84,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Tuple Fields', async () => { // Generate calldata @@ -108,9 +98,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Strings', async () => { // Description: @@ -127,9 +115,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Bytes', async () => { // Description: @@ -147,9 +133,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Tuples', async () => { // Generate calldata @@ -164,9 +148,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Fields Across Two Tuples', async () => { // Description: @@ -182,9 +164,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Arrays, Nested in Separate Tuples', async () => { // Generate calldata @@ -200,9 +180,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Tuples, Nested in Separate Tuples', async () => { // Generate calldata @@ -218,9 +196,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Two-Dimensional Arrays', async () => { // Generate calldata @@ -235,9 +211,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Duplicate Array, Nested within Separate Two-Dimensional Arrays', async () => { // Generate calldata @@ -252,9 +226,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Array Elements Duplicated as Tuple Fields', async () => { // Generate calldata @@ -269,9 +241,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); it('Array Elements Duplicated as Separate Parameter', async () => { // Generate calldata @@ -286,8 +256,6 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); // Validate decoding const decodedArgs = method.decode(optimizedCalldata); - const decodedArgsJson = JSON.stringify(decodedArgs); - const argsJson = JSON.stringify(args); - expect(decodedArgsJson).to.be.equal(argsJson); + expect(decodedArgs).to.be.deep.equal(args); }); }); diff --git a/packages/utils/test/abi_encoder/return_values_test.ts b/packages/utils/test/abi_encoder/return_values_test.ts index 850cb1746a..3215509f16 100644 --- a/packages/utils/test/abi_encoder/return_values_test.ts +++ b/packages/utils/test/abi_encoder/return_values_test.ts @@ -16,9 +16,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { const returnValue = '0x'; const decodedReturnValue = method.decodeReturnValues(returnValue); const expectedDecodedReturnValue: any[] = []; - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(expectedDecodedReturnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + expect(decodedReturnValue).to.be.deep.equal(expectedDecodedReturnValue); }); it('Single static return value', async () => { // Generate Return Value @@ -27,9 +25,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { const encodedReturnValue = method.encodeReturnValues(returnValue); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + expect(decodedReturnValue).to.be.deep.equal(returnValue); }); it('Multiple static return values', async () => { // Generate Return Value @@ -38,9 +34,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { const encodedReturnValue = method.encodeReturnValues(returnValue); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + expect(decodedReturnValue).to.be.deep.equal(returnValue); }); it('Single dynamic return value', async () => { // Generate Return Value @@ -49,9 +43,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { const encodedReturnValue = method.encodeReturnValues(returnValue); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + expect(decodedReturnValue).to.be.deep.equal(returnValue); }); it('Multiple dynamic return values', async () => { // Generate Return Value @@ -60,9 +52,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { const encodedReturnValue = method.encodeReturnValues(returnValue); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + expect(decodedReturnValue).to.be.deep.equal(returnValue); }); it('Mixed static/dynamic return values', async () => { // Generate Return Value @@ -71,8 +61,6 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { const encodedReturnValue = method.encodeReturnValues(returnValue); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value - const decodedReturnValueJson = JSON.stringify(decodedReturnValue); - const expectedDecodedReturnValueJson = JSON.stringify(returnValue); - expect(decodedReturnValueJson).to.be.equal(expectedDecodedReturnValueJson); + expect(decodedReturnValue).to.be.deep.equal(returnValue); }); }); From f31d4ddffd8dd97f2b2dc226f4f132d1c3192c76 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 13:10:34 -0800 Subject: [PATCH 173/230] Replaced null/undefined checks with lodash --- .../abstract_data_types/types/set.ts | 22 +++++++++---------- .../abi_encoder/calldata/blocks/pointer.ts | 3 ++- .../src/abi_encoder/calldata/calldata.ts | 8 +++---- .../src/abi_encoder/calldata/iterator.ts | 4 ++-- .../src/abi_encoder/calldata/raw_calldata.ts | 3 ++- .../src/abi_encoder/evm_data_types/array.ts | 13 ++++++----- .../src/abi_encoder/evm_data_types/int.ts | 3 ++- .../evm_data_types/static_bytes.ts | 2 +- .../src/abi_encoder/evm_data_types/uint.ts | 3 ++- 9 files changed, 33 insertions(+), 28 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index 427122ad6b..5a188d6fa2 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -32,7 +32,7 @@ export abstract class Set extends DataType { this._isArray = isArray; this._arrayLength = arrayLength; this._arrayElementType = arrayElementType; - if (isArray && arrayLength !== undefined) { + if (isArray && !_.isUndefined(arrayLength)) { [this._members, this._memberIndexByName] = this._createMembersWithLength(dataItem, arrayLength); } else if (!isArray) { [this._members, this._memberIndexByName] = this._createMembersWithKeys(dataItem); @@ -51,7 +51,7 @@ export abstract class Set extends DataType { let members = this._members; // Case 1: This is an array of undefined length, which means that `this._members` was not // populated in the constructor. So, construct the set of members it now. - if (this._isArray && this._arrayLength === undefined) { + if (this._isArray && _.isUndefined(this._arrayLength)) { const arrayLengthBuf = calldata.popWord(); const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); const arrayLength = new BigNumber(arrayLengthHex, Constants.HEX_BASE); @@ -83,20 +83,20 @@ export abstract class Set extends DataType { public isStatic(): boolean { // An array with an undefined length is never static. - if (this._isArray && this._arrayLength === undefined) { + if (this._isArray && _.isUndefined(this._arrayLength)) { return false; } // If any member of the set is a pointer then the set is not static. const dependentMember = _.find(this._members, (member: DataType) => { return member instanceof Pointer; }); - const isStatic = dependentMember === undefined; + const isStatic = _.isUndefined(dependentMember); return isStatic; } protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): CalldataBlocks.Set { // Sanity check: if the set has a defined length then `value` must have the same length. - if (this._arrayLength !== undefined && value.length !== this._arrayLength) { + if (!_.isUndefined(this._arrayLength) && value.length !== this._arrayLength) { throw new Error( `Expected array of ${JSON.stringify( this._arrayLength, @@ -104,7 +104,7 @@ export abstract class Set extends DataType { ); } // Create a new calldata block for this set. - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); const block: CalldataBlocks.Set = new CalldataBlocks.Set( this.getDataItem().name, this.getSignature(), @@ -112,7 +112,7 @@ export abstract class Set extends DataType { ); // If this set has an undefined length then set its header to be the number of elements. let members = this._members; - if (this._isArray && this._arrayLength === undefined) { + if (this._isArray && _.isUndefined(this._arrayLength)) { [members] = this._createMembersWithLength(this.getDataItem(), value.length); const lenBuf = ethUtil.setLengthLeft( ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), @@ -132,7 +132,7 @@ export abstract class Set extends DataType { protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): CalldataBlocks.Set { // Create a new calldata block for this set. - const parentName = parentBlock === undefined ? '' : parentBlock.getName(); + const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); const block: CalldataBlocks.Set = new CalldataBlocks.Set( this.getDataItem().name, this.getSignature(), @@ -175,7 +175,7 @@ export abstract class Set extends DataType { private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberIndexByName] { // Sanity check - if (dataItem.components === undefined) { + if (_.isUndefined(dataItem.components)) { throw new Error(`Expected components`); } // Create one member for each component of `dataItem` @@ -187,7 +187,7 @@ export abstract class Set extends DataType { name: `${dataItem.name}.${memberItem.name}`, }; const components = memberItem.components; - if (components !== undefined) { + if (!_.isUndefined(components)) { childDataItem.components = components; } const child = this.getFactory().create(childDataItem, this); @@ -208,7 +208,7 @@ export abstract class Set extends DataType { name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`, }; const components = dataItem.components; - if (components !== undefined) { + if (!_.isUndefined(components)) { memberDataItem.components = components; } const memberType = this.getFactory().create(memberDataItem, this); diff --git a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts index 1c49a8c6c9..654cbe26c1 100644 --- a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts @@ -1,4 +1,5 @@ import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; import * as Constants from '../../utils/constants'; @@ -24,7 +25,7 @@ export class Pointer extends CalldataBlock { public toBuffer(): Buffer { const destinationOffset = - this._aliasFor !== undefined ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes(); + !_.isUndefined(this._aliasFor) ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes(); const parentOffset = this._parent.getOffsetInBytes(); const parentHeaderSize = this._parent.getHeaderSizeInBytes(); const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index a662f30b97..e93d638039 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -44,7 +44,7 @@ export class Calldata { */ public toString(): string { // Sanity check: root block must be set - if (this._root === undefined) { + if (_.isUndefined(this._root)) { throw new Error('expected root'); } // Optimize, if flag set @@ -103,7 +103,7 @@ export class Calldata { */ private _optimize(): void { // Step 1/1 Create a reverse iterator (starts from the end of the calldata to the beginning) - if (this._root === undefined) { + if (_.isUndefined(this._root)) { throw new Error('expected root'); } const iterator = new ReverseCalldataIterator(this._root); @@ -136,7 +136,7 @@ export class Calldata { */ private _toCondensedString(): string { // Sanity check: must have a root block. - if (this._root === undefined) { + if (_.isUndefined(this._root)) { throw new Error('expected root'); } // Construct an array of buffers (one buffer for each block). @@ -175,7 +175,7 @@ export class Calldata { */ private _toAnnotatedString(): string { // Sanity check: must have a root block. - if (this._root === undefined) { + if (_.isUndefined(this._root)) { throw new Error('expected root'); } // Constants for constructing annotated string diff --git a/packages/utils/src/abi_encoder/calldata/iterator.ts b/packages/utils/src/abi_encoder/calldata/iterator.ts index 8e2b16a5ae..5307f7944b 100644 --- a/packages/utils/src/abi_encoder/calldata/iterator.ts +++ b/packages/utils/src/abi_encoder/calldata/iterator.ts @@ -55,7 +55,7 @@ abstract class BaseIterator implements Iterable { _.each(set.getMembers(), (member: CalldataBlock) => { // Traverse child if it is a unique pointer. // A pointer that is an alias for another pointer is ignored. - if (member instanceof CalldataBlocks.Pointer && member.getAlias() === undefined) { + if (member instanceof CalldataBlocks.Pointer && _.isUndefined(member.getAlias())) { const dependency = member.getDependency(); queue.mergeBack(BaseIterator._createQueue(dependency)); } @@ -74,7 +74,7 @@ abstract class BaseIterator implements Iterable { return { next: () => { const nextBlock = this.nextBlock(); - if (nextBlock !== undefined) { + if (!_.isUndefined(nextBlock)) { return { value: nextBlock, done: false, diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts index b13cbdfd95..dfd4cfa72c 100644 --- a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -1,4 +1,5 @@ import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; import * as Constants from '../utils/constants'; import { Queue } from '../utils/queue'; @@ -68,7 +69,7 @@ export class RawCalldata { public toAbsoluteOffset(relativeOffset: number): number { const scopeOffset = this._scopes.peekFront(); - if (scopeOffset === undefined) { + if (_.isUndefined(scopeOffset)) { throw new Error(`Tried to access undefined scope.`); } const absoluteOffset = relativeOffset + scopeOffset; diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 8cf2cf7cf1..a86283c2ac 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -1,4 +1,5 @@ import { DataItem } from 'ethereum-types'; +import * as _ from 'lodash'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import * as Constants from '../utils/constants'; @@ -14,15 +15,15 @@ export class Array extends AbstractDataTypes.Set { private static _decodeElementTypeAndLengthFromType(type: string): [string, undefined | number] { const matches = Array._MATCHER.exec(type); - if (matches === null || matches.length !== 3) { + if (_.isNull(matches) || matches.length !== 3) { throw new Error(`Could not parse array: ${type}`); - } else if (matches[1] === undefined) { + } else if (_.isUndefined(matches[1])) { throw new Error(`Could not parse array type: ${type}`); - } else if (matches[2] === undefined) { + } else if (_.isUndefined(matches[2])) { throw new Error(`Could not parse array length: ${type}`); } const arrayElementType = matches[1]; - const arrayLength = matches[2] === '' ? undefined : parseInt(matches[2], Constants.DEC_BASE); + const arrayLength = _.isEmpty(matches[2]) ? undefined : parseInt(matches[2], Constants.DEC_BASE); return [arrayElementType, arrayLength]; } @@ -47,13 +48,13 @@ export class Array extends AbstractDataTypes.Set { name: 'N/A', }; const elementComponents = this.getDataItem().components; - if (elementComponents !== undefined) { + if (!_.isUndefined(elementComponents)) { elementDataItem.components = elementComponents; } const elementDataType = this.getFactory().create(elementDataItem); const elementSignature = elementDataType.getSignature(); // Construct signature for array of type `element` - if (this._arrayLength === undefined) { + if (_.isUndefined(this._arrayLength)) { return `${elementSignature}[]`; } else { return `${elementSignature}[${this._arrayLength}]`; diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 032cd045a4..5c5193644b 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -1,5 +1,6 @@ /* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; +import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; @@ -25,7 +26,7 @@ export class Int extends AbstractDataTypes.Blob { private static _decodeWidthFromType(type: string): number { const matches = Int._MATCHER.exec(type); const width = - matches !== null && matches.length === 2 && matches[1] !== undefined + !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1]) ? parseInt(matches[1], Constants.DEC_BASE) : Int._DEFAULT_WIDTH; return width; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 2c649cb33b..3a2ad410f5 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -21,7 +21,7 @@ export class StaticBytes extends AbstractDataTypes.Blob { private static _decodeWidthFromType(type: string): number { const matches = StaticBytes._MATCHER.exec(type); const width = - matches !== null && matches.length === 3 && matches[2] !== undefined + !_.isNull(matches) && matches.length === 3 && !_.isUndefined(matches[2]) ? parseInt(matches[2], Constants.DEC_BASE) : StaticBytes._DEFAULT_WIDTH; return width; diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index b5b7683a2a..76b9446106 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -1,5 +1,6 @@ /* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; +import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; @@ -25,7 +26,7 @@ export class UInt extends AbstractDataTypes.Blob { private static _decodeWidthFromType(type: string): number { const matches = UInt._MATCHER.exec(type); const width = - matches !== null && matches.length === 2 && matches[1] !== undefined + !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1]) ? parseInt(matches[1], Constants.DEC_BASE) : UInt._DEFAULT_WIDTH; return width; From 3f545da9f86856b54cd226c29174ac1ae085e35b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 13:28:17 -0800 Subject: [PATCH 174/230] Switched implicit conversions to explicit lodash calls --- .../src/abi_encoder/abstract_data_types/data_type.ts | 12 ++++++------ .../abi_encoder/abstract_data_types/types/blob.ts | 2 +- .../src/abi_encoder/abstract_data_types/types/set.ts | 2 +- .../utils/src/abi_encoder/evm_data_types/bool.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index ab7df6eccb..61d3ac3a9f 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -25,9 +25,9 @@ export abstract class DataType { } public encode(value: any, rules?: EncodingRules, selector?: string): string { - const rules_ = rules ? rules : Constants.DEFAULT_ENCODING_RULES; + const rules_ = _.isUndefined(rules) ? Constants.DEFAULT_ENCODING_RULES : rules; const calldata = new Calldata(rules_); - if (selector) { + if (!_.isUndefined(selector)) { calldata.setSelector(selector); } const block = this.generateCalldataBlock(value); @@ -37,14 +37,14 @@ export abstract class DataType { } public decode(calldata: string, rules?: DecodingRules, selector?: string): any { - if (selector && !calldata.startsWith(selector)) { + if (!_.isUndefined(selector) && !calldata.startsWith(selector)) { throw new Error( - `Tried to decode calldata, but it was missing the function selector. Expected '${selector}'.`, + `Tried to decode calldata, but it was missing the function selector. Expected prefix '${selector}'. Got '${calldata}'.`, ); } - const hasSelector = selector ? true : false; + const hasSelector = !_.isUndefined(selector); const rawCalldata = new RawCalldata(calldata, hasSelector); - const rules_ = rules ? rules : Constants.DEFAULT_DECODING_RULES; + const rules_ = _.isUndefined(rules) ? Constants.DEFAULT_DECODING_RULES : rules; const value = this.generateValue(rawCalldata, rules_); return value; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts index 35ccc05860..9657380569 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts @@ -19,7 +19,7 @@ export abstract class Blob extends DataType { const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); - const parentName = parentBlock ? parentBlock.getName() : ''; + const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); const block = new CalldataBlocks.Blob(name, signature, parentName, encodedValue); return block; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index 5a188d6fa2..637abfb7dc 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -204,7 +204,7 @@ export abstract class Set extends DataType { const range = _.range(length); _.each(range, (idx: number) => { const memberDataItem: DataItem = { - type: this._arrayElementType ? this._arrayElementType : '', + type: _.isUndefined(this._arrayElementType) ? '' : this._arrayElementType, name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`, }; const components = dataItem.components; diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 031acc88ae..fa115bb87b 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -39,7 +39,7 @@ export class Bool extends AbstractDataTypes.Blob { throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`); } /* tslint:disable boolean-naming */ - const value: boolean = valueNumber.equals(0) ? false : true; + const value: boolean = !valueNumber.equals(0); /* tslint:enable boolean-naming */ return value; } From 1f693ea12142bc761f4067871d92e7d2662cf256 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 13:31:44 -0800 Subject: [PATCH 175/230] Changed from .startsWith to _.startsWith --- packages/utils/src/abi_encoder/abstract_data_types/data_type.ts | 2 +- packages/utils/src/abi_encoder/calldata/calldata.ts | 2 +- packages/utils/src/abi_encoder/calldata/raw_calldata.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/address.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts | 2 +- packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts | 2 +- packages/utils/src/abi_encoder/utils/math.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index 61d3ac3a9f..4cef601726 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -37,7 +37,7 @@ export abstract class DataType { } public decode(calldata: string, rules?: DecodingRules, selector?: string): any { - if (!_.isUndefined(selector) && !calldata.startsWith(selector)) { + if (!_.isUndefined(selector) && !_.startsWith(calldata, selector)) { throw new Error( `Tried to decode calldata, but it was missing the function selector. Expected prefix '${selector}'. Got '${calldata}'.`, ); diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index e93d638039..c57ff08c24 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -29,7 +29,7 @@ export class Calldata { * If the root block was created by a Method then a selector will likely be set. */ public setSelector(selector: string): void { - if (!selector.startsWith('0x')) { + if (!_.startsWith(selector, '0x')) { throw new Error(`Expected selector to be hex. Missing prefix '0x'`); } else if (selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) { throw new Error(`Invalid selector '${selector}'`); diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts index dfd4cfa72c..fbe592fc74 100644 --- a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -13,7 +13,7 @@ export class RawCalldata { public constructor(value: string | Buffer, hasSelector: boolean = true) { // Sanity check - if (typeof value === 'string' && !value.startsWith('0x')) { + if (typeof value === 'string' && !_.startsWith(value, '0x')) { throw new Error(`Expected raw calldata to start with '0x'`); } // Construct initial values diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 71aa293b53..52fc8e7b9f 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -31,7 +31,7 @@ export class Address extends AbstractDataTypes.Blob { } public encodeValue(value: string): Buffer { - if (!value.startsWith('0x')) { + if (!_.startsWith(value, '0x')) { throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); } const valueBuf = ethUtil.toBuffer(value); diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index 01d83d11a7..98d90b7e45 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -60,7 +60,7 @@ export class DynamicBytes extends AbstractDataTypes.Blob { if (typeof value !== 'string') { return; } - if (!value.startsWith('0x')) { + if (!_.startsWith(value, '0x')) { throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); } else if (value.length % 2 !== 0) { throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 3a2ad410f5..68f212f79b 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -59,7 +59,7 @@ export class StaticBytes extends AbstractDataTypes.Blob { private _sanityCheckValue(value: string | Buffer): void { if (typeof value === 'string') { - if (!value.startsWith('0x')) { + if (!_.startsWith(value, '0x')) { throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); } else if (value.length % 2 !== 0) { throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); diff --git a/packages/utils/src/abi_encoder/utils/math.ts b/packages/utils/src/abi_encoder/utils/math.ts index c5fa10e73f..bc344c6952 100644 --- a/packages/utils/src/abi_encoder/utils/math.ts +++ b/packages/utils/src/abi_encoder/utils/math.ts @@ -79,7 +79,7 @@ export function decodeNumericValue(encodedValue: Buffer, minValue: BigNumber): B } // Case 2/3: value is non-negative because there is no leading 1 (encoded as two's-complement) const valueBin = value.toString(Constants.BIN_BASE); - const valueIsNegative = valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && valueBin[0].startsWith('1'); + const valueIsNegative = valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && _.startsWith(valueBin[0], '1'); if (!valueIsNegative) { return value; } From ffb8b0a619be3b8fb30a6acc99f590a6b40d49e1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 13:35:39 -0800 Subject: [PATCH 176/230] Changed remaining instances of implicit `bool` casts to explicit lodash calls --- .../src/abi_encoder/abstract_data_types/types/pointer.ts | 2 +- packages/utils/src/abi_encoder/calldata/blocks/set.ts | 4 ++-- packages/utils/src/abi_encoder/evm_data_type_factory.ts | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts index 46e60979ae..dc9de8a3d7 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts @@ -21,7 +21,7 @@ export abstract class Pointer extends DataType { } public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlocks.Pointer { - if (!parentBlock) { + if (_.isUndefined(parentBlock)) { throw new Error(`DependentDataType requires a parent block to generate its block`); } const destinationBlock = this._destination.generateCalldataBlock(value, parentBlock); diff --git a/packages/utils/src/abi_encoder/calldata/blocks/set.ts b/packages/utils/src/abi_encoder/calldata/blocks/set.ts index e4de22c5cc..81455b3642 100644 --- a/packages/utils/src/abi_encoder/calldata/blocks/set.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/set.ts @@ -14,7 +14,7 @@ export class Set extends CalldataBlock { public getRawData(): Buffer { const rawDataComponents: Buffer[] = []; - if (this._header) { + if (!_.isUndefined(this._header)) { rawDataComponents.push(this._header); } _.each(this._members, (member: CalldataBlock) => { @@ -35,7 +35,7 @@ export class Set extends CalldataBlock { } public toBuffer(): Buffer { - if (this._header) { + if (!_.isUndefined(this._header)) { return this._header; } return new Buffer(''); diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index bfe457367d..16a0b724d3 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -2,6 +2,7 @@ /* tslint:disable max-classes-per-file */ /* tslint:disable no-construct */ import { DataItem, MethodAbi } from 'ethereum-types'; +import * as _ from 'lodash'; import { DataType, DataTypeFactory } from './abstract_data_types'; import * as Impl from './evm_data_types'; @@ -105,9 +106,9 @@ export class EvmDataTypeFactory implements DataTypeFactory { dataType = new String(dataItem); } // @TODO: Implement Fixed/UFixed types - if (!dataType) { + if (_.isUndefined(dataType)) { throw new Error(`Unrecognized data type: '${dataItem.type}'`); - } else if (parentDataType && !dataType.isStatic()) { + } else if (!_.isUndefined(parentDataType) && !dataType.isStatic()) { const pointerToDataType = new Pointer(dataType, parentDataType); return pointerToDataType; } From f479212410b238a7673983148f403b3a220083af Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 15:28:26 -0800 Subject: [PATCH 177/230] Style cleanup. Improved wording of some error messages. --- .../abstract_data_types/types/pointer.ts | 4 ++- .../abstract_data_types/types/set.ts | 4 +-- .../src/abi_encoder/calldata/calldata.ts | 11 +++----- .../src/abi_encoder/evm_data_type_factory.ts | 6 ++-- .../src/abi_encoder/evm_data_types/address.ts | 12 ++++---- .../src/abi_encoder/evm_data_types/bool.ts | 4 ++- .../evm_data_types/dynamic_bytes.ts | 28 ++++++++++--------- .../src/abi_encoder/evm_data_types/int.ts | 1 - .../src/abi_encoder/evm_data_types/string.ts | 4 ++- .../src/abi_encoder/evm_data_types/uint.ts | 1 - packages/utils/src/abi_encoder/utils/math.ts | 4 +-- 11 files changed, 43 insertions(+), 36 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts index dc9de8a3d7..8e597636b4 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts @@ -1,4 +1,3 @@ -/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -44,7 +43,10 @@ export abstract class Pointer extends DataType { return value; } + // Disable prefer-function-over-method for inherited abstract method. + /* tslint:disable prefer-function-over-method */ public isStatic(): boolean { return true; } + /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index 637abfb7dc..aeea7919f1 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -50,7 +50,7 @@ export abstract class Set extends DataType { public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object { let members = this._members; // Case 1: This is an array of undefined length, which means that `this._members` was not - // populated in the constructor. So, construct the set of members it now. + // populated in the constructor. So we must construct the set of members now. if (this._isArray && _.isUndefined(this._arrayLength)) { const arrayLengthBuf = calldata.popWord(); const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); @@ -176,7 +176,7 @@ export abstract class Set extends DataType { private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberIndexByName] { // Sanity check if (_.isUndefined(dataItem.components)) { - throw new Error(`Expected components`); + throw new Error(`Tried to create a set using key/value pairs, but no components were defined by the input DataItem '${dataItem.name}'.`); } // Create one member for each component of `dataItem` const members: DataType[] = []; diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index c57ff08c24..6d8814e06d 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -59,7 +59,7 @@ export class Calldata { offset += block.getSizeInBytes(); } // Generate hex string - const hexString = this._rules.annotate ? this._toAnnotatedString() : this._toCondensedString(); + const hexString = this._rules.annotate ? this._toHumanReadableCallData() : this._toEvmCompatibeCallDataHex(); return hexString; } /** @@ -131,10 +131,7 @@ export class Calldata { } } } - /** - * Returns EVM-compatible calldata as a Hex string. - */ - private _toCondensedString(): string { + private _toEvmCompatibeCallDataHex(): string { // Sanity check: must have a root block. if (_.isUndefined(this._root)) { throw new Error('expected root'); @@ -152,7 +149,7 @@ export class Calldata { return hexValue; } /** - * Returns human-redable calldata. + * Returns human-readable calldata. * * Example: * simpleFunction(string[], string[]) @@ -173,7 +170,7 @@ export class Calldata { * 0xe0 0000000000000000000000000000000000000000000000000000000000000005 array2[1] * 0x100 576f726c64000000000000000000000000000000000000000000000000000000 */ - private _toAnnotatedString(): string { + private _toHumanReadableCallData(): string { // Sanity check: must have a root block. if (_.isUndefined(this._root)) { throw new Error('expected root'); diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index 16a0b724d3..bc68e05b78 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -1,6 +1,4 @@ -/* tslint:disable prefer-function-over-method */ /* tslint:disable max-classes-per-file */ -/* tslint:disable no-construct */ import { DataItem, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; @@ -73,6 +71,7 @@ export class Method extends Impl.Method { } } +/* tslint:disable no-construct */ export class EvmDataTypeFactory implements DataTypeFactory { private static _instance: DataTypeFactory; @@ -83,6 +82,7 @@ export class EvmDataTypeFactory implements DataTypeFactory { return EvmDataTypeFactory._instance; } + /* tslint:disable prefer-function-over-method */ public create(dataItem: DataItem, parentDataType?: DataType): DataType { // Create data type let dataType: undefined | DataType; @@ -114,6 +114,8 @@ export class EvmDataTypeFactory implements DataTypeFactory { } return dataType; } + /* tslint:enable prefer-function-over-method */ private constructor() {} } +/* tslint:enable no-construct */ diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 52fc8e7b9f..25ff559030 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -1,4 +1,3 @@ -/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -26,10 +25,8 @@ export class Address extends AbstractDataTypes.Blob { } } - public getSignature(): string { - return 'address'; - } - + // Disable prefer-function-over-method for inherited abstract methods. + /* tslint:disable prefer-function-over-method */ public encodeValue(value: string): Buffer { if (!_.startsWith(value, '0x')) { throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); @@ -48,4 +45,9 @@ export class Address extends AbstractDataTypes.Blob { const value = ethUtil.bufferToHex(valueBuf); return value; } + + public getSignature(): string { + return 'address'; + } + /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index fa115bb87b..7e135aba99 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -1,4 +1,3 @@ -/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -22,6 +21,8 @@ export class Bool extends AbstractDataTypes.Blob { } } + // Disable prefer-function-over-method for inherited abstract methods. + /* tslint:disable prefer-function-over-method */ public encodeValue(value: boolean): Buffer { const encodedValue = value ? '0x1' : '0x0'; const encodedValueBuf = ethUtil.setLengthLeft( @@ -47,4 +48,5 @@ export class Bool extends AbstractDataTypes.Blob { public getSignature(): string { return 'bool'; } + /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index 98d90b7e45..ad22c8c6e4 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -1,4 +1,3 @@ -/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -14,6 +13,17 @@ export class DynamicBytes extends AbstractDataTypes.Blob { return type === 'bytes'; } + private static _sanityCheckValue(value: string | Buffer): void { + if (typeof value !== 'string') { + return; + } + if (!_.startsWith(value, '0x')) { + throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); + } else if (value.length % 2 !== 0) { + throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); + } + } + public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { super(dataItem, dataTypeFactory, DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME); if (!DynamicBytes.matchType(dataItem.type)) { @@ -21,6 +31,8 @@ export class DynamicBytes extends AbstractDataTypes.Blob { } } + // Disable prefer-function-over-method for inherited abstract methods. + /* tslint:disable prefer-function-over-method */ public encodeValue(value: string | Buffer): Buffer { // Encoded value is of the form: , with each field padded to be word-aligned. // 1/3 Construct the length @@ -48,22 +60,12 @@ export class DynamicBytes extends AbstractDataTypes.Blob { const valueBufPadded = calldata.popWords(wordsToStoreValuePadded); const valueBuf = valueBufPadded.slice(0, length); const value = ethUtil.bufferToHex(valueBuf); - this._sanityCheckValue(value); + DynamicBytes._sanityCheckValue(value); return value; } public getSignature(): string { return 'bytes'; } - - private _sanityCheckValue(value: string | Buffer): void { - if (typeof value !== 'string') { - return; - } - if (!_.startsWith(value, '0x')) { - throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`); - } else if (value.length % 2 !== 0) { - throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); - } - } + /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 5c5193644b..9d328bba94 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -1,4 +1,3 @@ -/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 6ab3513c97..08f928d8ad 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -1,4 +1,3 @@ -/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -21,6 +20,8 @@ export class String extends AbstractDataTypes.Blob { } } + // Disable prefer-function-over-method for inherited abstract methods. + /* tslint:disable prefer-function-over-method */ public encodeValue(value: string): Buffer { // Encoded value is of the form: , with each field padded to be word-aligned. // 1/3 Construct the length @@ -53,4 +54,5 @@ export class String extends AbstractDataTypes.Blob { public getSignature(): string { return 'string'; } + /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index 76b9446106..4357f15d26 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -1,4 +1,3 @@ -/* tslint:disable prefer-function-over-method */ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; diff --git a/packages/utils/src/abi_encoder/utils/math.ts b/packages/utils/src/abi_encoder/utils/math.ts index bc344c6952..9bcdc3af1d 100644 --- a/packages/utils/src/abi_encoder/utils/math.ts +++ b/packages/utils/src/abi_encoder/utils/math.ts @@ -79,8 +79,8 @@ export function decodeNumericValue(encodedValue: Buffer, minValue: BigNumber): B } // Case 2/3: value is non-negative because there is no leading 1 (encoded as two's-complement) const valueBin = value.toString(Constants.BIN_BASE); - const valueIsNegative = valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && _.startsWith(valueBin[0], '1'); - if (!valueIsNegative) { + const isValueNegative = valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && _.startsWith(valueBin[0], '1'); + if (!isValueNegative) { return value; } // Case 3/3: value is negative From e7bdf4717da9d1fd50cda3dba4e9549dfbc337f7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 15:32:50 -0800 Subject: [PATCH 178/230] Fixed build error: was using `this` instead of class name to reference a static function that used to be an instance method. --- packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index ad22c8c6e4..fecd1db6b5 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -42,7 +42,7 @@ export class DynamicBytes extends AbstractDataTypes.Blob { const lengthBuf = ethUtil.toBuffer(valueBuf.byteLength); const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); // 2/3 Construct the value - this._sanityCheckValue(value); + DynamicBytes._sanityCheckValue(value); const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); // 3/3 Combine length and value const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]); From f196dc9e35d12aad281142371af4d2c32db1fd60 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 16:20:56 -0800 Subject: [PATCH 179/230] Use ethUti.isValidAddress in encoder --- packages/utils/src/abi_encoder/evm_data_types/address.ts | 9 ++------- packages/utils/test/abi_encoder/evm_data_types_test.ts | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 25ff559030..950901ea83 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -7,8 +7,6 @@ import { RawCalldata } from '../calldata'; import * as Constants from '../utils/constants'; export class Address extends AbstractDataTypes.Blob { - public static ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X = "Address must start with '0x'"; - public static ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES = 'Address must be 20 bytes'; private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - @@ -28,13 +26,10 @@ export class Address extends AbstractDataTypes.Blob { // Disable prefer-function-over-method for inherited abstract methods. /* tslint:disable prefer-function-over-method */ public encodeValue(value: string): Buffer { - if (!_.startsWith(value, '0x')) { - throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); + if (!ethUtil.isValidAddress(value)) { + throw new Error(`Invalid address: '${value}'`); } const valueBuf = ethUtil.toBuffer(value); - if (valueBuf.byteLength !== Address._ADDRESS_SIZE_IN_BYTES) { - throw new Error(Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); - } const encodedValueBuf = ethUtil.setLengthLeft(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); return encodedValueBuf; } diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts index b995002654..11cebda55a 100644 --- a/packages/utils/test/abi_encoder/evm_data_types_test.ts +++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts @@ -387,7 +387,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Encode Args and validate result expect(() => { dataType.encode(args); - }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_START_WITH_0X); + }).to.throw(`Invalid address: '${args}'`); }); it('Invalid Address - input is not 20 bytes', async () => { // Create DataType object @@ -398,7 +398,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Encode Args and validate result expect(() => { dataType.encode(args); - }).to.throw(AbiEncoder.Address.ERROR_MESSAGE_ADDRESS_MUST_BE_20_BYTES); + }).to.throw(`Invalid address: '${args}'`); }); }); From 14c094d050e7b2d0a4b31d02dbe58a54153be7bb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 16:33:51 -0800 Subject: [PATCH 180/230] Use SolidityTypes from `ethereum-types` package. --- packages/ethereum-types/src/index.ts | 5 +++++ packages/utils/src/abi_encoder/evm_data_types/address.ts | 6 +++--- packages/utils/src/abi_encoder/evm_data_types/bool.ts | 6 +++--- .../utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts | 6 +++--- packages/utils/src/abi_encoder/evm_data_types/int.ts | 4 ++-- .../utils/src/abi_encoder/evm_data_types/static_bytes.ts | 4 ++-- packages/utils/src/abi_encoder/evm_data_types/string.ts | 6 +++--- packages/utils/src/abi_encoder/evm_data_types/tuple.ts | 4 ++-- packages/utils/src/abi_encoder/evm_data_types/uint.ts | 4 ++-- 9 files changed, 25 insertions(+), 20 deletions(-) diff --git a/packages/ethereum-types/src/index.ts b/packages/ethereum-types/src/index.ts index eff38711aa..9430fdc98b 100644 --- a/packages/ethereum-types/src/index.ts +++ b/packages/ethereum-types/src/index.ts @@ -283,6 +283,11 @@ export interface RawLogEntry { export enum SolidityTypes { Address = 'address', + Bool = 'bool', + Bytes = 'bytes', + Int = 'int', + String = 'string', + Tuple = 'tuple', Uint256 = 'uint256', Uint8 = 'uint8', Uint = 'uint', diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 950901ea83..07a0bd10c2 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -13,7 +13,7 @@ export class Address extends AbstractDataTypes.Blob { Address._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { - return type === 'address'; + return type === SolidityTypes.Address; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { @@ -42,7 +42,7 @@ export class Address extends AbstractDataTypes.Blob { } public getSignature(): string { - return 'address'; + return SolidityTypes.Address; } /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 7e135aba99..7af13506bc 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -11,7 +11,7 @@ export class Bool extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; public static matchType(type: string): boolean { - return type === 'bool'; + return type === SolidityTypes.Bool; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { @@ -46,7 +46,7 @@ export class Bool extends AbstractDataTypes.Blob { } public getSignature(): string { - return 'bool'; + return SolidityTypes.Bool; } /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index fecd1db6b5..ac2a1fb6e3 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -10,7 +10,7 @@ export class DynamicBytes extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { - return type === 'bytes'; + return type === SolidityTypes.Bytes; } private static _sanityCheckValue(value: string | Buffer): void { @@ -65,7 +65,7 @@ export class DynamicBytes extends AbstractDataTypes.Blob { } public getSignature(): string { - return 'bytes'; + return SolidityTypes.Bytes; } /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 9d328bba94..3e465fc158 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; @@ -53,6 +53,6 @@ export class Int extends AbstractDataTypes.Blob { } public getSignature(): string { - return `int${this._width}`; + return `${SolidityTypes.Int}${this._width}`; } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 68f212f79b..ed1f51f7e5 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -37,7 +37,7 @@ export class StaticBytes extends AbstractDataTypes.Blob { public getSignature(): string { // Note that `byte` reduces to `bytes1` - return `bytes${this._width}`; + return `${SolidityTypes.Bytes}${this._width}`; } public encodeValue(value: string | Buffer): Buffer { diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 08f928d8ad..e5b2d5f338 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -10,7 +10,7 @@ export class String extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { - return type === 'string'; + return type === SolidityTypes.String; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { @@ -52,7 +52,7 @@ export class String extends AbstractDataTypes.Blob { } public getSignature(): string { - return 'string'; + return SolidityTypes.String; } /* tslint:enable prefer-function-over-method */ } diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 3802f96c00..40859f62e2 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; @@ -6,7 +6,7 @@ export class Tuple extends AbstractDataTypes.Set { private readonly _signature: string; public static matchType(type: string): boolean { - return type === 'tuple'; + return type === SolidityTypes.Tuple; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index 4357f15d26..970400a574 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -1,4 +1,4 @@ -import { DataItem } from 'ethereum-types'; +import { DataItem, SolidityTypes } from 'ethereum-types'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; @@ -52,6 +52,6 @@ export class UInt extends AbstractDataTypes.Blob { } public getSignature(): string { - return `uint${this._width}`; + return `${SolidityTypes.Uint}${this._width}`; } } From 029b8d59507df25aa9c7d1b096c8d873eb6ae4da Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 17:11:15 -0800 Subject: [PATCH 181/230] Changed constants to an exported enum; this is 0x convention --- .../abstract_data_types/data_type.ts | 6 ++--- .../abstract_data_types/types/pointer.ts | 4 ++-- .../abstract_data_types/types/set.ts | 12 +++++----- .../abi_encoder/calldata/blocks/pointer.ts | 6 ++--- .../src/abi_encoder/calldata/calldata.ts | 14 ++++++------ .../src/abi_encoder/calldata/raw_calldata.ts | 6 ++--- .../src/abi_encoder/evm_data_types/address.ts | 6 ++--- .../src/abi_encoder/evm_data_types/array.ts | 4 ++-- .../src/abi_encoder/evm_data_types/bool.ts | 6 ++--- .../evm_data_types/dynamic_bytes.ts | 12 +++++----- .../src/abi_encoder/evm_data_types/int.ts | 4 ++-- .../src/abi_encoder/evm_data_types/method.ts | 4 ++-- .../evm_data_types/static_bytes.ts | 6 ++--- .../src/abi_encoder/evm_data_types/string.ts | 12 +++++----- .../src/abi_encoder/evm_data_types/uint.ts | 4 ++-- .../utils/src/abi_encoder/utils/constants.ts | 22 ++++++++++--------- packages/utils/src/abi_encoder/utils/math.ts | 20 ++++++++--------- 17 files changed, 75 insertions(+), 73 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index 4cef601726..10e5c25402 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -2,7 +2,7 @@ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; import { Calldata, CalldataBlock, RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; import { DataTypeFactory } from './interfaces'; @@ -25,7 +25,7 @@ export abstract class DataType { } public encode(value: any, rules?: EncodingRules, selector?: string): string { - const rules_ = _.isUndefined(rules) ? Constants.DEFAULT_ENCODING_RULES : rules; + const rules_ = _.isUndefined(rules) ? constants.DEFAULT_ENCODING_RULES : rules; const calldata = new Calldata(rules_); if (!_.isUndefined(selector)) { calldata.setSelector(selector); @@ -44,7 +44,7 @@ export abstract class DataType { } const hasSelector = !_.isUndefined(selector); const rawCalldata = new RawCalldata(calldata, hasSelector); - const rules_ = _.isUndefined(rules) ? Constants.DEFAULT_DECODING_RULES : rules; + const rules_ = _.isUndefined(rules) ? constants.DEFAULT_DECODING_RULES : rules; const value = this.generateValue(rawCalldata, rules_); return value; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts index 8e597636b4..3d38deb945 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts @@ -3,7 +3,7 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; -import * as Constants from '../../utils/constants'; +import { constants } from '../../utils/constants'; import { DecodingRules } from '../../utils/rules'; import { DataType } from '../data_type'; @@ -34,7 +34,7 @@ export abstract class Pointer extends DataType { public generateValue(calldata: RawCalldata, rules: DecodingRules): any { const destinationOffsetBuf = calldata.popWord(); const destinationOffsetHex = ethUtil.bufferToHex(destinationOffsetBuf); - const destinationOffsetRelative = parseInt(destinationOffsetHex, Constants.HEX_BASE); + const destinationOffsetRelative = parseInt(destinationOffsetHex, constants.HEX_BASE); const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative); const currentOffset = calldata.getOffset(); calldata.setOffset(destinationOffsetAbsolute); diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index aeea7919f1..f50ed82984 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../../configured_bignumber'; import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; -import * as Constants from '../../utils/constants'; +import { constants } from '../../utils/constants'; import { DecodingRules } from '../../utils/rules'; import { DataType } from '../data_type'; @@ -54,7 +54,7 @@ export abstract class Set extends DataType { if (this._isArray && _.isUndefined(this._arrayLength)) { const arrayLengthBuf = calldata.popWord(); const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf); - const arrayLength = new BigNumber(arrayLengthHex, Constants.HEX_BASE); + const arrayLength = new BigNumber(arrayLengthHex, constants.HEX_BASE); [members] = this._createMembersWithLength(this.getDataItem(), arrayLength.toNumber()); } // Create a new scope in the calldata, before descending into the members of this set. @@ -115,8 +115,8 @@ export abstract class Set extends DataType { if (this._isArray && _.isUndefined(this._arrayLength)) { [members] = this._createMembersWithLength(this.getDataItem(), value.length); const lenBuf = ethUtil.setLengthLeft( - ethUtil.toBuffer(`0x${value.length.toString(Constants.HEX_BASE)}`), - Constants.EVM_WORD_WIDTH_IN_BYTES, + ethUtil.toBuffer(`0x${value.length.toString(constants.HEX_BASE)}`), + constants.EVM_WORD_WIDTH_IN_BYTES, ); block.setHeader(lenBuf); } @@ -205,14 +205,14 @@ export abstract class Set extends DataType { _.each(range, (idx: number) => { const memberDataItem: DataItem = { type: _.isUndefined(this._arrayElementType) ? '' : this._arrayElementType, - name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`, + name: `${dataItem.name}[${idx.toString(constants.DEC_BASE)}]`, }; const components = dataItem.components; if (!_.isUndefined(components)) { memberDataItem.components = components; } const memberType = this.getFactory().create(memberDataItem, this); - memberIndexByName[idx.toString(Constants.DEC_BASE)] = members.length; + memberIndexByName[idx.toString(constants.DEC_BASE)] = members.length; members.push(memberType); }); return [members, memberIndexByName]; diff --git a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts index 654cbe26c1..1daf33f7e3 100644 --- a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts @@ -1,7 +1,7 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import * as Constants from '../../utils/constants'; +import { constants } from '../../utils/constants'; import { CalldataBlock } from '../calldata_block'; @@ -29,9 +29,9 @@ export class Pointer extends CalldataBlock { const parentOffset = this._parent.getOffsetInBytes(); const parentHeaderSize = this._parent.getHeaderSizeInBytes(); const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); - const pointerHex = `0x${pointer.toString(Constants.HEX_BASE)}`; + const pointerHex = `0x${pointer.toString(constants.HEX_BASE)}`; const pointerBuf = ethUtil.toBuffer(pointerHex); - const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, constants.EVM_WORD_WIDTH_IN_BYTES); return pointerBufPadded; } diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index 6d8814e06d..e5858b5249 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -1,7 +1,7 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; import { EncodingRules } from '../utils/rules'; import * as CalldataBlocks from './blocks'; @@ -31,7 +31,7 @@ export class Calldata { public setSelector(selector: string): void { if (!_.startsWith(selector, '0x')) { throw new Error(`Expected selector to be hex. Missing prefix '0x'`); - } else if (selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) { + } else if (selector.length !== constants.HEX_SELECTOR_LENGTH_IN_CHARS) { throw new Error(`Invalid selector '${selector}'`); } this._selector = selector; @@ -206,11 +206,11 @@ export class Calldata { lineStr = `\n${offsetStr}${valueStr}${nameStr}`; } else { // This block has at least one word of value. - offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); + offsetStr = `0x${offset.toString(constants.HEX_BASE)}`.padEnd(offsetPadding); valueStr = ethUtil .stripHexPrefix( ethUtil.bufferToHex( - block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES), + block.toBuffer().slice(evmWordStartIndex, constants.EVM_WORD_WIDTH_IN_BYTES), ), ) .padEnd(valuePadding); @@ -223,11 +223,11 @@ export class Calldata { } } // This block has a value that is more than 1 word. - for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) { - offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding); + for (let j = constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += constants.EVM_WORD_WIDTH_IN_BYTES) { + offsetStr = `0x${(offset + j).toString(constants.HEX_BASE)}`.padEnd(offsetPadding); valueStr = ethUtil .stripHexPrefix( - ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES)), + ethUtil.bufferToHex(block.toBuffer().slice(j, j + constants.EVM_WORD_WIDTH_IN_BYTES)), ) .padEnd(valuePadding); nameStr = ' '.repeat(namePadding); diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts index fbe592fc74..189841989e 100644 --- a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts @@ -1,7 +1,7 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; import { Queue } from '../utils/queue'; export class RawCalldata { @@ -24,8 +24,8 @@ export class RawCalldata { this._offset = RawCalldata._INITIAL_OFFSET; // If there's a selector then slice it if (hasSelector) { - const selectorBuf = this._value.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); - this._value = this._value.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); + const selectorBuf = this._value.slice(constants.HEX_SELECTOR_LENGTH_IN_BYTES); + this._value = this._value.slice(constants.HEX_SELECTOR_LENGTH_IN_BYTES); this._selector = ethUtil.bufferToHex(selectorBuf); } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 07a0bd10c2..c45355639f 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -4,12 +4,12 @@ import * as _ from 'lodash'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; export class Address extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; - private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = Constants.EVM_WORD_WIDTH_IN_BYTES - + private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = constants.EVM_WORD_WIDTH_IN_BYTES - Address._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { @@ -30,7 +30,7 @@ export class Address extends AbstractDataTypes.Blob { throw new Error(`Invalid address: '${value}'`); } const valueBuf = ethUtil.toBuffer(value); - const encodedValueBuf = ethUtil.setLengthLeft(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + const encodedValueBuf = ethUtil.setLengthLeft(valueBuf, constants.EVM_WORD_WIDTH_IN_BYTES); return encodedValueBuf; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index a86283c2ac..272cc41324 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -2,7 +2,7 @@ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; export class Array extends AbstractDataTypes.Set { private static readonly _MATCHER = RegExp('^(.+)\\[([0-9]*)\\]$'); @@ -23,7 +23,7 @@ export class Array extends AbstractDataTypes.Set { throw new Error(`Could not parse array length: ${type}`); } const arrayElementType = matches[1]; - const arrayLength = _.isEmpty(matches[2]) ? undefined : parseInt(matches[2], Constants.DEC_BASE); + const arrayLength = _.isEmpty(matches[2]) ? undefined : parseInt(matches[2], constants.DEC_BASE); return [arrayElementType, arrayLength]; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 7af13506bc..0c29f690ac 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -5,7 +5,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; export class Bool extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; @@ -27,7 +27,7 @@ export class Bool extends AbstractDataTypes.Blob { const encodedValue = value ? '0x1' : '0x0'; const encodedValueBuf = ethUtil.setLengthLeft( ethUtil.toBuffer(encodedValue), - Constants.EVM_WORD_WIDTH_IN_BYTES, + constants.EVM_WORD_WIDTH_IN_BYTES, ); return encodedValueBuf; } @@ -35,7 +35,7 @@ export class Bool extends AbstractDataTypes.Blob { public decodeValue(calldata: RawCalldata): boolean { const valueBuf = calldata.popWord(); const valueHex = ethUtil.bufferToHex(valueBuf); - const valueNumber = new BigNumber(valueHex, Constants.HEX_BASE); + const valueNumber = new BigNumber(valueHex, constants.HEX_BASE); if (!(valueNumber.equals(0) || valueNumber.equals(1))) { throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`); } diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index ac2a1fb6e3..2c256cfa90 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; export class DynamicBytes extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; @@ -37,10 +37,10 @@ export class DynamicBytes extends AbstractDataTypes.Blob { // Encoded value is of the form: , with each field padded to be word-aligned. // 1/3 Construct the length const valueBuf = ethUtil.toBuffer(value); - const wordsToStoreValuePadded = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); - const bytesToStoreValuePadded = wordsToStoreValuePadded * Constants.EVM_WORD_WIDTH_IN_BYTES; + const wordsToStoreValuePadded = Math.ceil(valueBuf.byteLength / constants.EVM_WORD_WIDTH_IN_BYTES); + const bytesToStoreValuePadded = wordsToStoreValuePadded * constants.EVM_WORD_WIDTH_IN_BYTES; const lengthBuf = ethUtil.toBuffer(valueBuf.byteLength); - const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, constants.EVM_WORD_WIDTH_IN_BYTES); // 2/3 Construct the value DynamicBytes._sanityCheckValue(value); const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); @@ -54,9 +54,9 @@ export class DynamicBytes extends AbstractDataTypes.Blob { // 1/2 Decode length const lengthBuf = calldata.popWord(); const lengthHex = ethUtil.bufferToHex(lengthBuf); - const length = parseInt(lengthHex, Constants.HEX_BASE); + const length = parseInt(lengthHex, constants.HEX_BASE); // 2/2 Decode value - const wordsToStoreValuePadded = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const wordsToStoreValuePadded = Math.ceil(length / constants.EVM_WORD_WIDTH_IN_BYTES); const valueBufPadded = calldata.popWords(wordsToStoreValuePadded); const valueBuf = valueBufPadded.slice(0, length); const value = ethUtil.bufferToHex(valueBuf); diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 3e465fc158..244b720e31 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; export class Int extends AbstractDataTypes.Blob { @@ -26,7 +26,7 @@ export class Int extends AbstractDataTypes.Blob { const matches = Int._MATCHER.exec(type); const width = !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1]) - ? parseInt(matches[1], Constants.DEC_BASE) + ? parseInt(matches[1], constants.DEC_BASE) : Int._DEFAULT_WIDTH; return width; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index bd4732097f..7256a93d9d 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -3,7 +3,7 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { AbstractDataTypes, DataType, DataTypeFactory } from '../abstract_data_types'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; import { Tuple } from './tuple'; @@ -62,7 +62,7 @@ export class Method extends AbstractDataTypes.Set { ethUtil.toBuffer( ethUtil .sha3(signature) - .slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES), + .slice(constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, constants.HEX_SELECTOR_LENGTH_IN_BYTES), ), ); return selector; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index ed1f51f7e5..5453d47a0a 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; export class StaticBytes extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; @@ -22,7 +22,7 @@ export class StaticBytes extends AbstractDataTypes.Blob { const matches = StaticBytes._MATCHER.exec(type); const width = !_.isNull(matches) && matches.length === 3 && !_.isUndefined(matches[2]) - ? parseInt(matches[2], Constants.DEC_BASE) + ? parseInt(matches[2], constants.DEC_BASE) : StaticBytes._DEFAULT_WIDTH; return width; } @@ -45,7 +45,7 @@ export class StaticBytes extends AbstractDataTypes.Blob { this._sanityCheckValue(value); const valueBuf = ethUtil.toBuffer(value); // 2/2 Store value as hex - const valuePadded = ethUtil.setLengthRight(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + const valuePadded = ethUtil.setLengthRight(valueBuf, constants.EVM_WORD_WIDTH_IN_BYTES); return valuePadded; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index e5b2d5f338..ac62ea2646 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; export class String extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; @@ -25,10 +25,10 @@ export class String extends AbstractDataTypes.Blob { public encodeValue(value: string): Buffer { // Encoded value is of the form: , with each field padded to be word-aligned. // 1/3 Construct the length - const wordsToStoreValuePadded = Math.ceil(value.length / Constants.EVM_WORD_WIDTH_IN_BYTES); - const bytesToStoreValuePadded = wordsToStoreValuePadded * Constants.EVM_WORD_WIDTH_IN_BYTES; + const wordsToStoreValuePadded = Math.ceil(value.length / constants.EVM_WORD_WIDTH_IN_BYTES); + const bytesToStoreValuePadded = wordsToStoreValuePadded * constants.EVM_WORD_WIDTH_IN_BYTES; const lengthBuf = ethUtil.toBuffer(value.length); - const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, constants.EVM_WORD_WIDTH_IN_BYTES); // 2/3 Construct the value const valueBuf = new Buffer(value); const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); @@ -42,9 +42,9 @@ export class String extends AbstractDataTypes.Blob { // 1/2 Decode length const lengthBufPadded = calldata.popWord(); const lengthHexPadded = ethUtil.bufferToHex(lengthBufPadded); - const length = parseInt(lengthHexPadded, Constants.HEX_BASE); + const length = parseInt(lengthHexPadded, constants.HEX_BASE); // 2/2 Decode value - const wordsToStoreValuePadded = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); + const wordsToStoreValuePadded = Math.ceil(length / constants.EVM_WORD_WIDTH_IN_BYTES); const valueBufPadded = calldata.popWords(wordsToStoreValuePadded); const valueBuf = valueBufPadded.slice(0, length); const value = valueBuf.toString('ascii'); diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index 970400a574..df7ea38a40 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; export class UInt extends AbstractDataTypes.Blob { @@ -26,7 +26,7 @@ export class UInt extends AbstractDataTypes.Blob { const matches = UInt._MATCHER.exec(type); const width = !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1]) - ? parseInt(matches[1], Constants.DEC_BASE) + ? parseInt(matches[1], constants.DEC_BASE) : UInt._DEFAULT_WIDTH; return width; } diff --git a/packages/utils/src/abi_encoder/utils/constants.ts b/packages/utils/src/abi_encoder/utils/constants.ts index 05c6783e7a..82eeab010c 100644 --- a/packages/utils/src/abi_encoder/utils/constants.ts +++ b/packages/utils/src/abi_encoder/utils/constants.ts @@ -1,12 +1,14 @@ import { DecodingRules, EncodingRules } from './rules'; -export const EVM_WORD_WIDTH_IN_BYTES = 32; -export const EVM_WORD_WIDTH_IN_BITS = 256; -export const HEX_BASE = 16; -export const DEC_BASE = 10; -export const BIN_BASE = 2; -export const HEX_SELECTOR_LENGTH_IN_CHARS = 10; -export const HEX_SELECTOR_LENGTH_IN_BYTES = 4; -export const HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA = 0; -export const DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false }; -export const DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false }; +export const constants = { + EVM_WORD_WIDTH_IN_BYTES: 32, + EVM_WORD_WIDTH_IN_BITS: 256, + HEX_BASE: 16, + DEC_BASE: 10, + BIN_BASE: 2, + HEX_SELECTOR_LENGTH_IN_CHARS: 10, + HEX_SELECTOR_LENGTH_IN_BYTES: 4, + HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA: 0, + DEFAULT_DECODING_RULES: { structsAsObjects: false } as DecodingRules, + DEFAULT_ENCODING_RULES: { optimize: false, annotate: false } as EncodingRules, +}; diff --git a/packages/utils/src/abi_encoder/utils/math.ts b/packages/utils/src/abi_encoder/utils/math.ts index 9bcdc3af1d..d84983c5b9 100644 --- a/packages/utils/src/abi_encoder/utils/math.ts +++ b/packages/utils/src/abi_encoder/utils/math.ts @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import * as Constants from '../utils/constants'; +import { constants } from '../utils/constants'; function sanityCheckBigNumberRange( value_: BigNumber | string | number, @@ -17,9 +17,9 @@ function sanityCheckBigNumberRange( } } function bigNumberToPaddedBuffer(value: BigNumber): Buffer { - const valueHex = `0x${value.toString(Constants.HEX_BASE)}`; + const valueHex = `0x${value.toString(constants.HEX_BASE)}`; const valueBuf = ethUtil.toBuffer(valueHex); - const valueBufPadded = ethUtil.setLengthLeft(valueBuf, Constants.EVM_WORD_WIDTH_IN_BYTES); + const valueBufPadded = ethUtil.setLengthLeft(valueBuf, constants.EVM_WORD_WIDTH_IN_BYTES); return valueBufPadded; } /** @@ -37,13 +37,13 @@ export function encodeNumericValue(value_: BigNumber | string | number): Buffer // Case 2/2: Value is negative // Use two's-complement to encode the value // Step 1/3: Convert negative value to positive binary string - const valueBin = value.times(-1).toString(Constants.BIN_BASE); + const valueBin = value.times(-1).toString(constants.BIN_BASE); // Step 2/3: Invert binary value - let invertedValueBin = '1'.repeat(Constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length); + let invertedValueBin = '1'.repeat(constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length); _.each(valueBin, (bit: string) => { invertedValueBin += bit === '1' ? '0' : '1'; }); - const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); + const invertedValue = new BigNumber(invertedValueBin, constants.BIN_BASE); // Step 3/3: Add 1 to inverted value const negativeValue = invertedValue.plus(1); const encodedValue = bigNumberToPaddedBuffer(negativeValue); @@ -73,13 +73,13 @@ export function safeEncodeNumericValue( export function decodeNumericValue(encodedValue: Buffer, minValue: BigNumber): BigNumber { const valueHex = ethUtil.bufferToHex(encodedValue); // Case 1/3: value is definitely non-negative because of numeric boundaries - const value = new BigNumber(valueHex, Constants.HEX_BASE); + const value = new BigNumber(valueHex, constants.HEX_BASE); if (!minValue.lessThan(0)) { return value; } // Case 2/3: value is non-negative because there is no leading 1 (encoded as two's-complement) - const valueBin = value.toString(Constants.BIN_BASE); - const isValueNegative = valueBin.length === Constants.EVM_WORD_WIDTH_IN_BITS && _.startsWith(valueBin[0], '1'); + const valueBin = value.toString(constants.BIN_BASE); + const isValueNegative = valueBin.length === constants.EVM_WORD_WIDTH_IN_BITS && _.startsWith(valueBin[0], '1'); if (!isValueNegative) { return value; } @@ -89,7 +89,7 @@ export function decodeNumericValue(encodedValue: Buffer, minValue: BigNumber): B _.each(valueBin, (bit: string) => { invertedValueBin += bit === '1' ? '0' : '1'; }); - const invertedValue = new BigNumber(invertedValueBin, Constants.BIN_BASE); + const invertedValue = new BigNumber(invertedValueBin, constants.BIN_BASE); // Step 2/3: Add 1 to inverted value // The result is the two's-complement representation of the input value. const positiveValue = invertedValue.plus(1); From d6645b8a9116d92ac15f0b64f46566bf7f5447f1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 17:23:01 -0800 Subject: [PATCH 182/230] Explicit encoding rules in tests. --- .../test/abi_encoder/evm_data_types_test.ts | 129 +++++++++--------- .../utils/test/abi_encoder/methods_test.ts | 37 ++--- .../utils/test/abi_encoder/optimizer_test.ts | 31 +++-- .../test/abi_encoder/return_values_test.ts | 11 +- 4 files changed, 106 insertions(+), 102 deletions(-) diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts index 11cebda55a..9ef80a5602 100644 --- a/packages/utils/test/abi_encoder/evm_data_types_test.ts +++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts @@ -10,6 +10,7 @@ chaiSetup.configure(); const expect = chai.expect; describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { + const encodingRules: AbiEncoder.EncodingRules = { optimize: false }; // optimizer is tested separately. describe('Array', () => { it('Fixed size; Static elements', async () => { // Create DataType object @@ -18,7 +19,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = [new BigNumber(5), new BigNumber(6)]; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -33,7 +34,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = [new BigNumber(5), new BigNumber(6)]; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -48,7 +49,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = ['Hello', 'world']; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -63,7 +64,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = ['Hello', 'world']; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -81,7 +82,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const array3 = ['0x18192021']; const args = [array1, array2, array3]; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405060708000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000041011121300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414151617000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -99,7 +100,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const array3 = ['0x18192021']; const args = [array1, array2, array3]; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000301020304000000000000000000000000000000000000000000000000000000000506070800000000000000000000000000000000000000000000000000000000091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021011121300000000000000000000000000000000000000000000000000000000141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011819202100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -116,7 +117,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const array2 = ['0x10111213', '0x14151617', '0x18192021']; const args = [array1, array2]; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x010203040000000000000000000000000000000000000000000000000000000005060708000000000000000000000000000000000000000000000000000000000910111200000000000000000000000000000000000000000000000000000000101112130000000000000000000000000000000000000000000000000000000014151617000000000000000000000000000000000000000000000000000000001819202100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -133,7 +134,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const array2 = ['0x10111213', '0x14151617', '0x18192021']; const args = [array1, array2]; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000401020304000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004050607080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040910111200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000410111213000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -149,7 +150,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = ['Hello', 'world']; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw('Expected array of 3 elements, but got array of length 2'); }); it('Static size; Too Many Elements', async () => { @@ -160,7 +161,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = ['Hello', 'world']; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw('Expected array of 1 elements, but got array of length 2'); }); it('Element Type Mismatch', async () => { @@ -171,7 +172,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = [new BigNumber(1), 'Bad Argument']; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); }); @@ -188,7 +189,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = { field_1: new BigNumber(-5), field_2: true }; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -208,7 +209,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = { field_1: 'Hello, World!', field_2: '0xabcdef0123456789' }; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -228,7 +229,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = { field: [new BigNumber(1), new BigNumber(2)] }; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -248,7 +249,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = { field: [new BigNumber(1), new BigNumber(2)] }; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -270,7 +271,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const array2 = ['0x09101112', '0x13141516']; const args = { field: [array1, array2] }; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -292,7 +293,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const array2 = ['0x09101112', '0x13141516']; const args = { field: [array1, array2] }; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -322,7 +323,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { field_4: '0xabcdef0123456789', }; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -343,7 +344,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = { field_1: new BigNumber(-5) }; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw('Could not assign tuple to object: missing keys field_2'); }); it('Bad Key', async () => { @@ -358,7 +359,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = { unknown_field: new BigNumber(-5) }; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw("Could not assign tuple to object: unrecognized key 'unknown_field' in object Tuple"); }); }); @@ -371,7 +372,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = '0xe41d2489571d322189246dafa5ebde1f4699f498'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -386,7 +387,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = 'e4'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(`Invalid address: '${args}'`); }); it('Invalid Address - input is not 20 bytes', async () => { @@ -397,7 +398,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = '0xe4'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(`Invalid address: '${args}'`); }); }); @@ -410,7 +411,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = true; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -424,7 +425,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = false; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -448,7 +449,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = new BigNumber(1); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -462,7 +463,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = new BigNumber(-1); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -476,7 +477,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = max256BitInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -490,7 +491,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = min256BitInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = `0x8000000000000000000000000000000000000000000000000000000000000000`; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -505,7 +506,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = max256BitInteger.plus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); it('Int256 - Value too small', async () => { @@ -516,7 +517,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = min256BitInteger.minus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); it('Int32 - Positive Base Case', async () => { @@ -526,7 +527,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = new BigNumber(1); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -540,7 +541,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = new BigNumber(-1); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -554,7 +555,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = max32BitInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000007fffffff'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -568,7 +569,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = min32BitInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000`; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -583,7 +584,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = max32BitInteger.plus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); it('Int32 - Value too small', async () => { @@ -594,7 +595,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = min32BitInteger.minus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); }); @@ -614,7 +615,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = new BigNumber(1); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -628,7 +629,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = max256BitUnsignedInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -642,7 +643,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = min256BitUnsignedInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -657,7 +658,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = max256BitUnsignedInteger.plus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); it('UInt256 - Value too small', async () => { @@ -668,7 +669,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = min256BitUnsignedInteger.minus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); it('UInt32 - Positive Base Case', async () => { @@ -678,7 +679,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = new BigNumber(1); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -692,7 +693,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = max32BitUnsignedInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000ffffffff'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -706,7 +707,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = min32BitUnsignedInteger; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -721,7 +722,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = max32BitUnsignedInteger.plus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); it('UInt32 - Value too small', async () => { @@ -732,7 +733,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = min32BitUnsignedInteger.minus(1); // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw(); }); }); @@ -745,7 +746,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = '0x05'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -759,7 +760,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = '0x05'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -773,7 +774,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = '0x00010203'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0001020300000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -788,7 +789,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = '0x1a18'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x1a18000000000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -803,7 +804,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded const args = '0x0001020304050607080911121314151617181920212223242526272829303132'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x0001020304050607080911121314151617181920212223242526272829303132'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -818,7 +819,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = '0x1a18bf61'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); // Decode Encoded Args and validate result @@ -834,7 +835,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = '0x0102030405'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw( 'Tried to assign 0x0102030405 (5 bytes), which exceeds max bytes that can be stored in a bytes4', ); @@ -847,7 +848,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = '0x010203040506070809101112131415161718192021222324252627282930313233'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw( 'Tried to assign 0x010203040506070809101112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32', ); @@ -860,7 +861,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = '0102030405060708091011121314151617181920212223242526272829303132'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); }); it('Should throw when pass in bad hex (include a half-byte)', async () => { @@ -871,7 +872,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = '0x010'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); }); }); @@ -885,7 +886,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = '0x1a18bf61'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -902,7 +903,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const bytesLength = 40; const args = '0x' + '61'.repeat(bytesLength); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -935,7 +936,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = '01'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix."); }); it('Should throw when pass in bad hex (include a half-byte)', async () => { @@ -946,7 +947,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const args = '0x010'; // Encode Args and validate result expect(() => { - dataType.encode(args); + dataType.encode(args, encodingRules); }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.'); }); }); @@ -960,7 +961,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const args = 'five'; // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -977,7 +978,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const bytesLength = 40; const args = 'a'.repeat(bytesLength); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); @@ -994,7 +995,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { const strLength = 40; const args = '0x' + 'a'.repeat(strLength); // Encode Args and validate result - const encodedArgs = dataType.encode(args); + const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000000000002a30786161616161616161616161616161616161616161616161616161616161616161616161616161616100000000000000000000000000000000000000000000'; expect(encodedArgs).to.be.equal(expectedEncodedArgs); diff --git a/packages/utils/test/abi_encoder/methods_test.ts b/packages/utils/test/abi_encoder/methods_test.ts index c870f4f048..8370208836 100644 --- a/packages/utils/test/abi_encoder/methods_test.ts +++ b/packages/utils/test/abi_encoder/methods_test.ts @@ -10,11 +10,12 @@ chaiSetup.configure(); const expect = chai.expect; describe('ABI Encoder: Method Encoding / Decoding', () => { + const encodingRules: AbiEncoder.EncodingRules = { optimize: false }; // optimizer is tested separately. it('Types with default widths', async () => { // Generate calldata const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; @@ -33,7 +34,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); } const args = [arrayOfTuples]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; @@ -52,7 +53,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); } const args = [arrayOfTuples]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; @@ -71,7 +72,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); } const args = [arrayOfTuples]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; @@ -90,7 +91,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]); } const args = [arrayOfTuples]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; @@ -112,7 +113,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { [[new BigNumber(++value), new BigNumber(++value)], [new BigNumber(++value), new BigNumber(++value)]], ]); } - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; @@ -141,7 +142,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { ], ]); } - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; @@ -154,7 +155,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // Generate calldata const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); const args = [['Brave', 'New', 'World']]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; @@ -167,7 +168,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // Generaet calldata const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); const args = [['Brave', 'New', 'World']]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; @@ -180,7 +181,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // Generate calldata const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); const args = [['Brave', 'New', 'World']]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; @@ -193,7 +194,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // Generate calldata const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; @@ -206,7 +207,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // Generate calldata const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; @@ -219,7 +220,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // Generate calldata const method = new AbiEncoder.Method(AbiSamples.stringAbi); const args = [['five', 'six', 'seven']]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; @@ -233,7 +234,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // This is dynamic because it has dynamic members const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi); const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; @@ -247,7 +248,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // This is dynamic because it has dynamic members const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); const args = [[new BigNumber(5), 'five']]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; @@ -261,7 +262,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { // This is dynamic because it has dynamic members const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); const args = [[new BigNumber(5), 'five']]; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; @@ -285,7 +286,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { true, ]; // Validate calldata - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); const expectedCalldata = '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; expect(calldata).to.be.equal(expectedCalldata); @@ -353,7 +354,7 @@ describe('ABI Encoder: Method Encoding / Decoding', () => { someTupleWithDynamicTypes, someArrayOfTuplesWithDynamicTypes, }; - const calldata = method.encode(args); + const calldata = method.encode(args, encodingRules); // Validate calldata const expectedCalldata = '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; diff --git a/packages/utils/test/abi_encoder/optimizer_test.ts b/packages/utils/test/abi_encoder/optimizer_test.ts index 27443fe488..18aa6549ae 100644 --- a/packages/utils/test/abi_encoder/optimizer_test.ts +++ b/packages/utils/test/abi_encoder/optimizer_test.ts @@ -10,6 +10,7 @@ chaiSetup.configure(); const expect = chai.expect; describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { + const encodingRules: AbiEncoder.EncodingRules = { optimize: true }; it('Duplicate Dynamic Arrays with Static Elements', async () => { // Generate calldata const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithStaticElements); @@ -17,7 +18,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const array2 = array1; const args = [array1, array2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x7221063300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -32,7 +33,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const array2 = array1; const args = [array1, array2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0xbb4f12e300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -47,7 +48,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const array2 = array1; const args = [array1, array2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x7f8130430000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -64,7 +65,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const array2 = array1; const args = [array1, array2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x9fe31f8e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -78,7 +79,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const strings = ['Hello', 'World', 'Hello', 'World']; const args = [strings]; // Validate calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -92,7 +93,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const tuple = ['Hello', 'Hello']; const args = [tuple]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x16780a5e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -109,7 +110,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const method = new AbiEncoder.Method(OptimizedAbis.duplicateStrings); const args = ['Hello', 'Hello']; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x07370bfa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -127,7 +128,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const value = '0x01020304050607080910111213141516171819202122232425262728293031323334353637383940'; const args = [value, value]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x6045e42900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002801020304050607080910111213141516171819202122232425262728293031323334353637383940000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -142,7 +143,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const tuple2 = tuple1; const args = [tuple1, tuple2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000006792a000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -158,7 +159,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const tuple2 = [tuple1[0], new BigNumber(2)]; const args = [tuple1, tuple2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -174,7 +175,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const tuple2 = [array, 'extra argument to prevent exactly matching the tuples']; const args = [tuple1, tuple2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x18970a9e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c80000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -190,7 +191,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const tuple2 = [nestedTuple, 'extra argument to prevent exactly matching the tuples']; const args = [tuple1, tuple2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x0b4d2e6a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -220,7 +221,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const twoDimArray2 = [['Hello', 'World'], ['Bar']]; const args = [twoDimArray1, twoDimArray2]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x0d28c4f900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003466f6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034261720000000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -235,7 +236,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const tuple = [[array[0]], [array[1]], [array[2]], [array[3]]]; const args = [array, tuple]; // Validata calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0x5b5c78fd0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000e1'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); @@ -250,7 +251,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { const str = 'Hello'; const args = [array, str]; // Validate calldata - const optimizedCalldata = method.encode(args, { optimize: true }); + const optimizedCalldata = method.encode(args, encodingRules); const expectedOptimizedCalldata = '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); diff --git a/packages/utils/test/abi_encoder/return_values_test.ts b/packages/utils/test/abi_encoder/return_values_test.ts index 3215509f16..a8cdd6ca35 100644 --- a/packages/utils/test/abi_encoder/return_values_test.ts +++ b/packages/utils/test/abi_encoder/return_values_test.ts @@ -10,6 +10,7 @@ chaiSetup.configure(); const expect = chai.expect; describe('ABI Encoder: Return Value Encoding/Decoding', () => { + const encodingRules: AbiEncoder.EncodingRules = { optimize: false }; // optimizer is tested separately. it('No Return Value', async () => { // Decode return value const method = new AbiEncoder.Method(ReturnValueAbis.noReturnValues); @@ -22,7 +23,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { // Generate Return Value const method = new AbiEncoder.Method(ReturnValueAbis.singleStaticReturnValue); const returnValue = ['0x01020304']; - const encodedReturnValue = method.encodeReturnValues(returnValue); + const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value expect(decodedReturnValue).to.be.deep.equal(returnValue); @@ -31,7 +32,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { // Generate Return Value const method = new AbiEncoder.Method(ReturnValueAbis.multipleStaticReturnValues); const returnValue = ['0x01020304', '0x05060708']; - const encodedReturnValue = method.encodeReturnValues(returnValue); + const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value expect(decodedReturnValue).to.be.deep.equal(returnValue); @@ -40,7 +41,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { // Generate Return Value const method = new AbiEncoder.Method(ReturnValueAbis.singleDynamicReturnValue); const returnValue = ['0x01020304']; - const encodedReturnValue = method.encodeReturnValues(returnValue); + const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value expect(decodedReturnValue).to.be.deep.equal(returnValue); @@ -49,7 +50,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { // Generate Return Value const method = new AbiEncoder.Method(ReturnValueAbis.multipleDynamicReturnValues); const returnValue = ['0x01020304', '0x05060708']; - const encodedReturnValue = method.encodeReturnValues(returnValue); + const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value expect(decodedReturnValue).to.be.deep.equal(returnValue); @@ -58,7 +59,7 @@ describe('ABI Encoder: Return Value Encoding/Decoding', () => { // Generate Return Value const method = new AbiEncoder.Method(ReturnValueAbis.mixedStaticAndDynamicReturnValues); const returnValue = ['0x01020304', '0x05060708']; - const encodedReturnValue = method.encodeReturnValues(returnValue); + const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules); const decodedReturnValue = method.decodeReturnValues(encodedReturnValue); // Validate decoded return value expect(decodedReturnValue).to.be.deep.equal(returnValue); From 5c13353fb2512411c0f2c8cba9395235188f5df8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 27 Nov 2018 17:24:14 -0800 Subject: [PATCH 183/230] Optimize calldata by default. --- packages/utils/src/abi_encoder/utils/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/abi_encoder/utils/constants.ts b/packages/utils/src/abi_encoder/utils/constants.ts index 82eeab010c..acc06329cb 100644 --- a/packages/utils/src/abi_encoder/utils/constants.ts +++ b/packages/utils/src/abi_encoder/utils/constants.ts @@ -10,5 +10,5 @@ export const constants = { HEX_SELECTOR_LENGTH_IN_BYTES: 4, HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA: 0, DEFAULT_DECODING_RULES: { structsAsObjects: false } as DecodingRules, - DEFAULT_ENCODING_RULES: { optimize: false, annotate: false } as EncodingRules, + DEFAULT_ENCODING_RULES: { optimize: true, annotate: false } as EncodingRules, }; From a172ab158e2eaca8256ef881c3f2d4098987ec8a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 13:22:18 -0800 Subject: [PATCH 184/230] Explicit imports for EVM Data Types --- .../src/abi_encoder/evm_data_type_factory.ts | 36 ++++++++++++------- .../src/abi_encoder/evm_data_types/address.ts | 10 +++--- .../src/abi_encoder/evm_data_types/array.ts | 8 ++--- .../src/abi_encoder/evm_data_types/bool.ts | 6 ++-- .../evm_data_types/dynamic_bytes.ts | 10 +++--- .../src/abi_encoder/evm_data_types/index.ts | 11 ------ .../src/abi_encoder/evm_data_types/int.ts | 16 ++++----- .../src/abi_encoder/evm_data_types/method.ts | 6 ++-- .../src/abi_encoder/evm_data_types/pointer.ts | 2 +- .../evm_data_types/static_bytes.ts | 14 ++++---- .../src/abi_encoder/evm_data_types/string.ts | 6 ++-- .../src/abi_encoder/evm_data_types/tuple.ts | 4 +-- .../src/abi_encoder/evm_data_types/uint.ts | 20 +++++------ packages/utils/src/abi_encoder/index.ts | 14 +++++++- 14 files changed, 87 insertions(+), 76 deletions(-) delete mode 100644 packages/utils/src/abi_encoder/evm_data_types/index.ts diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index bc68e05b78..4d04d4ed74 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -3,69 +3,79 @@ import { DataItem, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; import { DataType, DataTypeFactory } from './abstract_data_types'; -import * as Impl from './evm_data_types'; +import { AddressDataType } from './evm_data_types/address'; +import { ArrayDataType } from './evm_data_types/array'; +import { BoolDataType } from './evm_data_types/bool'; +import { DynamicBytesDataType } from './evm_data_types/dynamic_bytes'; +import { IntDataType } from './evm_data_types/int'; +import { MethodDataType } from './evm_data_types/method'; +import { PointerDataType } from './evm_data_types/pointer'; +import { StaticBytesDataType } from './evm_data_types/static_bytes'; +import { StringDataType } from './evm_data_types/string'; +import { TupleDataType } from './evm_data_types/tuple'; +import { UIntDataType } from './evm_data_types/uint'; -export class Address extends Impl.Address { +export class Address extends AddressDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class Bool extends Impl.Bool { +export class Bool extends BoolDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class Int extends Impl.Int { +export class Int extends IntDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class UInt extends Impl.UInt { +export class UInt extends UIntDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class StaticBytes extends Impl.StaticBytes { +export class StaticBytes extends StaticBytesDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class DynamicBytes extends Impl.DynamicBytes { +export class DynamicBytes extends DynamicBytesDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class String extends Impl.String { +export class String extends StringDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class Pointer extends Impl.Pointer { +export class Pointer extends PointerDataType { public constructor(destDataType: DataType, parentDataType: DataType) { super(destDataType, parentDataType, EvmDataTypeFactory.getInstance()); } } -export class Tuple extends Impl.Tuple { +export class Tuple extends TupleDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class Array extends Impl.Array { +export class Array extends ArrayDataType { public constructor(dataItem: DataItem) { super(dataItem, EvmDataTypeFactory.getInstance()); } } -export class Method extends Impl.Method { +export class Method extends MethodDataType { public constructor(abi: MethodAbi) { super(abi, EvmDataTypeFactory.getInstance()); } @@ -105,7 +115,7 @@ export class EvmDataTypeFactory implements DataTypeFactory { } else if (String.matchType(dataItem.type)) { dataType = new String(dataItem); } - // @TODO: Implement Fixed/UFixed types + // @TODO: DataTypeement Fixed/UFixed types if (_.isUndefined(dataType)) { throw new Error(`Unrecognized data type: '${dataItem.type}'`); } else if (!_.isUndefined(parentDataType) && !dataType.isStatic()) { diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index c45355639f..769c5a81c1 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -6,19 +6,19 @@ import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class Address extends AbstractDataTypes.Blob { +export class AddressDataType extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = constants.EVM_WORD_WIDTH_IN_BYTES - - Address._ADDRESS_SIZE_IN_BYTES; + AddressDataType._ADDRESS_SIZE_IN_BYTES; public static matchType(type: string): boolean { return type === SolidityTypes.Address; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, Address._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Address.matchType(dataItem.type)) { + super(dataItem, dataTypeFactory, AddressDataType._SIZE_KNOWN_AT_COMPILE_TIME); + if (!AddressDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`); } } @@ -36,7 +36,7 @@ export class Address extends AbstractDataTypes.Blob { public decodeValue(calldata: RawCalldata): string { const valueBufPadded = calldata.popWord(); - const valueBuf = valueBufPadded.slice(Address._DECODED_ADDRESS_OFFSET_IN_BYTES); + const valueBuf = valueBufPadded.slice(AddressDataType._DECODED_ADDRESS_OFFSET_IN_BYTES); const value = ethUtil.bufferToHex(valueBuf); return value; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 272cc41324..1736bcef0e 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -4,17 +4,17 @@ import * as _ from 'lodash'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { constants } from '../utils/constants'; -export class Array extends AbstractDataTypes.Set { +export class ArrayDataType extends AbstractDataTypes.Set { private static readonly _MATCHER = RegExp('^(.+)\\[([0-9]*)\\]$'); private readonly _arraySignature: string; private readonly _elementType: string; public static matchType(type: string): boolean { - return Array._MATCHER.test(type); + return ArrayDataType._MATCHER.test(type); } private static _decodeElementTypeAndLengthFromType(type: string): [string, undefined | number] { - const matches = Array._MATCHER.exec(type); + const matches = ArrayDataType._MATCHER.exec(type); if (_.isNull(matches) || matches.length !== 3) { throw new Error(`Could not parse array: ${type}`); } else if (_.isUndefined(matches[1])) { @@ -30,7 +30,7 @@ export class Array extends AbstractDataTypes.Set { public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { // Construct parent const isArray = true; - const [arrayElementType, arrayLength] = Array._decodeElementTypeAndLengthFromType(dataItem.type); + const [arrayElementType, arrayLength] = ArrayDataType._decodeElementTypeAndLengthFromType(dataItem.type); super(dataItem, dataTypeFactory, isArray, arrayLength, arrayElementType); // Set array properties this._elementType = arrayElementType; diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 0c29f690ac..32eda9c391 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -7,7 +7,7 @@ import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class Bool extends AbstractDataTypes.Blob { +export class BoolDataType extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; public static matchType(type: string): boolean { @@ -15,8 +15,8 @@ export class Bool extends AbstractDataTypes.Blob { } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, Bool._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Bool.matchType(dataItem.type)) { + super(dataItem, dataTypeFactory, BoolDataType._SIZE_KNOWN_AT_COMPILE_TIME); + if (!BoolDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`); } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index 2c256cfa90..c8ff47c3d5 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -6,7 +6,7 @@ import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class DynamicBytes extends AbstractDataTypes.Blob { +export class DynamicBytesDataType extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { @@ -25,8 +25,8 @@ export class DynamicBytes extends AbstractDataTypes.Blob { } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME); - if (!DynamicBytes.matchType(dataItem.type)) { + super(dataItem, dataTypeFactory, DynamicBytesDataType._SIZE_KNOWN_AT_COMPILE_TIME); + if (!DynamicBytesDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Dynamic Bytes with bad input: ${dataItem}`); } } @@ -42,7 +42,7 @@ export class DynamicBytes extends AbstractDataTypes.Blob { const lengthBuf = ethUtil.toBuffer(valueBuf.byteLength); const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, constants.EVM_WORD_WIDTH_IN_BYTES); // 2/3 Construct the value - DynamicBytes._sanityCheckValue(value); + DynamicBytesDataType._sanityCheckValue(value); const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); // 3/3 Combine length and value const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]); @@ -60,7 +60,7 @@ export class DynamicBytes extends AbstractDataTypes.Blob { const valueBufPadded = calldata.popWords(wordsToStoreValuePadded); const valueBuf = valueBufPadded.slice(0, length); const value = ethUtil.bufferToHex(valueBuf); - DynamicBytes._sanityCheckValue(value); + DynamicBytesDataType._sanityCheckValue(value); return value; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/index.ts b/packages/utils/src/abi_encoder/evm_data_types/index.ts deleted file mode 100644 index fc0edabf1c..0000000000 --- a/packages/utils/src/abi_encoder/evm_data_types/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './address'; -export * from './bool'; -export * from './int'; -export * from './uint'; -export * from './static_bytes'; -export * from './dynamic_bytes'; -export * from './string'; -export * from './pointer'; -export * from './tuple'; -export * from './array'; -export * from './method'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 244b720e31..aee8320a60 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -7,36 +7,36 @@ import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; -export class Int extends AbstractDataTypes.Blob { +export class IntDataType extends AbstractDataTypes.Blob { private static readonly _MATCHER = RegExp( '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _MAX_WIDTH: number = 256; - private static readonly _DEFAULT_WIDTH: number = Int._MAX_WIDTH; + private static readonly _DEFAULT_WIDTH: number = IntDataType._MAX_WIDTH; private readonly _width: number; private readonly _minValue: BigNumber; private readonly _maxValue: BigNumber; public static matchType(type: string): boolean { - return Int._MATCHER.test(type); + return IntDataType._MATCHER.test(type); } private static _decodeWidthFromType(type: string): number { - const matches = Int._MATCHER.exec(type); + const matches = IntDataType._MATCHER.exec(type); const width = !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1]) ? parseInt(matches[1], constants.DEC_BASE) - : Int._DEFAULT_WIDTH; + : IntDataType._DEFAULT_WIDTH; return width; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, Int._SIZE_KNOWN_AT_COMPILE_TIME); - if (!Int.matchType(dataItem.type)) { + super(dataItem, dataTypeFactory, IntDataType._SIZE_KNOWN_AT_COMPILE_TIME); + if (!IntDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Int with bad input: ${dataItem}`); } - this._width = Int._decodeWidthFromType(dataItem.type); + this._width = IntDataType._decodeWidthFromType(dataItem.type); this._minValue = new BigNumber(2).toPower(this._width - 1).times(-1); this._maxValue = new BigNumber(2).toPower(this._width - 1).sub(1); } diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index 7256a93d9d..de8809edfe 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -6,9 +6,9 @@ import { AbstractDataTypes, DataType, DataTypeFactory } from '../abstract_data_t import { constants } from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; -import { Tuple } from './tuple'; +import { TupleDataType } from './tuple'; -export class Method extends AbstractDataTypes.Set { +export class MethodDataType extends AbstractDataTypes.Set { private readonly _methodSignature: string; private readonly _methodSelector: string; private readonly _returnDataType: DataType; @@ -19,7 +19,7 @@ export class Method extends AbstractDataTypes.Set { this._methodSignature = this._computeSignature(); this._methodSelector = this._computeSelector(); const returnDataItem: DataItem = { type: 'tuple', name: abi.name, components: abi.outputs }; - this._returnDataType = new Tuple(returnDataItem, this.getFactory()); + this._returnDataType = new TupleDataType(returnDataItem, this.getFactory()); } public encode(value: any, rules?: EncodingRules): string { diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts index e7c172afb1..7ca4287604 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts @@ -2,7 +2,7 @@ import { DataItem } from 'ethereum-types'; import { AbstractDataTypes, DataType, DataTypeFactory } from '../abstract_data_types'; -export class Pointer extends AbstractDataTypes.Pointer { +export class PointerDataType extends AbstractDataTypes.Pointer { constructor(destDataType: DataType, parentDataType: DataType, dataTypeFactory: DataTypeFactory) { const destDataItem = destDataType.getDataItem(); const dataItem: DataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` }; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 5453d47a0a..3b270630ec 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -6,7 +6,7 @@ import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class StaticBytes extends AbstractDataTypes.Blob { +export class StaticBytesDataType extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _MATCHER = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', @@ -15,24 +15,24 @@ export class StaticBytes extends AbstractDataTypes.Blob { private readonly _width: number; public static matchType(type: string): boolean { - return StaticBytes._MATCHER.test(type); + return StaticBytesDataType._MATCHER.test(type); } private static _decodeWidthFromType(type: string): number { - const matches = StaticBytes._MATCHER.exec(type); + const matches = StaticBytesDataType._MATCHER.exec(type); const width = !_.isNull(matches) && matches.length === 3 && !_.isUndefined(matches[2]) ? parseInt(matches[2], constants.DEC_BASE) - : StaticBytes._DEFAULT_WIDTH; + : StaticBytesDataType._DEFAULT_WIDTH; return width; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, StaticBytes._SIZE_KNOWN_AT_COMPILE_TIME); - if (!StaticBytes.matchType(dataItem.type)) { + super(dataItem, dataTypeFactory, StaticBytesDataType._SIZE_KNOWN_AT_COMPILE_TIME); + if (!StaticBytesDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Static Bytes with bad input: ${dataItem}`); } - this._width = StaticBytes._decodeWidthFromType(dataItem.type); + this._width = StaticBytesDataType._decodeWidthFromType(dataItem.type); } public getSignature(): string { diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index ac62ea2646..d7e9ec7fe8 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -6,7 +6,7 @@ import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class String extends AbstractDataTypes.Blob { +export class StringDataType extends AbstractDataTypes.Blob { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { @@ -14,8 +14,8 @@ export class String extends AbstractDataTypes.Blob { } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, String._SIZE_KNOWN_AT_COMPILE_TIME); - if (!String.matchType(dataItem.type)) { + super(dataItem, dataTypeFactory, StringDataType._SIZE_KNOWN_AT_COMPILE_TIME); + if (!StringDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate String with bad input: ${dataItem}`); } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 40859f62e2..5ba875b9ec 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -2,7 +2,7 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; -export class Tuple extends AbstractDataTypes.Set { +export class TupleDataType extends AbstractDataTypes.Set { private readonly _signature: string; public static matchType(type: string): boolean { @@ -11,7 +11,7 @@ export class Tuple extends AbstractDataTypes.Set { public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { super(dataItem, dataTypeFactory); - if (!Tuple.matchType(dataItem.type)) { + if (!TupleDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); } this._signature = this._computeSignatureOfMembers(); diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index df7ea38a40..a5989ea116 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -7,47 +7,47 @@ import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; -export class UInt extends AbstractDataTypes.Blob { +export class UIntDataType extends AbstractDataTypes.Blob { private static readonly _MATCHER = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _MAX_WIDTH: number = 256; - private static readonly _DEFAULT_WIDTH: number = UInt._MAX_WIDTH; + private static readonly _DEFAULT_WIDTH: number = UIntDataType._MAX_WIDTH; private static readonly _MIN_VALUE = new BigNumber(0); private readonly _width: number; private readonly _maxValue: BigNumber; public static matchType(type: string): boolean { - return UInt._MATCHER.test(type); + return UIntDataType._MATCHER.test(type); } private static _decodeWidthFromType(type: string): number { - const matches = UInt._MATCHER.exec(type); + const matches = UIntDataType._MATCHER.exec(type); const width = !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1]) ? parseInt(matches[1], constants.DEC_BASE) - : UInt._DEFAULT_WIDTH; + : UIntDataType._DEFAULT_WIDTH; return width; } public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { - super(dataItem, dataTypeFactory, UInt._SIZE_KNOWN_AT_COMPILE_TIME); - if (!UInt.matchType(dataItem.type)) { + super(dataItem, dataTypeFactory, UIntDataType._SIZE_KNOWN_AT_COMPILE_TIME); + if (!UIntDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate UInt with bad input: ${dataItem}`); } - this._width = UInt._decodeWidthFromType(dataItem.type); + this._width = UIntDataType._decodeWidthFromType(dataItem.type); this._maxValue = new BigNumber(2).toPower(this._width).sub(1); } public encodeValue(value: BigNumber | string | number): Buffer { - const encodedValue = EncoderMath.safeEncodeNumericValue(value, UInt._MIN_VALUE, this._maxValue); + const encodedValue = EncoderMath.safeEncodeNumericValue(value, UIntDataType._MIN_VALUE, this._maxValue); return encodedValue; } public decodeValue(calldata: RawCalldata): BigNumber { const valueBuf = calldata.popWord(); - const value = EncoderMath.safeDecodeNumericValue(valueBuf, UInt._MIN_VALUE, this._maxValue); + const value = EncoderMath.safeDecodeNumericValue(valueBuf, UIntDataType._MIN_VALUE, this._maxValue); return value; } diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts index ea037b40a0..baf844ac65 100644 --- a/packages/utils/src/abi_encoder/index.ts +++ b/packages/utils/src/abi_encoder/index.ts @@ -1,2 +1,14 @@ export { EncodingRules, DecodingRules } from './utils/rules'; -export * from './evm_data_type_factory'; +export { + Address, + Array, + Bool, + DynamicBytes, + Int, + Method, + Pointer, + StaticBytes, + String, + Tuple, + UInt, +} from './evm_data_type_factory'; From b8ea322541e291b84f261bffcc77baf85dae08c1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 13:35:53 -0800 Subject: [PATCH 185/230] Explicit imports for abstract data types. --- packages/utils/src/abi_encoder/abstract_data_types/index.ts | 4 ---- .../utils/src/abi_encoder/abstract_data_types/types/blob.ts | 2 +- .../src/abi_encoder/abstract_data_types/types/index.ts | 3 --- .../src/abi_encoder/abstract_data_types/types/pointer.ts | 2 +- .../utils/src/abi_encoder/abstract_data_types/types/set.ts | 6 +++--- packages/utils/src/abi_encoder/evm_data_type_factory.ts | 3 ++- packages/utils/src/abi_encoder/evm_data_types/address.ts | 5 +++-- packages/utils/src/abi_encoder/evm_data_types/array.ts | 5 +++-- packages/utils/src/abi_encoder/evm_data_types/bool.ts | 5 +++-- .../utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts | 5 +++-- packages/utils/src/abi_encoder/evm_data_types/int.ts | 5 +++-- packages/utils/src/abi_encoder/evm_data_types/method.ts | 6 ++++-- packages/utils/src/abi_encoder/evm_data_types/pointer.ts | 6 ++++-- .../utils/src/abi_encoder/evm_data_types/static_bytes.ts | 5 +++-- packages/utils/src/abi_encoder/evm_data_types/string.ts | 5 +++-- packages/utils/src/abi_encoder/evm_data_types/tuple.ts | 5 +++-- packages/utils/src/abi_encoder/evm_data_types/uint.ts | 5 +++-- 17 files changed, 42 insertions(+), 35 deletions(-) delete mode 100644 packages/utils/src/abi_encoder/abstract_data_types/index.ts delete mode 100644 packages/utils/src/abi_encoder/abstract_data_types/types/index.ts diff --git a/packages/utils/src/abi_encoder/abstract_data_types/index.ts b/packages/utils/src/abi_encoder/abstract_data_types/index.ts deleted file mode 100644 index d1c7d93e4d..0000000000 --- a/packages/utils/src/abi_encoder/abstract_data_types/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './interfaces'; -export * from './data_type'; -import * as AbstractDataTypes from './types'; -export { AbstractDataTypes }; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts index 9657380569..cd21196733 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts @@ -7,7 +7,7 @@ import { DecodingRules } from '../../utils/rules'; import { DataType } from '../data_type'; import { DataTypeFactory } from '../interfaces'; -export abstract class Blob extends DataType { +export abstract class AbstractBlobDataType extends DataType { protected _sizeKnownAtCompileTime: boolean; public constructor(dataItem: DataItem, factory: DataTypeFactory, sizeKnownAtCompileTime: boolean) { diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/index.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/index.ts deleted file mode 100644 index 958582dae4..0000000000 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './blob'; -export * from './pointer'; -export * from './set'; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts index 3d38deb945..b6a6a7613b 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts @@ -9,7 +9,7 @@ import { DecodingRules } from '../../utils/rules'; import { DataType } from '../data_type'; import { DataTypeFactory } from '../interfaces'; -export abstract class Pointer extends DataType { +export abstract class AbstractPointerDataType extends DataType { protected _destination: DataType; protected _parent: DataType; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index f50ed82984..4c8bb7b1a6 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -10,9 +10,9 @@ import { DecodingRules } from '../../utils/rules'; import { DataType } from '../data_type'; import { DataTypeFactory, MemberIndexByName } from '../interfaces'; -import { Pointer } from './pointer'; +import { AbstractPointerDataType } from './pointer'; -export abstract class Set extends DataType { +export abstract class AbstractSetDataType extends DataType { protected readonly _arrayLength: number | undefined; protected readonly _arrayElementType: string | undefined; private readonly _memberIndexByName: MemberIndexByName; @@ -88,7 +88,7 @@ export abstract class Set extends DataType { } // If any member of the set is a pointer then the set is not static. const dependentMember = _.find(this._members, (member: DataType) => { - return member instanceof Pointer; + return member instanceof AbstractPointerDataType; }); const isStatic = _.isUndefined(dependentMember); return isStatic; diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index 4d04d4ed74..4cc124e0a7 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -2,7 +2,8 @@ import { DataItem, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; -import { DataType, DataTypeFactory } from './abstract_data_types'; +import { DataType } from './abstract_data_types/data_type'; +import { DataTypeFactory } from './abstract_data_types/interfaces'; import { AddressDataType } from './evm_data_types/address'; import { ArrayDataType } from './evm_data_types/array'; import { BoolDataType } from './evm_data_types/bool'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 769c5a81c1..17363b5f36 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -2,11 +2,12 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class AddressDataType extends AbstractDataTypes.Blob { +export class AddressDataType extends AbstractBlobDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _ADDRESS_SIZE_IN_BYTES = 20; private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = constants.EVM_WORD_WIDTH_IN_BYTES - diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 1736bcef0e..7595cb667b 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -1,10 +1,11 @@ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractSetDataType } from '../abstract_data_types/types/set'; import { constants } from '../utils/constants'; -export class ArrayDataType extends AbstractDataTypes.Set { +export class ArrayDataType extends AbstractSetDataType { private static readonly _MATCHER = RegExp('^(.+)\\[([0-9]*)\\]$'); private readonly _arraySignature: string; private readonly _elementType: string; diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 32eda9c391..778a01d8a3 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -3,11 +3,12 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class BoolDataType extends AbstractDataTypes.Blob { +export class BoolDataType extends AbstractBlobDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index c8ff47c3d5..ac8e2b716e 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -2,11 +2,12 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class DynamicBytesDataType extends AbstractDataTypes.Blob { +export class DynamicBytesDataType extends AbstractBlobDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index aee8320a60..6b5c3bf784 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -2,12 +2,13 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; -export class IntDataType extends AbstractDataTypes.Blob { +export class IntDataType extends AbstractBlobDataType { private static readonly _MATCHER = RegExp( '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index de8809edfe..b1cd1377f4 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -2,13 +2,15 @@ import { DataItem, MethodAbi } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { AbstractDataTypes, DataType, DataTypeFactory } from '../abstract_data_types'; +import { DataType } from '../abstract_data_types/data_type'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractSetDataType } from '../abstract_data_types/types/set'; import { constants } from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; import { TupleDataType } from './tuple'; -export class MethodDataType extends AbstractDataTypes.Set { +export class MethodDataType extends AbstractSetDataType { private readonly _methodSignature: string; private readonly _methodSelector: string; private readonly _returnDataType: DataType; diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts index 7ca4287604..389e759271 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts @@ -1,8 +1,10 @@ import { DataItem } from 'ethereum-types'; -import { AbstractDataTypes, DataType, DataTypeFactory } from '../abstract_data_types'; +import { DataType } from '../abstract_data_types/data_type'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractPointerDataType } from '../abstract_data_types/types/pointer'; -export class PointerDataType extends AbstractDataTypes.Pointer { +export class PointerDataType extends AbstractPointerDataType { constructor(destDataType: DataType, parentDataType: DataType, dataTypeFactory: DataTypeFactory) { const destDataItem = destDataType.getDataItem(); const dataItem: DataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` }; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 3b270630ec..28584d4458 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -2,11 +2,12 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class StaticBytesDataType extends AbstractDataTypes.Blob { +export class StaticBytesDataType extends AbstractBlobDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true; private static readonly _MATCHER = RegExp( '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$', diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index d7e9ec7fe8..7b6af747b8 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -2,11 +2,12 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; -export class StringDataType extends AbstractDataTypes.Blob { +export class StringDataType extends AbstractBlobDataType { private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 5ba875b9ec..31593c882a 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -1,8 +1,9 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractSetDataType } from '../abstract_data_types/types/set'; -export class TupleDataType extends AbstractDataTypes.Set { +export class TupleDataType extends AbstractSetDataType { private readonly _signature: string; public static matchType(type: string): boolean { diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index a5989ea116..45cb366f7f 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -2,12 +2,13 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; -import { AbstractDataTypes, DataTypeFactory } from '../abstract_data_types'; +import { DataTypeFactory } from '../abstract_data_types/interfaces'; +import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; import { RawCalldata } from '../calldata'; import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; -export class UIntDataType extends AbstractDataTypes.Blob { +export class UIntDataType extends AbstractBlobDataType { private static readonly _MATCHER = RegExp( '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$', ); From 2da7cadefa877ff824da8fbaecd59dbff5028728 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 13:47:01 -0800 Subject: [PATCH 186/230] Explicit imports for calldata --- .../abi_encoder/abstract_data_types/data_type.ts | 4 +++- .../abi_encoder/abstract_data_types/interfaces.ts | 2 +- .../abi_encoder/abstract_data_types/types/blob.ts | 8 +++++--- .../abstract_data_types/types/pointer.ts | 8 +++++--- .../abi_encoder/abstract_data_types/types/set.ts | 14 ++++++++------ .../utils/src/abi_encoder/calldata/blocks/blob.ts | 2 +- .../utils/src/abi_encoder/calldata/blocks/index.ts | 3 --- .../src/abi_encoder/calldata/blocks/pointer.ts | 10 +++++----- .../utils/src/abi_encoder/calldata/blocks/set.ts | 2 +- .../utils/src/abi_encoder/calldata/calldata.ts | 7 ++++--- packages/utils/src/abi_encoder/calldata/index.ts | 5 ----- .../utils/src/abi_encoder/calldata/iterator.ts | 10 ++++++---- .../src/abi_encoder/evm_data_types/address.ts | 2 +- .../utils/src/abi_encoder/evm_data_types/bool.ts | 2 +- .../abi_encoder/evm_data_types/dynamic_bytes.ts | 2 +- .../utils/src/abi_encoder/evm_data_types/int.ts | 2 +- .../src/abi_encoder/evm_data_types/static_bytes.ts | 2 +- .../utils/src/abi_encoder/evm_data_types/string.ts | 2 +- .../utils/src/abi_encoder/evm_data_types/uint.ts | 2 +- 19 files changed, 46 insertions(+), 43 deletions(-) delete mode 100644 packages/utils/src/abi_encoder/calldata/blocks/index.ts delete mode 100644 packages/utils/src/abi_encoder/calldata/index.ts diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index 10e5c25402..13cc87e2a6 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -1,7 +1,9 @@ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; -import { Calldata, CalldataBlock, RawCalldata } from '../calldata'; +import { Calldata } from '../calldata/calldata'; +import { CalldataBlock } from '../calldata/calldata_block'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; import { DecodingRules, EncodingRules } from '../utils/rules'; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts index bd4d2effd5..2f2f60871e 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts @@ -1,6 +1,6 @@ import { DataItem } from 'ethereum-types'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { DataType } from './data_type'; diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts index cd21196733..a091e55b9d 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts @@ -1,7 +1,9 @@ import { DataItem } from 'ethereum-types'; import * as _ from 'lodash'; -import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; +import { BlobCalldataBlock } from '../../calldata/blocks/blob'; +import { CalldataBlock } from '../../calldata/calldata_block'; +import { RawCalldata } from '../../calldata/raw_calldata'; import { DecodingRules } from '../../utils/rules'; import { DataType } from '../data_type'; @@ -15,12 +17,12 @@ export abstract class AbstractBlobDataType extends DataType { this._sizeKnownAtCompileTime = sizeKnownAtCompileTime; } - public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlocks.Blob { + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): BlobCalldataBlock { const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); - const block = new CalldataBlocks.Blob(name, signature, parentName, encodedValue); + const block = new BlobCalldataBlock(name, signature, parentName, encodedValue); return block; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts index b6a6a7613b..0f3c55280c 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts @@ -2,7 +2,9 @@ import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; +import { PointerCalldataBlock } from '../../calldata/blocks/pointer'; +import { CalldataBlock } from '../../calldata/calldata_block'; +import { RawCalldata } from '../../calldata/raw_calldata'; import { constants } from '../../utils/constants'; import { DecodingRules } from '../../utils/rules'; @@ -19,7 +21,7 @@ export abstract class AbstractPointerDataType extends DataType { this._parent = parent; } - public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlocks.Pointer { + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PointerCalldataBlock { if (_.isUndefined(parentBlock)) { throw new Error(`DependentDataType requires a parent block to generate its block`); } @@ -27,7 +29,7 @@ export abstract class AbstractPointerDataType extends DataType { const name = this.getDataItem().name; const signature = this.getSignature(); const parentName = parentBlock.getName(); - const block = new CalldataBlocks.Pointer(name, signature, parentName, destinationBlock, parentBlock); + const block = new PointerCalldataBlock(name, signature, parentName, destinationBlock, parentBlock); return block; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index 4c8bb7b1a6..bbe29eff09 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -3,7 +3,9 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { BigNumber } from '../../../configured_bignumber'; -import { CalldataBlock, CalldataBlocks, RawCalldata } from '../../calldata'; +import { SetCalldataBlock } from '../../calldata/blocks/set'; +import { CalldataBlock } from '../../calldata/calldata_block'; +import { RawCalldata } from '../../calldata/raw_calldata'; import { constants } from '../../utils/constants'; import { DecodingRules } from '../../utils/rules'; @@ -39,7 +41,7 @@ export abstract class AbstractSetDataType extends DataType { } } - public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): CalldataBlocks.Set { + public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): SetCalldataBlock { const block = value instanceof Array ? this._generateCalldataBlockFromArray(value, parentBlock) @@ -94,7 +96,7 @@ export abstract class AbstractSetDataType extends DataType { return isStatic; } - protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): CalldataBlocks.Set { + protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): SetCalldataBlock { // Sanity check: if the set has a defined length then `value` must have the same length. if (!_.isUndefined(this._arrayLength) && value.length !== this._arrayLength) { throw new Error( @@ -105,7 +107,7 @@ export abstract class AbstractSetDataType extends DataType { } // Create a new calldata block for this set. const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); - const block: CalldataBlocks.Set = new CalldataBlocks.Set( + const block = new SetCalldataBlock( this.getDataItem().name, this.getSignature(), parentName, @@ -130,10 +132,10 @@ export abstract class AbstractSetDataType extends DataType { return block; } - protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): CalldataBlocks.Set { + protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): SetCalldataBlock { // Create a new calldata block for this set. const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); - const block: CalldataBlocks.Set = new CalldataBlocks.Set( + const block = new SetCalldataBlock( this.getDataItem().name, this.getSignature(), parentName, diff --git a/packages/utils/src/abi_encoder/calldata/blocks/blob.ts b/packages/utils/src/abi_encoder/calldata/blocks/blob.ts index 210ef64202..219ea6c612 100644 --- a/packages/utils/src/abi_encoder/calldata/blocks/blob.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/blob.ts @@ -1,6 +1,6 @@ import { CalldataBlock } from '../calldata_block'; -export class Blob extends CalldataBlock { +export class BlobCalldataBlock extends CalldataBlock { private readonly _blob: Buffer; constructor(name: string, signature: string, parentName: string, blob: Buffer) { diff --git a/packages/utils/src/abi_encoder/calldata/blocks/index.ts b/packages/utils/src/abi_encoder/calldata/blocks/index.ts deleted file mode 100644 index 958582dae4..0000000000 --- a/packages/utils/src/abi_encoder/calldata/blocks/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './blob'; -export * from './pointer'; -export * from './set'; diff --git a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts index 1daf33f7e3..c706fe908f 100644 --- a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts @@ -5,7 +5,7 @@ import { constants } from '../../utils/constants'; import { CalldataBlock } from '../calldata_block'; -export class Pointer extends CalldataBlock { +export class PointerCalldataBlock extends CalldataBlock { public static readonly RAW_DATA_START = new Buffer('<'); public static readonly RAW_DATA_END = new Buffer('>'); private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32; @@ -15,8 +15,8 @@ export class Pointer extends CalldataBlock { private _aliasFor: CalldataBlock | undefined; constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) { - const headerSizeInBytes = Pointer._EMPTY_HEADER_SIZE; - const bodySizeInBytes = Pointer._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; + const headerSizeInBytes = PointerCalldataBlock._EMPTY_HEADER_SIZE; + const bodySizeInBytes = PointerCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES; super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes); this._parent = parent; this._dependency = dependency; @@ -51,9 +51,9 @@ export class Pointer extends CalldataBlock { public getRawData(): Buffer { const dependencyRawData = this._dependency.getRawData(); const rawDataComponents: Buffer[] = []; - rawDataComponents.push(Pointer.RAW_DATA_START); + rawDataComponents.push(PointerCalldataBlock.RAW_DATA_START); rawDataComponents.push(dependencyRawData); - rawDataComponents.push(Pointer.RAW_DATA_END); + rawDataComponents.push(PointerCalldataBlock.RAW_DATA_END); const rawData = Buffer.concat(rawDataComponents); return rawData; } diff --git a/packages/utils/src/abi_encoder/calldata/blocks/set.ts b/packages/utils/src/abi_encoder/calldata/blocks/set.ts index 81455b3642..d1abc4986e 100644 --- a/packages/utils/src/abi_encoder/calldata/blocks/set.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/set.ts @@ -2,7 +2,7 @@ import * as _ from 'lodash'; import { CalldataBlock } from '../calldata_block'; -export class Set extends CalldataBlock { +export class SetCalldataBlock extends CalldataBlock { private _header: Buffer | undefined; private _members: CalldataBlock[]; diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts index e5858b5249..5f3eee94a6 100644 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ b/packages/utils/src/abi_encoder/calldata/calldata.ts @@ -4,7 +4,8 @@ import * as _ from 'lodash'; import { constants } from '../utils/constants'; import { EncodingRules } from '../utils/rules'; -import * as CalldataBlocks from './blocks'; +import { PointerCalldataBlock } from './blocks/pointer'; +import { SetCalldataBlock } from './blocks/set'; import { CalldataBlock } from './calldata_block'; import { CalldataIterator, ReverseCalldataIterator } from './iterator'; @@ -112,7 +113,7 @@ export class Calldata { for (const block of iterator) { // If a block is a pointer and its value has already been observed, then update // the pointer to resolve to the existing value. - if (block instanceof CalldataBlocks.Pointer) { + if (block instanceof PointerCalldataBlock) { const dependencyBlockHashBuf = block.getDependency().computeHash(); const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf); if (dependencyBlockHash in blocksByHash) { @@ -214,7 +215,7 @@ export class Calldata { ), ) .padEnd(valuePadding); - if (block instanceof CalldataBlocks.Set) { + if (block instanceof SetCalldataBlock) { nameStr = `### ${prettyName.padEnd(namePadding)}`; lineStr = `\n${offsetStr}${valueStr}${nameStr}`; } else { diff --git a/packages/utils/src/abi_encoder/calldata/index.ts b/packages/utils/src/abi_encoder/calldata/index.ts deleted file mode 100644 index 2ef75e8d00..0000000000 --- a/packages/utils/src/abi_encoder/calldata/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './calldata'; -export * from './calldata_block'; -export * from './raw_calldata'; -import * as CalldataBlocks from './blocks'; -export { CalldataBlocks }; diff --git a/packages/utils/src/abi_encoder/calldata/iterator.ts b/packages/utils/src/abi_encoder/calldata/iterator.ts index 5307f7944b..333b32b4fc 100644 --- a/packages/utils/src/abi_encoder/calldata/iterator.ts +++ b/packages/utils/src/abi_encoder/calldata/iterator.ts @@ -3,7 +3,9 @@ import * as _ from 'lodash'; import { Queue } from '../utils/queue'; -import * as CalldataBlocks from './blocks'; +import { BlobCalldataBlock } from './blocks/blob'; +import { PointerCalldataBlock } from './blocks/pointer'; +import { SetCalldataBlock } from './blocks/set'; import { CalldataBlock } from './calldata_block'; /** @@ -42,7 +44,7 @@ abstract class BaseIterator implements Iterable { private static _createQueue(block: CalldataBlock): Queue { const queue = new Queue(); // Base case - if (!(block instanceof CalldataBlocks.Set)) { + if (!(block instanceof SetCalldataBlock)) { queue.pushBack(block); return queue; } @@ -55,7 +57,7 @@ abstract class BaseIterator implements Iterable { _.each(set.getMembers(), (member: CalldataBlock) => { // Traverse child if it is a unique pointer. // A pointer that is an alias for another pointer is ignored. - if (member instanceof CalldataBlocks.Pointer && _.isUndefined(member.getAlias())) { + if (member instanceof PointerCalldataBlock && _.isUndefined(member.getAlias())) { const dependency = member.getDependency(); queue.mergeBack(BaseIterator._createQueue(dependency)); } @@ -82,7 +84,7 @@ abstract class BaseIterator implements Iterable { } return { done: true, - value: new CalldataBlocks.Blob('', '', '', new Buffer('')), + value: new BlobCalldataBlock('', '', '', new Buffer('')), }; }, }; diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 17363b5f36..88846b1fa4 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; export class AddressDataType extends AbstractBlobDataType { diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index 778a01d8a3..d713d5a942 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -5,7 +5,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; export class BoolDataType extends AbstractBlobDataType { diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index ac8e2b716e..5277efd6cb 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; export class DynamicBytesDataType extends AbstractBlobDataType { diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index 6b5c3bf784..f1dcf5ea15 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 28584d4458..2e371c5058 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; export class StaticBytesDataType extends AbstractBlobDataType { diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 7b6af747b8..91a72ad3fa 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; export class StringDataType extends AbstractBlobDataType { diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index 45cb366f7f..5180f0cf31 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { BigNumber } from '../../configured_bignumber'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractBlobDataType } from '../abstract_data_types/types/blob'; -import { RawCalldata } from '../calldata'; +import { RawCalldata } from '../calldata/raw_calldata'; import { constants } from '../utils/constants'; import * as EncoderMath from '../utils/math'; From 04503200e5eafda6617039b113ef26cf48184f62 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 13:52:32 -0800 Subject: [PATCH 187/230] Linter / Prettier --- .../abstract_data_types/types/set.ts | 18 +++++++----------- .../src/abi_encoder/calldata/blocks/pointer.ts | 5 +++-- .../utils/src/abi_encoder/utils/constants.ts | 3 +++ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index bbe29eff09..089d046596 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -107,11 +107,7 @@ export abstract class AbstractSetDataType extends DataType { } // Create a new calldata block for this set. const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); - const block = new SetCalldataBlock( - this.getDataItem().name, - this.getSignature(), - parentName, - ); + const block = new SetCalldataBlock(this.getDataItem().name, this.getSignature(), parentName); // If this set has an undefined length then set its header to be the number of elements. let members = this._members; if (this._isArray && _.isUndefined(this._arrayLength)) { @@ -135,11 +131,7 @@ export abstract class AbstractSetDataType extends DataType { protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): SetCalldataBlock { // Create a new calldata block for this set. const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName(); - const block = new SetCalldataBlock( - this.getDataItem().name, - this.getSignature(), - parentName, - ); + const block = new SetCalldataBlock(this.getDataItem().name, this.getSignature(), parentName); // Create blocks for members of set. const memberCalldataBlocks: CalldataBlock[] = []; const childMap = _.cloneDeep(this._memberIndexByName); @@ -178,7 +170,11 @@ export abstract class AbstractSetDataType extends DataType { private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberIndexByName] { // Sanity check if (_.isUndefined(dataItem.components)) { - throw new Error(`Tried to create a set using key/value pairs, but no components were defined by the input DataItem '${dataItem.name}'.`); + throw new Error( + `Tried to create a set using key/value pairs, but no components were defined by the input DataItem '${ + dataItem.name + }'.`, + ); } // Create one member for each component of `dataItem` const members: DataType[] = []; diff --git a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts index c706fe908f..72d6a31730 100644 --- a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts +++ b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts @@ -24,8 +24,9 @@ export class PointerCalldataBlock extends CalldataBlock { } public toBuffer(): Buffer { - const destinationOffset = - !_.isUndefined(this._aliasFor) ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes(); + const destinationOffset = !_.isUndefined(this._aliasFor) + ? this._aliasFor.getOffsetInBytes() + : this._dependency.getOffsetInBytes(); const parentOffset = this._parent.getOffsetInBytes(); const parentHeaderSize = this._parent.getHeaderSizeInBytes(); const pointer: number = destinationOffset - (parentOffset + parentHeaderSize); diff --git a/packages/utils/src/abi_encoder/utils/constants.ts b/packages/utils/src/abi_encoder/utils/constants.ts index acc06329cb..2f43ba04d9 100644 --- a/packages/utils/src/abi_encoder/utils/constants.ts +++ b/packages/utils/src/abi_encoder/utils/constants.ts @@ -9,6 +9,9 @@ export const constants = { HEX_SELECTOR_LENGTH_IN_CHARS: 10, HEX_SELECTOR_LENGTH_IN_BYTES: 4, HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA: 0, + // Disable no-object-literal-type-assertion so we can enforce cast + /* tslint:disable no-object-literal-type-assertion */ DEFAULT_DECODING_RULES: { structsAsObjects: false } as DecodingRules, DEFAULT_ENCODING_RULES: { optimize: true, annotate: false } as EncodingRules, + /* tslint:enable no-object-literal-type-assertion */ }; From b55f7aef7f508debf190d6a09b81023493925481 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 16:38:21 -0800 Subject: [PATCH 188/230] feat: add URL to other parts of README --- packages/instant/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/README.md b/packages/instant/README.md index a38e4f85cb..fece84d2aa 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -2,11 +2,11 @@ ## Installation -The package is available as a UMD module named `zeroExInstant`. +The package is available as a UMD module named `zeroExInstant` at https://instant.0xproject.com/instant.js. ```html - +
From 42e83ae643bbcb04d9c9ccf4c9eca476d055f47a Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 28 Nov 2018 16:39:27 -0800 Subject: [PATCH 189/230] fix: add clarification to NOTE in README --- packages/instant/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/README.md b/packages/instant/README.md index fece84d2aa..c6621cbed3 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -44,7 +44,7 @@ To build and deploy to this bundle, run yarn deploy_live ``` -**NOTE: On deploying the site, it will say the site is available at a non-existent URL. Please ignore and use the (now updated) URL above.** +**NOTE: On deploying the site to staging and dogfood, it will say the site is available at a non-existent URL. Please ignore and use the (now updated) URL above.** ## Contributing From b15531fe683df906b4988e63bbca2b3ebfbfc6b2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 15:50:36 -0800 Subject: [PATCH 190/230] Changelog for ABI Encoder --- packages/utils/CHANGELOG.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 8c6fb124f4..bbaf67062a 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1543448882, + "version": "2.0.7", + "changes": [ + { + "note": "Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development." + } + ] + }, { "timestamp": 1542821676, "version": "2.0.6", From bcb2af2861952c91ea4fc02462f52d8bc37bac5d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 17:30:02 -0800 Subject: [PATCH 191/230] Ran prettier --- packages/utils/CHANGELOG.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index bbaf67062a..08801a8916 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -4,7 +4,8 @@ "version": "2.0.7", "changes": [ { - "note": "Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development." + "note": + "Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development." } ] }, From 5c66f9117fa0bf6a5be29243dbecbfe016b95345 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 29 Nov 2018 08:19:39 -0800 Subject: [PATCH 192/230] assetAmount -> assetBuyAmount --- packages/instant/src/util/analytics.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 1468ef4a8f..4b8aff4c9b 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -206,10 +206,10 @@ export const analytics = { ...buyQuoteEventProperties(buyQuote), fetchOrigin, }), - trackQuoteError: (errorMessage: string, assetAmount: BigNumber, fetchOrigin: QuoteFetchOrigin) => { + trackQuoteError: (errorMessage: string, assetBuyAmount: BigNumber, fetchOrigin: QuoteFetchOrigin) => { trackingEventFnWithPayload(EventNames.QUOTE_ERROR)({ errorMessage, - assetAmount: assetAmount.toString(), + assetBuyAmount: assetBuyAmount.toString(), fetchOrigin, }); }, From b68273e592dc7736d21608e2543ba6bffdb03db2 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 29 Nov 2018 17:30:21 +0000 Subject: [PATCH 193/230] Fix import export so that it works with doc gen --- packages/utils/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index f59cbec8c4..082aff6bbe 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -10,5 +10,4 @@ export { NULL_BYTES } from './constants'; export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; -import * as AbiEncoder from './abi_encoder'; -export { AbiEncoder }; +export import AbiEncoder = require('./abi_encoder'); From 2b87d20290084a4bc547a0bfa1222e2152037988 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 29 Nov 2018 09:55:30 -0800 Subject: [PATCH 194/230] Set .env in builds, and update profile to use 0xproject --- packages/instant/.dogfood.discharge.json | 4 ++-- packages/instant/.production.discharge.json | 4 ++-- packages/instant/.staging.discharge.json | 4 ++-- packages/instant/package.json | 7 ++----- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json index ca36b3861d..6e669ebaad 100644 --- a/packages/instant/.dogfood.discharge.json +++ b/packages/instant/.dogfood.discharge.json @@ -1,12 +1,12 @@ { "domain": "0x-instant-dogfood", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.dogfood", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", "trailing_slashes": true, "cache": 3600, - "aws_profile": "default", + "aws_profile": "0xproject", "aws_region": "us-east-1", "cdn": false, "dns_configured": true diff --git a/packages/instant/.production.discharge.json b/packages/instant/.production.discharge.json index 447fa1756f..b2369d43ee 100644 --- a/packages/instant/.production.discharge.json +++ b/packages/instant/.production.discharge.json @@ -1,12 +1,12 @@ { "domain": "instant.0xproject.com", - "build_command": "yarn build", + "build_command": "yarn build --env.production_cdn", "upload_directory": "umd", "index_key": "instant.js", "error_key": "404.html", "trailing_slashes": true, "cache": 3600, - "aws_profile": "default", + "aws_profile": "0xproject", "aws_region": "us-east-1", "cdn": true, "dns_configured": true diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json index c917a650b6..6df436f731 100644 --- a/packages/instant/.staging.discharge.json +++ b/packages/instant/.staging.discharge.json @@ -1,12 +1,12 @@ { "domain": "0x-instant-staging", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod --env.staging", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", "trailing_slashes": true, "cache": 3600, - "aws_profile": "default", + "aws_profile": "0xproject", "aws_region": "us-east-1", "cdn": false, "dns_configured": true diff --git a/packages/instant/package.json b/packages/instant/package.json index 427d5984e5..bc46aa7327 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -8,7 +8,7 @@ "main": "umd/instant.js", "private": true, "scripts": { - "build": "webpack --mode production", + "build": "webpack --mode production --env.production_standalone", "build:ci": "yarn build", "watch_without_deps": "tsc -w", "dev": "webpack-dev-server --mode development", @@ -25,10 +25,7 @@ }, "config": { "postpublish": { - "assets": [ - "packages/instant/umd/instant.js", - "packages/instant/umd/instant.js.map" - ] + "assets": ["packages/instant/umd/instant.js", "packages/instant/umd/instant.js.map"] } }, "repository": { From bd03df8af02169ef88207eb14051bc79231927aa Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 29 Nov 2018 12:05:20 -0800 Subject: [PATCH 195/230] feat(instant): Cleaner config-specific setup --- packages/instant/.dogfood.discharge.json | 2 +- packages/instant/.production.discharge.json | 2 +- packages/instant/.staging.discharge.json | 2 +- packages/instant/package.json | 7 +- packages/instant/src/constants.ts | 6 ++ packages/instant/webpack.config.js | 81 +++++++++++++++------ 6 files changed, 73 insertions(+), 27 deletions(-) diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json index 6e669ebaad..5a11f8a1da 100644 --- a/packages/instant/.dogfood.discharge.json +++ b/packages/instant/.dogfood.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-dogfood", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.dogfood", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod --env.dogfood", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/.production.discharge.json b/packages/instant/.production.discharge.json index b2369d43ee..ecd3a12d44 100644 --- a/packages/instant/.production.discharge.json +++ b/packages/instant/.production.discharge.json @@ -1,6 +1,6 @@ { "domain": "instant.0xproject.com", - "build_command": "yarn build --env.production_cdn", + "build_command": "yarn build:prod --env.production_cdn", "upload_directory": "umd", "index_key": "instant.js", "error_key": "404.html", diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json index 6df436f731..ba2ca5f99c 100644 --- a/packages/instant/.staging.discharge.json +++ b/packages/instant/.staging.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-staging", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod --env.staging", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod:prod --env.staging", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/package.json b/packages/instant/package.json index bc46aa7327..964e5c9b36 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -8,10 +8,11 @@ "main": "umd/instant.js", "private": true, "scripts": { - "build": "webpack --mode production --env.production_standalone", - "build:ci": "yarn build", + "build": "yarn build:prod --env.production_standalone", + "build:prod": "webpack --mode production", + "build:ci": "build", "watch_without_deps": "tsc -w", - "dev": "webpack-dev-server --mode development", + "dev": "webpack-dev-server --mode development --env.development", "lint": "tslint --format stylish --project .", "test": "jest", "test:coverage": "jest --coverage", diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index be6077ca92..e70b3f1d86 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -20,6 +20,12 @@ export const HEAP_ANALYTICS_ID = process.env.HEAP_ANALYTICS_ID; export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; export const PROGRESS_STALL_AT_WIDTH = '95%'; export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; +export const INSTANT_ENVIRONMENT = process.env.INSTANT_ENVIRONMENT as + | 'dogfood' + | 'staging' + | 'development' + | 'production_cdn' + | 'production_standalone'; export const COINBASE_WALLET_IOS_APP_STORE_URL = 'https://itunes.apple.com/us/app/coinbase-wallet/id1278383455?mt=8'; export const COINBASE_WALLET_ANDROID_APP_STORE_URL = 'https://play.google.com/store/apps/details?id=org.toshi&hl=en'; export const COINBASE_WALLET_SITE_URL = 'https://wallet.coinbase.com/'; diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 41276809c9..284276c1a5 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -6,27 +6,64 @@ const webpack = require('webpack'); // The common js bundle (not this one) is built using tsc. // The umd bundle (this one) has a different entrypoint. +const ACCEPTABLE_ENV_NAMES = ['production_standalone', 'production_cdn', 'staging', 'dogfood', 'development']; +const getEnvironmentName = env => { + if (!env) { + throw new Error('Please specify env via --env to webpack'); + } + const foundName = ACCEPTABLE_ENV_NAMES.find(e => (env[e] ? e : false)); + if (!foundName) { + throw new Error( + `Couldn't find env name, please specify via one of the following CLI arguments: ${acceptableEnvNames.map( + i => `--env.${i}`, + )}`, + ); + } + return foundName; +}; + +const getConfigForEnv = environmentName => { + switch (environmentName) { + case 'production_standalone': + case 'production_cdn': + return { + heapAnalyticsIdEnvName: 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION', + heapAnalyticsIdRequired: environmentName !== 'production_standalone', + }; + case 'staging': + case 'dogfood': + case 'development': + return { + heapAnalyticsIdEnvName: 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT', + heapAnalyticsIdRequired: environmentName !== 'development', + }; + } +}; + const GIT_SHA = childProcess .execSync('git rev-parse HEAD') .toString() .trim(); - -const HEAP_PRODUCTION_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION'; -const HEAP_DEVELOPMENT_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT'; -const getHeapAnalyticsId = modeName => { - if (modeName === 'production') { - return process.env[HEAP_PRODUCTION_ENV_VAR_NAME]; - } - - if (modeName === 'development') { - return process.env[HEAP_DEVELOPMENT_ENV_VAR_NAME]; - } - - return undefined; -}; - -module.exports = (env, argv) => { +const generateConfig = (environmentName, configOptions) => { const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; + + const { heapAnalyticsIdEnvName, heapAnalyticsIdRequired } = configOptions; + const heapAnalyticsId = process.env[heapAnalyticsIdEnvName]; + if (heapAnalyticsIdRequired && !heapAnalyticsId) { + throw new Error( + `Must define heap analytics id in ENV var ${heapAnalyticsIdEnvName} when building for ${environmentName}`, + ); + } + + const envVars = { + GIT_SHA: JSON.stringify(GIT_SHA), + INSTANT_ENVIRONMENT: JSON.stringify(environmentName), + NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), + }; + if (heapAnalyticsId) { + envVars.HEAP_ANALYTICS_ID = JSON.stringify(heapAnalyticsId); + } + const config = { entry: { instant: './src/index.umd.ts', @@ -39,11 +76,7 @@ module.exports = (env, argv) => { }, plugins: [ new webpack.DefinePlugin({ - 'process.env': { - GIT_SHA: JSON.stringify(GIT_SHA), - HEAP_ANALYTICS_ID: getHeapAnalyticsId(argv.mode), - NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), - }, + 'process.env': envVars, }), ], devtool: 'source-map', @@ -79,3 +112,9 @@ module.exports = (env, argv) => { }; return config; }; + +module.exports = (env, _argv) => { + const environmentName = getEnvironmentName(env); + const configOptions = getConfigForEnv(environmentName); + return generateConfig(environmentName, configOptions); +}; From 4616c51e199b3e5c37f8b5f23788645632f2faf6 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 29 Nov 2018 14:36:05 -0800 Subject: [PATCH 196/230] Fix build command --- packages/instant/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/package.json b/packages/instant/package.json index 964e5c9b36..068900b481 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "yarn build:prod --env.production_standalone", "build:prod": "webpack --mode production", - "build:ci": "build", + "build:ci": "yarn build", "watch_without_deps": "tsc -w", "dev": "webpack-dev-server --mode development --env.development", "lint": "tslint --format stylish --project .", From d99bdcf036eebc6613274fecfdd19187eaf4aa1a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 29 Nov 2018 15:13:55 -0800 Subject: [PATCH 197/230] cdn approach to identifyign environments --- packages/instant/.dogfood.discharge.json | 2 +- packages/instant/.production.discharge.json | 2 +- packages/instant/.staging.discharge.json | 2 +- packages/instant/package.json | 3 +- packages/instant/src/constants.ts | 7 +-- packages/instant/webpack.config.js | 53 ++++++--------------- 6 files changed, 20 insertions(+), 49 deletions(-) diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json index 5a11f8a1da..f9b96ac954 100644 --- a/packages/instant/.dogfood.discharge.json +++ b/packages/instant/.dogfood.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-dogfood", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod --env.dogfood", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod --env.cdn=staging", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/.production.discharge.json b/packages/instant/.production.discharge.json index ecd3a12d44..70dd4e04c7 100644 --- a/packages/instant/.production.discharge.json +++ b/packages/instant/.production.discharge.json @@ -1,6 +1,6 @@ { "domain": "instant.0xproject.com", - "build_command": "yarn build:prod --env.production_cdn", + "build_command": "yarn build --env.cdn=production", "upload_directory": "umd", "index_key": "instant.js", "error_key": "404.html", diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json index ba2ca5f99c..44fc3abc8e 100644 --- a/packages/instant/.staging.discharge.json +++ b/packages/instant/.staging.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-staging", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod:prod --env.staging", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.cdn=staging", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/package.json b/packages/instant/package.json index 068900b481..381d78c885 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -8,8 +8,7 @@ "main": "umd/instant.js", "private": true, "scripts": { - "build": "yarn build:prod --env.production_standalone", - "build:prod": "webpack --mode production", + "build": "webpack --mode production", "build:ci": "yarn build", "watch_without_deps": "tsc -w", "dev": "webpack-dev-server --mode development --env.development", diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index e70b3f1d86..3370af0949 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -20,12 +20,7 @@ export const HEAP_ANALYTICS_ID = process.env.HEAP_ANALYTICS_ID; export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; export const PROGRESS_STALL_AT_WIDTH = '95%'; export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; -export const INSTANT_ENVIRONMENT = process.env.INSTANT_ENVIRONMENT as - | 'dogfood' - | 'staging' - | 'development' - | 'production_cdn' - | 'production_standalone'; +export const INSTANT_CDN = process.env.INSTANT_CDN as 'production' | 'dogfood' | 'staging' | undefined; export const COINBASE_WALLET_IOS_APP_STORE_URL = 'https://itunes.apple.com/us/app/coinbase-wallet/id1278383455?mt=8'; export const COINBASE_WALLET_ANDROID_APP_STORE_URL = 'https://play.google.com/store/apps/details?id=org.toshi&hl=en'; export const COINBASE_WALLET_SITE_URL = 'https://wallet.coinbase.com/'; diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 284276c1a5..020fab5068 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -6,60 +6,37 @@ const webpack = require('webpack'); // The common js bundle (not this one) is built using tsc. // The umd bundle (this one) has a different entrypoint. -const ACCEPTABLE_ENV_NAMES = ['production_standalone', 'production_cdn', 'staging', 'dogfood', 'development']; -const getEnvironmentName = env => { - if (!env) { - throw new Error('Please specify env via --env to webpack'); - } - const foundName = ACCEPTABLE_ENV_NAMES.find(e => (env[e] ? e : false)); - if (!foundName) { - throw new Error( - `Couldn't find env name, please specify via one of the following CLI arguments: ${acceptableEnvNames.map( - i => `--env.${i}`, - )}`, - ); - } - return foundName; -}; - -const getConfigForEnv = environmentName => { - switch (environmentName) { - case 'production_standalone': - case 'production_cdn': - return { - heapAnalyticsIdEnvName: 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION', - heapAnalyticsIdRequired: environmentName !== 'production_standalone', - }; - case 'staging': - case 'dogfood': - case 'development': - return { - heapAnalyticsIdEnvName: 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT', - heapAnalyticsIdRequired: environmentName !== 'development', - }; - } +const CDNS_THAT_REQUIRE_HEAP = ['production', 'staging', 'dogfood']; +const getConfigForCdn = cdnName => { + return { + heapAnalyticsIdEnvName: + cdnName === 'production' ? 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION' : 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT', + heapAnalyticsIdRequired: CDNS_THAT_REQUIRE_HEAP.includes(cdnName), + }; }; const GIT_SHA = childProcess .execSync('git rev-parse HEAD') .toString() .trim(); -const generateConfig = (environmentName, configOptions) => { +const generateConfig = (cdnName, configOptions) => { const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; const { heapAnalyticsIdEnvName, heapAnalyticsIdRequired } = configOptions; const heapAnalyticsId = process.env[heapAnalyticsIdEnvName]; if (heapAnalyticsIdRequired && !heapAnalyticsId) { throw new Error( - `Must define heap analytics id in ENV var ${heapAnalyticsIdEnvName} when building for ${environmentName}`, + `Must define heap analytics id in ENV var ${heapAnalyticsIdEnvName} when building for ${cdnName}`, ); } const envVars = { GIT_SHA: JSON.stringify(GIT_SHA), - INSTANT_ENVIRONMENT: JSON.stringify(environmentName), NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), }; + if (cdnName) { + envVars.INSTANT_CDN = JSON.stringify(cdnName); + } if (heapAnalyticsId) { envVars.HEAP_ANALYTICS_ID = JSON.stringify(heapAnalyticsId); } @@ -114,7 +91,7 @@ const generateConfig = (environmentName, configOptions) => { }; module.exports = (env, _argv) => { - const environmentName = getEnvironmentName(env); - const configOptions = getConfigForEnv(environmentName); - return generateConfig(environmentName, configOptions); + const cdnName = env ? env.cdn : undefined; + const configOptions = getConfigForCdn(cdnName); + return generateConfig(cdnName, configOptions); }; From e59d47eac82d2db85b301b24aad4c1e41246daa8 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 29 Nov 2018 15:32:05 -0800 Subject: [PATCH 198/230] Take out old --env --- packages/instant/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/package.json b/packages/instant/package.json index 381d78c885..511edff3dd 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -11,7 +11,7 @@ "build": "webpack --mode production", "build:ci": "yarn build", "watch_without_deps": "tsc -w", - "dev": "webpack-dev-server --mode development --env.development", + "dev": "webpack-dev-server --mode development", "lint": "tslint --format stylish --project .", "test": "jest", "test:coverage": "jest --coverage", From 6c941eebeae4828177b0d195168f5f1b757432e2 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 08:40:25 -0800 Subject: [PATCH 199/230] add missing import from merge --- packages/instant/src/util/analytics.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 2f76c4a764..714ff8bcdf 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -6,6 +6,7 @@ import { AffiliateInfo, Asset, Network, + OrderProcessState, OrderSource, ProviderState, QuoteFetchOrigin, From a1d4aa66bc6b3de041ec6e4eb4fe40383945510b Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Fri, 30 Nov 2018 09:59:37 -0800 Subject: [PATCH 200/230] feat(order_utils.py): schema resolver cache (#1317) * Implemented basic functionality for using cache layer of LocalRefResolver * Use `importlib` instead of `imp`, since it's been deprecated. Legacy `load_module()` reloads modules even if they are already imported, causing tests to fail when run in non-deterministic ordering, so we replace it with `import_module()` --- .../src/zero_ex/json_schemas/__init__.py | 81 +++++++++++-------- .../order_utils/stubs/jsonschema/__init__.pyi | 10 ++- .../order_utils/test/test_doctest.py | 5 +- .../order_utils/test/test_json_schemas.py | 23 ++++++ 4 files changed, 81 insertions(+), 38 deletions(-) create mode 100644 python-packages/order_utils/test/test_json_schemas.py diff --git a/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py b/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py index 2a1728b8af..a76a2fa3b2 100644 --- a/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py +++ b/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py @@ -8,6 +8,51 @@ from pkg_resources import resource_string import jsonschema +class _LocalRefResolver(jsonschema.RefResolver): + """Resolve package-local JSON schema id's.""" + + def __init__(self): + """Initialize a new instance.""" + self.ref_to_file = { + "/addressSchema": "address_schema.json", + "/hexSchema": "hex_schema.json", + "/orderSchema": "order_schema.json", + "/wholeNumberSchema": "whole_number_schema.json", + "/ECSignature": "ec_signature_schema.json", + "/signedOrderSchema": "signed_order_schema.json", + "/ecSignatureParameterSchema": ( + "ec_signature_parameter_schema.json" + "" + ), + } + jsonschema.RefResolver.__init__(self, "", "") + + def resolve_from_url(self, url: str) -> str: + """Resolve the given URL. + + :param url: a string representing the URL of the JSON schema to fetch. + :returns: a string representing the deserialized JSON schema + :raises jsonschema.ValidationError: when the resource associated with + `url` does not exist. + """ + ref = url.replace("file://", "") + if ref in self.ref_to_file: + return json.loads( + resource_string( + "zero_ex.json_schemas", f"schemas/{self.ref_to_file[ref]}" + ) + ) + raise jsonschema.ValidationError( + f"Unknown ref '{ref}'. " + + f"Known refs: {list(self.ref_to_file.keys())}." + ) + + +# Instantiate the `_LocalRefResolver()` only once so that `assert_valid()` can +# perform multiple schema validations without reading from disk the schema +# every time. +_LOCAL_RESOLVER = _LocalRefResolver() + + def assert_valid(data: Mapping, schema_id: str) -> None: """Validate the given `data` against the specified `schema`. @@ -24,38 +69,6 @@ def assert_valid(data: Mapping, schema_id: str) -> None: ... ) """ # noqa - class LocalRefResolver(jsonschema.RefResolver): - """Resolve package-local JSON schema id's.""" - def __init__(self): - self.ref_to_file = { - "/addressSchema": "address_schema.json", - "/hexSchema": "hex_schema.json", - "/orderSchema": "order_schema.json", - "/wholeNumberSchema": "whole_number_schema.json", - "/ECSignature": "ec_signature_schema.json", - "/ecSignatureParameterSchema": ( - "ec_signature_parameter_schema.json" + "" - ), - } - jsonschema.RefResolver.__init__(self, "", "") - - def resolve_from_url(self, url): - """Resolve the given URL.""" - ref = url.replace("file://", "") - if ref in self.ref_to_file: - return json.loads( - resource_string( - "zero_ex.json_schemas", - f"schemas/{self.ref_to_file[ref]}", - ) - ) - raise jsonschema.ValidationError( - f"Unknown ref '{ref}'. " - + f"Known refs: {list(self.ref_to_file.keys())}." - ) - - resolver = LocalRefResolver() - jsonschema.validate( - data, resolver.resolve_from_url(schema_id), resolver=resolver - ) + _, schema = _LOCAL_RESOLVER.resolve(schema_id) + jsonschema.validate(data, schema, resolver=_LOCAL_RESOLVER) diff --git a/python-packages/order_utils/stubs/jsonschema/__init__.pyi b/python-packages/order_utils/stubs/jsonschema/__init__.pyi index 762b58b224..442e2f65e5 100644 --- a/python-packages/order_utils/stubs/jsonschema/__init__.pyi +++ b/python-packages/order_utils/stubs/jsonschema/__init__.pyi @@ -1,5 +1,11 @@ -from typing import Any, Dict +from typing import Any, Dict, Tuple -class RefResolver: pass + +class RefResolver: + def resolve(self, url: str) -> Tuple[str, Dict]: + ... + + +class ValidationError(Exception): pass def validate(instance: Any, schema: Dict, cls=None, *args, **kwargs) -> None: pass diff --git a/python-packages/order_utils/test/test_doctest.py b/python-packages/order_utils/test/test_doctest.py index f692b3b6cf..297f75e756 100644 --- a/python-packages/order_utils/test/test_doctest.py +++ b/python-packages/order_utils/test/test_doctest.py @@ -2,16 +2,17 @@ from doctest import testmod import pkgutil +import importlib import zero_ex def test_all_doctests(): """Gather zero_ex.* modules and doctest them.""" - for (importer, modname, _) in pkgutil.walk_packages( + for (_, modname, _) in pkgutil.walk_packages( path=zero_ex.__path__, prefix="zero_ex." ): - module = importer.find_module(modname).load_module(modname) + module = importlib.import_module(modname) print(module) (failure_count, _) = testmod(module) assert failure_count == 0 diff --git a/python-packages/order_utils/test/test_json_schemas.py b/python-packages/order_utils/test/test_json_schemas.py new file mode 100644 index 0000000000..51cecbd4f5 --- /dev/null +++ b/python-packages/order_utils/test/test_json_schemas.py @@ -0,0 +1,23 @@ +"""Tests of zero_ex.json_schemas""" + + +from zero_ex.order_utils import make_empty_order +from zero_ex.json_schemas import _LOCAL_RESOLVER, assert_valid + + +def test_assert_valid_caches_resources(): + """Test that the JSON ref resolver in `assert_valid()` caches resources + + In order to test the cache we much access the private class of + `json_schemas` and reset the LRU cache on `_LocalRefResolver`. + For this to happen, we need to disable errror `W0212` + on _LOCAL_RESOLVER + """ + _LOCAL_RESOLVER._remote_cache.cache_clear() # pylint: disable=W0212 + + assert_valid(make_empty_order(), "/orderSchema") + cache_info = ( + _LOCAL_RESOLVER._remote_cache.cache_info() # pylint: disable=W0212 + ) + assert cache_info.currsize == 4 + assert cache_info.hits == 10 From 09813cb1d83da4c571cf69a448c35c5f6af94dec Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 30 Nov 2018 11:20:07 -0800 Subject: [PATCH 201/230] fix: address PR feedback --- packages/instant/.dogfood.discharge.json | 2 +- packages/instant/.staging.discharge.json | 2 +- packages/instant/package.json | 1 - packages/instant/src/index.umd.ts | 4 ++-- packages/instant/webpack.config.js | 3 --- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json index ca36b3861d..85a48044bc 100644 --- a/packages/instant/.dogfood.discharge.json +++ b/packages/instant/.dogfood.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-dogfood", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json index c917a650b6..93fd94f40e 100644 --- a/packages/instant/.staging.discharge.json +++ b/packages/instant/.staging.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-staging", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:umd:prod", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/package.json b/packages/instant/package.json index 427d5984e5..4c26d2dae2 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -10,7 +10,6 @@ "scripts": { "build": "webpack --mode production", "build:ci": "yarn build", - "watch_without_deps": "tsc -w", "dev": "webpack-dev-server --mode development", "lint": "tslint --format stylish --project .", "test": "jest", diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 45369d9ee4..95080f829d 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -112,5 +112,5 @@ export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_Z }; // Write version info to the exported object for debugging -export const GitSha = process.env.GIT_SHA; -export const NpmVersion = process.env.NPM_PACKAGE_VERSION; +export const GIT_SHA = process.env.GIT_SHA; +export const NPM_VERSION = process.env.NPM_PACKAGE_VERSION; diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 41276809c9..2a517bb596 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -3,9 +3,6 @@ const ip = require('ip'); const path = require('path'); const webpack = require('webpack'); -// The common js bundle (not this one) is built using tsc. -// The umd bundle (this one) has a different entrypoint. - const GIT_SHA = childProcess .execSync('git rev-parse HEAD') .toString() From a7fc5975c1fe1d82d9250f0849e17b4764260176 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 30 Nov 2018 11:28:26 -0800 Subject: [PATCH 202/230] feat: update tsconfig --- packages/instant/tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/instant/tsconfig.json b/packages/instant/tsconfig.json index 14b0ad8f79..21bd3b8217 100644 --- a/packages/instant/tsconfig.json +++ b/packages/instant/tsconfig.json @@ -7,6 +7,5 @@ "noImplicitAny": true, "allowSyntheticDefaultImports": true }, - "include": ["./src/**/*"], - "exclude": ["./src/index.umd.ts"] + "include": ["./src/**/*"] } From d6ba7298d45d60d204b556a3b29e91d253a0824a Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 30 Nov 2018 11:59:59 -0800 Subject: [PATCH 203/230] chore: deploy_live -> deploy_production --- packages/instant/README.md | 2 +- packages/instant/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/README.md b/packages/instant/README.md index c6621cbed3..45a871124d 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -41,7 +41,7 @@ Finally, we have our live production bundle that is only meant to be updated wit To build and deploy to this bundle, run ``` -yarn deploy_live +yarn deploy_production ``` **NOTE: On deploying the site to staging and dogfood, it will say the site is available at a non-existent URL. Please ignore and use the (now updated) URL above.** diff --git a/packages/instant/package.json b/packages/instant/package.json index 4c26d2dae2..cfd681cc99 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -19,7 +19,7 @@ "clean": "shx rm -rf lib coverage scripts", "deploy_dogfood": "discharge deploy -c .dogfood.discharge.json", "deploy_staging": "discharge deploy -c .staging.discharge.json", - "deploy_live": "discharge deploy -c .production.discharge.json", + "deploy_production": "discharge deploy -c .production.discharge.json", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, "config": { From fc2055cd93a5e847947dad10073dd0d29bfb9e29 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 30 Nov 2018 12:26:59 -0800 Subject: [PATCH 204/230] feat: remove instant from project references --- packages/instant/tsconfig.json | 5 ++++- tsconfig.json | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/instant/tsconfig.json b/packages/instant/tsconfig.json index 21bd3b8217..2b3c11c9f7 100644 --- a/packages/instant/tsconfig.json +++ b/packages/instant/tsconfig.json @@ -5,7 +5,10 @@ "rootDir": "src", "jsx": "react", "noImplicitAny": true, - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "declaration": false, + "declarationMap": false, + "composite": false }, "include": ["./src/**/*"] } diff --git a/tsconfig.json b/tsconfig.json index eaaca8e4ec..0e2fefbac2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,7 +34,6 @@ { "path": "./packages/dev-utils" }, { "path": "./packages/ethereum-types" }, { "path": "./packages/fill-scenarios" }, - { "path": "./packages/instant" }, { "path": "./packages/json-schemas" }, { "path": "./packages/metacoin" }, { "path": "./packages/migrations" }, @@ -57,5 +56,8 @@ // Skipping website because it requires allowJs: false and this is // incompatible with project references. // { "path": "./packages/website" } + // Skipping instant because it only produces a UMD bundle + // which it uses webpack to create + // { "path": "./packages/instant" }, ] } From ab631060a05fc6344ef6e2de7b0e6a0f0096e8ed Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 12:42:40 -0800 Subject: [PATCH 205/230] cdn -> dischargeTarget, and report to heap --- packages/instant/.dogfood.discharge.json | 2 +- packages/instant/.production.discharge.json | 2 +- packages/instant/.staging.discharge.json | 2 +- .../components/zero_ex_instant_provider.tsx | 7 +++++- packages/instant/src/constants.ts | 6 ++++- packages/instant/src/util/analytics.ts | 1 + packages/instant/webpack.config.js | 25 +++++++++++-------- 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json index f9b96ac954..b0e4edaff0 100644 --- a/packages/instant/.dogfood.discharge.json +++ b/packages/instant/.dogfood.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-dogfood", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build:prod --env.cdn=staging", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.discharge_target=dogfood", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/.production.discharge.json b/packages/instant/.production.discharge.json index 70dd4e04c7..4aa5337ba2 100644 --- a/packages/instant/.production.discharge.json +++ b/packages/instant/.production.discharge.json @@ -1,6 +1,6 @@ { "domain": "instant.0xproject.com", - "build_command": "yarn build --env.cdn=production", + "build_command": "yarn build --env.discharge_target=production", "upload_directory": "umd", "index_key": "instant.js", "error_key": "404.html", diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json index 44fc3abc8e..56ffee4e9d 100644 --- a/packages/instant/.staging.discharge.json +++ b/packages/instant/.staging.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-staging", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.cdn=staging", + "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.discharge_target=staging", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 9814aabf89..dc57513d12 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -5,7 +5,11 @@ import * as _ from 'lodash'; import * as React from 'react'; import { Provider as ReduxProvider } from 'react-redux'; -import { ACCOUNT_UPDATE_INTERVAL_TIME_MS, BUY_QUOTE_UPDATE_INTERVAL_TIME_MS } from '../constants'; +import { + ACCOUNT_UPDATE_INTERVAL_TIME_MS, + BUY_QUOTE_UPDATE_INTERVAL_TIME_MS, + INSTANT_DISCHARGE_TARGET, +} from '../constants'; import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider'; import { asyncData } from '../redux/async_data'; import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer'; @@ -132,6 +136,7 @@ export class ZeroExInstantProvider extends React.Component { +const DISCHARGE_TARGETS_THAT_REQUIRED_HEAP = ['production', 'staging', 'dogfood']; +const getConfigForDischargeTarget = dischargeTarget => { return { heapAnalyticsIdEnvName: - cdnName === 'production' ? 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION' : 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT', - heapAnalyticsIdRequired: CDNS_THAT_REQUIRE_HEAP.includes(cdnName), + dischargeTarget === 'production' + ? 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION' + : 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT', + heapAnalyticsIdRequired: DISCHARGE_TARGETS_THAT_REQUIRED_HEAP.includes(dischargeTarget), }; }; @@ -19,14 +21,14 @@ const GIT_SHA = childProcess .execSync('git rev-parse HEAD') .toString() .trim(); -const generateConfig = (cdnName, configOptions) => { +const generateConfig = (dischargeTarget, configOptions) => { const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; const { heapAnalyticsIdEnvName, heapAnalyticsIdRequired } = configOptions; const heapAnalyticsId = process.env[heapAnalyticsIdEnvName]; if (heapAnalyticsIdRequired && !heapAnalyticsId) { throw new Error( - `Must define heap analytics id in ENV var ${heapAnalyticsIdEnvName} when building for ${cdnName}`, + `Must define heap analytics id in ENV var ${heapAnalyticsIdEnvName} when building for ${dischargeTarget}`, ); } @@ -34,12 +36,13 @@ const generateConfig = (cdnName, configOptions) => { GIT_SHA: JSON.stringify(GIT_SHA), NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), }; - if (cdnName) { - envVars.INSTANT_CDN = JSON.stringify(cdnName); + if (dischargeTarget) { + envVars.INSTANT_DISCHARGE_TARGET = JSON.stringify(dischargeTarget); } if (heapAnalyticsId) { envVars.HEAP_ANALYTICS_ID = JSON.stringify(heapAnalyticsId); } + console.log(envVars); const config = { entry: { @@ -91,7 +94,7 @@ const generateConfig = (cdnName, configOptions) => { }; module.exports = (env, _argv) => { - const cdnName = env ? env.cdn : undefined; - const configOptions = getConfigForCdn(cdnName); - return generateConfig(cdnName, configOptions); + const dischargeTarget = env ? env.discharge_target : undefined; + const configOptions = getConfigForDischargeTarget(dischargeTarget); + return generateConfig(dischargeTarget, configOptions); }; From de8dcf9a72283711d41617fc2c0e849a4a8c7461 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 12:47:16 -0800 Subject: [PATCH 206/230] takeout console.log --- packages/instant/webpack.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 917e84ff0e..803240e76a 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -39,7 +39,6 @@ const generateConfig = (dischargeTarget, configOptions) => { if (heapAnalyticsId) { envVars.HEAP_ANALYTICS_ID = JSON.stringify(heapAnalyticsId); } - console.log(envVars); const config = { entry: { From d77efb611ebbeaf54aea6388f3e417ed326ccc62 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 12:49:07 -0800 Subject: [PATCH 207/230] Remove env var check for INSTANT_HEAP_ANALYTICS_ID_PRODUCTION since this is only needed on discharge --- packages/monorepo-scripts/src/prepublish_checks.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/monorepo-scripts/src/prepublish_checks.ts b/packages/monorepo-scripts/src/prepublish_checks.ts index 60cdccf1d5..36e61714b8 100644 --- a/packages/monorepo-scripts/src/prepublish_checks.ts +++ b/packages/monorepo-scripts/src/prepublish_checks.ts @@ -17,7 +17,6 @@ async function prepublishChecksAsync(): Promise { await checkChangelogFormatAsync(updatedPublicPackages); await checkGitTagsForNextVersionAndDeleteIfExistAsync(updatedPublicPackages); await checkPublishRequiredSetupAsync(); - checkRequiredEnvVariables(); } async function checkGitTagsForNextVersionAndDeleteIfExistAsync(updatedPublicPackages: Package[]): Promise { @@ -184,16 +183,6 @@ async function checkPublishRequiredSetupAsync(): Promise { } } -const checkRequiredEnvVariables = () => { - utils.log('Checking required environment variables...'); - const requiredEnvVars = ['INSTANT_HEAP_ANALYTICS_ID_PRODUCTION']; - requiredEnvVars.forEach(requiredEnvVarName => { - if (_.isUndefined(process.env[requiredEnvVarName])) { - throw new Error(`Must have ${requiredEnvVarName} set`); - } - }); -}; - prepublishChecksAsync().catch(err => { utils.log(err); process.exit(1); From 34b2f4736e30b50f94a3110c313131b56e35bc02 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 12:58:42 -0800 Subject: [PATCH 208/230] removing unused references --- packages/instant/src/components/standard_sliding_panel.tsx | 2 +- packages/instant/src/redux/analytics_middleware.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/components/standard_sliding_panel.tsx b/packages/instant/src/components/standard_sliding_panel.tsx index f587ff79a9..9f517d273c 100644 --- a/packages/instant/src/components/standard_sliding_panel.tsx +++ b/packages/instant/src/components/standard_sliding_panel.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { SlideAnimationState, StandardSlidingPanelContent, StandardSlidingPanelSettings } from '../types'; +import { StandardSlidingPanelContent, StandardSlidingPanelSettings } from '../types'; import { InstallWalletPanelContent } from './install_wallet_panel_content'; import { SlidingPanel } from './sliding_panel'; diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts index 3dc5fe924e..47876ca2dc 100644 --- a/packages/instant/src/redux/analytics_middleware.ts +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import { Middleware } from 'redux'; import { ETH_DECIMALS } from '../constants'; -import { Account, AccountState, StandardSlidingPanelContent } from '../types'; +import { AccountState, StandardSlidingPanelContent } from '../types'; import { analytics } from '../util/analytics'; import { Action, ActionTypes } from './actions'; From 9ebe8d63c829ea4b6b1fbbdf7a132c7f9b8f56dc Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 30 Nov 2018 13:34:03 -0800 Subject: [PATCH 209/230] fix: push to history edge case for second render where onpopstate is not defined on first render --- packages/instant/src/index.umd.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 95080f829d..869b523535 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -91,10 +91,11 @@ export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_Z const anyWindow = window as any; if (window.onpopstate && !anyWindow.__zeroExInstantIntegratorsPopStateHandler) { anyWindow.__zeroExInstantIntegratorsPopStateHandler = window.onpopstate.bind(window); + } else { + anyWindow.__zeroExInstantIntegratorsPopStateHandler = util.boundNoop; } - const integratorsOnPopStateHandler = anyWindow.__zeroExInstantIntegratorsPopStateHandler || util.boundNoop; const onPopStateHandler = (e: PopStateEvent) => { - integratorsOnPopStateHandler(e); + anyWindow.__zeroExInstantIntegratorsPopStateHandler(e); const newState = e.state; if (newState && newState.zeroExInstantShowing) { // We have returned to a history state that expects instant to be rendered. From e47dd4a83ed52dd246beddeb93fc5de3378fae24 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 30 Nov 2018 13:47:47 -0800 Subject: [PATCH 210/230] chore: lint --- .../instant/src/components/standard_sliding_panel.tsx | 2 +- packages/instant/src/index.umd.ts | 9 ++++----- packages/instant/src/redux/analytics_middleware.ts | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/instant/src/components/standard_sliding_panel.tsx b/packages/instant/src/components/standard_sliding_panel.tsx index f587ff79a9..9f517d273c 100644 --- a/packages/instant/src/components/standard_sliding_panel.tsx +++ b/packages/instant/src/components/standard_sliding_panel.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { SlideAnimationState, StandardSlidingPanelContent, StandardSlidingPanelSettings } from '../types'; +import { StandardSlidingPanelContent, StandardSlidingPanelSettings } from '../types'; import { InstallWalletPanelContent } from './install_wallet_panel_content'; import { SlidingPanel } from './sliding_panel'; diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 869b523535..3d77748589 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -89,11 +89,10 @@ export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_Z // If the integrator defined a popstate handler, save it to __zeroExInstantIntegratorsPopStateHandler // unless we have already done so on a previous render. const anyWindow = window as any; - if (window.onpopstate && !anyWindow.__zeroExInstantIntegratorsPopStateHandler) { - anyWindow.__zeroExInstantIntegratorsPopStateHandler = window.onpopstate.bind(window); - } else { - anyWindow.__zeroExInstantIntegratorsPopStateHandler = util.boundNoop; - } + const popStateExistsAndNotSetPreviously = window.onpopstate && !anyWindow.__zeroExInstantIntegratorsPopStateHandler; + anyWindow.__zeroExInstantIntegratorsPopStateHandler = popStateExistsAndNotSetPreviously + ? anyWindow.onpopstate.bind(window) + : util.boundNoop; const onPopStateHandler = (e: PopStateEvent) => { anyWindow.__zeroExInstantIntegratorsPopStateHandler(e); const newState = e.state; diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts index 3dc5fe924e..47876ca2dc 100644 --- a/packages/instant/src/redux/analytics_middleware.ts +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import { Middleware } from 'redux'; import { ETH_DECIMALS } from '../constants'; -import { Account, AccountState, StandardSlidingPanelContent } from '../types'; +import { AccountState, StandardSlidingPanelContent } from '../types'; import { analytics } from '../util/analytics'; import { Action, ActionTypes } from './actions'; From b19700221ff53d2a9d9ce716c6d6011b69e4cd4a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 14:09:43 -0800 Subject: [PATCH 211/230] update with new CDN --- packages/instant/src/constants.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 2fe0f4b89e..ab644e0a41 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -28,8 +28,7 @@ export const HOST_DOMAINS = [ 'localhost', '127.0.0.1', '0.0.0.0', - 'unpkg.com', - 'jsdelivr.com', + 'https://instant.0xproject.com', ]; export const ROLLBAR_CLIENT_TOKEN = process.env.ROLLBAR_CLIENT_TOKEN; export const ROLLBAR_ENABLED = process.env.ROLLBAR_ENABLED; From c342940b5d03d8496639feab9f2b305fe86c7240 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 14:09:51 -0800 Subject: [PATCH 212/230] get rid of double private def --- packages/instant/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/instant/package.json b/packages/instant/package.json index ed8c460d9c..76398c12a1 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -7,7 +7,6 @@ "private": true, "description": "0x Instant React Component", "main": "umd/instant.js", - "private": true, "scripts": { "build": "webpack --mode production", "build:ci": "yarn build", From 6a3f295b1fcb184b3dda24f06a0f5609beb70427 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 14:16:49 -0800 Subject: [PATCH 213/230] Take out https:// prefix --- packages/instant/src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index ab644e0a41..1194cf8811 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -28,7 +28,7 @@ export const HOST_DOMAINS = [ 'localhost', '127.0.0.1', '0.0.0.0', - 'https://instant.0xproject.com', + 'instant.0xproject.com', ]; export const ROLLBAR_CLIENT_TOKEN = process.env.ROLLBAR_CLIENT_TOKEN; export const ROLLBAR_ENABLED = process.env.ROLLBAR_ENABLED; From 91b0fd951708b0fff0733c2e1eed45a403f3cc73 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 30 Nov 2018 14:19:07 -0800 Subject: [PATCH 214/230] Fix variable name --- packages/instant/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index 60c07db61a..a1db01db9f 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -20,9 +20,9 @@ const getHeapConfigForDischargeTarget = dischargeTarget => { }; }; -const DISCHARGE_TARGETS_THAT_REQUIRE = ['production', 'staging', 'dogfood']; +const DISCHARGE_TARGETS_THAT_REQUIRE_ROLLBAR = ['production', 'staging', 'dogfood']; const getRollbarConfigForDischargeTarget = dischargeTarget => { - if (DISCHARGE_TARGETS_THAT_REQUIRE.includes(dischargeTarget)) { + if (DISCHARGE_TARGETS_THAT_REQUIRE_ROLLBAR.includes(dischargeTarget)) { const rollbarSourceMapPublicPath = dischargeTarget === 'production' ? 'https://instant.0xproject.com' From 937235c4fd872912accfd33cf45afdebd3f372a7 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 30 Nov 2018 16:26:19 -0800 Subject: [PATCH 215/230] feat: add dotenv to make env dependencies more explicit --- packages/instant/.dogfood.discharge.json | 2 +- packages/instant/.env_example | 4 ++++ packages/instant/.gitignore | 3 ++- packages/instant/.npmignore | 3 ++- packages/instant/.production.discharge.json | 2 +- packages/instant/.staging.discharge.json | 2 +- packages/instant/README.md | 2 ++ packages/instant/package.json | 7 +++++-- yarn.lock | 12 ++++++++++-- 9 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 packages/instant/.env_example diff --git a/packages/instant/.dogfood.discharge.json b/packages/instant/.dogfood.discharge.json index b0e4edaff0..5d6a09a16f 100644 --- a/packages/instant/.dogfood.discharge.json +++ b/packages/instant/.dogfood.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-dogfood", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.discharge_target=dogfood", + "build_command": "WEBPACK_OUTPUT_PATH=public dotenv yarn build --env.discharge_target=dogfood", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/.env_example b/packages/instant/.env_example new file mode 100644 index 0000000000..ebbbebc06e --- /dev/null +++ b/packages/instant/.env_example @@ -0,0 +1,4 @@ +INSTANT_ROLLBAR_PUBLISH_TOKEN= +INSTANT_ROLLBAR_CLIENT_TOKEN= +INSTANT_HEAP_ANALYTICS_ID_PRODUCTION= +INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT= \ No newline at end of file diff --git a/packages/instant/.gitignore b/packages/instant/.gitignore index a99cea187d..2e65f192d4 100644 --- a/packages/instant/.gitignore +++ b/packages/instant/.gitignore @@ -1,3 +1,4 @@ public/instant.js public/instant.js.map -umd/* \ No newline at end of file +umd/* +.env \ No newline at end of file diff --git a/packages/instant/.npmignore b/packages/instant/.npmignore index a4f7810c03..563923652e 100644 --- a/packages/instant/.npmignore +++ b/packages/instant/.npmignore @@ -2,4 +2,5 @@ * */ !lib/**/* -!umd/**/* \ No newline at end of file +!umd/**/* +.env \ No newline at end of file diff --git a/packages/instant/.production.discharge.json b/packages/instant/.production.discharge.json index 4aa5337ba2..947f68b1ae 100644 --- a/packages/instant/.production.discharge.json +++ b/packages/instant/.production.discharge.json @@ -1,6 +1,6 @@ { "domain": "instant.0xproject.com", - "build_command": "yarn build --env.discharge_target=production", + "build_command": "dotenv yarn build --env.discharge_target=production", "upload_directory": "umd", "index_key": "instant.js", "error_key": "404.html", diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json index 56ffee4e9d..bd5f28ba84 100644 --- a/packages/instant/.staging.discharge.json +++ b/packages/instant/.staging.discharge.json @@ -1,6 +1,6 @@ { "domain": "0x-instant-staging", - "build_command": "WEBPACK_OUTPUT_PATH=public yarn build --env.discharge_target=staging", + "build_command": "dotenv WEBPACK_OUTPUT_PATH=public yarn build --env.discharge_target=staging", "upload_directory": "public", "index_key": "index.html", "error_key": "index.html", diff --git a/packages/instant/README.md b/packages/instant/README.md index 45a871124d..2092b45d97 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -20,6 +20,8 @@ The package is available as a UMD module named `zeroExInstant` at https://instan ## Deploying +To run any of the following commands you need to configure your `.env` file. There is an example `.env_example` file to show you what values are required. + You can deploy a work-in-progress version of 0x Instant at http://0x-instant-dogfood.s3-website-us-east-1.amazonaws.com/instant.js for easy sharing. To build and deploy the bundle run diff --git a/packages/instant/package.json b/packages/instant/package.json index 62904949ba..4daec883b4 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -7,7 +7,6 @@ "private": true, "description": "0x Instant React Component", "main": "umd/instant.js", - "private": true, "scripts": { "build": "webpack --mode production", "build:ci": "yarn build", @@ -25,7 +24,10 @@ }, "config": { "postpublish": { - "assets": ["packages/instant/umd/instant.js", "packages/instant/umd/instant.js.map"] + "assets": [ + "packages/instant/umd/instant.js", + "packages/instant/umd/instant.js.map" + ] } }, "repository": { @@ -75,6 +77,7 @@ "@types/redux": "^3.6.0", "@types/styled-components": "^4.0.1", "awesome-typescript-loader": "^5.2.1", + "dotenv-cli": "^1.4.0", "enzyme": "^3.6.0", "enzyme-adapter-react-16": "^1.5.0", "ip": "^1.1.5", diff --git a/yarn.lock b/yarn.lock index a7a55a7b3d..141838ab05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4505,7 +4505,7 @@ cross-fetch@^2.1.0: node-fetch "2.1.1" whatwg-fetch "2.0.3" -cross-spawn@^4: +cross-spawn@^4, cross-spawn@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" dependencies: @@ -5270,6 +5270,14 @@ dot-prop@^4.1.0, dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" +dotenv-cli@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-1.4.0.tgz#e8e80830ed88b48a03b5eb7ec26147ca717f7409" + dependencies: + cross-spawn "^4.0.0" + dotenv "^4.0.0" + minimist "^1.1.3" + dotenv@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" @@ -6778,7 +6786,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep: ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default" ethereumjs-util "^5.2.0" ethereumjs-vm "2.3.5" - ethereumjs-wallet "0.6.0" + ethereumjs-wallet "~0.6.0" fake-merkle-patricia-tree "~1.0.1" heap "~0.2.6" js-scrypt "^0.2.0" From 800016b430a941682d4cdb89f85b46c27b835e39 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sat, 1 Dec 2018 00:03:07 -0800 Subject: [PATCH 216/230] fix(instant) various token selector fixes --- packages/instant/src/components/search_input.tsx | 8 ++++---- packages/instant/src/components/ui/container.tsx | 1 + packages/instant/src/components/ui/input.tsx | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/instant/src/components/search_input.tsx b/packages/instant/src/components/search_input.tsx index 3a693b9f81..71bc189153 100644 --- a/packages/instant/src/components/search_input.tsx +++ b/packages/instant/src/components/search_input.tsx @@ -13,10 +13,10 @@ export interface SearchInputProps extends InputProps { } export const SearchInput: React.StatelessComponent = props => ( - - - - + + + + ); diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index e7d909d923..636eb8fc9a 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -77,6 +77,7 @@ export const Container = ${props => cssRuleIfExists(props, 'opacity')} ${props => cssRuleIfExists(props, 'cursor')} ${props => cssRuleIfExists(props, 'overflow')} + ${props => (props.overflow === 'scroll' ? `-webkit-overflow-scrolling: touch` : '')}; ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')}; ${props => props.display && stylesForMedia('display', props.display)} ${props => props.width && stylesForMedia('width', props.width)} diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx index 1ea5d8fe16..863c970ef7 100644 --- a/packages/instant/src/components/ui/input.tsx +++ b/packages/instant/src/components/ui/input.tsx @@ -10,6 +10,7 @@ export interface InputProps { fontSize?: string; fontColor?: ColorOption; placeholder?: string; + type?: string; onChange?: (event: React.ChangeEvent) => void; } From c0f4a35cfd67b5de387786a582bc615375a2dd4b Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sat, 1 Dec 2018 00:04:21 -0800 Subject: [PATCH 217/230] fix(instant): DGD and DGX colors --- packages/instant/src/data/asset_meta_data_map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/data/asset_meta_data_map.ts b/packages/instant/src/data/asset_meta_data_map.ts index b24c9c83d6..0553be7f5c 100644 --- a/packages/instant/src/data/asset_meta_data_map.ts +++ b/packages/instant/src/data/asset_meta_data_map.ts @@ -83,14 +83,14 @@ export const assetMetaDataMap: ObjectMap = { '0xf47261b0000000000000000000000000e0b7927c4af23765cb51314a0e0521a9645f0e2a': { assetProxyId: AssetProxyId.ERC20, decimals: 9, - primaryColor: '#DEB564', + primaryColor: '#E1AA3E', symbol: 'dgd', name: 'DigixDao', }, '0xf47261b00000000000000000000000004f3afec4e5a3f2a6a1a411def7d7dfe50ee057bf': { assetProxyId: AssetProxyId.ERC20, decimals: 9, - primaryColor: '#DEB564', + primaryColor: '#E1AA3E', symbol: 'dgx', name: 'Digix Gold Token', }, From 593ed12d91e2e3c3f6d6feda2468b20a1fb364ac Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sat, 1 Dec 2018 00:06:36 -0800 Subject: [PATCH 218/230] fix(instant): update zrx icon --- packages/instant/src/assets/icons/zrx.svg | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/instant/src/assets/icons/zrx.svg b/packages/instant/src/assets/icons/zrx.svg index 07518f551c..da623710b3 100644 --- a/packages/instant/src/assets/icons/zrx.svg +++ b/packages/instant/src/assets/icons/zrx.svg @@ -1,3 +1,6 @@ - - + + + + + From 074944247dfdcd91fb7acbe064ccf3c3d2139ddc Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sat, 1 Dec 2018 00:22:09 -0800 Subject: [PATCH 219/230] feat(instant): add token symbol to buy button text --- packages/instant/src/components/buy_button.tsx | 12 +++++++++--- .../src/components/buy_order_state_buttons.tsx | 4 +++- .../selected_asset_buy_order_state_buttons.ts | 5 ++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index eeb42d6fc4..1489b94d40 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -1,4 +1,5 @@ import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; +import { AssetProxyId } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; @@ -7,7 +8,7 @@ import { oc } from 'ts-optchain'; import { WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants'; import { ColorOption } from '../style/theme'; -import { AffiliateInfo, ZeroExInstantError } from '../types'; +import { AffiliateInfo, Asset, ZeroExInstantError } from '../types'; import { analytics } from '../util/analytics'; import { gasPriceEstimator } from '../util/gas_price_estimator'; import { util } from '../util/util'; @@ -21,6 +22,7 @@ export interface BuyButtonProps { assetBuyer: AssetBuyer; web3Wrapper: Web3Wrapper; affiliateInfo?: AffiliateInfo; + selectedAsset?: Asset; onValidationPending: (buyQuote: BuyQuote) => void; onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; onSignatureDenied: (buyQuote: BuyQuote) => void; @@ -36,8 +38,12 @@ export class BuyButton extends React.Component { onBuyFailure: util.boundNoop, }; public render(): React.ReactNode { - const { buyQuote, accountAddress } = this.props; + const { buyQuote, accountAddress, selectedAsset } = this.props; const shouldDisableButton = _.isUndefined(buyQuote) || _.isUndefined(accountAddress); + const buttonText = + !_.isUndefined(selectedAsset) && selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20 + ? `Buy ${selectedAsset.metaData.symbol.toUpperCase()}` + : 'Buy Now'; return ( ); } diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx index e563bec73d..833818900b 100644 --- a/packages/instant/src/components/buy_order_state_buttons.tsx +++ b/packages/instant/src/components/buy_order_state_buttons.tsx @@ -4,7 +4,7 @@ import { Web3Wrapper } from '@0x/web3-wrapper'; import * as React from 'react'; import { ColorOption } from '../style/theme'; -import { AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types'; +import { AffiliateInfo, Asset, OrderProcessState, ZeroExInstantError } from '../types'; import { BuyButton } from './buy_button'; import { PlacingOrderButton } from './placing_order_button'; @@ -21,6 +21,7 @@ export interface BuyOrderStateButtonProps { assetBuyer: AssetBuyer; web3Wrapper: Web3Wrapper; affiliateInfo?: AffiliateInfo; + selectedAsset?: Asset; onViewTransaction: () => void; onValidationPending: (buyQuote: BuyQuote) => void; onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; @@ -60,6 +61,7 @@ export const BuyOrderStateButtons: React.StatelessComponent void; } @@ -41,6 +42,7 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButt const account = state.providerState.account; const accountAddress = account.state === AccountState.Ready ? account.address : undefined; const accountEthBalanceInWei = account.state === AccountState.Ready ? account.ethBalanceInWei : undefined; + const selectedAsset = state.selectedAsset; return { accountAddress, accountEthBalanceInWei, @@ -49,6 +51,7 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButt web3Wrapper, buyQuote: state.latestBuyQuote, affiliateInfo: state.affiliateInfo, + selectedAsset, onViewTransaction: () => { if ( state.buyOrderState.processState === OrderProcessState.Processing || From 6f44944ffdb3c529b2135414818bc7e5dcc198b0 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sat, 1 Dec 2018 00:47:07 -0800 Subject: [PATCH 220/230] fix(instant): darken overlay color --- packages/instant/public/augur_screenshot.png | Bin 0 -> 359383 bytes packages/instant/public/index.html | 6 ++++++ packages/instant/src/components/ui/overlay.tsx | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 packages/instant/public/augur_screenshot.png diff --git a/packages/instant/public/augur_screenshot.png b/packages/instant/public/augur_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..c61cf6e7cd0f5695ea05ee4df7502431f8aa1170 GIT binary patch literal 359383 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 rtVNb$z*G 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%0D!c7dVz5{ux7?b}2_-xtw~9 zYMj&(`@X zH9EeM5jV_ptUqU3Z8HM9y_~~wU0)Czvv^s5CP_HI$Ltp>1}LYX{j=lL^A+Fwocvk@ z^b;R$?6+R4dwp#b^m%t%v2`$94z%`w*_b&A2pCu zLgATSYTOt&)`GL__N0*01a@r97q^Urb(Oa00oP|^9VSG0h8LXDe!^;i+HKUVPx5H3 zoQKBF)qJmgkt*r_i4W2nW7qO>tmpge=oYr>_~h9uc>;~@;!b@DDU*Ax$k_I-O*yqb zJB#FbEuiq!Bbg9%)zI^`Yo?K6<^2wL6YJXHl{VOC&V-y#sl(z{A`*Lmm5yTQ^^CvY zF_7$I`wsT>aY>lLt$Mg$KG}O<`~ep`IPpBddqa$M*zQII+|LYCR}B5$tK<$o{CvX0 sUapfx#*Ga--f)BI_|j8DuN}$%A49O{o0>8=3IG5A07*qoM6N<$f+)tRTL1t6 literal 0 HcmV?d00001 diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html index df39994ef0..8e08756452 100644 --- a/packages/instant/public/index.html +++ b/packages/instant/public/index.html @@ -31,6 +31,11 @@ height: 100vh; background-color: rgba(0, 0, 0, 0.2); } + + .full-screen { + height: 100vh; + width: 100vw; + } @@ -58,6 +63,7 @@
+