Subnet-EVM Stateful Precompiles on Avalanche

Subnet-EVM Stateful Precompiles on Avalanche

Aug 22, 2023 / By Avalanche / 15 Minute Read

Use Subnet-EVM Stateful Precompiles on Avalanche to Rapidly Deploy Your High Performance dApp

Introduction

Avalanche is a platform that powers the execution transactions for decentralized applications. It’s comprised of subnetworks called Subnets consisting of custom virtual machines (VMs) and complex validator rulesets. One such VM is Subnet-EVM. This technical deep-dive reviews how Subnet-EVM can customize the VM’s deployment via Stateful Precompiles, allowing for Subnets to adapt to all of your dApp’s needs.

Subnet-EVM

Avalanche is a network composed of many blockchains. Each blockchain is an instance of a VM, much like an object is an instance of a class in an object-oriented language. That is, the VM defines the behavior of the blockchain.

Subnet-EVM is a simplified version of Coreth, the VM of the Contract Chain (C-Chain). It implements the Ethereum Virtual Machine (EVM) and supports Solidity smart contracts, as well as most other Ethereum client functionality, and is compatible with many of EVM toolchains and wallets.

EVM Precompile

Originally defined in Appendix E of the Ethereum Yellowpaper, precompiled contracts are written in low-level code and are responsible for implementing operations or computations that are used by smart contracts. These precompiled contracts provide efficient and optimized implementations of cryptographic operations. They’re included in the EVM protocol to enable smart contracts to perform complex computations, without the need for developers to write custom code.

PrecompiledContract

All Precompiles must implement the PrecompiledContract interface

// PrecompiledContract is the basic interface for native Go contracts. // The implementation requires a deterministic gas count based on the // input size of the Run method of the contract.type PrecompiledContract interface {    RequiredGas(input []byte) uint64    Run(input []byte) ([]byte, error)}

PrecompileContract has 2 functions which need to be implemented. RequiredGas calculates the contract gas use and Run runs the precompiled contract.

NOTE the PrecompiledContract is different than the StatefulPrecompiledContract listed below.

These precompile addresses start from 0x0000000000000000000000000000000000000001 and increment by 1.

EVM Precompiled contracts include the following. The numbering is the address of the contract:

  1. Recovery of ECDSA signature

  2. Hash function SHA256

  3. Hash function RIPEMD160

  4. Identity

  5. Modular exponentiation (EIP 198)

  6. Addition on elliptic curve alt_bn128 (EIP 196)

  7. Scalar multiplication on elliptic curve alt_bn128 (EIP 196)

  8. Checking a pairing equation on curve alt_bn128 (EIP 197)

  9. BLAKE2b hash function (EIP 152)

Precompiles are shortcuts to execute a function implemented by the EVM itself, rather than an actual contract. They’re useful when the desired functionality would be cumbersome and awkward to implement in Solidity, such as cryptographic functions. Here’s the RIPEMD160 cryptographic function implemented as a precompile. Note that it implements the RequiredGas and Run functions per the PrecompiledContract interface.

RIPEMD160 Implementation

// RIPEMD160 implemented as a native contract.type ripemd160hash struct{}// RequiredGas returns the gas required to execute the pre-compiled contract.//// This method does not require any overflow checking as the input size // gas costs// required for anything significant is so high it's impossible to pay for.func (c *ripemd160hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas}func (c *ripemd160hash) Run(input []byte) ([]byte, error) { ripemd := ripemd160.New() ripemd.Write(input) return common.LeftPadBytes(ripemd.Sum(nil), 32), nil}

Developers can leverage precompiles by invoking the keywords:

return ripemd160hash(input)

Behind the scenes, precompiles are triggered through CALL opcodes utilizing specific addresses. These CALL opcodes, including CALL, STATICCALL, DELEGATECALL, and CALLCODE, have the ability to invoke a precompile. Typically, these opcodes are employed to call a smart contract, with the input representing encoded parameters for the smart contract call. However, when a precompile is involved, the input is passed to the precompile, allowing it to execute the operation and deduct the necessary gas from the transaction's execution context.

Here are the original 9 cryptographic functions implemented as precompiles:

  1. Recovery of ECDSA signature

  2. Hash function SHA256

  3. Hash function RIPEMD160

  4. Identity

  5. Modular exponentiation

  6. Addition on elliptic curve alt_bn128

  7. Scalar multiplication on elliptic curve alt_bn128

  8. Checking a pairing equation on curve alt_bn128

  9. BLAKE2b hash function

We can see these precompile mappings from address to function here in Coreth:

