Skip to content

Latest commit

 

History

History
417 lines (299 loc) · 18.4 KB

File metadata and controls

417 lines (299 loc) · 18.4 KB

HIP-1195: Hiero hooks and an application to allowances

HIP: https://hips.hedera.com/hip/hip-1195

This HIP introduces hooks, programmable Hiero extension points that let users customize the behavior of their entities. The initial implementation focuses on EVM hooks and account allowance hooks as the first extension point.

New APIs

Utility Classes

Note: All new classes need to implement:

  • Getters and setters and setters should return the instance for method chaining.
  • From and toProtobuf private functions.

HookExtensionPoint

enum HookExtensionPoint {
    ACCOUNT_ALLOWANCE_HOOK = 0
}

HookType

enum HookExtensionPoint {
	NO_HOOK HookType = 0
	PRE_HOOK = 1
	PRE_POST_HOOK = 2
	PRE_HOOK_SENDER = 3
	PRE_POST_HOOK_SENDER = 4
	PRE_HOOK_RECEIVER = 5
	PRE_POST_HOOK_RECEIVER = 6
}

HookCreationDetails

// The details of a hook's creation.
type HookCreationDetails {
    // The extension point for the hook.
    HookExtensionPoint extensionPoint

    // The id to create the hook at.
    uint64 hookId

    // The hook implementation.
    // A hook programmed in EVM bytecode that may access state or interact with external contracts.
    LambdaEvmHook lambdaHook 

    // A key that that can be used to remove or replace the hook.
    Key adminKey
}

LambdaEvmHook

// Definition of a lambda EVM hook.
type LambdaEvmHook extends EvmHookSpec {
    // Initial storage updates for the lambda, if any.
    List<LambdaStorageUpdate> storageUpdates
}

// Shared specifications for an EVM hook. May be used for any extension point.
type EvmHookSpec {
    // The source of the EVM bytecode for the hook.
    ContractId contractId
}

LambdaStorageUpdate

// Specifies a key/value pair in the storage of a lambda, either by the explicit storage slot contents; or by a combination of a Solidity mapping's slot key and the key into that mapping.
type LambdaStorageUpdate {
    // oneof (mutually exclusive):

    // An explicit storage slot update.
    LambdaStorageSlot storageSlot  

    // An implicit storage slot update specified as a Solidity mapping entry.
    LambdaMappingEntry mappingEntry 
}

type LambdaStorageSlot {
    // 32-byte storage slot key
    bytes key

    // 32-byte storage slot value (empty to delete)
    bytes value
}

type LambdaMappingEntry {
    // Slot corresponding to Solidity mapping
    bytes mappingSlot

    // 32-byte key of mapping entry
    bytes key

    // 32-byte value of mapping entry (empty to delete)
    bytes value
}

HookCall and EvmHookCall

// Specifies a call to a hook from within a transaction where the hook owner is implied by the point of use.
type HookCall {
    // The id of the hook to call.
    uint64 hookId

    // Specification of how to call an EVM hook.
    EvmHookCall evmHookCall 
}

// Specifies details of a call to an EVM hook.
type EvmHookCall {
    // Call data to pass to the hook.
    bytes data

    // The gas limit to use.
    uint64 gasLimit
}

HookId and HookEntityId

type HookId {
  HookEntityId entityId
  int64 hookId
}


type HookEntityId {
  AccountId accountId
}

Transactions

LambdaSStoreTransaction

// Adds or removes key/value pairs in the storage of a lambda.
type LambdaSStoreTransaction {
    // The id of the lambda whose storage is being updated.
    HookId hookId

    // The updates to the storage of the lambda.
    List<LambdaStorageUpdate> storageUpdates 
}

Methods:

  • Getters and setters
  • clearStorageUpdates() - Removes the storage updates from state

Updated APIs

AccountCreateTransaction

  • addHook(HookCreationDetails) - Add a hook to be created with the account
  • setHooks(List<HookCreationDetails>) - Set hooks to be created with the account
  • List<HookCreationDetails> getHooks() - Get the list of hooks to be created

AccountUpdateTransaction

  • addHook(HookCreationDetails) - Add a hook to be created for the account
  • setHooks(List<HookCreationDetails>) - Set hooks to be created with the account
  • deleteHook(uint64) - Mark a hook for deletion from the account
  • deleteHooks(List<uint64>) - Mark hooks for deletion from the account
  • List<HookCreationDetails> getHooksToCreate() - Get the list of hooks to be created
  • List<uint64> getHooksToDelete() - Get the list of hook IDs to be deleted

