Skip to main content

How to Upgrade Your Precompiles and Subnet-EVM for Durango

Introduction​

Durango will be activated on the Avalanche Mainnet at 11 AM ET (4 PM UTC) on Wednesday, March 6th, 2024. Subnet-EVM introduces a set of new features and backwards-incompatible changes with the Durango network upgrade. This guide will walk you through the process of upgrading your Subnet-EVM and precompiles for the Durango network upgrade. The Durango network upgrade introduces new features and improvements to the Avalanche platform and Subnet-EVM. This guide will help you ensure that your Subnet-EVM and precompiles are compatible with the Durango network upgrade.

Note: Subnet-EVM already performs these upgrades in native stateful precompiles. This guide is for users who have custom precompiles and need to upgrade them for Durango.

Durango introduces following changes to Subnet-EVM:

  • Avalanche Warp Messaging
  • Events in Precompiles
  • Manager Role
  • Non-Strict Mode
  • Shanghai Upgrade (ACP-24)
    • Shangai Upgrade will be activated with Durango without any further modification in Subnet-EVM.

Avalanche Warp Messaging​

Avalanche Warp Messaging (AWM) is a new feature introduced with the Durango network upgrade. It enables native cross-Subnet communication and allows Virtual Machine (VM) developers to implement arbitrary communication protocols between any two Subnets. For more information about AWM see here.

Warp Precompile must be enabled for Subnets after Durango. For more information the precompile and activation see here

Events​

Subnet-EVM Native Precompiles will start emitting events with the Durango network upgrade. This will allow you to listen to events emitted by Subnet-EVM native precompiles. Following events will be introduced:

  • event RoleSet(uint256 indexed role, address indexed account, address indexed sender, uint256 oldRole): This event will be emitted when a role is set for an account. Precompiles that uses IAllowList interface will emit this event without requiring any changes. The event contains the role, account, sender as indexed parameters and old role as non-indexed parameter.
  • event FeeConfigChanged(address indexed sender, FeeConfig oldFeeConfig, FeeConfig newFeeConfig): This event will be emitted when the fee configuration is changed in FeeManager precompile. The event contains the sender as indexed parameter and old and new fee configuration as non-indexed parameters.
  • event NativeCoinMinted(address indexed sender, address indexed recipient, uint256 amount): This event will be emitted when new native coins are minted. The event contains the sender, recipient as indexed parameters and the amount as non-indexed parameter.
  • event RewardAddressChanged(address indexed sender, address indexed oldRewardAddress, address indexed newRewardAddress): This event will be emitted when the reward address is changed in RewardManager precompile. The event contains the sender, old and new reward addresses as non-indexed parameters.
  • event FeeRecipientsAllowed(address indexed sender): This event will be emitted when the fee recipients are allowed in RewardManager precompile. The event contains the sender as indexed parameter.
  • event RewardsDisabled(address indexed sender): This event will be emitted when the rewards are disabled in RewardManager precompile. The event contains the sender as indexed parameter.

Custom Events​

Events above are already introduced and handled in Subnet-EVM native precompiles. If you have a custom precompile, you can start emitting your custom events using Durango activation. In order to do this you can define your custom event in your solidity interface and regenerate the go bindings using precompilegen tool, for more information see here.

Generally you will generate an event.go file in addition to your existing precompile files. You need to implement how to emit your events and your events' gas costs, as in hello world example. In this guide we will use the hello world example to demonstrate how to emit custom events. The event that will be introduced is:

event GreetingChanged(address indexed sender, string oldGreeting, string newGreeting)

It will be emitted when the greeting is changed in the hello world precompile. You can find the hello world precompile here. We also assume that hello world precompile is already deployed before Durango and we will be upgrading it for Durango.

Adjusting Gas Costs​

Adjusting gas costs for your custom events is very important. Emitted events are written to state and consume resources. You should make sure you're charging the right amount of gas before emitting your event. precompilegen tool automatically generates a scaffold for your gas calculations, however you should review and adjust the gas costs according to your needs, especially if you're using any arbitrary size data in your events. Gas cost function for the event GreetingChanged(address indexed sender, string oldGreeting, string newGreeting) looks like this:

func GetGreetingChangedEventGasCost(data GreetingChangedEventData) uint64 {
gas := contract.LogGas // base gas cost

// Add topics gas cost (2 topics)
// Topics always include the signature hash of the event. The rest are the indexed event arguments.
gas += contract.LogTopicGas * 2

// CUSTOM CODE STARTS HERE
// Keep in mind that the data here will be encoded using the ABI encoding scheme.
// So the computation cost might change according to the data type + data size and should be charged accordingly.
// i.e gas += LogDataGas * uint64(len(data.oldGreeting))
gas += contract.LogDataGas * uint64(len(data.OldGreeting)) // * ...
// CUSTOM CODE ENDS HERE
// CUSTOM CODE STARTS HERE
// Keep in mind that the data here will be encoded using the ABI encoding scheme.
// So the computation cost might change according to the data type + data size and should be charged accordingly.
// i.e gas += LogDataGas * uint64(len(data.newGreeting))
gas += contract.LogDataGas * uint64(len(data.NewGreeting)) // * ...
// CUSTOM CODE ENDS HERE

// CUSTOM CODE STARTS HERE
return gas
}

