Developers Forum for XinFin XDC Network

Vikash Kumar
Vikash Kumar

Posted on

Issue Report – Unexpected Gas Behavior on XDC Network

Scenario 1 — TX Sent but Reverts With “Out of Gas”
What happens

With a lower gas fee configuration (lower gasPrice or lower gasLimit), the transaction is:

successfully broadcast

TX hash is produced

…but then fails during execution.

Image description

Scenario 2 — After Increasing Gas Fee → TX Sent & Successful, But Gas Fee Extremely High (~0.4 XDC)

When we increase gasPrice (and/or gasLimit) so that the first error disappears:

What happens

The transaction is broadcast

It is mined
Image description

It succeeds (no revert, no error)

But…

Problem

The transaction fee becomes extremely high.
Image description

Discussion (6)

Collapse
gzliudan profile image
Daniel Liu • Edited on

For the tx 0xfea1216fa41fdb5f116d6275d1dc785d... on XDC chain, the gas price is 12.5 GWei, and the used gas is 29355011(0x1bfec03), so the tx cost is 0.3669376375 XDC.


Request:

RPC="https://earpc.xinfin.network/"
hash=0xfea1216fa41fdb5f116d6275d1dc785dd5072e99798aa7611d7c1cf1cb396b11

curl -s -X POST -H "Content-Type: application/json" ${RPC} -d '{
  "jsonrpc": "2.0",
  "id": 5002,
  "method": "eth_getTransactionReceipt",
  "params": [
    "'"${hash}"'"
  ]
}' | jq
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "jsonrpc": "2.0",
  "id": 5002,
  "result": {
    "blockHash": "0xae171f38ff190472b5076cf92069f16156c26bdd1fa391629fc7d70d6e967664",
    "blockNumber": "0x5bd0be4",
    "contractAddress": null,
    "cumulativeGasUsed": "0x1bfec03",
    "from": "0x177073570f9ac28aec0074340e193a3a71454aea",
    "gasUsed": "0x1bfec03",
    "logs": [
      {
        "address": "0x45895b2cf3381e3f0022b249bd18a6772b04bf45",
        "topics": [
          "0x5686b160fa72829552d45956674c9b431d51600d10e79e4525679c6206131179"
        ],
        "data": "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000004061356461353364613531343164343735333236363232343666623131666234343664666630303938636135383564643831653738383366653938303639666264000000000000000000000000000000000000000000000000000000000000000b53616e7961204465736169000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000645647562756b0000000000000000000000000000000000000000000000000000",
        "blockNumber": "0x5bd0be4",
        "transactionHash": "0xfea1216fa41fdb5f116d6275d1dc785dd5072e99798aa7611d7c1cf1cb396b11",
        "transactionIndex": "0x0",
        "blockHash": "0xae171f38ff190472b5076cf92069f16156c26bdd1fa391629fc7d70d6e967664",
        "logIndex": "0x0",
        "removed": false
      }
    ],
    "logsBloom": "0x00000000000000000000000000000000001002000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "status": "0x1",
    "to": "0x45895b2cf3381e3f0022b249bd18a6772b04bf45",
    "transactionHash": "0xfea1216fa41fdb5f116d6275d1dc785dd5072e99798aa7611d7c1cf1cb396b11",
    "transactionIndex": "0x0",
    "type": "0x0"
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
vikash_kumar_5e685df6ec9c profile image
Vikash Kumar Author

Thank you for the calculation (12.5 GWei × 29,355,011 gas = 0.3669376375 XDC).
We understand how this value is derived.

However, our main concern is not the math, but the fact that the transaction cost has been continuously increasing over time, even though:

Our smart contract code has not changed

Our input data size is the same

Our function call parameters are identical

We are performing the exact same postCertificate() operation each time

What we observed

At the beginning:

Gas used per transaction was much lower

Total transaction fee was around ~0.003 XDC

As we continued sending transactions, the gas behavior changed:

Gas used kept increasing with each certificate

Transaction cost kept rising

Now each transaction costs ~0.36 XDC, which is more than 100× higher than before

Collapse
gzliudan profile image
Daniel Liu • Edited on

A better version:

function postCertificate(
        string memory _studentname,
        address _studentAdd,
        string memory _uri,
        string memory _hash,
        string memory _type,
        string memory _issuerName
    ) external onlyInstitute {
        bytes32 byte_hash = stringToBytes32(_hash);
        require(
            certificates[byte_hash].timestamp == 0,
            "Certificate with this hash already exists"
        );

        uint256 id = institute_ID[msg.sender];

        // write certificate (keeps existing Cert layout)
        certificates[byte_hash] = Cert(
            institutes[id],
            _studentname,
            _studentAdd,
            _hash,
            _type,
            _uri,
            block.timestamp,
            msg.sender,
            _issuerName
        );

        // update student info in-place to avoid copying storage arrays to memory
        studentInstituteId[_studentAdd] = id;
        Student storage s = studentInfo[_studentAdd];
        s.uri.push(_uri);
        uint256 len = s.instituteName.length;
        if (
            len == 0 ||
            !compareStrings(
                s.instituteName[len - 1],
                institutes[id].instituteName
            )
        ) {
            s.instituteName.push(institutes[id].instituteName);
        }
        s.name = _studentname;
        s.studentAdd = _studentAdd;

        emit CertificatePosted(_hash, id, _studentname, _issuerName);
    }
Enter fullscreen mode Exit fullscreen mode

What I changed:

  • Replaced the whole-struct assignment that copied studentInfo[_studentAdd].instituteName and .uri into a new Student with an in-place storage update:
    • `Student storage s = studentInfo[_studentAdd];
    • s.uri.push(_uri);
    • s.name = _studentname;
    • s.studentAdd = _studentAdd;
  • Kept certificate storage logic intact (still assigns Cert to certificates[byte_hash]) to avoid larger structural changes in this patch.
  • This removes the expensive pattern of copying storage arrays into memory & back, preventing gasUsed from growing linearly with array length for this function.

Why this helps

  • Avoids copying dynamic arrays from storage to memory and then back on assignment — that copy cost grows with array length and was the main cause of per-tx gas inflation.
  • Now only the necessary storage writes are performed: push new URI, optionally push instituteName if new, set simple fields. This reduces both SLOAD/SSTORE counts and memory copy overhead.

Next recommendations (optional)

  • Convert Cert.institute to store only instituteId (uint256) instead of embedding Institute to avoid repeated string writes for every certificate.
  • Apply the same storage-in-place pattern to bulkUpload.
  • Consider changing URI storage to mapping(uint256 => string) + counter for very large histories.
Collapse
gzliudan profile image
Daniel Liu • Edited on

Why gasUsed grows when calling postCertificate:

  • On-chain state growth (dynamic arrays): studentInfo is a mapping whose value (Student) contains dynamic arrays (uri, instituteName). As these arrays grow, any operation that reads/writes many elements costs more gas.
  • Whole-struct assignment triggers array copy: Code that does something like studentInfo[addr] = Student(..., studentInfo[addr].uri, ...) forces the compiler to copy storage arrays into memory and back. That copy cost scales linearly with array length, so per-tx gasUsed increases as arrays get longer.
  • Duplicated string storage (embedded struct): Cert embeds a full Institute (which contains strings). Each certificates[hash] = Cert(institutes[id], ...) duplicates those strings into storage for every certificate, multiplying SSTOREs and increasing gas usage.
  • More SSTOREs when state expands: Adding certificates/URIs creates new storage slots (0→non-zero) which are expensive SSTORE operations. As more slots are consumed over time, transactions that create or update them consume more gas.
  • Larger event/log payloads: Events like CertificatePosted(string hash, ...) include strings. If logged strings or number of logs grow, log-writing gas rises.
Collapse
gzliudan profile image
Daniel Liu • Edited on

Let me look into your smart contract code on xdcscan explorer.