ContractCreateTransaction

  • addHook(HookCreationDetails) - Add a hook to be created with the contract
  • setHooks(List<HookCreationDetails>) - Set hooks to be created with the contract
  • List<HookCreationDetails> getHooks() - Get the list of hooks to be created

ContractUpdateTransaction

  • addHook(HookCreationDetails) - Add a hook to be created for the contract
  • setHooks(List<HookCreationDetails>) - Set hooks to be created with the contract
  • deleteHook(uint64) - Mark a hook for deletion from the contract
  • deleteHooks(List<uint64>) - Mark hooks for deletion from the contract
  • List<HookCreationDetails> getHooksToCreate() - Get the list of hooks to be created
  • List<uint64> getHooksToDelete() - Get the list of hook IDs to be deleted

TransferTransaction

HBAR Transfer Methods with Hooks:

  • addHbarTransferWithHook(accountId, amount, hookCall, hookType) - Add HBAR transfer with hook (PRE_HOOK or PRE_POST_HOOK)

NFT Transfer Methods with Hooks:

  • addNftTransferWithHook(nftId, senderAccountId, receiverAccountId, hookCall, hookType) - Add NFT transfer with receiver hook (PRE_HOOK_SENDER, PRE_POST_HOOK_SENDER, PRE_HOOK_RECEIVER or PRE_POST_HOOK_RECEIVER)

Token Transfer Methods with Hooks:

  • addTokenTransferWithHook(tokenId, accountId, amount, hookCall, hookType) - Add token transfer with hook (PRE_HOOK or PRE_POST_HOOK)

Test Plan

AccountCreateTransaction

  1. Given an AccountCreateTransaction is configured with a basic lambda EVM hook (without storage updates), when the transaction is executed, then the account is created with the lambda hook successfully.

  2. Given an AccountCreateTransaction is configured with a lambda EVM hook with storage updates, when the transaction is executed, then the account is created with the lambda hook successfully.

  3. Given an AccountCreateTransaction is configured with a lambda EVM hook that has no contract ID specified, when the transaction is executed, then the transaction fails with an INVALID_HOOK_CREATION_SPEC error.

  4. Given an AccountCreateTransaction is configured with duplicate hook IDs in the same creation details, when the transaction is executed, then the transaction fails with a HOOK_ID_REPEATED_IN_CREATION_DETAILS error during precheck.

  5. Given an AccountCreateTransaction is configured with a lambda EVM hook that has an admin key specified, when the transaction is executed with the admin key signature, then the account is created with the lambda hook and admin key successfully.

AccountUpdateTransaction

  1. Given an account exists without hooks, when an AccountUpdateTransaction adds a basic lambda EVM hook, then the hook is successfully attached to the account.

  2. Given an AccountUpdateTransaction is configured with duplicate hook IDs in the same creation details, when the transaction is executed, then the transaction fails with a HOOK_ID_REPEATED_IN_CREATION_DETAILS error.

  3. Given an account exists with a hook, when an AccountUpdateTransaction attempts to add a hook with the same ID that already exists on the account, then the transaction fails with a HOOK_ID_IN_USE error.

  4. Given an account exists without hooks, when an AccountUpdateTransaction adds a lambda EVM hook with initial storage updates, then the hook is attached and storage is initialized correctly.

  5. Given an account exists with an existing hook, when an AccountUpdateTransaction attempts to add another hook with the same ID that is already in use, then the transaction fails with a HOOK_ID_IN_USE error.

  6. Given an account exists with a hook, when an AccountUpdateTransaction deletes the hook by ID with valid signatures, then the hook is successfully removed from the account.

  7. Given an account exists with hooks, when an AccountUpdateTransaction attempts to delete a hook ID that doesn't exist on the account, then the transaction fails with a HOOK_NOT_FOUND error.

  8. Given an AccountUpdateTransaction attempts to add and delete hooks with the same ID in the same transaction, when the transaction is executed, then the transaction fails with a HOOK_NOT_FOUND error.

  9. Given an account exists with a hook that has been previously deleted, when an AccountUpdateTransaction attempts to delete the same hook again, then the transaction fails with a HOOK_DELETED error.

ContractCreateTransaction

  1. Given a ContractCreateTransaction is configured with a basic lambda EVM hook (without storage updates), when the transaction is executed, then the contract is created with the lambda hook successfully.

  2. Given a ContractCreateTransaction is configured with a lambda EVM hook with storage updates, when the transaction is executed, then the contract is created with the lambda hook and storage initialized correctly.

  3. Given a ContractCreateTransaction is configured with a lambda EVM hook that has no contract ID specified, when the transaction is executed, then the transaction fails with an INVALID_HOOK_CREATION_SPEC error.

  4. Given a ContractCreateTransaction is configured with duplicate hook IDs in the same creation details, when the transaction is executed, then the transaction fails with a HOOK_ID_REPEATED_IN_CREATION_DETAILS error during precheck.

  5. Given a ContractCreateTransaction is configured with a lambda EVM hook that has an admin key specified, when the transaction is executed with the admin key signature, then the contract is created with the lambda hook and admin key successfully.

ContractUpdateTransaction

  1. Given a contract exists without hooks, when a ContractUpdateTransaction adds a basic lambda EVM hook with valid signatures, then the hook is successfully attached to the contract.

  2. Given a ContractUpdateTransaction is configured with duplicate hook IDs in the same creation details, when the transaction is executed, then the transaction fails with a HOOK_ID_REPEATED_IN_CREATION_DETAILS error during precheck.

  3. Given a contract exists with a hook, when a ContractUpdateTransaction attempts to add a hook with the same ID that already exists on the contract, then the transaction fails with a HOOK_ID_IN_USE error.

  4. Given a contract exists without hooks, when a ContractUpdateTransaction adds a lambda EVM hook with initial storage updates, then the hook is attached and storage is initialized correctly.

  5. Given a contract exists with an existing hook, when a ContractUpdateTransaction attempts to add another hook with the same ID that is already in use, then the transaction fails with a HOOK_ID_IN_USE error.

  6. Given a contract exists with a hook, when a ContractUpdateTransaction deletes the hook by ID with valid signatures, then the hook is successfully removed from the contract.

  7. Given a contract exists with hooks, when a ContractUpdateTransaction attempts to delete a hook ID that doesn't exist on the contract, then the transaction fails with a HOOK_NOT_FOUND error.

  8. Given a ContractUpdateTransaction attempts to add and delete hooks with the same ID in the same transaction, when the transaction is executed, then the transaction fails with a HOOK_NOT_FOUND error.

  9. Given a contract exists with a hook that has been previously deleted, when a ContractUpdateTransaction attempts to delete the same hook again, then the transaction fails with a HOOK_DELETED error.

LambdaSStoreTransaction

  1. Given a lambda hook exists with storage, when a LambdaSStoreTransaction updates storage slots with valid signatures, then the storage is updated successfully.

  2. Given a lambda hook exists, when a LambdaSStoreTransaction attempts to update storage without proper signatures, then the transaction fails with an unauthorized error.

TransferTransaction

  1. Given an account has a pre-transaction allowance hook configured, when a TransferTransaction attempts to transfer HBAR from that account, then the hook is called before the transfer and approves the transaction.

  2. Given an account has a pre-transaction allowance hook that validates transfer limits, when a TransferTransaction exceeds the allowed amount, then the hook rejects the transfer and the transaction fails.

  3. Given an account has a pre/post-transaction allowance hook configured, when a successful HBAR transfer occurs, then the hook is called both before and after the transfer execution.

  4. Given an account has an allowance hook for token transfers, when a TransferTransaction includes token transfers from that account, then the hook validates the token allowance and approves valid transfers.

  5. Given an account has an NFT allowance hook configured, when a TransferTransaction attempts to transfer an NFT from that account, then the hook validates the NFT allowance and processes the transfer accordingly.

  6. Given multiple accounts in a transfer have different allowance hooks, when a TransferTransaction involves all accounts, then each account's respective hooks are called and must all approve for the transaction to succeed.

  7. Given an account has an allowance hook that tracks daily transfer limits, when multiple TransferTransactions are executed within the same day, then the hook maintains state and enforces cumulative limits correctly.

  8. Given a sender account has an allowance hook and receiver account has a different allowance hook, when a TransferTransaction occurs between them, then both sender and receiver hooks are executed in the correct order.

  9. Given a TransferTransaction uses the combined addHbarTransferWithPreTxHook method, when the transaction is executed, then both the transfer and hook are applied correctly in a single operation.

  10. Given a TransferTransaction uses the combined addNftTransferWithSenderReceiverHooks method, when the transaction is executed, then the NFT transfer occurs with both sender and receiver hooks properly configured.

  11. Given a TransferTransaction uses addTokenTransferWithPrePostTxHook for a fungible token, when the transaction is executed, then the token transfer occurs with the hook called before and after the transaction.

  12. Given multiple transfer methods with hooks are combined in a single TransferTransaction, when the transaction is executed, then all transfers and their respective hooks are processed correctly without conflicts.

