feat: add core-utils

parent df4643e8
# Optimism Monorepo # Optimism Monorepo (VERY WIP)
## Taming the Monorepo ## Taming the Monorepo
1. You solely use yarn workspaces for the Mono-Repo workflow. 1. You solely use yarn workspaces for the Mono-Repo workflow.
1. You use lerna’s utility commands to optimize managing of multiple packages, e.g., selective execution of npm scripts for testing. 1. You use lerna’s utility commands to optimize managing of multiple packages, e.g., selective execution of npm scripts for testing.
1. You use lerna for publishing packages since lerna provides sophisticated features with its version and publish commands. 1. You use lerna for publishing packages since lerna provides sophisticated features with its version and publish commands.
## Incremental Tests
```
BRANCH_POINT="$(git merge-base $(git rev-parse --abbrev-ref HEAD) $(git describe origin/master))"
changedPackages="$(npx lerna ls -p --since $BRANCH_POINT --include-dependents)"
```
## Goals
## Ops
https://github.com/connext/vector/tree/main/ops
https://github.com/connext/vector/blob/main/Makefile
https://github.com/connext/vector/blob/main/.github/workflows/prod.yml
https://github.com/connext/vector/blob/main/.github/workflows/feature.yml
https://www.npmjs.com/package/depcheck
## Lerna import
https://medium.com/zocdoc-engineering/lerna-you-a-monorepo-the-nuts-and-bolts-of-building-a-ci-pipeline-with-lerna-850e6a290bb2
...@@ -11,6 +11,6 @@ ...@@ -11,6 +11,6 @@
"lerna": "^4.0.0" "lerna": "^4.0.0"
}, },
"scripts": { "scripts": {
"postinstall": "lerna bootstrap --hoist" "test": "yarn workspaces run test"
} }
} }
node_modules/
build/
\ No newline at end of file
(The MIT License)
Copyright 2020 Optimism
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# @eth-optimism/core-utils
## What is this?
`@eth-optimism/core-utils` contains the Optimistic Virtual Machine core utilities.
## Getting started
### Building and usage
After cloning and switching to the repository, install dependencies:
```bash
$ yarn
```
Use the following commands to build, use, test, and lint:
```bash
$ yarn build
$ yarn start
$ yarn test
$ yarn lint
```
{
"name": "@eth-optimism/core-utils",
"version": "0.1.10",
"main": "build/src/index.js",
"files": [
"build/src/**/*"
],
"types": "build/src/index.d.ts",
"repository": "git@github.com:ethereum-optimism/core-utils.git",
"author": "Kelvin Fichter <kelvinfichter@gmail.com>",
"license": "MIT",
"scripts": {
"all": "yarn clean && yarn build && yarn test && yarn lint:fix && yarn lint",
"build": "tsc -p .",
"clean": "rimraf build/",
"lint": "tslint --format stylish --project .",
"lint:fix": "prettier --config prettier-config.json --write '{src,test}/**/*.ts'",
"test": "ts-mocha test/**/*.spec.ts"
},
"devDependencies": {
"@types/pino": "^6.3.6",
"chai": "^4.3.0",
"mocha": "^8.3.0",
"prettier": "^2.2.1",
"ts-mocha": "^8.0.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"tslint-no-focused-test": "^0.5.0",
"tslint-plugin-prettier": "^2.3.0",
"typescript": "^4.2.3"
},
"dependencies": {
"@ethersproject/abstract-provider": "^5.0.9",
"colors": "^1.4.0",
"debug": "^4.3.1",
"ethers": "^5.0.31",
"pino": "^6.11.1"
}
}
{
"$schema": "http://json.schemastore.org/prettierrc",
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"arrowParens": "always"
}
/* Internal Imports */
import { add0x, remove0x, toVerifiedBytes, encodeHex, getLen } from '../common'
import { Coder, Signature, Uint16, Uint8, Uint24, Address } from './types'
/***********************
* TxTypes and TxData *
**********************/
export enum TxType {
EIP155 = 0,
EthSign = 1,
}
export const txTypePlainText = {
0: TxType.EIP155,
1: TxType.EthSign,
EIP155: TxType.EIP155,
EthSign: TxType.EthSign,
}
export interface DefaultEcdsaTxData {
sig: Signature
gasLimit: Uint16
gasPrice: Uint8
nonce: Uint24
target: Address
data: string
type: TxType
}
export interface EIP155TxData extends DefaultEcdsaTxData {}
export interface EthSignTxData extends DefaultEcdsaTxData {}
/***********************
* Encoding Positions *
**********************/
/*
* The positions in the tx data for the different transaction types
*/
export const TX_TYPE_POSITION = { start: 0, end: 1 }
/*
* The positions in the tx data for the EIP155TxData and EthSignTxData
*/
export const SIGNATURE_FIELD_POSITIONS = {
r: { start: 1, end: 33 }, // 32 bytes
s: { start: 33, end: 65 }, // 32 bytes
v: { start: 65, end: 66 }, // 1 byte
}
export const DEFAULT_ECDSA_TX_FIELD_POSITIONS = {
txType: TX_TYPE_POSITION, // 1 byte
sig: SIGNATURE_FIELD_POSITIONS, // 65 bytes
gasLimit: { start: 66, end: 69 }, // 3 bytes
gasPrice: { start: 69, end: 72 }, // 3 byte
nonce: { start: 72, end: 75 }, // 3 bytes
target: { start: 75, end: 95 }, // 20 bytes
data: { start: 95 }, // byte 95 onward
}
export const EIP155_TX_FIELD_POSITIONS = DEFAULT_ECDSA_TX_FIELD_POSITIONS
export const ETH_SIGN_TX_FIELD_POSITIONS = DEFAULT_ECDSA_TX_FIELD_POSITIONS
export const CTC_TX_GAS_PRICE_MULT_FACTOR = 1_000_000
/***************
* EcdsaCoders *
**************/
class DefaultEcdsaTxCoder implements Coder {
constructor(readonly txType: TxType) {}
public encode(txData: DefaultEcdsaTxData): string {
const txType = encodeHex(
this.txType,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.txType)
)
const r = toVerifiedBytes(
txData.sig.r,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.r)
)
const s = toVerifiedBytes(
txData.sig.s,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.s)
)
const v = encodeHex(
txData.sig.v,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.sig.v)
)
const gasLimit = encodeHex(
txData.gasLimit,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.gasLimit)
)
if (txData.gasPrice % CTC_TX_GAS_PRICE_MULT_FACTOR !== 0) {
throw new Error(`Gas Price ${txData.gasPrice} cannot be encoded`)
}
const gasPrice = encodeHex(
txData.gasPrice / CTC_TX_GAS_PRICE_MULT_FACTOR,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.gasPrice)
)
const nonce = encodeHex(
txData.nonce,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.nonce)
)
const target = toVerifiedBytes(
txData.target,
getLen(DEFAULT_ECDSA_TX_FIELD_POSITIONS.target)
)
// Make sure that the data is even
if (txData.data.length % 2 !== 0) {
throw new Error('Non-even hex string for tx data!')
}
const encoding =
'0x' +
txType +
r +
s +
v +
gasLimit +
gasPrice +
nonce +
target +
remove0x(txData.data)
return encoding
}
public decode(txData: string): DefaultEcdsaTxData {
txData = remove0x(txData)
const sliceBytes = (position: { start; end? }): string =>
txData.slice(position.start * 2, position.end * 2)
const pos = DEFAULT_ECDSA_TX_FIELD_POSITIONS
if (parseInt(sliceBytes(pos.txType), 16) !== this.txType) {
throw new Error('Invalid tx type')
}
return {
sig: {
r: add0x(sliceBytes(pos.sig.r)),
s: add0x(sliceBytes(pos.sig.s)),
v: parseInt(sliceBytes(pos.sig.v), 16),
},
gasLimit: parseInt(sliceBytes(pos.gasLimit), 16),
gasPrice:
parseInt(sliceBytes(pos.gasPrice), 16) * CTC_TX_GAS_PRICE_MULT_FACTOR,
nonce: parseInt(sliceBytes(pos.nonce), 16),
target: add0x(sliceBytes(pos.target)),
data: add0x(txData.slice(pos.data.start * 2)),
type: this.txType,
}
}
}
class EthSignTxCoder extends DefaultEcdsaTxCoder {
constructor() {
super(TxType.EthSign)
}
public encode(txData: EthSignTxData): string {
return super.encode(txData)
}
public decode(txData: string): EthSignTxData {
return super.decode(txData)
}
}
class Eip155TxCoder extends DefaultEcdsaTxCoder {
constructor() {
super(TxType.EIP155)
}
public encode(txData: EIP155TxData): string {
return super.encode(txData)
}
public decode(txData: string): EIP155TxData {
return super.decode(txData)
}
}
/*************
* ctcCoder *
************/
function encode(data: EIP155TxData): string {
if (data.type === TxType.EIP155) {
return new Eip155TxCoder().encode(data)
}
if (data.type === TxType.EthSign) {
return new EthSignTxCoder().encode(data)
}
return null
}
function decode(data: string | Buffer): EIP155TxData {
if (Buffer.isBuffer(data)) {
data = data.toString()
}
data = remove0x(data)
const type = parseInt(data.slice(0, 2), 16)
if (type === TxType.EIP155) {
return new Eip155TxCoder().decode(data)
}
if (type === TxType.EthSign) {
return new EthSignTxCoder().decode(data)
}
return null
}
/*
* Encoding and decoding functions for all txData types.
*/
export const ctcCoder = {
eip155TxData: new Eip155TxCoder(),
ethSignTxData: new EthSignTxCoder(),
encode,
decode,
}
export * from './ecdsa-coder'
export * from './types'
export * from './sequencer-batch'
import { add0x, remove0x, encodeHex } from '../common'
import { Contract, BigNumber, ethers } from 'ethers'
import { keccak256 } from 'ethers/lib/utils'
import { TransactionResponse } from '@ethersproject/abstract-provider'
export interface BatchContext {
numSequencedTransactions: number
numSubsequentQueueTransactions: number
timestamp: number
blockNumber: number
}
export interface AppendSequencerBatchParams {
shouldStartAtElement: number // 5 bytes -- starts at batch
totalElementsToAppend: number // 3 bytes -- total_elements_to_append
contexts: BatchContext[] // total_elements[fixed_size[]]
transactions: string[] // total_size_bytes[],total_size_bytes[]
}
/**********************
* Internal Functions *
*********************/
const APPEND_SEQUENCER_BATCH_METHOD_ID = 'appendSequencerBatch()'
const appendSequencerBatch = async (
OVM_CanonicalTransactionChain: Contract,
batch: AppendSequencerBatchParams
): Promise<TransactionResponse> => {
const methodId = keccak256(
Buffer.from(APPEND_SEQUENCER_BATCH_METHOD_ID)
).slice(2, 10)
const calldata = encodeAppendSequencerBatch(batch)
return OVM_CanonicalTransactionChain.signer.sendTransaction({
to: OVM_CanonicalTransactionChain.address,
data: '0x' + methodId + calldata,
})
}
export const encodeAppendSequencerBatch = (
b: AppendSequencerBatchParams
): string => {
const encodeShouldStartAtElement = encodeHex(b.shouldStartAtElement, 10)
const encodedTotalElementsToAppend = encodeHex(b.totalElementsToAppend, 6)
const encodedContextsHeader = encodeHex(b.contexts.length, 6)
const encodedContexts =
encodedContextsHeader +
b.contexts.reduce((acc, cur) => acc + encodeBatchContext(cur), '')
const encodedTransactionData = b.transactions.reduce((acc, cur) => {
if (cur.length % 2 !== 0) {
throw new Error('Unexpected uneven hex string value!')
}
const encodedTxDataHeader = remove0x(
BigNumber.from(remove0x(cur).length / 2).toHexString()
).padStart(6, '0')
return acc + encodedTxDataHeader + remove0x(cur)
}, '')
return (
encodeShouldStartAtElement +
encodedTotalElementsToAppend +
encodedContexts +
encodedTransactionData
)
}
const encodeBatchContext = (context: BatchContext): string => {
return (
encodeHex(context.numSequencedTransactions, 6) +
encodeHex(context.numSubsequentQueueTransactions, 6) +
encodeHex(context.timestamp, 10) +
encodeHex(context.blockNumber, 10)
)
}
export const decodeAppendSequencerBatch = (
b: string
): AppendSequencerBatchParams => {
b = remove0x(b)
const shouldStartAtElement = b.slice(0, 10)
const totalElementsToAppend = b.slice(10, 16)
const contextHeader = b.slice(16, 22)
const contextCount = parseInt(contextHeader, 16)
let offset = 22
const contexts = []
for (let i = 0; i < contextCount; i++) {
const numSequencedTransactions = b.slice(offset, offset + 6)
offset += 6
const numSubsequentQueueTransactions = b.slice(offset, offset + 6)
offset += 6
const timestamp = b.slice(offset, offset + 10)
offset += 10
const blockNumber = b.slice(offset, offset + 10)
offset += 10
contexts.push({
numSequencedTransactions: parseInt(numSequencedTransactions, 16),
numSubsequentQueueTransactions: parseInt(
numSubsequentQueueTransactions,
16
),
timestamp: parseInt(timestamp, 16),
blockNumber: parseInt(blockNumber, 16),
})
}
const transactions = []
for (const context of contexts) {
for (let i = 0; i < context.numSequencedTransactions; i++) {
const size = b.slice(offset, offset + 6)
offset += 6
const raw = b.slice(offset, offset + parseInt(size, 16) * 2)
transactions.push(add0x(raw))
offset += raw.length
}
}
return {
shouldStartAtElement: parseInt(shouldStartAtElement, 16),
totalElementsToAppend: parseInt(totalElementsToAppend, 16),
contexts,
transactions,
}
}
export const sequencerBatch = {
encode: (b: AppendSequencerBatchParams) => {
return (
ethers.utils.id(APPEND_SEQUENCER_BATCH_METHOD_ID).slice(0, 10) +
encodeAppendSequencerBatch(b)
)
},
decode: (b: string): AppendSequencerBatchParams => {
b = remove0x(b)
const functionSelector = b.slice(0, 8)
if (
functionSelector !==
ethers.utils.id(APPEND_SEQUENCER_BATCH_METHOD_ID).slice(2, 10)
) {
throw new Error('Incorrect function signature')
}
return decodeAppendSequencerBatch(b.slice(8))
},
}
export interface Signature {
r: string
s: string
v: number
}
export type Bytes32 = string
export type Uint16 = number
export type Uint8 = number
export type Uint24 = number
export type Address = string
export interface Coder {
encode: Function
decode: Function
}
/* Imports: External */
import { ethers } from 'ethers'
/* Imports: Internal */
import { getRandomHexString } from './hex-strings'
/* @returns a random Ethereum address as a string of 40 hex characters, normalized as a checksum address. */
export const getRandomAddress = (): string => {
return ethers.utils.getAddress(getRandomHexString(20))
}
export const assert = (condition: () => boolean, reason?: string) => {
try {
if (condition() === false) {
throw new Error(`Assertion failed: ${reason}`)
}
} catch (err) {
throw new Error(`Assertion failed: ${reason}\n${err}`)
}
}
/* Imports: External */
import { BigNumber } from 'ethers'
/**
* Removes "0x" from start of a string if it exists.
* @param str String to modify.
* @returns the string without "0x".
*/
export const remove0x = (str: string): string => {
if (str === undefined) {
return str
}
return str.startsWith('0x') ? str.slice(2) : str
}
/**
* Adds "0x" to the start of a string if necessary.
* @param str String to modify.
* @returns the string with "0x".
*/
export const add0x = (str: string): string => {
if (str === undefined) {
return str
}
return str.startsWith('0x') ? str : '0x' + str
}
/**
* Returns whether or not the provided string is a hex string.
* @param str The string to test.
* @returns True if the provided string is a hex string, false otherwise.
*/
export const isHexString = (inp: any): boolean => {
return typeof inp === 'string' && inp.startsWith('0x')
}
/**
* Casts a hex string to a buffer.
* @param inp Input to cast to a buffer.
* @return Input cast as a buffer.
*/
export const fromHexString = (inp: Buffer | string): Buffer => {
if (typeof inp === 'string' && inp.startsWith('0x')) {
return Buffer.from(inp.slice(2), 'hex')
}
return Buffer.from(inp)
}
/**
* Casts an input to a hex string.
* @param inp Input to cast to a hex string.
* @return Input cast as a hex string.
*/
export const toHexString = (inp: Buffer | string | number | null): string => {
if (typeof inp === 'number') {
return BigNumber.from(inp).toHexString()
} else {
return '0x' + fromHexString(inp).toString('hex')
}
}
export const toRpcHexString = (n: number): string => {
if (n === 0) {
return '0x0'
} else {
return '0x' + toHexString(n).slice(2).replace(/^0+/, '')
}
}
export const padHexString = (str: string, length: number): string => {
if (str.length === 2 + length * 2) {
return str
} else {
return '0x' + str.slice(2).padStart(length * 2, '0')
}
}
export const getLen = (pos: { start; end }) => (pos.end - pos.start) * 2
export const encodeHex = (val: any, len: number) =>
remove0x(BigNumber.from(val).toHexString()).padStart(len, '0')
export const toVerifiedBytes = (val: string, len: number) => {
val = remove0x(val)
if (val.length !== len) {
throw new Error('Invalid length!')
}
return val
}
/**
* @param byteLength The length of the hex string in bytes
* @returns a random hex string of the specified byteLength (string length will be byteLength*2)
*/
export const getRandomHexString = (byteLength: number): string => {
return (
'0x' +
[...Array(byteLength * 2)]
.map(() => {
return Math.floor(Math.random() * 16).toString(16)
})
.join('')
)
}
export * from './addresses'
export * from './hex-strings'
export * from './logger'
export * from './misc'
export * from './common'
import pino, {
LoggerOptions as PinoLoggerOptions,
DestinationObjectOptions,
} from 'pino'
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
export interface LoggerOptions {
name: string
level?: LogLevel
destination?: DestinationObjectOptions
}
/**
* Temporary wrapper class to maintain earlier module interface.
*/
export class Logger {
options: LoggerOptions
inner: pino.Logger
constructor(options: LoggerOptions) {
this.options = options
const loggerOptions: PinoLoggerOptions = {
name: options.name,
level: options.level || 'debug',
// Remove pid and hostname considering production runs inside docker
base: null,
}
this.inner = options.destination
? pino(loggerOptions, pino.destination(options.destination))
: pino(loggerOptions)
}
child(bindings: pino.Bindings): Logger {
const inner = this.inner.child(bindings)
const logger = new Logger(this.options)
logger.inner = inner
return logger
}
trace(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.trace(o, msg, ...args)
} else {
this.inner.trace(msg, ...args)
}
}
debug(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.debug(o, msg, ...args)
} else {
this.inner.debug(msg, ...args)
}
}
info(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.info(o, msg, ...args)
} else {
this.inner.info(msg, ...args)
}
}
warn(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.warn(o, msg, ...args)
} else {
this.inner.warn(msg, ...args)
}
}
warning(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.warn(o, msg, ...args)
} else {
this.inner.warn(msg, ...args)
}
}
error(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.error(o, msg, ...args)
} else {
this.inner.error(msg, ...args)
}
}
fatal(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.fatal(o, msg, ...args)
} else {
this.inner.fatal(msg, ...args)
}
}
crit(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.fatal(o, msg, ...args)
} else {
this.inner.fatal(msg, ...args)
}
}
critical(msg: string, o?: object, ...args: any[]): void {
if (o) {
this.inner.fatal(o, msg, ...args)
} else {
this.inner.fatal(msg, ...args)
}
}
}
/**
* Basic timeout-based async sleep function.
* @param ms Number of milliseconds to sleep.
*/
export const sleep = async (ms: number): Promise<void> => {
return new Promise<void>((resolve, reject) => {
setTimeout(() => {
resolve(null)
}, ms)
})
}
export * from './coders'
export * from './common'
import '../setup'
/* Internal Imports */
import {
ctcCoder,
encodeAppendSequencerBatch,
decodeAppendSequencerBatch,
TxType,
sequencerBatch,
} from '../../src'
import { expect } from 'chai'
describe('BatchEncoder', () => {
describe('eip155TxData', () => {
it('should encode & then decode to the correct value', () => {
const eip155TxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: 1000000,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: TxType.EIP155,
}
const encoded = ctcCoder.eip155TxData.encode(eip155TxData)
const decoded = ctcCoder.eip155TxData.decode(encoded)
expect(eip155TxData).to.deep.equal(decoded)
})
it('should fail encoding a bad gas price', () => {
const badGasPrice = 1000001
const eip155TxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: badGasPrice,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: TxType.EIP155,
}
let error
try {
ctcCoder.eip155TxData.encode(eip155TxData)
} catch (e) {
error = e
}
expect(error.message).to.equal(
`Gas Price ${badGasPrice} cannot be encoded`
)
})
})
describe('appendSequencerBatch', () => {
it('should work with the simple case', () => {
const batch = {
shouldStartAtElement: 0,
totalElementsToAppend: 0,
contexts: [],
transactions: [],
}
const encoded = encodeAppendSequencerBatch(batch)
const decoded = decodeAppendSequencerBatch(encoded)
expect(decoded).to.deep.equal(batch)
})
it('should work with more complex case', () => {
const batch = {
shouldStartAtElement: 10,
totalElementsToAppend: 1,
contexts: [
{
numSequencedTransactions: 2,
numSubsequentQueueTransactions: 1,
timestamp: 100,
blockNumber: 200,
},
],
transactions: ['0x45423400000011', '0x45423400000012'],
}
const encoded = encodeAppendSequencerBatch(batch)
const decoded = decodeAppendSequencerBatch(encoded)
expect(decoded).to.deep.equal(batch)
})
it('should work with mainnet calldata', () => {
const data = require('../fixtures/appendSequencerBatch.json')
for (const calldata of data.calldata) {
const decoded = sequencerBatch.decode(calldata)
const encoded = sequencerBatch.encode(decoded)
expect(encoded).to.equal(calldata)
}
})
})
describe('generic ctcCoder', () => {
it('should decode EIP155 txs to the correct value', () => {
const eip155TxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: 1000000,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: TxType.EIP155,
}
const encoded = ctcCoder.encode(eip155TxData)
const decoded = ctcCoder.decode(encoded)
expect(eip155TxData).to.deep.equal(decoded)
})
it('should return null when encoding an unknown type', () => {
const weirdTypeTxData = {
sig: {
v: 1,
r: '0x' + '11'.repeat(32),
s: '0x' + '22'.repeat(32),
},
gasLimit: 500,
gasPrice: 100,
nonce: 100,
target: '0x' + '12'.repeat(20),
data: '0x' + '99'.repeat(10),
type: 420,
}
const encoded = ctcCoder.encode(weirdTypeTxData)
expect(encoded).to.be.null
})
})
})
import { expect } from '../setup'
/* Imports: Internal */
import { getRandomAddress } from '../../src'
describe('getRandomAddress', () => {
const random = global.Math.random
before(async () => {
global.Math.random = () => 0.5
})
after(async () => {
global.Math.random = random
})
it('returns a random address string', () => {
expect(getRandomAddress()).to.equal('0x' + '88'.repeat(20))
})
})
import { expect } from '../setup'
/* Imports: Internal */
import { getRandomHexString } from '../../src'
describe('getRandomHexString', () => {
const random = global.Math.random
before(async () => {
global.Math.random = () => 0.5
})
after(async () => {
global.Math.random = random
})
it('returns a random address string of the specified length', () => {
expect(getRandomHexString(8)).to.equal('0x' + '88'.repeat(8))
})
})
This diff is collapsed.
/* External Imports */
import chai = require('chai')
import Mocha from 'mocha'
const should = chai.should()
const expect = chai.expect
export { should, expect, Mocha }
{
"extends" : "../../tsconfig.base.json"
}
{
"extends": "../../tslint.base.json"
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment