Implementing Account Abstraction on the Harmony Protocol in Q2

Casey Gardiner
Harmony
Published in
14 min readJan 10, 2023

--

The Harmony Protocol is a high-performance, decentralized blockchain that utilizes a unique consensus algorithm called “sharding.” One of the future vital features of the Harmony Protocol will be account abstraction, which allows for creating multiple accounts, each with its unique functionality and features. In Q2 of 2023, the Harmony Protocol will implement account abstraction to provide greater flexibility and functionality for users and developers.

Background

The current implementation of the Harmony Protocol utilizes a single type of account called a “base account.” These accounts are limited in their functionality, as they can only be used for sending and receiving transactions and cannot execute any smart contracts. This lack of flexibility has led to the development of other types of accounts, such as smart contracts and token contracts, which can be used for more advanced functionality, such as the creation of decentralized applications (dApps) and the issuance of tokens.

Account Abstraction

Account abstraction is a feature that allows for the creation of multiple types of accounts, each with unique functionality and features. The Harmony Protocol will implement account abstraction to provide greater flexibility and functionality for users and developers. This feature will be implemented by creating a new smart contract that acts as a factory for creating new accounts. Based on the parameters passed to the factory contract, the factory contract will allow for the creation of different account types, such as base accounts, token contract accounts, and smart contract accounts.

Account Types

The Harmony Protocol will be introducing the following types of accounts:

  • Base Account: This is the standard account type and will be the default account type for all users. Base accounts can be used for sending and receiving transactions and participating in the consensus process. They cannot execute any smart contract functions. Base accounts will be created with a unique address, which will be derived from the user’s private key and the Harmony Protocol’s address generation algorithm.
  • Token Contract Account: This type of account is used for issuing tokens and managing token transfers. Token contract accounts can be used to create and manage custom tokens and track the total supply and individual token balances. These accounts can execute pre-defined smart contract functions specific to token management, such as minting new tokens and transferring tokens to other accounts. Token contract accounts will be created by deploying a smart contract that implements the ERC-20 standard on the Harmony Protocol.
  • Smart Contract Account: This type of account is used for the creation and execution of smart contracts. Smart contract accounts will be created by deploying a smart contract on the Harmony Protocol. Smart contract accounts can create decentralized applications (dApps) and interact with other smart contract accounts. These accounts can execute any smart contract function that the contract owner can define.

Account Creation

Creating a new account on the Harmony Protocol is done through a smart contract. The following is an example of how to create a new base account using the account factory contract:

pragma solidity ^0.8.0;

contract AccountFactory {

function createAccount() public returns (address) {
return new BaseAccount();
}
}

In this example, a new base account is created by calling the “createAccount” function, which creates a new instance of the “BaseAccount” contract. This contract can then be used for sending and receiving transactions, as well as participating in the consensus process. The function returns the address of the new account, which can be used to interact with it.

Creating a new token contract account or smart contract account is done in a similar manner, but by calling the appropriate functions in the account factory contract. Here is an example of creating a new token contract account:

pragma solidity ^0.8.0;

contract AccountFactory {

function createTokenContract(string memory name, string memory symbol, uint256 totalSupply) public returns (address) {
return new TokenContract(name, symbol, totalSupply);
}
}

In this example, the “createTokenContract” function is called to create a new instance of the “TokenContract” contract, which is used for managing tokens. The function takes in three arguments, the name of the token, the symbol of the token, and the total supply of tokens. It returns the address of the new token contract, which can be used to interact with it.

Similarly, here is an example of creating a new smart contract account:

pragma solidity ^0.8.0;

contract AccountFactory {

function createSmartContract(string memory contractName, bytes memory contractCode) public returns (address) {
return new SmartContract(contractName, contractCode);
}
}

In this example, the “createSmartContract” function is called to create a new instance of the “SmartContract” contract. The function takes in two arguments, the smart contract’s name, the smart contract, and the actual code of the smart contract in bytes. It returns the address of the new smart contract, which can be used to interact with it.

Account Management

Once an account is created, it can be managed and interacted with through smart contracts. The following is an example of how to transfer tokens from one token contract account to another:

pragma solidity ^0.8.0;

contract TokenTransfer {

function transfer(address from, address to, uint256 value) public {
TokenContract(from).transfer(to, value);
}
}