// PrecompiledContractsBanff contains the default set of pre-compiled Ethereum// contracts used in the Banff release.var PrecompiledContractsBanff = map[common.Address]precompile.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}),

By utilizing these precompiled contracts, smart contracts can perform cryptographic computations more efficiently and with reduced gas costs. Developers can call these precompiled contracts directly from their own smart contracts, saving computational resources and simplifying the development process.

Precompiled contracts are an integral part of any EVM ecosystem, providing optimized implementations of cryptographic operations for smart contracts.

Stateful Precompile

Avalanche enables precompiles to have greater functionality by adding state access. There are 5 stateful precompiles which can be enabled via ChainConfig in genesis or as an upgrade.

  • Restricting Smart Contract Deployers

  • Restricting Who Can Submit Transactions

  • Minting Native Coins

  • Configuring Dynamic Fees

  • Changing Fee Reward Mechanisms

AllowList

Permissions can be enforced by a stateful precompile on an address via the AllowList interface. The AllowList is not a contract itself, but a helper structure to provide a control mechanism for wrapping contracts. It provides an AllowListConfig to the precompile so that it can take an initial configuration from genesis/upgrade.

Each Precompile, which is using AllowList , has 3 permission levels. Admin lets you create and remove other Admins in addition to using the precompile. Enabled lets you use the precompile. None prevents you from using the precompile.

