Skip to content

Commit

Permalink
[xp] handle node minimal fee
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjah committed Apr 24, 2024
1 parent 7fcdbb4 commit 1d47a35
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 30 deletions.
4 changes: 4 additions & 0 deletions packages/massa-web3/open_rpc/massa.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -2765,6 +2765,10 @@
"chain_id": {
"description": "Chain id",
"type": "number"
},
"minimal_fees": {
"description": "Minimal fee",
"type": "string"
}
},
"additionalProperties": false
Expand Down
6 changes: 2 additions & 4 deletions packages/massa-web3/src/experimental/accountOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ export class AccountOperation {
}
const operation = new OperationManager(this.account.privateKey, this.client)
const details: RollOperation = {
// Todo: change with fetchMinimalFees once ready
fee: opts?.fee ?? 0n,
fee: opts?.fee ?? (await this.client.getMinimalFee()),
expirePeriod: await calculateExpirePeriod(
this.client,
opts?.periodToLive
Expand Down Expand Up @@ -97,8 +96,7 @@ export class AccountOperation {

const operation = new OperationManager(this.account.privateKey, this.client)
const details: TransferOperation = {
// Todo: change with fetchMinimalFees once ready
fee: opts?.fee ?? 0n,
fee: opts?.fee ?? (await this.client.getMinimalFee()),
expirePeriod: await calculateExpirePeriod(
this.client,
opts?.periodToLive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export enum OperationType {
* Period to live is the number of periods the operation is valid for.
* This value must be positive and if it's too big, the node will (sliently?) reject the operation.
*
* If no fee is provided, 0 is used.
* If no fee is provided, minimal fee of connected node is used.
* If no periodToLive is provided, the DefaultPeriodToLive is used.
*/
export type OptOpDetails = {
Expand Down Expand Up @@ -245,8 +245,8 @@ export class OperationManager {
throw new Error('blockchainClient is mandatory to send operations')
}

const networkId = await this.blockchainClient.fetchChainId()
const signature = await this.sign(networkId, operation)
const chainId = await this.blockchainClient.getChainId()
const signature = await this.sign(chainId, operation)
const data = OperationManager.serialize(operation)
const publicKey = await this.privateKey.getPublicKey()

Expand Down
6 changes: 3 additions & 3 deletions packages/massa-web3/src/experimental/basicElements/storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fromMAS } from '@massalabs/web3-utils'
import { toNanoMas } from '../utils'

/**
* Calculates the cost of deploying a smart contract.
Expand All @@ -12,7 +12,7 @@ export class StorageCost {
* @returns The cost in the smallest unit of the Massa currency.
*/
static bytes(numberOfBytes: number): bigint {
return BigInt(numberOfBytes) * fromMAS(0.0001)
return BigInt(numberOfBytes) * toNanoMas(0.0001)
}

/**
Expand All @@ -21,7 +21,7 @@ export class StorageCost {
* @returns The cost in the smallest unit of the Massa currency.
*/
static account(): bigint {
return fromMAS(0.001)
return toNanoMas(0.001)
}

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/massa-web3/src/experimental/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OperationStatus } from '../basicElements'
import { Slot } from '../generated/client'
import { NodeStatus, Slot } from '../generated/client'

export interface SendOperationInput {
data: Uint8Array
Expand All @@ -26,9 +26,11 @@ export interface EventFilter {
*/
export interface BlockchainClient {
sendOperation(data: SendOperationInput): Promise<string>
fetchChainId(): Promise<bigint>
fetchPeriod(): Promise<number>
getOperationStatus(operationId: string): Promise<OperationStatus>
getBalance(address: string, speculative?: boolean): Promise<bigint>
getEvents(filter: EventFilter): Promise<SCOutputEvent[]>
getChainId(): Promise<bigint>
getMinimalFee(): Promise<bigint>
status: NodeStatus
}
9 changes: 8 additions & 1 deletion packages/massa-web3/src/experimental/generated/client.ts

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions packages/massa-web3/src/experimental/publicAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
OperationId,
AddressFilter,
} from './generated/client'
import { toNanoMas } from './utils'

export enum Transport {
webSocket = 'websocket',
Expand Down Expand Up @@ -236,15 +237,22 @@ export class PublicAPI {
}

async getStatus(): Promise<NodeStatus> {
return this.connector.get_status()
this.status = await this.connector.get_status()
return this.status
}

async fetchChainId(): Promise<bigint> {
if (this.status) {
return BigInt(this.status.chain_id)
async getMinimalFee(): Promise<bigint> {
if (!this.status) {
await this.getStatus()
}
return toNanoMas(this.status.minimal_fees)
}

return this.getStatus().then((r) => BigInt(r.chain_id))
async getChainId(): Promise<bigint> {
if (!this.status) {
await this.getStatus()
}
return BigInt(this.status.chain_id)
}

async fetchPeriod(): Promise<number> {
Expand Down
4 changes: 2 additions & 2 deletions packages/massa-web3/src/experimental/smartContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class ByteCode {
): Promise<Operation> {
const operation = new OperationManager(privateKey, client)
const details: ExecuteOperation = {
fee: opts?.fee ?? 0n,
fee: opts?.fee ?? (await client.getMinimalFee()),
expirePeriod: await calculateExpirePeriod(client, opts?.periodToLive),
type: OperationType.ExecuteSmartContractBytecode,
coins: opts?.coins ?? 0n,
Expand Down Expand Up @@ -200,7 +200,7 @@ export class SmartContract {
account.privateKey,
byteCode,
{
fee: opts?.fee,
fee: opts?.fee ?? (await client.getMinimalFee()),
periodToLive: opts?.periodToLive,
coins: opts?.coins,
maxGas: opts?.maxGas,
Expand Down
1 change: 1 addition & 0 deletions packages/massa-web3/src/experimental/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './numbers'
16 changes: 16 additions & 0 deletions packages/massa-web3/src/experimental/utils/numbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const MAS_DECIMALS = 9

/**
* Convert Mas float amount to 9 decimals bigint
*
* @param amount MAS amount in floating point representation

Check warning on line 6 in packages/massa-web3/src/experimental/utils/numbers.ts

View workflow job for this annotation

GitHub Actions / test / build

tsdoc-param-tag-missing-hyphen: The @param block should be followed by a parameter name and then a hyphen

Check warning on line 6 in packages/massa-web3/src/experimental/utils/numbers.ts

View workflow job for this annotation

GitHub Actions / build

tsdoc-param-tag-missing-hyphen: The @param block should be followed by a parameter name and then a hyphen
* @returns amount in nanoMAS
*/
export const toNanoMas = (amount: string | number): bigint => {
if (typeof amount === 'number') {
amount = amount.toString()
}
let [integerPart, decimalPart] = amount.split('.')
decimalPart = decimalPart ?? ''
return BigInt(integerPart + decimalPart.padEnd(MAS_DECIMALS, '0'))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,34 @@ import { OperationStatus } from '../../../src/experimental/basicElements'
import { JsonRPCClient } from '../../../src/experimental/jsonRPCClient'
import 'dotenv/config'

describe('Basic use cases', () => {
test('AccountOperation - transfer', async () => {
const account = await Account.fromEnv()
describe('AccountOperation tests', () => {
let client: JsonRPCClient
let account: Account
let accountOperation: AccountOperation

const client = new JsonRPCClient('https://buildnet.massa.net/api/v2')
beforeAll(async () => {
client = new JsonRPCClient('https://buildnet.massa.net/api/v2')
account = await Account.fromEnv()
accountOperation = new AccountOperation(account, client)
})

const accountOperation = new AccountOperation(account, client)
test('transfer', async () => {
const transfer = await accountOperation.transfer(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53',
1n
)
expect(await transfer.getStatus()).toBe(OperationStatus.NotFound)
})

test('not enough fee', async () => {
expect(
accountOperation.transfer(
'AU1wN8rn4SkwYSTDF3dHFY4U28KtsqKL1NnEjDZhHnHEy6cEQm53',
1n,
{ fee: 1n }
)
).rejects.toThrow(
'Bad request: fee is too low provided: 0.000000001 , minimal_fees required: 0.01'
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import { ByteCode } from '../../../src/experimental/smartContract'
import 'dotenv/config'
import { JsonRPCClient } from '../../../src/experimental/jsonRPCClient'

describe('Basic use cases', () => {
test('Smart Contract - execute', async () => {
const account = await Account.fromEnv()
describe('Smart Contract', () => {
let client: JsonRPCClient
let account: Account

const client = new JsonRPCClient('https://buildnet.massa.net/api/v2')
beforeAll(async () => {
client = new JsonRPCClient('https://buildnet.massa.net/api/v2')
account = await Account.fromEnv()
})

test('execute', async () => {
const byteCode = new Uint8Array([1, 2, 3, 4])
const opts = {
fee: 1n,
periodToLive: 2,
coins: 3n,
maxGas: 4n,
Expand All @@ -24,4 +27,20 @@ describe('Basic use cases', () => {
)
expect(await contract.getSpeculativeEvents()).toHaveLength(1)
}, 61000)

test('not enough fee', async () => {
const byteCode = new Uint8Array([1, 2, 3, 4])
const opts = {
fee: 1n,
periodToLive: 2,
coins: 3n,
maxGas: 4n,
}

expect(
ByteCode.execute(client, account.privateKey, byteCode, opts)
).rejects.toThrow(
'Bad request: fee is too low provided: 0.000000001 , minimal_fees required: 0.01'
)
})
})
39 changes: 39 additions & 0 deletions packages/massa-web3/test/experimental/unit/numbers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { toNanoMas } from '../../../src/experimental/utils'

describe('amount format conversion tests', () => {
it('MAS(string) => nMAS', () => {
let amount = '0'
expect(toNanoMas(amount)).toStrictEqual(BigInt(0))

amount = '1.5234'
expect(toNanoMas(amount)).toStrictEqual(BigInt('1523400000'))

amount = '0.123456789'
expect(toNanoMas(amount)).toStrictEqual(BigInt('123456789'))

amount = '123456789'
expect(toNanoMas(amount)).toStrictEqual(BigInt('123456789000000000'))

amount = '123456789.123456789'
expect(toNanoMas(amount)).toStrictEqual(BigInt('123456789123456789'))
})

it('MAS(number) => nMAS', () => {
let amount = 0
expect(toNanoMas(amount)).toStrictEqual(BigInt(0))

amount = 1.5234
expect(toNanoMas(amount)).toStrictEqual(BigInt('1523400000'))

amount = 0.123456789
expect(toNanoMas(amount)).toStrictEqual(BigInt('123456789'))

amount = 123456789
expect(toNanoMas(amount)).toStrictEqual(BigInt('123456789000000000'))

// max safe floating point number
// see https://stackoverflow.com/questions/45929493/node-js-maximum-safe-floating-point-number
amount = 8388607.123456789
expect(toNanoMas(amount)).toStrictEqual(BigInt('8388607123456789'))
})
})

1 comment on commit 1d47a35

@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 39.15% 556/1420
🔴 Branches 15.36% 57/371
🔴 Functions 27.02% 77/285
🔴 Lines 38.88% 544/1399

Test suite run success

10 tests passing in 4 suites.

Report generated by 🧪jest coverage report action from 1d47a35

Please sign in to comment.