Skip to content

Commit

Permalink
Add test to operationManager (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben-Rey authored Apr 25, 2024
1 parent ac005b7 commit cf21d2b
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 27 deletions.
1 change: 1 addition & 0 deletions packages/massa-web3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"generate": "npm-run-all generate:*",
"test": "jest --detectOpenHandles --forceExit ./test/experimental/unit",
"test:cov": "jest --detectOpenHandles --coverage --forceExit ./test/experimental/unit",
"test:cov:watch": "jest --detectOpenHandles --coverage --watch ./test/experimental/unit",
"test:integration": "jest --detectOpenHandles --forceExit ./test/experimental/integration",
"test:all": "npm run test && npm run test:integration",
"test:watch": "jest --watch",
Expand Down
8 changes: 4 additions & 4 deletions packages/massa-web3/src/experimental/accountOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export class AccountOperation {
const operation = new OperationManager(this.account.privateKey, this.client)
const details: RollOperation = {
fee: opts?.fee ?? (await this.client.getMinimalFee()),
expirePeriod: await calculateExpirePeriod(
this.client,
expirePeriod: calculateExpirePeriod(
await this.client.fetchPeriod(),
opts?.periodToLive
),
type,
Expand Down Expand Up @@ -97,8 +97,8 @@ export class AccountOperation {
const operation = new OperationManager(this.account.privateKey, this.client)
const details: TransferOperation = {
fee: opts?.fee ?? (await this.client.getMinimalFee()),
expirePeriod: await calculateExpirePeriod(
this.client,
expirePeriod: calculateExpirePeriod(
await this.client.fetchPeriod(),
opts?.periodToLive
),
type: OperationType.Transaction,
Expand Down
14 changes: 5 additions & 9 deletions packages/massa-web3/src/experimental/basicElements/operation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EventFilter, SCOutputEvent } from '../client'
import { SCOutputEvent } from '../client'
import { BlockchainClient } from '../client'

/**
Expand Down Expand Up @@ -89,12 +89,10 @@ export class Operation {
return Promise.reject(new Error('Operation not found'))
}

const filter = {
return this.client.getEvents({
operationId: this.id,
isFinal: true,
} as EventFilter

return this.client.getEvents(filter)
})
}

/**
Expand All @@ -107,12 +105,10 @@ export class Operation {
return Promise.reject(new Error('Operation not found'))
}

const filter = {
return this.client.getEvents({
operationId: this.id,
isFinal: false,
} as EventFilter

return this.client.getEvents(filter)
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,23 +262,22 @@ export class OperationManager {
* Calculates the expire period.
*
* @remarks
* This function fetches the current period from the blockchain and adds the periodToLive to it.
* If the periodToLive is too big, the node will silently reject the operation.
* This is why the periodToLive is limited to an upper value.
*
* @param client - The blockchain client.
* @param period - The current period.
* @param periodToLive - The period to live.
*
* @returns The expire period.
* @throws An error if the periodToLive is too low or too big.
*/
export async function calculateExpirePeriod(
client: BlockchainClient,
export function calculateExpirePeriod(
period: number,
periodToLive: number = 10
): Promise<number> {
): number {
// Todo: adjust max value
if (periodToLive < 1 || periodToLive > 100) {
throw new Error('periodToLive must be between 1 and 100')
}
return (await client.fetchPeriod()) + periodToLive
return period + periodToLive
}
5 changes: 4 additions & 1 deletion packages/massa-web3/src/experimental/smartContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export class ByteCode {
const operation = new OperationManager(privateKey, client)
const details: ExecuteOperation = {
fee: opts?.fee ?? (await client.getMinimalFee()),
expirePeriod: await calculateExpirePeriod(client, opts?.periodToLive),
expirePeriod: calculateExpirePeriod(
await client.fetchPeriod(),
opts?.periodToLive
),
type: OperationType.ExecuteSmartContractBytecode,
coins: opts?.coins ?? 0n,
// TODO: implement max gas
Expand Down
2 changes: 2 additions & 0 deletions packages/massa-web3/src/web3/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ export class BaseClient {
rollsAmountEncoded,
])
}
default:
throw new Error('operation type not supported')
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { OperationStatus } from '../../../../src/experimental/basicElements/operation'
import {
BlockchainClient,
EventFilter,
SCOutputEvent,
SendOperationInput,
} from '../../../../src/experimental/client/interfaces'

import { NodeStatus } from '../../../../src/experimental/generated/client'

export const blockchainClientMock: BlockchainClient = {
sendOperation: jest.fn((data: SendOperationInput) =>
Promise.resolve('operationId')
),
fetchPeriod: jest.fn(() => Promise.resolve(1)),
getOperationStatus: jest.fn((operationId: string) =>
Promise.resolve(OperationStatus.Success)
),
getBalance: jest.fn((address: string, speculative?: boolean) =>
Promise.resolve(BigInt(1000))
),
getEvents: jest.fn((filter: EventFilter) =>
Promise.resolve([] as SCOutputEvent[])
),
getChainId: jest.fn(() => Promise.resolve(BigInt(1))),
getMinimalFee: jest.fn(() => Promise.resolve(BigInt(1))),
status: {} as NodeStatus,
}
180 changes: 173 additions & 7 deletions packages/massa-web3/test/experimental/unit/operationManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import {
} from '../../../src/index'
import { getOperationBufferToSign } from '../../../src/web3/accounts/Web3Account'
import { BaseClient, PERIOD_OFFSET } from '../../../src/web3/BaseClient'

import { blockchainClientMock } from './mock/blockchainClient.mock'
import {
ExecuteOperation,
OperationManager,
OperationType,
RollOperation,
Signature,
TransferOperation,
calculateExpirePeriod,
} from '../../../src/experimental/basicElements'
import {
PrivateKey,
Address as XPAddress,
} from '../../../src/experimental/basicElements'
import { PrivateKey, Address } from '../../../src/experimental/basicElements'
import 'dotenv/config'

const clientConfig: IClientConfig = {
Expand All @@ -39,7 +39,7 @@ describe('Unit tests', () => {
type: OperationType.Transaction,
expirePeriod: 2,
amount: 3n,
recipientAddress: XPAddress.fromString(
recipientAddress: Address.fromString(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53'
),
}
Expand All @@ -61,6 +61,31 @@ describe('Unit tests', () => {
)
})

test('serialize - sell roll', async () => {
const sellRoll: RollOperation = {
type: OperationType.RollSell,
expirePeriod: 2,
fee: 1n,
amount: 3n,
}

const transactionData: ITransactionData = {
fee: 1n,
amount: 3n,
recipientAddress: 'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53',
}

expect(OperationManager.serialize(sellRoll)).toEqual(
Uint8Array.from(
new BaseClient(clientConfig).compactBytesForOperation(
transactionData,
OperationTypeId.RollSell,
2
)
)
)
})

test('serialize - execute', async () => {
const execute: ExecuteOperation = {
fee: 1n,
Expand Down Expand Up @@ -95,13 +120,23 @@ describe('Unit tests', () => {
)
})

test('serialize - throw if OperationType is not supported', async () => {
const operation = {
type: -1,
}

expect(() =>
OperationManager.serialize(operation as ExecuteOperation)
).toThrow('Operation type not supported')
})

test('canonicalize', async () => {
const transfer: TransferOperation = {
fee: 1n,
type: OperationType.Transaction,
expirePeriod: 2,
amount: 3n,
recipientAddress: XPAddress.fromString(
recipientAddress: Address.fromString(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53'
),
}
Expand Down Expand Up @@ -168,4 +203,135 @@ describe('Unit tests', () => {
)
)
})

test('deserialize - transfer', async () => {
const transfer: TransferOperation = {
fee: 1n,
type: OperationType.Transaction,
expirePeriod: 2,
amount: 3n,
recipientAddress: Address.fromString(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53'
),
}

const serialized = OperationManager.serialize(transfer)
const deserialized = OperationManager.deserialize(serialized)

expect(deserialized).toEqual(transfer)
})

test('deserialize - roll sell', async () => {
const sellRoll: RollOperation = {
type: OperationType.RollSell,
expirePeriod: 2,
fee: 1n,
amount: 3n,
}

const serialized = OperationManager.serialize(sellRoll)
const deserialized = OperationManager.deserialize(serialized)

expect(deserialized).toEqual(sellRoll)
})

test('sign', async () => {
const transfer: TransferOperation = {
fee: 1n,
type: OperationType.Transaction,
expirePeriod: 2,
amount: 3n,
recipientAddress: Address.fromString(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53'
),
}

const privateKey = await PrivateKey.generate()
const operationManager = new OperationManager(privateKey)
const signature = await operationManager.sign(1n, transfer)

expect(signature).toBeInstanceOf(Signature)
})

test('send throw if blockchainClient is not defined', async () => {
const transfer: TransferOperation = {
fee: 1n,
type: OperationType.Transaction,
expirePeriod: 2,
amount: 3n,
recipientAddress: Address.fromString(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53'
),
}

const privateKey = await PrivateKey.generate()
const operationManager = new OperationManager(privateKey)

await expect(operationManager.send(transfer)).rejects.toThrow(
'blockchainClient is mandatory to send operations'
)
})

test('send', async () => {
const transfer: TransferOperation = {
fee: 1n,
type: OperationType.Transaction,
expirePeriod: 2,
amount: 3n,
recipientAddress: Address.fromString(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53'
),
}

const privateKey = await PrivateKey.generate()

const operationManager = new OperationManager(
privateKey,
blockchainClientMock
)

const operationId = await operationManager.send(transfer)

expect(blockchainClientMock.getChainId).toHaveBeenCalled()
expect(blockchainClientMock.sendOperation).toHaveBeenCalledWith({
data: OperationManager.serialize(transfer),
publicKey: (await privateKey.getPublicKey()).toString(),
signature: (await operationManager.sign(1n, transfer)).toString(),
})
expect(operationId).toBe('operationId')
})
})

describe('calculateExpirePeriod', () => {
test('returns correct expire period', () => {
const period = 1
const periodToLive = 10
const expectedExpirePeriod = period + periodToLive
const expirePeriod = calculateExpirePeriod(period, periodToLive)
expect(expirePeriod).toBe(expectedExpirePeriod)
})

test('returns correct expire period', () => {
const period = 1
const defaultPeriodToLive = 10
const expectedExpirePeriod = period + defaultPeriodToLive
const expirePeriod = calculateExpirePeriod(period)
expect(expirePeriod).toBe(expectedExpirePeriod)
})

test('throws error if periodToLive is less than 1', () => {
const period = 1
const periodToLive = 0
expect(() => calculateExpirePeriod(period, periodToLive)).toThrow(
'periodToLive must be between 1 and 100'
)
})
test('throws error if periodToLive is greater than 100', () => {
const period = 1
const periodToLive = 101

expect(() => calculateExpirePeriod(period, periodToLive)).toThrow(
'periodToLive must be between 1 and 100'
)
})
})

1 comment on commit cf21d2b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for experimental massa-web3

St.
Category Percentage Covered / Total
🔴 Statements 45.96% 659/1434
🔴 Branches 21.81% 82/376
🔴 Functions 37.54% 110/293
🔴 Lines 45.75% 646/1412

Test suite run success

48 tests passing in 7 suites.

Report generated by 🧪jest coverage report action from cf21d2b

Please sign in to comment.