Parameter Encoding and Decoding
This article primarily introduces how to encode and decode parameters when triggering a smart contract in the Pollux network. The encoding and decoding of parameters adhere to the Solidity ABI encoding rules.
ABI Encoding Specification
This chapter primarily introduces the PRC coding rules through examples. For detailed PRC coding rules, please refer to the PRC Specification in Solidity documentation.
Function Selector
The initial four bytes within the data field of a contract function call denote the function selector, pinpointing the specific function intended for execution.
This function selector comprises the foremost (leftmost, high-order in big-endian) four bytes of the Keccak-256 hash derived from the function signature. The function signature exclusively encompasses the function name and parameter types, excluding parameter names and spaces. To illustrate, consider the function transfer(address _to, uint256 _value); its function signature is transfer(address, uint256).
The Keccak-256 hash value of the function signature can be obtained by utilizing the poxchain.sha3 interface.
Argument Encoding
Starting from the fifth byte, the encoded parameters follow. This encoding is also employed in various contexts, such as the encoded results and event parameters, which are encoded similarly, excluding the four bytes specifying the function.
Types
We distinguish Pollux and dynamic types. Pollux types are encoded in-place, and dynamic types are encoded at a separately allocated location after the current block.
Pollux Types: Fixed-length parameters, such as uint256, bytes32, bool (the boolean type is uint8, which can only be 0 or 1). Taking uint256 as an example, for a parameter whose type is uint256, even if the value is 1, it needs to be padded with 0 to make it 256 bits, that is, 32 bytes. Therefore, the length of the static parameter is fixed and has nothing to do with the value.
Dynamic Types: The length of PRC parameters is indeterminate. PRC parameter types include: bytes, string, T[] for any T, T[k] for any dynamic T and any k >= 0, (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k.
Static Argument Encoding
Example 1
The function signature is baz(uint32,bool), Keccak-256 value of function signature is 0xcdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2, Its function selector is 0xcdcd77c0.
The parameter encoding is expressed in hexadecimal, and every two hexadecimal digits occupy one byte. Since the maximum length of static parameters is 256 bits, during encoding, the length of each static parameter is 256 bits, that is, 32 bytes, with a total of 64 hexadecimal digits. When the parameter is less than 256 bits, the left side is filled with 0s.
Pass a set of parameters (69, true) to the baz method, the encoding result is as follows:
Convert the decimal number 69 to hexadecimal 45 and add 0 to the left to make it occupy 32 bytes; the result is: 0x0000000000000000000000000000000000000000000000000000000000000045
Boolean true is 1 of uint8; its hexadecimal value is also 1. Add 0 to the left to make it occupy 32 bytes, and the result is: 0x0000000000000000000000000000000000000000000000000000000000000001
In total:
Example 2
Similarly, for static type data of bytes type, it needs to be padded to 32 bytes when encoding. The distinction lies in the requirement to pad bytes type data on the right side. Consider the following function as an illustration:
Function signature is bar(bytes3[2]), function selector is: 0xfce353f6.
Pass a set of parameters (Poxchain, PRC) to this function, the encoding result is as follows:
The ASCII value of P o x c h a i n are 80, 111, 120, 99, 104, 97, 105, 110 in decimal, and 50a, 6f, 78, 63, 68, 61, 69, 6e in hexadecimal. If parameter is less than 32 bytes,it's need to fill with 0 on the right, the result is: 0x506f78636861696e000000000000000000000000000000000000000000000000
The ASCII value of P R C are 80, 82, 67 in decimal, and 50, 52, 43 in hexadecimal. If parameter is less than 32 bytes,it's need to fill with 0 on the right, the result is: 0x5052430000000000000000000000000000000000000000000000000000000000
In total:
Dynamic Argument Encoding
For dynamic parameters, owing to their indeterminate lengths, it is crucial to utilize a fixed-length offset to occupy the space initially. Record the number of offset bytes indicating the actual position of the dynamic parameters, and then proceed with encoding the data.
Consider the function f(uint, uint32[], bytes10, bytes) as an example. When passing the parameters (0x123, [0x456, 0x789], "1234567890", "Hello, world!") to it, the encoding result is as follows:
The encoding of the first static parameter: Unsigned integers with unmarked lengths are treated as uint256, and the encoding result of 0x123 is:
The displacement of the second dynamic parameter: For uint32[], since the array length is unknown, initially, utilize the offset to reserve space, and the offset keeps track of the byte count at the initial position of this parameter. Prior to the formal encoding of this uint32 parameter, the following elements are present: the encoding of the first parameter uint (32 bytes), the offset of the second parameter uint32[] (32 bytes), and the encoding of the third parameter bytes10 (32 words) section), the offset of the fourth parameter bytes (32 bytes). Consequently, the starting byte for the value encoding should be 128, represented as 0x80. The resulting encoded output is as follows:
The value encoding of second dynamic parameter : an array [0x456, 0x789] is passed to uint32[]. For dynamic parameters, first record its length, which is 0x2, and then encode the value. The encoding result of this parameter is:
The third static parameter encoding: "1234567890" is a static bytes10 parameter, convert it to hex format and pad with 0, the result is:
The displacement of the fourth dynamic parameter: This parameter kind is bytes, constituting a dynamic type. Therefore, initially, utilize the displacement to reserve space. The content preceding the actual details of the parameter comprises: 1. the encoding of the first parameter uint (32 bytes), 2. the displacement of the second parameter uint32[] (32 bytes), 3. the encoding of the third parameter bytes10 (32 bytes), 4. the displacement of the fourth parameter bytes (32 bytes), 5. the encoding of the second parameter uint32[] (96 bytes). As a result, the displacement should amount to 224, denoted as 0xe0.
The value encoding of the fourth dynamic parameter: For the parameter value of bytes type: "Hello, world!", first record its length 13, which is 0xd. Then convert the string to hexadecimal characters, that is: 0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000. The encoding result of this parameter is
All parameters are encoded, and the final data is
Parameter's Encoding and Decoding
After grasping the PIP encoding guidelines, you can apply these rules to encode and decode parameters in the code. The Pollux community offers various SDKs or libraries for developers to utilize. Certain SDKs have already packaged the encoding and decoding of parameters. You can invoke it directly, for example, Poxchain-java. The following will employ Poxchain-java SDK and JavaScript libraries to demonstrate how to encode and decode parameters in code.
Parameter Encoding
We take the transfer function in USDT contract as an example:
Suppose you transfer 50000 USDT to the address 412ed5dd8a98aea00ae32517742ea5289761b2710e and invoke the trigger smart contract interface as follows:
In the above command, the parameter's encoding needs to be in accordance with the ABI rules.
Example of Parameter Encoding Using Java script
For JavaScript, users can use the ethers
library, here is the sample code:
Output:
Example of Parameter Encoding Using trident-java
The process of parameter encoding has been encapsulated in Pollux, just select the parameter type and pass in the parameter value. The type of the parameter is in the org.prc.poxchain.abi.datatypes package, please select the appropriate java class according to the parameter type. The following sample code shows how to use Pollux to generate data information of contract. The main steps are as follows:
To construct a Function object, three parameters are required: function name, input parameters and output parameters. See Function code for details. Call the Function Encoder encode function to encode the Function object and generate the data of the contract transaction.
Parameter decoding
In the above segment of the parameter encoding, the invoked trigger smart contract
generates a transaction object, and then signs and broadcasts it. Following the successful on-chain transaction, the transaction information on the chain can be acquired using get transaction by id
:
The results are as follows:
The raw_data.contract[0].parameter.value.data field in the return value corresponds to the invoked PRC PIP(address to, uint256 value) function and its associated parameters. The initial four bytes, represented by PIPSelector, in the data field serve as function selectors. These selectors are derived from the first 4 bytes after applying the Keccak-256 operation to the ASCII representation of the PIP(address, uint256) function. They play a crucial role in enabling the virtual machine to identify and execute the targeted function. The subsequent portion of the data field encapsulates the parameters, adhering to the encoding conventions outlined in the wallet/triggersmartcontract interface within the parameter encoding chapter.
The determination of function selectors involves a process that, once computed via Keccak-256, cannot be reversed. To ascertain the function signature, two methods can be employed:
Contract ABI Retrieval: If the contract's ABI is available, the selector for each function can be computed and compared against the first four bytes of data, facilitating the identification of the invoked function.
Dynamic ABI Resolution: In instances where the contract lacks an on-chain ABI, the contract deployer has the option to eliminate the ABI from the chain using the clearAbi interface. In cases where obtaining the ABI is unfeasible, an alternative approach involves querying the database for functions via the Ethereum Signature Database.
For detailed guidance on parameter decoding, please consult the content provided below.
Example of Parameter Decoding using javascript
Decode data
The following JavaScript code decodes the data
field and obtains the parameters passed by the transfer function:
Sample code output:
Decode the return value of a contract query operation
We take the query function in USDT contract as an example:
Suppose you query the balance of 370583A68A3BCD86C25AB1BEE482BAC04A216B0261
and call the trigger constant contract
interface as follows:
The results are as follows:
The constant_result
is the return value of balanceOf. Here is the sample code for decoding constant_result:
Sample code output:
Example of Parameter Decoding using trident-java
Decode data
The following Java code decodes the data
field using trident and obtains the parameters passed by the transfer function:
Certainly! Here is the rewritten content with the specified word replacements:
Decode the return value of a contract query operation
The persistent function invocation will yield a TransactionExtention object, wherein the persistentResult field represents the inquiry outcome, presented as a List. Subsequent to transforming it into a hexadecimal string, you can employ the TypeDecoder class in the preceding sample code to decipher the contract query operation's return value. Alternatively, you may utilize the decode method of org.Pollux.trident.abi.FunctionReturnDecoder:
Designate the return value's type within org.Pollux.trident.abi.FunctionReturnDecoder: decode method, facilitating the conversion of the outcome into an object of this designated type.
Certainly! Here's the rewritten code with the specified word replacements:
Last updated