Examples

Creating an Account with a Lambda EVM Hook

import {
  LambdaEvmHook,
  LambdaStorageUpdate,
  LambdaStorageSlot
} from "@hashgraph/sdk";

// Step 1: Create initial storage updates
const storageUpdates = [
  new LambdaStorageUpdate().setStorageSlot(
    new LambdaStorageSlot()
      .setKey(new Uint8Array(32).fill(1)) 
      .setValue(new Uint8Array(32).fill(100))
  )
];

// Step 2: Create lambda hook with storage
const lambdaHookDetails = new HookCreationDetails()
  .setExtensionPoint(HookExtensionPoint.ACCOUNT_ALLOWANCE_HOOK)
  .setHookId(1002)
  .setLambdaHook(new LambdaEvmHook()
    .setContractId(ContractId.fromString("0.0.67890"))
    .setStorageUpdates(storageUpdates))
  .setAdminKey(adminKey.publicKey);

// Step 3: Create account with lambda hook
const accountCreateTx = new AccountCreateTransaction()
  .setKey(accountKey.publicKey)
  .addHook(lambdaHookDetails)
  .freezeWith(client)
  .sign(accountKey);

const response = await accountCreateTx.execute(client);
const accountId = (await response.getReceipt(client)).accountId;

Adding Hooks to an Existing Account

import { AccountUpdateTransaction } from "@hashgraph/sdk";

const accountUpdateTx = new AccountUpdateTransaction()
  .setAccountId(existingAccountId)
  .addHook(hookDetails1)
  .addHook(hookDetails2)
  .freezeWith(client)
  .sign(accountKey)
  .sign(adminKey);

await accountUpdateTx.execute(client);

Updating Lambda Hook Storage

import { LambdaSStoreTransaction } from "@hashgraph/sdk";

const storageUpdate = new LambdaStorageUpdate()
  .setStorageSlot(new LambdaStorageSlot()
    .setKey(new Uint8Array(32).fill(1))
    .setValue(new Uint8Array(32).fill(200)));

const lambdaStoreTx = new LambdaSStoreTransaction()
  .setHookId(1002)
  .addStorageUpdate(storageUpdate)
  .freezeWith(client)
  .sign(adminKey);

await lambdaStoreTx.execute(client);

Transferring HBAR, NFTs or FTs

// Create different hooks for different transfer types
const hbarHook = new HookCall()
  .setHookId(1001)
  .setEvmHookCall(new EvmHookCall()
    .setData(new Uint8Array([0x01, 0x02])) 
    .setGasLimit(50000));

const nftSenderHook = new HookCall()
  .setHookId(1002)
  .setEvmHookCall(new EvmHookCall()
    .setData(new Uint8Array([0x03, 0x04]))
    .setGasLimit(60000));

const nftReceiverHook = new HookCall()
  .setHookId(1003)
  .setEvmHookCall(new EvmHookCall()
    .setData(new Uint8Array([0x05, 0x06]))
    .setGasLimit(40000));

const fungibleTokenHook = new HookCall()
  .setHookId(1004)
  .setEvmHookCall(new EvmHookCall()
    .setData(new Uint8Array([0x07, 0x08])) 
    .setGasLimit(70000));

const nftId = new NftId(TokenId.fromString("0.0.12345"), 1);
const tokenId = TokenId.fromString("0.0.54321");

const tx = new TransferTransaction()
  // HBAR transfers with hook
  .addHbarTransferWithHook(senderAccountId, Hbar.from(-100), hbarHook, HookType.PRE_HOOK)
  .addHbarTransfer(receiverAccountId, Hbar.from(100))
  // NFT transfer with sender hook
  .addNftTransferWithHook(nftId, senderAccountId, receiverAccountId, nftSenderHook, HookType.PRE_HOOK_SENDER)
  // NFT transfer with receiver hook
  .addNftTransferWithHook(nftId, senderAccountId, receiverAccountId, nftReceiverHook, HookType.PRE_HOOK_RECEIVER)
  // Fungible token transfers with hook
  .addTokenTransferWithHook(tokenId, senderAccountId, -1000, fungibleTokenHook, HookType.PRE_POST_HOOK)
  .addTokenTransfer(tokenId, receiverAccountId, 1000)
  .freezeWith(client)
  .sign(senderKey);

const result = await tx.execute(client);
console.log(`Transaction executed with ID: ${result.transactionId}`);