In this example, the “transfer” function transfers tokens from one token contract account to another. The function uses the token contract’s “transfer” function to perform the transfer. The function takes in three arguments, the address of the sender, the address of the recipient, and the number of tokens to be transferred.

The same approach can also be used to manage smart contract accounts by calling appropriate smart contract functions to interact with the smart contract.

Core Changes

Account abstraction is a novel way of improving the transaction validity conditions in Ethereum Virtual Machine (EVM) by allowing any arbitrary EVM bytecode to be executed. This expands the range of possible valid transactions beyond those limited by existing standards, providing contracts with more expressive and secure means of coding entire applications on top of the blockchain. To demonstrate this concept, we propose a new EVM opcode called PAYGAS. This opcode allows accounts to signal their willingness to pay specific amounts for specific types of transactions — permitting tighter control over exactly which transactions will execute and which will not proceed further. We have split account abstraction into two tiers: single-tenant AA, which is intended to support wallets or other use cases with few participants; and multi-tenant AA, which is intended to support applications with many participants like tornado.cash or Uniswap. The primary benefit here is its ability to enforce additional rules for the determination of transaction validity in order to ensure improved security and scalability across various industries such as payment systems, games, supply chain management and many others within blockchain development. For example, a PAYGAS implementation could prevent malicious actors from wasting resources by submitting arbitrarily large transaction fees in order to carry out expensive computations or accidentally executing duplicate payments since it requires every transaction to pay a set amount before being allowed to proceed further.

A new EIP-2718 transaction with type AA_TX_TYPE is introduced, referred to as “AA transactions”. This type of transaction requires its payload to be interpreted as rlp([nonce, target, data]), and has its base gas cost set at 15000 (instead of 21000) due to the lack of “intrinsic” ECDSA and signature. Additionally, nonces are processed similarly to existing transactions — if tx.nonce == tx.target.nonce fails then the transaction is invalid — otherwise proceeding with setting tx.nonce += 1 immediately afterwards. Furthermore, the PAYGAS opcode sets the gas price and gas limit for each contract willing to pay for specific transactions — preventing malicious actors from wasting resources by submitting large fees or executing duplicate payments since it requires every transaction to have an associated cost in order for it to be validly processed. Lastly, introducing a few new transaction-wide global variables works similarly (in particular, have similar reversion logic) to SSTORE refunds counter.

Single Tenant Account Abstraction introduces a new transaction type that requires its payload to be interpreted as rlp([nonce, target, data]), and has its base gas cost set at 15000 (instead of 21000) due to the lack of “intrinsic” ECDSA and signature. Additionally, introducing a few new transaction-wide global variables are used alongside the PAYGAS opcode. These include:

The NONCE (0x48) Opcode is also introduced with gas cost G_base which pushes the nonce of the callee onto the stack. The PAYGAS (0x49) Opcode is utilized for setting the gas price and gas limit for each contract willing to pay for specific transactions. In particular, it takes two arguments off the stack — version number and memory start — where it performs an assertion on version number being equal to zero before reading: bytes_to_int(vm.memory[memory_start: memory_start + 32]) for gas price and bytes to int(vm.memory[memory_start + 32: memory _start + 64]) for gas limit in order to prevent malicious actors from wasting resources or executing duplicate payments by requiring associated costs on each transaction accepted.

The PAYGAS (0x49) opcode is introduced to help set the gas price and gas limit for each contract willing to pay for transactions. The similarity in both reads to MLOAD and CALL means that memory will be expanded if needed. In accordance with future hard forks, different version numbers can be added which will cause the opcode to take different-sized memory slices and interpret them differently — two particular potential use cases are EIP-1559 and the escalator mechanism. The PAYGAS opcode works as follows: if the three conditions below are met, along with the version number check mentioned previously, then it sets the parameters accordingly. — The account’s balance must be greater than or equal to gas_price * gas_limit — globals.transaction_fee_paid should be False — In a top level AA execution frame, such that if the EVM execution of the entire transaction exits or reverts then it has finished its purpose. Once all these conditions are met, it will subtract (gas_price * gas_limit) from the account’s balance, setting globals.transaction_fee_paid to True and adjusting globals.gas_price and globals.gas_limit according to their respective arguments before setting the remaining gas in the current context equal to (gas_limit minus already consumed gas).

To ensure replay protection is effective and to prevent malicious actors from carrying out replay attacks, two key approaches must be implemented. The first involves requiring SET_INDESTRUCTIBLE when nonce values are checked or updated; this means the value cannot be reverted, forcing any subsequent transactions using the same nonce value to fail. The second approach requires the use of a new opcode called NonceVerify that will verify if a particular transaction has been executed before, i.e. if it uses an already used nonce value, it will simply fail and not be accepted on the blockchain. These two approaches are meant to work in tandem with one another to help ensure replay protection is effective against potentially malicious actors.

Requiring that contracts targeted by AA transactions begin with EIP-2937’s SET_INDESTRUCTIBLE opcode is essential for a successful transaction. If the contract does not include this code, then any transactions involving it will be invalid and cannot be accepted onto the blockchain. To ensure success, AA_PREFIX must be modified to include this opcode as well. An additional defensive measure against replay attacks involves preserving contract nonces across SELFDESTRUCT invocations instead of setting the nonce value to zero. This guards against malicious actors trying to manipulate the nonce value in order to perform an attack on the system. Miscellaneous considerations include requiring CALLER (0x33) to return AA_ENTRY_POINT if it is invoked in the first frame of execution of a call initiated by an AA transaction and ORIGIN (0x32) returning AA_ENTRY_POINT when invoked in any frame of execution of an AA transaction. Lastly, GASPRICE (0x3A) now pushes the value globals.gas_price, thus making it easier for users to monitor gas prices more closely.

In order to ensure efficient and secure transactions, miners and validating nodes have adopted strategies to determine if a transaction is worthy of being included in blocks or rebroadcast. Miners must first perform a small amount of processing to check that the transaction will pay the fee required for them to include it in blocks; this protects against DoS attacks. Similarly, validating nodes also need to verify that there is an appropriate fee before rebroadcasting it. The EIP on account abstraction keeps these consensus changes minimal, enabling miners and validating nodes to gradually introduce support for AA mempool with an initial focus on simple use cases. As more complex use cases are developed, additional steps are taken accordingly, allowing for seamless integration into the blockchain network. In all scenarios, earlier stages are given more attention due to their importance and complexity compared to later stages.

When a node receives an AA transaction, they process it against the current chain head’s post-state to determine its validity. If the code of the target is not prefixed with AA_PREFIX, execution will immediately exit with failure. Additionally, if environment opcodes such as BLOCKHASH, COINBASE, TIMESTAMP, and GASLIMIT are encountered or BALANCE is read from any account (including the target itself), then execution will also fail. Furthermore, any external call/create that changes the callee to anything but the target or a precompile will result in failure. Lastly, if more gas is consumed than VERIFICATION_GAS_CAP (specified above) or more gas than is available in the block, then exit with failure. If PAYGAS is reached then success or failure depends on whether or not the balance is sufficient (e.g. balance >= gas_price * gas_limit).

Nodes strictly adhere to the rule of only keeping transactions with valid nonces in the mempool. This means that if a new transaction to a specific contract and with the same nonce is encountered, then it will replace the original one (if its gas price is higher). Thus, at any given time the mempool contains at most one pending transaction per account. When a new block is processed, all accounts which were targeted by an AA transaction (around 833 when considering each block currently has 12500000 gas and an AA transaction costs >= 15000 ) should be noted and all pending transactions targeting those accounts should be dropped. All other transactions remain in the mempool.

Single & Multiple Tenants

If the Indestructible Contracts EIP is adopted inside the Harmony core code, Single Tenant AA can be adapted to allow for DELEGATECALL during transaction verification. The external state access of any contract whose first byte is SET_INDESTRUCTIBLE will no longer be banned, however calls that change the callee other than CALLCODE and DELEGATECALL are still not permitted. Furthermore, if the IS_STATIC EIP is implemented then the list of allowed prefixes can be extended to enable incoming static calls without changing the state. This could also be used for logging incoming payments as well as other benign use cases.