We have charged the base gas cost, topics gas cost and data gas cost for the old and new greeting. Topic gas cost includes 2 topics, one for the signature hash of the event and the other for the indexed sender argument. Data gas cost is calculated according to the data type and size. If you're not using any arbitrary size data in your events, you can define it as a constant.

Durango Activation Check​

After completing defining your custom events, you need to pack your events, charge your event gas and emit them in your precompile functions. Since this procedure is non-backward compatible, you need to ensure that this will only be activated after the Durango network upgrade.

You can use the contract.IsDurangoActivated function to check if the Durango network upgrade is activated. For Hello World example, we will be changing setGreeting function starting here via following:

if contract.IsDurangoActivated(accessibleState) {
...
}

Note: this activation won't be needed if you plan to deploy your custom precompile after Durango.

Event Packers​

Event packers and unpackers will be automatically generated with the precompilegen tool. You can use these packers and unpackers to pack and unpack your custom events. For hello world example:

if remainingGas, err = contract.DeductGas(remainingGas, contract.ReadGasCostPerSlot); err != nil {
return nil, 0, err
}
oldGreeting := GetGreeting(stateDB)

eventData := GreetingChangedEventData{
OldGreeting: oldGreeting,
NewGreeting: inputStruct,
}
topics, data, err := PackGreetingChangedEvent(caller, eventData)
if err != nil {
return nil, remainingGas, err
}

This will first charge gas for fetching the old greeting from the state fetch and then fetch it from the state. Then it will pack the event data with old and new greeting as non-indexed event data.

Emitting Events​

After packing the event data, you can charge the event gas and emit your custom events using the stateDB.AddLog function. For hello world example it starts here:

// Charge the gas for emitting the event.
eventGasCost := GetGreetingChangedEventGasCost(eventData)
if remainingGas, err = contract.DeductGas(remainingGas, eventGasCost); err != nil {
return nil, 0, err
}

// Emit the event
stateDB.AddLog(
ContractAddress,
topics,
data,
accessibleState.GetBlockContext().Number().Uint64(),
)

Manager Role​

Durango introduces a new role called the manager role. The manager role is a mid-role between Admin and Enabled. The manager role is considered as an Enabled account and perform restricted state-changing operations in precompiles. Manager role also can modify other Enabled accounts. It can appoint new Enabled accounts and remove existing Enabled accounts. Manager role cannot modify other Manager or Admin accounts. For more information about AllowList see here.

If you have any precompile using AllowList, you can issue a call to setManager in your precompile to appoint new Manager accounts. For upgrades or new precompiles with AllowList config you can use managerAddresses as follow:

{
"feeManagerConfig": {
"blockTimestamp": 0,
"adminAddresses": [<list of addresses>],
"managerAddresses": [<list of addresses>],
"enabledAddresses": [<list of addresses>],
}
}

Non-Strict Mode​

Strict mode unpacking prevents using extra padded bytes in inputs. This created some issue in few legacy contracts like Gnosis Multisig wallet. For more information about strict mode see solidity docs.

For Hello world example we were using this UnpackSetGreetingInput with strict mode enabled before:

func UnpackSetGreetingInput(input []byte) (string, error) {
// This function was using strict mode unpacking by default.
res, err := HelloWorldABI.UnpackInput("setGreeting", input)
if err != nil {
return "", err
}
unpacked := *abi.ConvertType(res[0], new(string)).(*string)
return unpacked, nil
}

In order to handle extra padded bytes, Subnet-EVM will start using non-strict mode with Durango in Input Unpackers. However since this change will be non-backward compatible you need to ensure that this will only be activated after the Durango network upgrade. You can use the contract.IsDurangoActivated function to check if the Durango network upgrade is activated. Now we will using this function to start using non-strict mode unpacking:

// UnpackSetGreetingInput attempts to unpack [input] into the string type argument
// assumes that [input] does not include selector (omits first 4 func signature bytes)
// if [useStrictMode] is true, it will return an error if the length of [input] is not [common.HashLength]
func UnpackSetGreetingInput(input []byte, useStrictMode bool) (string, error) {
// Initially we had this check to ensure that the input was the correct length.
// However solidity does not always pack the input to the correct length, and allows
// for extra padding bytes to be added to the end of the input. Therefore, we have removed
// this check with the Durango. We still need to keep this check for backwards compatibility.
if useStrictMode && len(input) > common.HashLength {
return "", ErrInputExceedsLimit
}
res, err := HelloWorldABI.UnpackInput("setGreeting", input, useStrictMode)
if err != nil {
return "", err
}
unpacked := *abi.ConvertType(res[0], new(string)).(*string)
return unpacked, nil
}

To call this function in setGreeting we should use Durango activation as follows here:

// do not use strict mode after Durango
useStrictMode := !contract.IsDurangoActivated(accessibleState)
// attempts to unpack [input] into the arguments to the SetGreetingInput.
// Assumes that [input] does not include selector
// You can use unpacked [inputStruct] variable in your code
inputStruct, err := UnpackSetGreetingInput(input, useStrictMode)
if err != nil {
return nil, remainingGas, err
}

This will ensure that non-strict mode unpacking is used after Durango activation.

Note: This should not impose any critical issue for your custom precompiles. If you want to keep using the old strict mode and keep the backward compatibility you can use true for the useStrictMode parameter. However if your precompile is mainly used from other deployed contracts (Solidity) you should do this transition in order to increase the compatibility of your precompile.

Was this page helpful?