pragma solidity ^0.8.0;interface IAllowList {  // Set [addr] to have the admin role over the precompile  function setAdmin(address addr) external;  // Set [addr] to be enabled on the precompile contract.  function setEnabled(address addr) external;  // Set [addr] to have no role the precompile contract.  function setNone(address addr) external;  // Read the status of [addr].  function readAllowList(address addr) external view returns (uint256 role);}

ContractDeployerAllowList

AllowList adds adminAddresses and enabledAddresses fields to precompile contract configurations. For instance, configure the contract deployer precompile contract configuration by adding the following JSON to your genesis or upgrade JSON.

ContractDeployerAllowList enables restricting which accounts can deploy a smart contract to your instance of Subnet EVM. Configure by adding the following JSON to your genesis or upgrade JSON.

"contractDeployerAllowListConfig": {    "adminAddresses": [        "0x41B3E74d0dC7c5f573c8cFB007C45a494B1B10F7"    ],    "enabledAddresses": [        "0x7326165202aed51E8dd3750Ff59a048F73579960"    ],    "blockTimestamp": 0},

The Stateful Precompile contract, which powers the ContractDeployerAllowList, adheres to the AllowList Solidity interface at 0x0200000000000000000000000000000000000000 (you can load this interface and interact directly in Remix).

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

ContractNativeMinter

ContractNativeMinter enables restricting, which accounts can mint native(gas) coins. Configure this by adding the following JSON to your genesis or upgrade JSON.

"contractNativeMinterConfig": {    "adminAddresses": [        "0x717b7948AA264DeCf4D780aa6914482e5F46Da3e"    ],    "enabledAddresses": [        "0x3287591FC6C6Ef2E7AcF9293f458ECAA6Ed9bc63"    ],    "blockTimestamp": 0},

The Stateful Precompile contract powering the ContractNativeMinter adheres to the following Solidity interface at 0x0200000000000000000000000000000000000001 (you can load this interface and interact directly in Remix):

pragma solidity ^0.8.0;import "./IAllowList.sol";interface INativeMinter is IAllowList {// Mint [amount] number of native coins and send to [addr]  function mintNativeCoin(address addr, uint256 amount) external;}

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

TxAllowList

TxAllowList enables restricting which accounts can submit a transaction to your instance of Subnet EVM. Configure this by adding the following JSON to your genesis or upgrade JSON.

"txAllowListConfig": {    "adminAddresses": [        "0xB79FFB4eCaAb0135062c86F24dd2ff75112b646C"    ],    "enabledAddresses": [        "0xCa0F57D295bbcE554DA2c07b005b7d6565a58fCE"    ],    "blockTimestamp": 0},

The Stateful Precompile contract powering the TxAllowList adheres to the AllowList Solidity interface at 0x0200000000000000000000000000000000000002 (you can load this interface and interact directly in Remix):

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

FeeManager

FeeManager enables configuring the parameters of the dynamic fee algorithm. Configure this by adding the following JSON to your genesis or upgrade JSON.

"feeManagerConfig": {    "adminAddresses": [        "0x99ef71E554E54a7135d032ab30FC476DC55f9315"    ],    "enabledAddresses": [        "0x7B137BB4704b977a23Cc8055C2C87674B730aa0a"    ],    "blockTimestamp": 0},

The Stateful Precompile contract powering the FeeConfigManager adheres to the following Solidity interface at 0x0200000000000000000000000000000000000003 (you can load this interface and interact directly in Remix).

pragma solidity ^0.8.0;import "./IAllowList.sol";interface IFeeManager is IAllowList {// Set fee config fields to contract storage  function setFeeConfig(    uint256 gasLimit,    uint256 targetBlockRate,    uint256 minBaseFee,    uint256 targetGas,    uint256 baseFeeChangeDenominator,    uint256 minBlockGasCost,    uint256 maxBlockGasCost,    uint256 blockGasCostStep  ) external;// Get fee config from the contract storage  function getFeeConfig()    external    view    returns (      uint256 gasLimit,      uint256 targetBlockRate,      uint256 minBaseFee,      uint256 targetGas,      uint256 baseFeeChangeDenominator,      uint256 minBlockGasCost,      uint256 maxBlockGasCost,      uint256 blockGasCostStep    );// Get the last block number changed the fee config from the contract storage  function getFeeConfigLastChangedAt() external view returns (uint256 blockNumber);}`

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

RewardManager

RewardManager enables configuring the fee reward mechanism, including burning fees, sending fees to a predefined address, or enabling fees to be collected by block producers. Configure this by adding the following JSON to your genesis or upgrade JSON.

"rewardManagerConfig": {    "adminAddresses": [        "0x99ef71E554E54a7135d032ab30FC476DC55f9315"    ],    "enabledAddresses": [        "0x7B137BB4704b977a23Cc8055C2C87674B730aa0a"    ],    "blockTimestamp": 0,    "initialRewardConfig": {        "allowFeeRecipients": true,        "rewardAddress": "0x0000000000000000000000000000000000000000"    }},

The Stateful Precompile contract powering the RewardManager adheres to the following Solidity interface at 0x0200000000000000000000000000000000000004 (you can load this interface and interact directly in Remix).

pragma solidity ^0.8.0;import "./IAllowList.sol";interface IRewardManager is IAllowList {// setRewardAddress sets the reward address to the given address  function setRewardAddress(address addr) external;// allowFeeRecipients allows block builders to claim fees  function allowFeeRecipients() external;// disableRewards disables block rewards and starts burning fees  function disableRewards() external;// currentRewardAddress returns the current reward address  function currentRewardAddress() external view returns (address rewardAddress);// areFeeRecipientsAllowed returns true if fee recipients are allowed  function areFeeRecipientsAllowed() external view returns (bool isAllowed);}

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

CALL

The CALL opcode (CALL, STATICCALL, DELEGATECALL, and CALLCODE) allows us to invoke this precompile.

The function signature of CALL in the EVM is as follows:

Call(    caller ContractRef,    addr common.Address,    input []byte,    gas uint64,    value *big.Int,)(ret []byte, leftOverGas uint64, err error)

When a precompile is called, the EVM checks if the input address is a precompile address. If the input address is a precompile address, it executes the precompile. Otherwise, it loads the smart contract at the input address and runs it on the EVM interpreter with the specified input data.

Calling a Precompile from Solidity

Remix comes with a simple contract called Storage.sol, which lets you write/read a number to/from the blockchain. Open Storage.sol and add the following hashNumber function.

/**     * @dev Return hash      * @return hash     */    function hashNumber() public view returns (bytes32 hash) {        (bool ok, bytes memory out) = address(2).staticcall(abi.encode(number)); require(ok);        hash = abi.decode(out, (bytes32));        return hash;    }

hashNumber returns the sha256 hash of number by calling the sha256 precompile located at address(2) or 0x000000000000000000000000000000000002.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

The complete Storage.sol contract should look like this:

// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.7.0 <0.9.0;/** * @title Storage * @dev Store & retrieve value in a variable * @custom:dev-run-script ./scripts/deploy_with_ethers.ts */contract Storage {    uint256 number;    /**     * @dev Store value in variable     * @param num value to store     */    function store(uint256 num) public {        number = num;    }    /**     * @dev Return value      * @return value of 'number'     */    function retrieve() public view returns (uint256){        return number;    }    /**     * @dev Return hash      * @return hash     */    function hashNumber() public view returns (bytes32 hash) {        (bool ok, bytes memory out) = address(2).staticcall(abi.encode(number));  require(ok);        hash = abi.decode(out, (bytes32));        return hash;    }}

Alternatively, you can interface with the sha256 interface directly. First, create a new file called Isha256.sol and paste in the following contents and save the file.

// SPDX-License-Identifier: MITpragma solidity >=0.8.0;interface ISHA256 {    // Computes the SHA256 hash of value    function run(string memory value) external view returns (bytes32 hash);}

Switch to the “Deploy & Run Transactions” view in the left sidebar. Select the Injected Provider as the environment, let Core connect to Remix, and paste this precompile address below in the “At Address” field: 0x0000000000000000000000000000000000000002

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Next, click the blue ”At Address” button. A new compiled contract appears below. Click the small arrow next to SHA256 and call the function with any string.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

You can see the return value of the hash function below the input field.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Avalanche Stateful Precompile

A stateful precompile builds on a precompile and adds state access. Stateful precompiles are not available in the default EVM, and are specific to Avalanche EVMs such as Coreth and Subnet-EVM.

A stateful precompile follows this interface:

// StatefulPrecompiledContract is the interface for executing a // precompiled contracttype StatefulPrecompiledContract interface {    // Run executes the precompiled contract.    Run(accessibleState PrecompileAccessibleState,    caller common.Address,    addr  common.Address,    input []byte,    suppliedGas uint64,    readOnly bool)    (ret []byte, remainingGas uint64, err error)}

NOTE the StatefulPrecompiledContract is different than the PrecompiledContract listed above.

Precompile-EVM

Precompile-EVM is a repository for registering precompiles to Subnet-EVM without forking the Subnet-EVM codebase. Subnet-EVM supports registering external precompiles through precompile/modules package. By importing Subnet-EVM as a library, you can register your precompiles to Subnet-EVM and build it together with Subnet-EVM.

HelloWorld is an example stateful precompile that ships with Precompile-EVM. Here we’re going to configure and deploy that precompile. Next, create a new precompile using code generation scripts that ship with Precompile-EVM. The example precompile will have the same functionality as HelloWorld and will serve primarily as example steps for using the code generation scripts.

Clone the Repo

1. Clone the Precompile-EVM GitHub repo to your local machine.

git clone <https://github.com/ava-labs/precompile-evm.git>cd precompile-evm/

Checkout the hello-world-example Branch

2. Once you have the Precompile-EVM repo, check out the appropriate branch.

git checkout hello-world-example

Install Dependencies

3. Next, you have to cd contracts/ and run npm install to get the dependencies. You’ll need the latest version of NodeJS installed which comes bundled with npm.

cd contracts/npm installcd ../

Config the HelloWorld Precompile

The configuration file for the HelloWorld precompile is located at tests/precompile/genesis/hello_world.json. In addition to the HelloWorld precompile, you can add config for any existing precompiles. You can also config the Subnet’s tokenomics, which include initial coin allocation. By default, it’s configured to airdrop 1M of your Subnet’s gas token to 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC, which is the address derived from 0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027 which is a private key that we share publicly in our docs and use for airdropping tokens onto new networks.

You can also add any other addresses and balances which you want to airdop on your new subnet.

"alloc": {    "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {      "balance": "0x52B7D2DCC80CD2E4000000"    }  },

Additionally, you can config additional precompiles. Let’s update hello_world.json to enable the 0x8db from above to be able to mint additional gas tokens.

"contractNativeMinterConfig": {      "adminAddresses": [          "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"      ],      "blockTimestamp": 0    }

You also want to enable and configure the HelloWorld precompile.

"helloWorldConfig": {      "blockTimestamp": 0,      "adminAddresses": [        "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"      ]    },

Start a Local Network

Using the Avalanche Network Runner, fire up a server

avalanche-network-runner server

In a different tab, start a 5 node network

avalanche-network-runner control start \\  --number-of-nodes=5 \\  --blockchain-specs '[{"vm_name": "subnetevm", "genesis": "./tests/precompile/genesis/hello_world.json"}]'

After the network is finished building, you should see RPC URIs in the logs

[07-25|17:02:18.113] INFO server/network.go:649 [blockchain RPC for "srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy"] "<http://127.0.0.1:9650/ext/bc/2RQiKekQrhAR7maFz1PSF9MCs6kH3SyMAMLWVP4zossXkq6SDJ>"

Connect a Wallet to the Subnet

In MetaMask, on the network dropdown, select “Add network”

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

On the next page, select “Add a network manually”

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

On the last page, enter the RPC information. Note you want to add /rpc at the end of the RPC uri from the Avalanche Network Runner logs.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Now, you should be successfully able to join

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Import Funded Account

In hello_world.json , we allocated 1m gas tokens to the 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC address. To import the key, select the dropdown menu from the first account and select “Import amount”

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Next, paste in the private key 0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Once the account is imported, it will have a 1M token balance.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Remix

Now, connect Remix to the wallet to interact with the precompiles on the Subnet. Head to Remix and under the “Deploy and Run Transactions” tab along the left select the “Environment” drop-down menu, select Injected Provider.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

A MetaMask popup will ask which accounts to connect to Remix. Select both addresses so that either can interact with the blockchain.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

You can now mint additional HW tokens. As mentioned above, you can interface with the ContractNativeMinter precompile at 0x0200000000000000000000000000000000000001. In the “File Explorer” tab, open INativeMinter.sol or create a new file and paste into the INativeMinter interface.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Once open, compile the interface in the “Deploy and Run Transactions” tab and paste the address for the INativeMinter interface, 0x0200000000000000000000000000000000000001 into the “Add Address” input field and click “Add Address.”

Remix will show a form with input fields, that match the Solidity interface. First, paste the address into the readAllowList input field and note it returns “role 2” which signifies that address has admin permissions for the precompile.

Enter the address of the account you want to mint more tokens to, in this case, 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC, and the amount of tokens to mint

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

We’ve minted 1234 new tokens and the balance shows in Metamask.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

HelloWorld Precompile

Now, we can interface with the HelloWorld precompile. Head over to Remix and create a new .sol file. Paste the IHelloWorld.sol interface

pragma solidity >=0.8.0;import "@avalabs/subnet-evm-contracts/contracts/interfaces/IAllowList.sol";interface IHelloWorld is IAllowList {  // sayHello returns the stored greeting string  function sayHello() external view returns (string calldata result);  // setGreeting  stores the greeting string  function setGreeting(string calldata response) external;}

The HelloWorld precompile is located in the helloworld/ directory. In helloworld/module.go , look for the ContractAddress variable to see at what hex address the precompile is deployed.

var ContractAddress = common.HexToAddress("0x0300000000000000000000000000000000000000")

Also, look in plugin/main.go and confirm that helloworld is being imported.

_ "github.com/ava-labs/precompile-evm/helloworld"

Open IHelloWorld.sol in Remix and compile it.

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

In the “Deploy and Run Transactions” tab, enter 0x0300000000000000000000000000000000000000 into the “At Address” input field to bring up a GUI for interacting with HelloWorld. Next, click sayHello and you will see the string “Hello World.” This is being read from the stateful precompile!

Now, enter “hola mundo” into the setGreeting field and submit that transaction. Once it’s successful, click sayHello again and you will see the string “hola mundo.” This has successfully wrote and read to the blockchain via the stateful precompile! 🏆

Subnet-EVM Stateful Precompiles on Avalanche

Inline image from: Subnet-EVM Stateful Precompiles on Avalanche

Conclusion

EVM Precompiles provides an API for developers to create optimized and efficient implementations of functionality in Go, as opposed to implementing the functionality in Solidity. Avalanche Stateful Precompiles go one step further by providing access to State.

We introduced precompiles and their history going all the way back to the Ethereum Yellow paper. We also introduced Stateful Precompiles and showed how to interface with the 5 stateful precompiles which come bundled with Subnet EVM. Using those learnings, we then showed you how to clone Precompile EVM and interact with the HelloWorld stateful precompile.

Join The Best Community in web3

The Avalanche culture goes beyond the chain. Get connected with the founders, investors, artists, gamers, and creators who call Avalanche home.

Link to Follow @avax social

Avalanche Global Events

Avalanche events are unmatched in experience and uniqueness, while offering unparalleled access to founders and leaders in the blockchain space.

View All Events
Institutions DeFi Builders Investors Founders Developers

Jun 30, 2025

Cannes, France

Avalanche Beach Break | EthCC Cannes

Learn More about Avalanche Beach Break | EthCC Cannes
Questions  about Avalanche? icon

Questions about Avalanche?

Head to the Avalanche Discord for tech support and community connections.

Get Support Questions about Avalanche?
Avalanche Team1 icon

Avalanche Team1

A global ambassador network of builders, gamers, developers and community members who build, mentor, and connect with people globally.

Learn More Avalanche Team1
The Community Hub icon

The Community Hub

The Community Hub is where Avalanche builders, businesses, and users can share resources and connect with each other.

Explore Now The Community Hub
builders background

Start building On Avalanche

Create, scale, and innovate with Avalanche’s powerful builder infrastructure.

Get Started
grants background

Join the Email List

Sign up today to stay up to date on the latest network developments.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

1 of 5 Steps

Contact us

Interested in building your project on Avalanche? Get in touch!

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.