External calls into AA accounts can be allowed with the help of the opcode RESERVE_GAS, which takes an argument value N and burns N gas and adds it to the refund. An AA_PREFIX that reserves >= AA_BASE_GAS_COST * 2 gas can be added as well in order for at least AA_BASE_GAS_COST gas to be spent when calling into an account. Accounts can also opt for a higher RESERVE_GAS value in order to have a higher VERIFICATION_GAS_CAP. This will preserve a VERIFICATION_GAS_MULTIPLIER-to-1 ratio between the minimum gas cost to edit an account and the set VERIFICATION_GAS_CAP while protecting against excessive reverification gas consumption.

To address the potential for a single transaction to invalidate other transactions targeting the same account, an EIP-2930-style access list must be included in incoming transactions. This access list will make it binding that any accesses outside of the list are invalid. Additionally, incoming transactions can only be included in the mempool if their access list is disjoint from those of other transactions or if their gasprice is higher. An alternative approach would be to create per-storage-slot mempools instead of per-account mempools, allowing a single transaction to be part of multiple storage slots’ mempools (capped at five). In order to use multi-tenant AA, miners may need to edit nonces of incoming transactions which would make their final hash unpredictable at publication time and require clients to explicitly work around this issue. To refine these ideas, more research is necessary and this will be left for later work.

Backwards Compatibility

User-defined accounts (AA) are a type of Harmony account that allows users to extend their abilities beyond those offered by an externally owned account (EOA). While AA transactions have the same format as EOA transactions, they will always have origin == AA_ENTRY_POINT instead of assert origin == caller. Unfortunately, using single-tenant AA contracts can break the transaction non-malleability invariant since it is possible for a modified version of the transaction to still be valid. To prevent this from happening, AA accounts must be designed carefully. Meanwhile, multi-tenant AA contracts break the transaction non-malleability invariant more thoroughly, making the transaction hash unpredictable even for legitimate applications that use multi-tenant AA features. To ensure replay protection on AA contracts, developers must explicitly implement it with the CHAINID (0x46) opcode introduced in EIP-1344.

Security Considerations

When a transaction is added to the mempool, it is quickly verified by the client to ensure validity. However, an attacker may still submit a transaction that invalidates all other transactions from the same account and requires the network to perform additional recomputation — costing more gas. The EIP ensures that recomputation cannot exceed six times the block gas limit in one block, making this slightly more expensive but not drastically different than before.

Account Abstraction (AA) does not introduce any new risks for peer Denial-of-Service attacks. Such attacks can already be accomplished against existing clients by sending invalid transactions. To limit the amount of computation an adversary can force a client to expend, miners should follow the recommended mining strategies. Doing so will help ensure that malicious actors are unable to exploit Account Abstraction technology and cause disruption on the network.

When implementing account abstraction on the Harmony Protocol, it’s important to consider security measures to ensure the safety of users’ funds and data. The account factory contract should be audited for security vulnerabilities and properly deployed on the Harmony Protocol’s mainnet. Additionally, all smart contract code should be thoroughly tested and reviewed before deployment to prevent any potential exploitations. Users should also practice safe account management by keeping their private keys secure and using secure methods for account creation and management.

Conclusion

The Harmony Protocol’s account abstraction feature will be implemented in Q2 of 2023, providing greater flexibility and functionality for users and developers. With the introduction of multiple account types, including base accounts, token contract accounts, and smart contract accounts, developers will have more options for creating and managing their projects on the Harmony Protocol. This will also make it easier for users to participate in and interact with these projects, as they will be able to use different types of accounts based on their needs. The added feature of account abstraction to the Harmony Protocol is a significant step forward in providing a more versatile and user-friendly blockchain platform. By introducing new account types and smart contract functionality, the Harmony Protocol will open up new opportunities for decentralized applications, decentralized finance, and many more possibilities. The introduction of account abstraction will also make it easier for developers to create and manage token-based projects, such as decentralized exchanges and prediction markets, on the Harmony Protocol.

In addition to the benefits for developers and users, account abstraction also improves the overall security of the Harmony Protocol by implementing a more secure account creation process. The account factory contract, which is responsible for creating new accounts, can be designed with security measures to ensure the safety of users’ funds and data.

Overall, the implementation of account abstraction on the Harmony Protocol in Q2 of 2023 will bring a new level of flexibility and functionality to the platform, allowing for a wider range of decentralized applications and use cases. The Harmony Protocol’s team and community should continue to work on ensuring the security and reliability of the feature for the better user experience and overall ecosystem.

Continued Reading

Pull Request

--

--