Elevating DevX with Agra HardFork

The recent Agra Hardfork deployment on the Polygon PoS has ushered in a host of improvements, specifically tailored for developers operating on the Application layer. These upgrades bring about enhanced visibility and streamlined debugging for state sync transactions, all while accommodating the latest Solidity version for smart contract development.

Before we explore practical applications of these advancements, let’s delve into the specifics of the relevant Polygon Improvement Proposals (PIPs) incorporated during the Agra Hardfork. You can find detailed information about these proposals through the following links:

• PIP-20: Proposal to introduce more visibility of state-sync transactions

• PIP-23: Proposal to activate Ethereum Shanghai features

State Sync Verbosity (PIP-20)

State Sync serves as the native mechanism to read Ethereum data onto the Polygon PoS chain playing a crucial role in facilitating the transfer of funds from Ethereum to Polygon. For example, when a user deposits USDC on Ethereum, the Polygon PoS chain needs to be aware of this transaction that happened on ethereum and the latest state on Ethereum. The minting of an equivalent amount of USDC on the Polygon PoS occurs only after confirming a successful deposit in the Ethereum deposit manager through state sync. This meticulous mechanism ensures security and prevents any loss of funds.
The State Sync process involves several steps:

  • Initiation: The process is triggered when the syncState function is called on the State Sender contract on Ethereum when a cross-chain asset transfer from Ethereum to Polygon is initiated.
  • Event Emission: The State Sender contract emits a StateSynced event, including a state ID, target contract address on the Bor chain, and relevant data
  • Heimdall Layer: Validators on the Heimdall layer intercept and relay the StateSynced event to the Bor layer.
  • Bor Update: After a designated number of blocks (sprint), the Bor node fetches the pending state-sync events from Heimdall and updates the Bor state by making a call to the target contract address with the relevant data from the State Receiver contract.

PIP-20 unveils State Sync verbosity to address the lack of visibility regarding the outcome of the state sync process. With the implementation of PIP-20, a fresh new event is emitted by the state receiver contract during state sync. This improvement empowers developers to easily monitor whether a state sync transaction succeeds or fails, removing uncertainties and greatly improving observability and debugging capabilities.

Previous Challenges

In previous scenarios, developers may face the following challenges when executing transactions involving data or funds transfer from Ethereum to Polygon PoS, such as deposit transactions:

  1. Lack of insight if a state sync transaction has failed due to execution issue on the Polygon PoS side (call made to the target/receiver contract)

  2. Direct debugging was not possible as you couldn’t get the Bor transaction Hash (state sync transaction on the Polygon side) easily corresponding to the Ethereum transaction Hash.

These both were pain points for the devs until and unless you are running your own node and querying the logs of the node through services like Datadog.

Addressing the Issues

To overcome these challenges, we introduced a crucial event: StateCommitted(uint256 indexed stateId, bool success)
This event serves as a lifesaver for developers, offering insights into the success or failure of state sync transactions for a specific stateID on the Polygon side and making debugging more straightforward.

Additionally, it facilitates easy mapping between Bor TxHash and Ethereum TxHash.

Now understanding when a deposit transaction from Ethereum to Polygon PoS has failed is simplified by checking the success status of StateCommitted event:

  • If success is false, we know that a failure occurred during state sync , specifically at the execution side of the Polygon POS chain (call to the receiver/target smart contract).

  • If success is true, it means that the state sync has happened successfully.

Furthermore, obtaining the Bor transaction hash no longer requires running your own node and sifting through logs. This information can be queried by utilising StateCommitted events emitted by the stateReceiver contract and matching the stateID from the Ethereum transaction through the stateSynced event emitted by the stateSender contract.

Here’s an example in JS illustrating how to verify the StateCommitted status for a specific stateID (retrieved from the StateSynced event in an Ethereum-to-Polygon deposit transaction) and obtaining the Bor transaction hash using ethers.js. The script interacts with the StateReceiver.sol and queries for the StateCommitted event, providing insights into the success of the state-sync.


// Import necessary libraries and configurations
import config from "./config";
import { ethers } from "ethers";
import { abi } from "./StateReceiver.json";
dotenv.config();

const RpcEndpoint = config.rpcEndpoint;

//Function to check StateCommitted status using stateId as input
const getStateCommitted = async (stateId) => {
  try {
      const provider = new ethers.providers.JsonRpcProvider(`${RpcEndpoint}`);

       // fetch statereceiver address and abi data
       const stateReceiverAddress = config.stateReceiver;
       const stateReceiverABI = abi;

       // initialize statereceiver instance
       const stateReceiverInstance = new ethers.Contract(stateReceiverAddress, stateReceiverABI, provider);

       // query the event for your stateId
       const eventFilter = stateReceiverInstance.filters.StateCommitted(stateId, null);

      let stateCommitted;
      stateCommitted = await stateReceiverInstance.queryFilter(eventFilter);

       // Check if stateCommitted is defined
       if (stateCommitted ) {
          const status = stateCommitted[0].args[1];
          const transactionHash = stateCommitted[0].transactionHash;
          return [status,transactionHash];
                                                  } else return false;
      } catch (error) {
      console.log(`Error in stateCommitted: ${error}`);
      process.exit(1);
  }
};

