Unit conversions
Many of the calculations related to Ethereum have values that exceed the maximum safe integer of the JavaScript
language. Therefore, some methods are needed on the FMZ Quant Trading Platform to handle large values, which we have used specifically in previous courses and have not covered in detail. This section will discuss this aspect in detail.
Print the maximum safe integer defined in the JavaScript
language:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Running results:
Number.MAX_SAFE_INTEGER: 9007199254740991
BigInt
The smallest unit defined in Ethereum is 1wei
, and the definition 1Gwei
is equal to 1000000000 wei
. 1Gwei
is not really a very large number in Ethereum-related calculations, and some data is much larger than it. So these data with very large values can easily exceed Number.MAX_SAFE_INTEGER: 9007199254740991
.
At FMZ Quant Trading Platform, we use the platform's BigInt
object to represent these very large integer data. Use the constructor BigInt()
to construct the BigInt
object. You can construct BigInt
objects using numeric, hexadecimal numeric strings as parameters. Use the toString()
method of BigInt
object to output the data represented by the object as a string.
The operations supported by the BigInt
object are:
- Addition:
+
- Subtraction:
-
- Multiplication:
*
- Division:
/
- Modulo operations:
%
- Power operations:
*
Refer to the following code examples:
function main() {
// Decimal representation of 1Gwei
var oneGwei = 1000000000
// Decimal to hexadecimal conversion of 1Gwei
var oneGweiForHex = "0x" + oneGwei.toString(16)
Log("oneGwei : ", oneGwei)
Log("oneGweiForHex : ", oneGweiForHex)
// Constructing BigInt objects
Log("1Gwei / 1Gwei : ", (BigInt(oneGwei) / BigInt(oneGweiForHex)).toString(10))
Log("1Gwei * 1Gwei : ", (BigInt(oneGwei) * BigInt(oneGweiForHex)).toString(10))
Log("1Gwei - 1Gwei : ", (BigInt(oneGwei) - BigInt(oneGweiForHex)).toString(10))
Log("1Gwei + 1Gwei : ", (BigInt(oneGwei) + BigInt(oneGweiForHex)).toString(10))
Log("(1Gwei + 1) % 1Gwei : ", (BigInt(oneGwei + 1) % BigInt(oneGweiForHex)).toString(10))
Log("1Gwei ** 2 : ", (BigInt(oneGwei) ** BigInt(2)).toString(10))
Log("The square root of 100 : ", (BigInt(100) ** BigFloat(0.5)).toString(10))
Log("Number.MAX_SAFE_INTEGER : ", BigInt(Number.MAX_SAFE_INTEGER).toString(10))
Log("Number.MAX_SAFE_INTEGER * 2 : ", (BigInt(Number.MAX_SAFE_INTEGER) * BigInt("2")).toString(10))
}
Debugging tool testing:
2023-06-08 11:39:50 Info Number.MAX_SAFE_INTEGER * 2 : 18014398509481982
2023-06-08 11:39:50 Info Number.MAX_SAFE_INTEGER : 9007199254740991
2023-06-08 11:39:50 Info The square root of 100 : 10
2023-06-08 11:39:50 Info 1Gwei ** 2 : 1000000000000000000
2023-06-08 11:39:50 Info (1Gwei + 1) % 1Gwei : 1
2023-06-08 11:39:50 Info 1Gwei + 1Gwei : 2000000000
2023-06-08 11:39:50 Info 1Gwei - 1Gwei : 0
2023-06-08 11:39:50 Info 1Gwei * 1Gwei : 1000000000000000000
2023-06-08 11:39:50 Info 1Gwei / 1Gwei : 1
2023-06-08 11:39:50 Info oneGweiForHex : 0x3b9aca00
2023-06-08 11:39:50 Info oneGwei : 1000000000
BigFloat
The BigFloat
object is used similarly to the BigInt
object to represent floating point numbers with larger values, and it also supports addition, subtraction, multiplication and division.
The BigFloat
object supports the toFixed()
method.
Refer to the following code example:
function main() {
var pi = 3.14
var oneGwei = "1000000000"
var oneGweiForHex = "0x3b9aca00"
Log("pi + oneGwei : ", (BigFloat(pi) + BigFloat(oneGwei)).toFixed(2))
Log("pi - oneGweiForHex : ", (BigFloat(pi) - BigFloat(oneGweiForHex)).toFixed(2))
Log("pi * 2.0 : ", (BigFloat(pi) * BigFloat(2.0)).toFixed(2))
Log("pi / 2.0 : ", (BigFloat(pi) / BigFloat(2.0)).toFixed(2))
}
Debugging tool testing:
2023-06-08 13:56:44 Info pi / 2.0 : 1.57
2023-06-08 13:56:44 Info pi * 2.0 : 6.28
2023-06-08 13:56:44 Info pi - oneGweiForHex : -999999996.86
2023-06-08 13:56:44 Info pi + oneGwei : 1000000003.14
BigDecimal
The BigDecimal
object is compatible with integer values and floating point values, and supports initialization with the BigInt
object and the BigFloat
object, and it also supports addition, subtraction, multiplication and division.
Refer to the following code example:
function main() {
var pi = 3.1415
var oneGwei = 1000000000
var oneGweiForHex = "0x3b9aca00"
Log("pi : ", BigDecimal(pi).toFixed(2))
Log("oneGwei : ", BigDecimal(oneGwei).toString())
Log("oneGweiForHex : ", BigDecimal(BigInt(oneGweiForHex)).toString())
Log("BigInt(oneGwei) : ", BigDecimal(BigInt(oneGwei)).toString())
Log("BigFloat(pi) : ", BigDecimal(BigFloat(pi)).toFixed(4))
Log("oneGwei + pi : ", (BigDecimal(oneGwei) + BigDecimal(pi)).toString())
Log("oneGwei - pi : ", (BigDecimal(oneGwei) - BigDecimal(pi)).toString())
Log("2.0 * pi : ", (BigDecimal(2.0) * BigDecimal(pi)).toString())
Log("pi / pi : ", (BigDecimal(pi) / BigDecimal(pi)).toString())
}
Running in the debugging tool:
2023-06-08 14:52:53 Info pi / pi : 1
2023-06-08 14:52:53 Info 2.0 * pi : 6.283
2023-06-08 14:52:53 Info oneGwei - pi : 999999996.8585
2023-06-08 14:52:53 Info oneGwei + pi : 1000000003.1415
2023-06-08 14:52:53 Info BigFloat(pi) : 3.1415
2023-06-08 14:52:53 Info BigInt(oneGwei) : 1e+9
2023-06-08 14:52:53 Info oneGweiForHex : 1e+9
2023-06-08 14:52:53 Info oneGwei : 1e+9
2023-06-08 14:52:53 Info pi : 3.14
Unit conversions
The following two functions: toAmount()
, toInnerAmount()
we have used many times in previous courses, these two functions are mainly used for data precision conversion.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
The toAmount()
function converts (reduces) a variable s
according to the precision parameter decimals
. In web3 practical development, it is often necessary to deal with some chained hexadecimal data.
We have often encountered this in our previous courses, for example, the data
field data in the Transfer(address,address,uint256)
event of a smart contract:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
When processing data "data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"
, we use the toAmount()
function. This processing is designed to do a good job of converting data field data to readable values.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function main() {
var data = "0x00000000000000000000000000000000000000000000000001c1a55000000000"
Log(toAmount(data, 18)) // Print out 0.12656402755905127
}
1 ETH token, as we know, is 1e18 wei
, if we get a data 126564027559051260
in wei
, how to convert it to ETH tokens?
Using the toAmount(, 18)
function is a very simple conversion method. The toInnerAmount()
function is the reverse operation of the toAmount()
function (depending on the precision, zoom in), and it is easy to convert the data using these two functions.
It is important to note the integer value safety range in the JavaScript language, Number.MAX_SAFE_INTEGER
, and the following example illustrates a hidden problem when converting data:
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
var amount = 0.01
var innerAmount = Number(toInnerAmount(amount, 18))
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER) // 9007199254740991
Log("innerAmount:", innerAmount) // 10000000000000000
Log("typeof(innerAmount):", typeof(innerAmount), ", innerAmount:", innerAmount)
// Decimal value 10000000000000000 -> Hexadecimal value 0x2386f26fc10000
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Log("Convert", BigInt(10000000000000000).toString(10), "to hexadecimal:", BigInt(10000000000000000).toString(16))
Log("0x" + BigInt(10000000000000000).toString(16), "Convert to decimal:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0))
}
It is possible to run in the debugging tool:
2023-06-15 16:21:40 Info Convert 0x2386f26fc10000 to decimal: 10000000000000000
2023-06-15 16:21:40 Info Convert 10000000000000000 to hexadecimal: 2386f26fc10000
2023-06-15 16:21:40 Info Convert 10000000000000000 to hexadecimal: 10000000000000000
2023-06-15 16:21:40 Info typeof(innerAmount): number , innerAmount: 10000000000000000
2023-06-15 16:21:40 Info innerAmount: 10000000000000000
2023-06-15 16:21:40 Info Number.MAX_SAFE_INTEGER: 9007199254740991
Through observation we found that:
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
This line of code corresponds to the log output: Converting 10000000000000000 to hex: 10000000000000000
, which is not converted correctly. The reason is naturally that 10000000000000000 is beyond Number.MAX_SAFE_INTEGER
.
But when the decimal value is within the safe range, i.e., less than Number.MAX_SAFE_INTEGER
, the toString(16)
function converts it properly again, for example:
function main() {
var value = 1000
Log("Convert value to hexadecimal:", "0x" + value.toString(16)) // 0x3e8
Log("Convert 0x3e8 to decimal:", Number("0x3e8")) // 1000
}
In blockchain, even 0.01
ETH converted to a value of 10000000000000000
in wei
will exceed Number.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:
BigInt(10000000000000000).toString(16)```.
Simulation Calls
Executing transactions and calling the Write
method of smart contracts on Ethereum costs a certain amount of gas and sometimes it fails. It is important to know which transactions are likely to fail before sending them and calling them. There are simulated calls on Ethereum for testing.
eth_call
Ethereum's RPC method eth_call
: it can simulate a transaction and return the result of a possible transaction, but it does not actually execute the transaction on the blockchain.
The eth_call
method has 2 parameters, the first one is a dictionary structure, transactionObject
:
// transactionObject
{
"from" : ..., // The address from which the transaction is sent
"to" : ..., // The address to which the transaction is addressed
"gas" : ..., // The integer of gas provided for the transaction execution
"gasPrice" : ..., // The integer of gasPrice used for each paid gas encoded as hexadecimal
"value" : ..., // The integer of value sent with this transaction encoded as hexadecimal
"data" : ..., // The hash of the method signature and encoded parameters. For more information, see the Contract ABI description in the Solidity documentation
}
The second parameter is blockNumber
: you can pass the label latest/pending/earliest
, etc:
/* blockNumber
The block number in hexadecimal format or the string latest, earliest, pending, safe or
finalized (safe and finalized tags are only supported on Ethereum, Gnosis, Arbitrum,
Arbitrum Nova and Avalanche C-chain), see the default block parameter description in
the official Ethereum documentation
*/
Next, we take the smart contract method approve
and transfer
calls of the token DAI
as an example for simulation calls, and the following test environment is the main Ethereum network.
To be continued...
Web3 is the future.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit