Background
Tron’s VM supports most of the features of modern solidity and it would be possible for us to build products on Tron in a similar fashion to what we do on EVM chains but most modern development tools do not work with Tron. Most of the differences lay at the RPC level, making it very hard to run core security tests (fork tests are not possible using Foundry, just basic forks do not work either)
Forking is a hard technical requirement for us, we need to be able to perform tests on our contracts with live state to ensure things are working properly. Currently we haven’t found any solution to get a fork (even tenderly has no support for tron unfortunately), so we tried to perform these manually using anvil / forge.
We found that Foundry is unable to perform the fork tests due to some diffs in the returned payload by the json RPCs.
Methods required to fork
By running fork tests on eth mainnet behind a proxy, we identified most of the methods required to perform them
eth_chainId
eth_gasPrice
eth_getBlockByNumber
eth_getStorageAt
eth_getBalance
eth_getTransactionCount
eth_getCode
eth_blockNumber
Unfortunately, Foundry is unable to work with Tron’s RPCs, as unmarshalling data returned by the endpoint fails.
anvil --fork-url https://powerful-polished-gas.tron-mainnet.quiknode.pro/xxx/jsonrpc
Error: failed to get fork block number
Caused by:
0: deserialization error: Invalid string length at line 1 column 26801
1: Invalid string length at line 1 column 26801
Location:
/Users/runner/work/foundry/foundry/crates/anvil/src/config.rs:1154:18
Probable root cause
After some digging, we identified the possible root cause for this. The block payload from the tron rpc is lacking the stateRoot field content, which is always equal to 0x
This single field could be the only reason why some of the Foundry tools are not working properly and crashing whenever some block payloads are deserialized.
curl --location 'https://api.trongrid.io/jsonrpc' --header 'Content-Type:application/json' --data '{
"jsonrpc": "2.0",
"method": "eth_getBlockByNumber",
"params": ["latest", true],
"id": 1
}' | jq .result
{
"baseFeePerGas": "0x0",
"difficulty": "0x0",
"extraData": "0x",
"gasLimit": "0x160227b88",
"gasUsed": "0x360d92",
"hash": "0x00000000040a0687e0fc7194aabd024a4786ce94ad63855774f8d48896d8750b",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner": "0x9a96c8003a1e3a6866c08acff9f629e2a6ef062b",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000",
"number": "0x40a0687",
"parentHash": "0x00000000040a068652c581a982a0d17976201ad44aa28eb4e24881e82f99ee04",
"receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
"size": "0xba05",
"stateRoot": "0x", // foundry is probably expecting a bytes32 here
"timestamp": "0x6759f2f1",
"totalDifficulty": "0x0",
"transactions": [...]
}
Mocking the stateRoot
To move further in our testing, we used mitmproxy to proxy all requests made to the RPC and manually mock the stateRoot in the eth_getBlockByNumber queries. We now hit another issue
anvil --fork-url http://localhost:8080
Error: failed to create genesis
Caused by:
failed to get account for 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266: server returned an error response: error code -32601: the method eth_getTransactionCount does not exist/is not available, data: "{}"
Location:
/Users/runner/work/foundry/foundry/crates/anvil/src/eth/backend/mem/mod.rs:345:39
The eth_getTransactionCount is not defined on the tron RPC.
Mocking the transaction count result
Next step is now to intercept eth_getTransactionCount methods and return a mocked value. We hit another core issue when the eth_getCode query is made
anvil --fork-url http://localhost:8080
Error: failed to create genesis
Caused by:
failed to get account for 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266: server returned an error response: error code -32602: QUANTITY not supported, just support TAG as latest, data: "{}"
Location:
/Users/runner/work/foundry/foundry/crates/anvil/src/eth/backend/mem/mod.rs:345:39
When replaying these queries manually, we can see that the method is unable to support arbitrary block tag values for the getCode query (only supports latest )
curl --location https://powerful-polished-gas.tron-mainnet.quiknode.pro/xxx/jsonrpc --header 'Content-Type:application/json' --data '{
"jsonrpc": "2.0",
"method": "eth_getCode",
"params": ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x1234"],
"id": 1
}'
{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"QUANTITY not supported, just support TAG as latest","data":"{}"}}
Deep state management issue
It seems that most issues are coming from the ability for the tron rpc nodes to retrieve state specific data. First, the inability to provide a stateRoot in the block body shows that there might be a completely different state handling from what the evm chains are used to do.
Same thing for eth_getCode where we are completely unable to retrieve anything that is not at the tip of the chain.
Rationale
Fork testing is essential for secure smart contract development as, it allows testing contracts against live network state. Currently, industry-standard tools like Foundry cannot perform fork tests on Tron due to RPC limitations, limiting the ecosystem development of Tron.
Use cases:
- Security testing of smart contracts against production state
- Integration testing with existing protocols
- Local development environment that mirrors mainnet
- Automated testing pipelines
Specification
The following features are requested in the RPC implementation for Tron:
Block Payload Enhancement
- Add proper stateRoot field in block responses (currently returns "0x")
Extended RPC Method Support
- Implement full eth_getTransactionCount functionality
- Add support for arbitrary block tags in eth_getCode (currently only supports "latest")
Test Specification
Success criteria:
- Foundry's anvil should successfully fork Tron mainnet
- Fork tests should execute without deserialization errors
- Historical state queries should return accurate data
Scope Of Impact
- Smart contract development workflows
- Testing frameworks and tools
- Security audit processes
Implementation
The Kiln team is willing to help test and provide feedback during implementation.
Background
Tron’s VM supports most of the features of modern solidity and it would be possible for us to build products on Tron in a similar fashion to what we do on EVM chains but most modern development tools do not work with Tron. Most of the differences lay at the RPC level, making it very hard to run core security tests (fork tests are not possible using Foundry, just basic forks do not work either)
Forking is a hard technical requirement for us, we need to be able to perform tests on our contracts with live state to ensure things are working properly. Currently we haven’t found any solution to get a fork (even tenderly has no support for tron unfortunately), so we tried to perform these manually using anvil / forge.
We found that Foundry is unable to perform the fork tests due to some diffs in the returned payload by the json RPCs.
Methods required to fork
By running fork tests on eth mainnet behind a proxy, we identified most of the methods required to perform them
Unfortunately, Foundry is unable to work with Tron’s RPCs, as unmarshalling data returned by the endpoint fails.
Probable root cause
After some digging, we identified the possible root cause for this. The block payload from the tron rpc is lacking the
stateRootfield content, which is always equal to0xThis single field could be the only reason why some of the Foundry tools are not working properly and crashing whenever some block payloads are deserialized.
Mocking the stateRoot
To move further in our testing, we used mitmproxy to proxy all requests made to the RPC and manually mock the stateRoot in the
eth_getBlockByNumberqueries. We now hit another issueThe
eth_getTransactionCountis not defined on the tron RPC.Mocking the transaction count result
Next step is now to intercept
eth_getTransactionCountmethods and return a mocked value. We hit another core issue when theeth_getCodequery is madeWhen replaying these queries manually, we can see that the method is unable to support arbitrary block tag values for the
getCodequery (only supportslatest)Deep state management issue
It seems that most issues are coming from the ability for the tron rpc nodes to retrieve state specific data. First, the inability to provide a
stateRootin the block body shows that there might be a completely different state handling from what the evm chains are used to do.Same thing for
eth_getCodewhere we are completely unable to retrieve anything that is not at the tip of the chain.Rationale
Fork testing is essential for secure smart contract development as, it allows testing contracts against live network state. Currently, industry-standard tools like Foundry cannot perform fork tests on Tron due to RPC limitations, limiting the ecosystem development of Tron.
Use cases:
Specification
The following features are requested in the RPC implementation for Tron:
Block Payload Enhancement
Extended RPC Method Support
Test Specification
Success criteria:
Scope Of Impact
Implementation
The Kiln team is willing to help test and provide feedback during implementation.