Skip to content

Commit

Permalink
chore: handle contract negative value calls
Browse files Browse the repository at this point in the history
Signed-off-by: nikolay <[email protected]>
  • Loading branch information
natanasow committed Jan 14, 2025
1 parent 67b61c6 commit bde9a91
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 10 deletions.
51 changes: 45 additions & 6 deletions packages/relay/src/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
*
*/

import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
import { BigNumber as BN } from 'bignumber.js';
import crypto from 'crypto';

import constants from './lib/constants';
import { BigNumber as BN } from 'bignumber.js';
import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
import { Transaction, Transaction1559, Transaction2930 } from './lib/model';

const EMPTY_HEX = '0x';
Expand Down Expand Up @@ -178,7 +179,7 @@ const formatContractResult = (cr: any) => {
transactionIndex: nullableNumberTo0x(cr.transaction_index),
type: cr.type === null ? '0x0' : nanOrNumberTo0x(cr.type),
v: cr.v === null ? '0x0' : nanOrNumberTo0x(cr.v),
value: nanOrNumberTo0x(tinybarsToWeibars(cr.amount)),
value: nanOrNumberInt64To0x(tinybarsToWeibars(cr.amount, true)),
// for legacy EIP155 with tx.chainId=0x0, mirror-node will return a '0x' (EMPTY_HEX) value for contract result's chain_id
// which is incompatibile with certain tools (i.e. foundry). By setting this field, chainId, to undefined, the end jsonrpc
// object will leave out this field, which is the proper behavior for other tools to be compatible with.
Expand Down Expand Up @@ -265,6 +266,33 @@ const nanOrNumberTo0x = (input: number | BigNumber | bigint | null): string => {
return input == null || Number.isNaN(input) ? numberTo0x(0) : numberTo0x(input);
};

const nanOrNumberInt64To0x = (input: number | BigNumber | bigint | null): string => {
if (input && input < 0) {
// the hex of a negative number can be obtained from the binary value of that number positive value,
// the binary value needs to be negated and then, to add 1,

// how the transformation works (using 16 bits)
// a 16 bits integer variables has values from -32768 to +32767, so:
// 0 - 0x0000 - 0000 0000 0000 0000
// 32767 - 0x7fff - 0111 1111 1111 1111
// -32768 - 0x8000 - 1000 0000 0000 0000
// -1 - 0xffff - 1111 1111 1111 1111

// converting int16 -10 will be done as following:
// - make it positive = 10
// - 16 bits binary value of 10 = 0000 0000 0000 1010
// - inverse the bytes = 1111 1111 1111 0101
// - adding +1 = 1111 1111 1111 0110
// - 1111 1111 1111 0110 bits = 0xfff6

// we're using 64 bits integer because that the type returned by the mirror node - int64
const bits = 64;
return numberTo0x((BigInt(input.toString()) + (BigInt(1) << BigInt(bits))) % (BigInt(1) << BigInt(bits)));

Check warning on line 290 in packages/relay/src/formatters.ts

View check run for this annotation

Codecov / codecov/patch

packages/relay/src/formatters.ts#L289-L290

Added lines #L289 - L290 were not covered by tests
}

return nanOrNumberTo0x(input);
};

const toHash32 = (value: string): string => {
return value.substring(0, 66);
};
Expand Down Expand Up @@ -303,8 +331,18 @@ const getFunctionSelector = (data?: string): string => {
return data.replace(/^0x/, '').substring(0, 8);
};

const tinybarsToWeibars = (value: number | null) => {
if (value && value < 0) throw new Error('Invalid value - cannot pass negative number');
const tinybarsToWeibars = (value: number | null, allowNegativeValues: boolean = false) => {
if (value && value < 0) {
// negative amount can be received only by CONTRACT_NEGATIVE_VALUE revert
// e.g. tx https://hashscan.io/mainnet/transaction/1735241436.856862230
// that's not a valid revert in the Ethereum world so we must NOT multiply
// the amount sent via CONTRACT_CALL SDK call by TINYBAR_TO_WEIBAR_COEF
// also, keep in mind that the mirror node returned amount is typed with int64
if (allowNegativeValues) return value;

throw new Error('Invalid value - cannot pass negative number');

Check warning on line 343 in packages/relay/src/formatters.ts

View check run for this annotation

Codecov / codecov/patch

packages/relay/src/formatters.ts#L343

Added line #L343 was not covered by tests
}

if (value && value > constants.TOTAL_SUPPLY_TINYBARS)
throw new Error('Value cannot be more than the total supply of tinybars in the blockchain');

Expand All @@ -324,6 +362,7 @@ export {
numberTo0x,
nullableNumberTo0x,
nanOrNumberTo0x,
nanOrNumberInt64To0x,
toHash32,
toNullableBigNumber,
toNullIfEmptyHex,
Expand Down
28 changes: 24 additions & 4 deletions packages/relay/tests/lib/formatters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
*
*/

import { BigNumber as BN } from 'bignumber.js';
import { expect } from 'chai';
import { AbiCoder, keccak256 } from 'ethers';

import {
ASCIIToHex,
decodeErrorMessage,
Expand All @@ -31,26 +34,25 @@ import {
isHex,
isValidEthereumAddress,
mapKeysAndValues,
nanOrNumberInt64To0x,
nanOrNumberTo0x,
nullableNumberTo0x,
numberTo0x,
parseNumericEnvVar,
prepend0x,
strip0x,
tinybarsToWeibars,
toHash32,
toHexString,
toNullableBigNumber,
toNullIfEmptyHex,
trimPrecedingZeros,
weibarHexToTinyBarInt,
tinybarsToWeibars,
} from '../../src/formatters';
import constants from '../../src/lib/constants';
import { BigNumber as BN } from 'bignumber.js';
import { AbiCoder, keccak256 } from 'ethers';
import { overrideEnvsInMochaDescribe } from '../helpers';

describe('Formatters', () => {
describe.only('Formatters', () => {
describe('formatRequestIdMessage', () => {
const exampleRequestId = '46530e63-e33a-4f42-8e44-b125f99f1a9b';
const expectedFormattedId = '[Request ID: 46530e63-e33a-4f42-8e44-b125f99f1a9b]';
Expand Down Expand Up @@ -399,6 +401,21 @@ describe('Formatters', () => {
});
});

describe('nanOrNumberInt64To0x', () => {
it('should return 0x0 for nullable input', () => {
expect(nanOrNumberInt64To0x(null)).to.equal('0x0');
});
it('should return 0x0 for NaN input', () => {
expect(nanOrNumberInt64To0x(NaN)).to.equal('0x0');
});
it('should convert a valid number', () => {
expect(nanOrNumberInt64To0x(593)).to.equal('0x251');
});
it('should covert a valid negative int64 number', () => {
expect(nanOrNumberInt64To0x(-10)).to.equal('0xfffffffffffffff6');
});
});

describe('toHash32', () => {
it('should format more than 32 bytes hash to 32 bytes', () => {
expect(
Expand Down Expand Up @@ -757,5 +774,8 @@ describe('Formatters', () => {
'Value cannot be more than the total supply of tinybars in the blockchain',
);
});
it('should return the negative number if allowNegativeValues flag is set to true', () => {
expect(tinybarsToWeibars(-10, true)).to.eql(-10);
});
});
});

0 comments on commit bde9a91

Please sign in to comment.