// Call the function with the stateID
(async () => {
  const result = await getStateCommitted(<stateID>);
  console.log(result);
})();

Unlocking Enhanced Functionality: PIP-20 Empowers Seamless Mapping of Bor-TxHash and Ethereum TxHash

We can now subscribe to the stateCommited event on the stateReceiver contract. This way, as we are indexing, a mapping between stateID and bor tx hash can be formed. Furthermore, we can use the stateID to get the ethereum tx hash using the heimdall API (https://heimdall-api.polygon.technology/clerk/event-record/<STATE_ID>).
In this way, the mapping between BorTxHash and Ethereum TxHash can be created. This can be used by the devs to debug txs, to enable better UX, for example with the bridge (eth <> polygon) by tracking and showing the status, and displaying both the txs hashes on the UI, etc.

Below is a simple JavaScript example demonstrating how to retrieve the state ID from Bor TxHash and subsequently utilise it to obtain the Ethereum TxHash through the Heimdall API.


// Import ethers
const { ethers } = require('ethers');

const RpcEndpoint = <RPC endpoint>;

// Create Provider using RPC endpoint
const provider = new ethers.providers.JsonRpcProvider(`${RpcEndpoint}`);

//encoded eventSignature corresponding to StateCommitted(uint256 indexed stateId, bool success)

const eventSignature = '0x5a22725590b0a51c923940223f7458512164b1113359a735e86e7f27f44791ee';


async function getEthTxnHash(borTransactionHash) {
  
 try {
   // Fetch transaction receipt
   const receipt = await provider.getTransactionReceipt(borTransactionHash);


   // Fetch stateId from the event logs
   const filteredLogs = receipt.logs.filter((log) => log.topics[0] === eventSignature);
   const stateid = parseInt(filteredLogs[0].topics[1], 16);
  
   // Query Heimdall API
   const response = await fetch(`https://heimdall-api.polygon.technology/clerk/event-record/${stateid}`);
   const data = await response.json();


 // Extract the Ethereum txn hash from the response
   const ethTxnHash = data.result.tx_hash;


   return ethTxnHash;


       }catch (error) {
   console.error('Error in getting the Eth txn hash', error);
   process.exit(1);
 }
}

// Call the function with the bor Transaction Hash
(async () => {
   const result = await getEthTxnHash(<borTransactionHash>);
   console.log(result);
 })();

Shanghai Activation (PIP-23)

Now, onto PIP-23 – the activation of Ethereum Shanghai features on the Polygon PoS network. Seamless integration of the latest and greatest from Ethereum Mainnet.

EIP-3651, EIP-3855, EIP-3860, and EIP-6049 – The Ethereum Shanghai features are now at our disposal on Polygon, opening up a world of possibilities on the smart contract layer.

Warm COINBASE (EIP-3651)

In particular, the ‘Warm COINBASE’ addition is the move to align with Ethereum’s trends. The gas cost adjustment ensures that COINBASE payments remain a preferred choice, keeping transactions smooth and efficient.

PUSH0 Instruction (EIP-3855)

Introducing the PUSH0 instruction with EIP-3855 is a small change with big implications. No more wasting gas and bytecode space on pushing zero values using cumbersome alternatives. This is a win for code size, optimization, and overall contract cleanliness.

The analysis of PUSH* instructions on Mainnet tells us that around 11.5% of them push a value of zero. With PUSH0, we now have a concise and cost-effective way to handle these scenarios. It’s the little things that make a big difference, and this change is a testament to that.

Deprecating SELFDESTRUCT (EIP-6049)

Change is the only constant, and EIP-6049 recognizes that by deprecating the SELFDESTRUCT opcode. Discussions about the future of SELFDESTRUCT are ongoing, and this proposal wisely warns us against its use. As the Ethereum landscape evolves, this deprecation ensures we stay ahead of potential breaking changes.

Initcode Size Limit (EIP-3860)

EIP-3860 introduces a maximum size limit for initcode. With a charge of 2 gas for every 32-byte chunk of initcode, this proposal ensures fair and proportional costs for the creation of contracts. No more surprises due to uncapped initcode sizes causing delays or cancellations of features.

The explicit limits on EVM code size, code offset, and jump offset fitting a 16-bit value simplify development. This ensures that initcode is fairly charged with a cost that is proportional to initcode’s length. Additionally, the proposed cost system is designed to be extendable in the future, offering adaptability. Simultaneously, EVM engines benefit from streamlined operations with the establishment of clear limits on code size, code offsets (PC), and jump offsets.

Comparison with Ethereum Shanghai Upgrade

While embracing Ethereum Shanghai features, it’s crucial to note the deliberate exclusion is EIP- 4898 (Beacon chain push withdrawals as operations). This EIP enables validators to withdraw their staked ETH from the beacon chain to the EVM. Exclusion of this change is deliberate, as Polygon PoS has no tconcept of a beacon chain. While Ethereum validators now have the capability to withdraw ETHs, Polygon PoS chain conducts validator staking directly on Ethereum L1, syncing its operations with the bor chain through the heimdall layer.

Conclusion

The Agra HardFork has not only tackled issues arising due to lack of visibility into the outcome of state sync but has also, enhanced the Polygon PoS chain’s capacity to seamlessly accommodate developers working on smart contracts through incorporation of Ethereum’s Shanghai features,

2 Likes