Skip to content

Commit

Permalink
refactor!: fix spend types
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `buildRawTx` not exported anymore
Use a general `buildTx` instead.
  • Loading branch information
davidyuk committed Jun 16, 2022
1 parent 8f54ea5 commit 6b089e7
Show file tree
Hide file tree
Showing 11 changed files with 53 additions and 86 deletions.
2 changes: 1 addition & 1 deletion src/ae/aens.ts
Expand Up @@ -366,5 +366,5 @@ export async function aensBid (
nameFee: BigNumber,
options: Omit<Parameters<typeof aensClaim>[2], 'nameFee' | 'VSN'>
): ReturnType<typeof aensClaim> {
return await aensClaim(name, 0, { ...options, nameFee, VSN: 2 })
return await aensClaim(name, 0, { ...options, nameFee })
}
4 changes: 2 additions & 2 deletions src/chain.ts
Expand Up @@ -428,8 +428,8 @@ export async function getName (
export async function resolveName <Type extends 'ak' | 'ct'> (
nameOrId: AensName | EncodedData<Type>,
key: string,
{ verify = true, resolveByNode, onNode }:
{ verify?: boolean, resolveByNode: boolean, onNode: Node }
{ verify = true, resolveByNode = false, onNode }:
{ verify?: boolean, resolveByNode?: boolean, onNode: Node }
): Promise<EncodedData<Type | 'nm'>> {
try {
const id = nameOrId as EncodedData<Type>
Expand Down
6 changes: 3 additions & 3 deletions src/channel/handlers.ts
Expand Up @@ -43,17 +43,17 @@ import {
import Channel from '.'
import { TX_TYPE } from '../tx/builder/schema'

async function appendSignature (
export async function appendSignature (
tx: EncodedData<'tx'>, signFn: SignTx
): Promise<EncodedData<'tx'> | number | null> {
const { signatures, encodedTx } = unpackTx(tx, TX_TYPE.signed).tx
const result = await signFn(encode(encodedTx.rlpEncoded, 'tx'))
if (typeof result === 'string') {
const { tx: signedTx, txType } = unpackTx(result, TX_TYPE.signed)
const { tx: signedTx } = unpackTx(result, TX_TYPE.signed)
return buildTx({
signatures: signatures.concat(signedTx.signatures),
encodedTx: signedTx.encodedTx.rlpEncoded
}, txType).tx
}, TX_TYPE.signed).tx
}
return result
}
Expand Down
2 changes: 1 addition & 1 deletion src/contract/ga.ts
Expand Up @@ -151,7 +151,7 @@ export async function createMetaTx (
}
// @ts-expect-error createMetaTx needs to be integrated into tx builder
const { fee } = await prepareTxParams(TX_TYPE.gaMeta, { ...params, onNode })
const { rlpEncoded: metaTxRlp } = buildTx({ ...params, fee }, TX_TYPE.gaMeta, { vsn: 2 })
const { rlpEncoded: metaTxRlp } = buildTx({ ...params, fee }, TX_TYPE.gaMeta)
return wrapInEmptySignedTx(metaTxRlp).tx
}

Expand Down
4 changes: 2 additions & 2 deletions src/tx/builder/field-types.ts
Expand Up @@ -97,9 +97,9 @@ export class Deposit extends Field {
}

export class GasPrice extends Field {
static serialize (value = MIN_GAS_PRICE): Buffer {
static serialize (value: number | string | BigNumber = MIN_GAS_PRICE): Buffer {
if (+value < MIN_GAS_PRICE) {
throw new IllegalArgumentError(`Gas price ${value} must be bigger then ${MIN_GAS_PRICE}`)
throw new IllegalArgumentError(`Gas price ${value.toString()} must be bigger then ${MIN_GAS_PRICE}`)
}
return writeInt(value)
}
Expand Down
76 changes: 24 additions & 52 deletions src/tx/builder/index.ts
Expand Up @@ -8,7 +8,6 @@ import { Field } from './field-types'
import {
DEFAULT_FEE,
FIELD_TYPES,
MIN_GAS_PRICE,
RawTxObject,
TxField,
TxTypeSchemas,
Expand Down Expand Up @@ -346,41 +345,6 @@ export function validateParams (
)
}

interface TxOptionsRaw {
excludeKeys?: string[]
denomination?: AE_AMOUNT_FORMATS
}
/**
* Build binary transaction
* @param params - Object with tx params
* @param schema - Transaction schema
* @param options - options
* @param options.excludeKeys - Array of keys to exclude for validation and build
* @param options.denomination - Denomination of amounts
* @throws {@link InvalidTxParamsError}
* @returns Array with binary fields of transaction
*/
export function buildRawTx (
params: TxParamsCommon,
schema: TxField[],
{ excludeKeys = [], denomination = AE_AMOUNT_FORMATS.AETTOS }: TxOptionsRaw = {}
): Buffer[] {
params.gasPrice ??= MIN_GAS_PRICE
const filteredSchema = schema.filter(([key]) => !excludeKeys.includes(key))

// Transform `amount` type fields to `aettos`
params = transformParams(params, filteredSchema, { denomination })
// Validation
const valid = validateParams(params, schema, { excludeKeys })
if (Object.keys(valid).length > 0) {
throw new InvalidTxParamsError('Transaction build error. ' + JSON.stringify(valid))
}

return filteredSchema
.map(([key, fieldType]: [keyof TxSchema, FIELD_TYPES, EncodingType]) => serializeField(
params[key], fieldType, params))
}

/**
* Unpack binary transaction
* @param binary - Array with binary transaction field's
Expand Down Expand Up @@ -409,46 +373,54 @@ export interface BuiltTx<Tx extends TxSchema, Prefix extends EncodingType> {
txObject: RawTxObject<Tx>
}

export type TxParamsBuild = TxParamsCommon & {
denomination?: AE_AMOUNT_FORMATS
}
/**
* Build transaction hash
* @param params - Object with tx params
* @param _params - Object with tx params
* @param type - Transaction type
* @param options - options
* @param options.excludeKeys - Array of keys to exclude for validation and build
* @param options.denomination - Denomination of amounts
* @param options.prefix - Prefix of transaction
* @throws {@link InvalidTxParamsError}
* @returns object
* @returns object.tx Base64Check transaction hash with 'tx_' prefix
* @returns object.rlpEncoded rlp encoded transaction
* @returns object.binary binary transaction
*/
export function buildTx<Prefix> (
params: TxParamsBuild,
type: TX_TYPE,
export function buildTx<TxType extends TX_TYPE, Prefix> (
_params: Omit<TxTypeSchemas[TxType], 'tag' | 'VSN'> & { VSN?: number },
type: TxType,
{ excludeKeys = [], prefix = 'tx', vsn, denomination = AE_AMOUNT_FORMATS.AETTOS }: {
excludeKeys?: string[]
prefix?: EncodingType
vsn?: number
denomination?: AE_AMOUNT_FORMATS
} = {}
): Prefix extends EncodingType
? BuiltTx<TxSchema, Prefix>
: BuiltTx<TxSchema, 'tx'> {
): BuiltTx<TxSchema, Prefix extends EncodingType ? Prefix : 'tx'> {
const schemas = TX_SCHEMA[type]

vsn ??= Math.max(...Object.keys(schemas).map(a => +a))
if (!isKeyOfObject(vsn, schemas)) throw new SchemaNotFoundError('serialization', TX_TYPE[type], vsn)

const schema = schemas[vsn]
const schema = schemas[vsn] as unknown as TxField[]

let params = _params as TxParamsCommon & { onNode: Node }
params.VSN = vsn
params.tag = type
const filteredSchema = schema.filter(([key]) => !excludeKeys.includes(key))

// Transform `amount` type fields to `aettos`
params = transformParams(params, filteredSchema, { denomination })
// Validation
const valid = validateParams(params, schema, { excludeKeys })
if (Object.keys(valid).length > 0) {
throw new InvalidTxParamsError('Transaction build error. ' + JSON.stringify(valid))
}

const binary = buildRawTx(
{ ...params, VSN: vsn, tag: type },
schema,
{ excludeKeys, denomination: params.denomination ?? denomination }
).filter(e => e !== undefined)
const binary = filteredSchema
.map(([key, fieldType]: [keyof TxSchema, FIELD_TYPES, EncodingType]) => serializeField(
params[key], fieldType, params))
.filter(e => e !== undefined)

const rlpEncoded = rlpEncode(binary)
const tx = encode(rlpEncoded, prefix)
Expand Down
9 changes: 7 additions & 2 deletions src/tx/builder/schema.ts
Expand Up @@ -198,7 +198,7 @@ interface BuildFieldTypes<Prefix extends undefined | EncodingType | readonly Enc
[FIELD_TYPES.abiVersion]: ABI_VERSIONS
[FIELD_TYPES.ttlType]: ORACLE_TTL_TYPES
[FIELD_TYPES.sophiaCodeTypeInfo]: any
[FIELD_TYPES.payload]: string
[FIELD_TYPES.payload]: string | undefined
[FIELD_TYPES.stateTree]: any
}

Expand Down Expand Up @@ -293,11 +293,16 @@ type BuildTxArgBySchemaType<
? BuildFieldTypes<Prefix>[Type]
: never

type NullablePartial<
T,
NK extends keyof T = { [K in keyof T]: undefined extends T[K] ? K : never }[keyof T]
> = Partial<Pick<T, NK>> & Omit<T, NK>

type BuildTxArgBySchema<SchemaLine> =
UnionToIntersection<
SchemaLine extends ReadonlyArray<infer Elem>
? Elem extends TxElem
? { [k in Elem[0]]: BuildTxArgBySchemaType<Elem[1], Elem[2]> }
? NullablePartial<{ [k in Elem[0]]: BuildTxArgBySchemaType<Elem[1], Elem[2]> }>
: never
: never
>
Expand Down
18 changes: 9 additions & 9 deletions src/tx/index.ts
Expand Up @@ -23,7 +23,7 @@
* These methods provide ability to create native transactions.
*/
import {
ABI_VERSIONS, CtVersion, PROTOCOL_VM_ABI, TX_TYPE, TX_TTL, TxParamsCommon, TxTypeSchemas
ABI_VERSIONS, CtVersion, PROTOCOL_VM_ABI, TX_TYPE, TX_TTL, TxParamsCommon
} from './builder/schema'
import {
ArgumentError, UnsupportedProtocolError, UnknownTxError, InvalidTxParamsError
Expand All @@ -35,6 +35,8 @@ import { buildTx as syncBuildTx, calculateFee, unpackTx } from './builder/index'
import { isKeyOfObject } from '../utils/other'
import AccountBase from '../account/base'

type Int = number | string | BigNumber

// uses a new feature, probably typescript-eslint doesn't support it yet
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type BuildTxOptions <TxType extends TX_TYPE, OmitFields extends string> =
Expand All @@ -43,7 +45,9 @@ export type BuildTxOptions <TxType extends TX_TYPE, OmitFields extends string> =
// TODO: find a better name or rearrange methods
export async function _buildTx<TxType extends TX_TYPE> (
txType: TxType,
{ onAccount, ..._params }: TxTypeSchemas[TxType] & { onNode: Node, onAccount: AccountBase }
{ onAccount, ..._params }:
Omit<Parameters<typeof syncBuildTx<TxType, 'tx'>>[0], 'fee' | 'nonce' | 'ttl'>
& { onNode: Node, onAccount: AccountBase, fee?: Int, nonce?: number, ttl?: number }
): Promise<EncodedData<'tx'>> {
// TODO: avoid this assertion
const params = _params as unknown as TxParamsCommon & { onNode: Node }
Expand Down Expand Up @@ -103,7 +107,7 @@ export async function _buildTx<TxType extends TX_TYPE> (
// TODO: do this check on TypeScript level
if (senderId == null) throw new InvalidTxParamsError(`Transaction field ${senderKey} is missed`)
const extraParams = await prepareTxParams(txType, { ...params, senderId })
return syncBuildTx({ ...params, ...extraParams }, txType).tx
return syncBuildTx({ ...params, ...extraParams } as any, txType).tx
}

/**
Expand Down Expand Up @@ -159,17 +163,13 @@ export async function prepareTxParams (
}: Pick<TxParamsCommon, 'nonce' | 'ttl' | 'fee'> & {
senderId: EncodedData<'ak'>
vsn?: number
gasLimit?: number | string | BigNumber
gasLimit?: Int
absoluteTtl?: boolean
strategy?: 'continuity' | 'max'
showWarning?: boolean
onNode: Node
}
): Promise<{
fee: number | string | BigNumber
ttl: number
nonce: number
}> {
): Promise<{ fee: Int, ttl: number, nonce: number }> {
nonce ??= (
await onNode.getAccountNextNonce(senderId, { strategy }).catch(() => ({ nextNonce: 1 }))
).nextNonce
Expand Down
15 changes: 3 additions & 12 deletions test/integration/channel.ts
Expand Up @@ -32,6 +32,7 @@ import {
ChannelConnectionError
} from '../../src/utils/errors'
import { EncodedData } from './../../src/utils/encoder'
import { appendSignature } from '../../src/channel/handlers'

const wsUrl = process.env.TEST_WS_URL ?? 'ws://localhost:3014/channel'

Expand Down Expand Up @@ -1146,16 +1147,6 @@ describe('Channel', function () {
})

it('can post backchannel update', async () => {
function appendSignature (target: EncodedData<'tx'>, source: EncodedData<'tx'>): EncodedData<'tx'> {
const { txType, tx: { signatures, encodedTx: { rlpEncoded } } } =
unpackTx(target, TX_TYPE.signed)
return buildTx(
{
signatures: signatures.concat(unpackTx(source, TX_TYPE.signed).tx.signatures),
encodedTx: rlpEncoded
}, txType).tx
}

initiatorCh.disconnect()
responderCh.disconnect()
initiatorCh = await Channel.initialize({
Expand Down Expand Up @@ -1183,9 +1174,9 @@ describe('Channel', function () {
await aeSdkInitiatior.address(),
await aeSdkResponder.address(),
100,
async (tx) => appendSignature(
async (tx) => await appendSignature(
await aeSdkResponder.signTransaction(tx),
await aeSdkInitiatior.signTransaction(tx)
async tx => await (aeSdkInitiatior.signTransaction(tx) as Promise<EncodedData<'tx'>>)
)
)
result.accepted.should.equal(true)
Expand Down
1 change: 0 additions & 1 deletion test/integration/index.ts
Expand Up @@ -55,7 +55,6 @@ export const BaseAe = async (
export const spendPromise = (async () => {
const ae = await BaseAe({ networkId, withoutGenesisAccount: false })
await ae.awaitHeight(2)
// @ts-expect-error
await ae.spend(1e26, account.publicKey)
})()

Expand Down
2 changes: 1 addition & 1 deletion test/unit/tx.ts
Expand Up @@ -115,7 +115,7 @@ describe('Tx', function () {
})

it('Serialize tx: invalid tx VSN', () => {
expect(() => buildTx({}, TX_TYPE.spend, { vsn: 5 }))
expect(() => buildTx({} as any, TX_TYPE.spend, { vsn: 5 }))
.to.throw(SchemaNotFoundError, 'Transaction serialization not implemented for spend version 5')
})
})

0 comments on commit 6b089e7

Please sign in to comment.