diff --git a/Examples/AccountHooks/main.swift b/Examples/AccountHooks/main.swift new file mode 100644 index 00000000..4500cb06 --- /dev/null +++ b/Examples/AccountHooks/main.swift @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import Hiero +import HieroExampleUtilities +import SwiftDotenv + +@main +internal enum Program { + internal static func main() async throws { + /// Grab the environment variables. + let env = try Dotenv.load() + + /// Initialize the client based on the provided environment. + let client = try Client.forName(env.networkName) + client.setOperator(env.operatorAccountId, env.operatorKey) + + print("Account Hooks Example") + print("====================") + + // Create a contract that will serve as our hook + print("Creating hook contract...") + + let contractKey = PrivateKey.generateEd25519() + let contractResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .adminKey(.single(contractKey.publicKey)) + .gas(100000) + .execute(client) + let contractReceipt = try await contractResponse.getReceipt(client) + let contractId = contractReceipt.contractId! + + print("Created hook contract: \(contractId)") + + // Create Lambda EVM Hook specification + print("Creating Lambda EVM Hook specification...") + + let evmHookSpec = EvmHookSpec(contractId: contractId) + let lambdaEvmHook = LambdaEvmHook(spec: evmHookSpec) + + // Create hook creation details + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, lambdaEvmHook: lambdaEvmHook, + adminKey: Key.single(contractKey.publicKey)) + + // Example 1: Create account with hooks + print("Example 1: Creating account with hooks") + + let accountKey = PrivateKey.generateEd25519() + let accountResponse = try await AccountCreateTransaction() + .keyWithoutAlias(.single(accountKey.publicKey)) + .initialBalance(10) + .addHook(hookCreationDetails) + .execute(client) + let accountReceipt = try await accountResponse.getReceipt(client) + let accountId = accountReceipt.accountId! + + print("Created account with hooks: \(accountId)") + + // Example 2: Update account to add more hooks + print("Example 2: Updating account to add more hooks") + + let hookCreationDetails2 = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, lambdaEvmHook: lambdaEvmHook, + adminKey: Key.single(contractKey.publicKey)) + + let updateResponse = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails2) + .execute(client) + let updateReceipt = try await updateResponse.getReceipt(client) + + print("Updated account with additional hooks: \(updateReceipt.status)") + + // Example 3: Update account to delete hooks + print("Example 3: Updating account to delete hooks") + + let deleteResponse = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToDelete(1) // Delete hook with ID 1 + .execute(client) + let deleteReceipt = try await deleteResponse.getReceipt(client) + + print("Updated account to delete hooks: \(deleteReceipt.status)") + + // Cleanup + print("Cleaning up...") + + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(env.operatorAccountId) + .execute(client) + .getReceipt(client) + + _ = try await ContractDeleteTransaction() + .contractId(contractId) + .transferAccountId(env.operatorAccountId) + .execute(client) + .getReceipt(client) + + print("Cleanup completed") + print("\nAccount Hooks Example completed successfully!") + } +} + +extension Environment { + /// Account ID for the operator to use in this example. + internal var operatorAccountId: AccountId { + AccountId(self["OPERATOR_ID"]!.stringValue)! + } + + /// Private key for the operator to use in this example. + internal var operatorKey: PrivateKey { + PrivateKey(self["OPERATOR_KEY"]!.stringValue)! + } + + /// The name of the Hiero network this example should run against. + internal var networkName: String { + self["HEDERA_NETWORK"]?.stringValue ?? "testnet" + } +} diff --git a/Examples/ContractHooks/main.swift b/Examples/ContractHooks/main.swift new file mode 100644 index 00000000..c44f9703 --- /dev/null +++ b/Examples/ContractHooks/main.swift @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import Hiero +import HieroExampleUtilities +import SwiftDotenv + +@main +internal enum Program { + internal static func main() async throws { + /// Grab the environment variables. + let env = try Dotenv.load() + + /// Initialize the client based on the provided environment. + let client = try Client.forName(env.networkName) + client.setOperator(env.operatorAccountId, env.operatorKey) + + print("Contract Hooks Example") + print("=====================") + + // Create a contract that will serve as our hook + print("Creating hook contract...") + + let hookContractKey = PrivateKey.generateEd25519() + let hookContractResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .adminKey(.single(hookContractKey.publicKey)) + .gas(100000) + .execute(client) + let hookContractReceipt = try await hookContractResponse.getReceipt(client) + let hookContractId = hookContractReceipt.contractId! + + print("Created hook contract: \(hookContractId)") + + // Create Lambda EVM Hook specification + print("Creating Lambda EVM Hook specification...") + + let evmHookSpec = EvmHookSpec(contractId: hookContractId) + let lambdaEvmHook = LambdaEvmHook(spec: evmHookSpec) + + // Create hook creation details + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, lambdaEvmHook: lambdaEvmHook, + adminKey: Key.single(hookContractKey.publicKey)) + + // Example 1: Create contract with hooks + print("Example 1: Creating contract with hooks") + + let contractKey = PrivateKey.generateEd25519() + let contractResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .adminKey(.single(contractKey.publicKey)) + .gas(200000) + .addHook(hookCreationDetails) + .execute(client) + let contractReceipt = try await contractResponse.getReceipt(client) + let contractId = contractReceipt.contractId! + + print("Created contract with hooks: \(contractId)") + + // Example 2: Update contract to add more hooks + print("Example 2: Updating contract to add more hooks") + + let hookCreationDetails2 = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, lambdaEvmHook: lambdaEvmHook, + adminKey: Key.single(hookContractKey.publicKey)) + + let updateResponse = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails2) + .execute(client) + let updateReceipt = try await updateResponse.getReceipt(client) + + print("Updated contract with additional hooks: \(updateReceipt.status)") + + // Example 3: Update contract to delete hooks + print("Example 3: Updating contract to delete hooks") + + let deleteResponse = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToDelete(1) // Delete hook with ID 1 + .execute(client) + let deleteReceipt = try await deleteResponse.getReceipt(client) + + print("Updated contract to delete hooks: \(deleteReceipt.status)") + + // Example 4: Execute contract with hooks + print("Example 4: Executing contract with hooks") + + let executeResponse = try await ContractExecuteTransaction() + .contractId(contractId) + .gas(100000) + .function("someFunction") + .execute(client) + let executeReceipt = try await executeResponse.getReceipt(client) + + print("Executed contract with hooks: \(executeReceipt.status)") + + // Cleanup + print("Cleaning up...") + + _ = try await ContractDeleteTransaction() + .contractId(contractId) + .transferAccountId(env.operatorAccountId) + .execute(client) + .getReceipt(client) + + _ = try await ContractDeleteTransaction() + .contractId(hookContractId) + .transferAccountId(env.operatorAccountId) + .execute(client) + .getReceipt(client) + + print("Cleanup completed") + print("\nContract Hooks Example completed successfully!") + } +} + +extension Environment { + /// Account ID for the operator to use in this example. + internal var operatorAccountId: AccountId { + AccountId(self["OPERATOR_ID"]!.stringValue)! + } + + /// Private key for the operator to use in this example. + internal var operatorKey: PrivateKey { + PrivateKey(self["OPERATOR_KEY"]!.stringValue)! + } + + /// The name of the Hiero network this example should run against. + internal var networkName: String { + self["HEDERA_NETWORK"]?.stringValue ?? "testnet" + } +} diff --git a/Examples/LambdaSStore/main.swift b/Examples/LambdaSStore/main.swift new file mode 100644 index 00000000..fac37b85 --- /dev/null +++ b/Examples/LambdaSStore/main.swift @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import Hiero +import HieroExampleUtilities +import SwiftDotenv + +@main +internal enum Program { + internal static func main() async throws { + /// Grab the environment variables. + let env = try Dotenv.load() + + /// Initialize the client based on the provided environment. + let client = try Client.forName(env.networkName) + client.setOperator(env.operatorAccountId, env.operatorKey) + + print("Lambda SSTORE Example") + print("====================") + + // Create a contract that will serve as our hook + print("Creating hook contract...") + + let contractKey = PrivateKey.generateEd25519() + let contractResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .adminKey(.single(contractKey.publicKey)) + .gas(100000) + .execute(client) + let contractReceipt = try await contractResponse.getReceipt(client) + let contractId = contractReceipt.contractId! + + print("Created hook contract: \(contractId)") + + // Create account with hooks + print("Creating account with hooks...") + + var evmHookSpec = EvmHookSpec() + evmHookSpec = evmHookSpec.contractId(contractId) + + let lambdaEvmHook = LambdaEvmHook(spec: evmHookSpec) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, lambdaEvmHook: lambdaEvmHook, + adminKey: Key.single(contractKey.publicKey)) + + let accountKey = PrivateKey.generateEd25519() + let accountResponse = try await AccountCreateTransaction() + .keyWithoutAlias(.single(accountKey.publicKey)) + .initialBalance(10) + .addHook(hookCreationDetails) + .execute(client) + let accountReceipt = try await accountResponse.getReceipt(client) + let accountId = accountReceipt.accountId! + + print("Created account with hooks: \(accountId)") + + // Create HookId for the Lambda SSTORE transaction + print("Creating HookId...") + + let hookEntityId = HookEntityId(accountId) + let hookId = HookId(entityId: hookEntityId, hookId: 1) + + print("Created HookId: \(hookId)") + + // Example 1: Update storage slot + print("Example 1: Updating storage slot") + + var storageSlot = LambdaStorageSlot() + storageSlot = storageSlot.key(Data([0x01, 0x02, 0x03, 0x04])) // 32-byte key + storageSlot = storageSlot.value(Data([0x05, 0x06, 0x07, 0x08])) // 32-byte value + + var storageUpdate = LambdaStorageUpdate() + storageUpdate = storageUpdate.setStorageSlot(storageSlot) + + let sstoreResponse1 = try await LambdaSStoreTransaction() + .hookId(hookId) + .addStorageUpdate(storageUpdate) + .execute(client) + let sstoreReceipt1 = try await sstoreResponse1.getReceipt(client) + + print("Updated storage slot: \(sstoreReceipt1.status)") + + // Example 2: Update mapping entries + print("Example 2: Updating mapping entries") + + var mappingEntry1 = LambdaMappingEntry() + mappingEntry1 = mappingEntry1.key(Data([0x09, 0x0A, 0x0B, 0x0C])) // 32-byte key + mappingEntry1 = mappingEntry1.value(Data([0x0D, 0x0E, 0x0F, 0x10])) // 32-byte value + + var mappingEntry2 = LambdaMappingEntry() + mappingEntry2 = mappingEntry2.preimage(Data([0x11, 0x12, 0x13, 0x14])) // Preimage + mappingEntry2 = mappingEntry2.value(Data([0x15, 0x16, 0x17, 0x18])) // 32-byte value + + var mappingEntries = LambdaMappingEntries() + mappingEntries = mappingEntries.mappingSlot(Data([0x19, 0x1A, 0x1B, 0x1C])) // Mapping slot + mappingEntries = mappingEntries.setEntries([mappingEntry1, mappingEntry2]) + + var mappingUpdate = LambdaStorageUpdate() + mappingUpdate = mappingUpdate.setMappingEntries(mappingEntries) + + let sstoreResponse2 = try await LambdaSStoreTransaction() + .hookId(hookId) + .addStorageUpdate(mappingUpdate) + .execute(client) + let sstoreReceipt2 = try await sstoreResponse2.getReceipt(client) + + print("Updated mapping entries: \(sstoreReceipt2.status)") + + // Example 3: Multiple storage updates + print("Example 3: Multiple storage updates") + + var storageSlot2 = LambdaStorageSlot() + storageSlot2 = storageSlot2.key(Data([0x1D, 0x1E, 0x1F, 0x20])) + storageSlot2 = storageSlot2.value(Data([0x21, 0x22, 0x23, 0x24])) + + var storageUpdate2 = LambdaStorageUpdate() + storageUpdate2 = storageUpdate2.setStorageSlot(storageSlot2) + + let sstoreResponse3 = try await LambdaSStoreTransaction() + .hookId(hookId) + .storageUpdates([storageUpdate, storageUpdate2]) // Multiple updates + .execute(client) + let sstoreReceipt3 = try await sstoreResponse3.getReceipt(client) + + print("Applied multiple storage updates: \(sstoreReceipt3.status)") + + // Example 4: Clear storage updates + print("Example 4: Clearing storage updates") + + let sstoreResponse4 = try await LambdaSStoreTransaction() + .hookId(hookId) + .clearStorageUpdates() // Clear all updates + .execute(client) + let sstoreReceipt4 = try await sstoreResponse4.getReceipt(client) + + print("Cleared storage updates: \(sstoreReceipt4.status)") + + // Cleanup + print("Cleaning up...") + + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(env.operatorAccountId) + .execute(client) + .getReceipt(client) + + _ = try await ContractDeleteTransaction() + .contractId(contractId) + .transferAccountId(env.operatorAccountId) + .execute(client) + .getReceipt(client) + + print("Cleanup completed") + print("\nLambda SSTORE Example completed successfully!") + } +} + +extension Environment { + /// Account ID for the operator to use in this example. + internal var operatorAccountId: AccountId { + AccountId(self["OPERATOR_ID"]!.stringValue)! + } + + /// Private key for the operator to use in this example. + internal var operatorKey: PrivateKey { + PrivateKey(self["OPERATOR_KEY"]!.stringValue)! + } + + /// The name of the Hiero network this example should run against. + internal var networkName: String { + self["HEDERA_NETWORK"]?.stringValue ?? "testnet" + } +} diff --git a/Examples/TransferWithHooks/main.swift b/Examples/TransferWithHooks/main.swift new file mode 100644 index 00000000..067c6ef9 --- /dev/null +++ b/Examples/TransferWithHooks/main.swift @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import Hiero + +/// Example demonstrating how to use hooks with transfer transactions. +/// +/// This example shows how to: +/// 1. Set up prerequisites - create tokens and NFTs +/// 2. Demonstrate TransferTransaction API with hooks +/// 3. Execute different types of transfers with hooks +@main +struct TransferWithHooksExample { + static func main() async throws { + // Initialize the client + let client = Client.forTestnet() + + // Create operator account + let operatorKey = PrivateKey.generateEd25519() + let operatorId = try AccountId.fromString("0.0.1234") // Replace with your account ID + + client.setOperator(operatorId, operatorKey) + + print("Transfer Transaction Hooks Example Start!") + + /* + * Step 1: Set up prerequisites - create tokens and NFTs + */ + print("Setting up prerequisites...") + + // Create hook contract bytecode (simplified for Swift example) + let hookBytecode = Data([ + 0x60, 0x80, 0x60, 0x40, 0x52, 0x34, 0x80, 0x15, 0x61, 0x00, 0x10, 0x57, 0x60, 0x00, 0x80, 0xfd, + 0x5b, 0x50, 0x60, 0x04, 0x36, 0x10, 0x61, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, + 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, + 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, 0x60, 0x00, 0x35, + ]) + + let hookContractResponse = try await ContractCreateTransaction() + .adminKey(.single(operatorKey.publicKey)) + .gas(1_000_000) + .bytecode(hookBytecode) + .execute(client) + + let hookContractReceipt = try await hookContractResponse.getReceipt(client) + guard let hookContractId = hookContractReceipt.contractId else { + print("Failed to create hook contract!") + return + } + + print("Created hook contract: \(hookContractId)") + + // Create hook details + var evmHookSpec = EvmHookSpec() + evmHookSpec.contractId = hookContractId + + let lambdaHook = LambdaEvmHook(spec: evmHookSpec) + + let hookDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaHook + ) + + // Create sender account + let senderKey = PrivateKey.generateEd25519() + let senderResponse = try await AccountCreateTransaction() + .keyWithoutAlias(.single(senderKey.publicKey)) + .initialBalance(10) + .addHook(hookDetails) + .execute(client) + + let senderReceipt = try await senderResponse.getReceipt(client) + guard let senderAccountId = senderReceipt.accountId else { + print("Failed to create sender account!") + return + } + + print("Created sender account: \(senderAccountId)") + + // Create receiver account + let receiverKey = PrivateKey.generateEd25519() + let receiverResponse = try await AccountCreateTransaction() + .keyWithoutAlias(.single(receiverKey.publicKey)) + .maxAutomaticTokenAssociations(100) + .initialBalance(10) + .addHook(hookDetails) + .execute(client) + + let receiverReceipt = try await receiverResponse.getReceipt(client) + guard let receiverAccountId = receiverReceipt.accountId else { + print("Failed to create receiver account!") + return + } + + print("Created receiver account: \(receiverAccountId)") + + // Create fungible token + print("Creating fungible token...") + let fungibleTokenResponse = try await TokenCreateTransaction() + .name("Example Fungible Token") + .symbol("EFT") + .tokenType(.fungibleCommon) + .decimals(2) + .initialSupply(10000) + .treasuryAccountId(senderAccountId) + .adminKey(.single(senderKey.publicKey)) + .supplyKey(.single(senderKey.publicKey)) + .execute(client) + + let fungibleTokenReceipt = try await fungibleTokenResponse.getReceipt(client) + guard let fungibleTokenId = fungibleTokenReceipt.tokenId else { + print("Failed to create fungible token!") + return + } + + print("Created fungible token with ID: \(fungibleTokenId)") + + // Create NFT token + print("Creating NFT token...") + let nftTokenResponse = try await TokenCreateTransaction() + .name("Example NFT Token") + .symbol("ENT") + .tokenType(.nonFungibleUnique) + .treasuryAccountId(senderAccountId) + .adminKey(.single(senderKey.publicKey)) + .supplyKey(.single(senderKey.publicKey)) + .execute(client) + + let nftTokenReceipt = try await nftTokenResponse.getReceipt(client) + guard let nftTokenId = nftTokenReceipt.tokenId else { + print("Failed to create NFT token!") + return + } + + print("Created NFT token with ID: \(nftTokenId)") + + // Mint NFT + print("Minting NFT...") + let nftMetadata = Data("Example NFT Metadata".utf8) + let mintResponse = try await TokenMintTransaction() + .tokenId(nftTokenId) + .metadata([nftMetadata]) + .execute(client) + + let mintReceipt = try await mintResponse.getReceipt(client) + guard let serialNumber = mintReceipt.serials?.first else { + print("Failed to mint NFT!") + return + } + + let nftId = NftId(tokenId: nftTokenId, serial: serialNumber) + print("Minted NFT with ID: \(nftId)") + + /* + * Step 2: Demonstrate TransferTransaction API with hooks (demonstration only) + */ + print("\n=== TransferTransaction with Hooks API Demonstration ===") + + // Create different hooks for different transfer types (for demonstration) + print("Creating hook call objects (demonstration)...") + + // HBAR transfer with pre-tx allowance hook + var hbarEvmHookCall = EvmHookCall() + hbarEvmHookCall.data = Data([0x01, 0x02]) + hbarEvmHookCall.gasLimit = 20000 + + let hbarHook = FungibleHookCall( + hookCall: HookCall(hookId: 1, evmHookCall: hbarEvmHookCall), + hookType: .preTxAllowanceHook + ) + + // NFT sender hook (pre-hook) + var nftSenderEvmHookCall = EvmHookCall() + nftSenderEvmHookCall.data = Data([0x03, 0x04]) + nftSenderEvmHookCall.gasLimit = 20000 + + let nftSenderHook = NftHookCall( + hookCall: HookCall(hookId: 1, evmHookCall: nftSenderEvmHookCall), + hookType: .preHook + ) + + // NFT receiver hook (pre-hook) + var nftReceiverEvmHookCall = EvmHookCall() + nftReceiverEvmHookCall.data = Data([0x05, 0x06]) + nftReceiverEvmHookCall.gasLimit = 20000 + + let nftReceiverHook = NftHookCall( + hookCall: HookCall(hookId: 1, evmHookCall: nftReceiverEvmHookCall), + hookType: .preHook + ) + + // Fungible token transfer with pre-post allowance hook + var fungibleTokenEvmHookCall = EvmHookCall() + fungibleTokenEvmHookCall.data = Data([0x07, 0x08]) + fungibleTokenEvmHookCall.gasLimit = 20000 + + let fungibleTokenHook = FungibleHookCall( + hookCall: HookCall(hookId: 1, evmHookCall: fungibleTokenEvmHookCall), + hookType: .prePostTxAllowanceHook + ) + + // Build separate TransferTransactions with hooks (demonstration) + print("Building separate TransferTransactions with hooks...") + + // Transaction 1: HBAR transfers with hook + print("\n1. Building HBAR TransferTransaction with hook...") + let hbarTransferResponse = try await TransferTransaction() + .hbarTransferWithHook(senderAccountId, Hbar(-1), hbarHook) + .hbarTransfer(receiverAccountId, Hbar(1)) + .execute(client) + + let hbarTransferReceipt = try await hbarTransferResponse.getReceipt(client) + print("HBAR transfer completed with status: \(hbarTransferReceipt.status)") + + // Transaction 2: NFT transfer with sender and receiver hooks + print("\n2. Building NFT TransferTransaction with hooks...") + let nftTransferResponse = try await TransferTransaction() + .nftTransferWithHooks(nftId, senderAccountId, receiverAccountId, nftSenderHook, nftReceiverHook) + .execute(client) + + let nftTransferReceipt = try await nftTransferResponse.getReceipt(client) + print("NFT transfer completed with status: \(nftTransferReceipt.status)") + + // Transaction 3: Fungible token transfers with hook + print("\n3. Building Fungible Token TransferTransaction with hook...") + let fungibleTransferResponse = try await TransferTransaction() + .tokenTransferWithHook(fungibleTokenId, senderAccountId, -1000, fungibleTokenHook) + .tokenTransfer(fungibleTokenId, receiverAccountId, 1000) + .execute(client) + + let fungibleTransferReceipt = try await fungibleTransferResponse.getReceipt(client) + print("Fungible token transfer completed with status: \(fungibleTransferReceipt.status)") + + print("\nAll TransferTransactions executed successfully with the following hook calls:") + print(" - Transaction 1: HBAR transfer with pre-tx allowance hook") + print(" - Transaction 2: NFT transfer with sender and receiver hooks") + print(" - Transaction 3: Fungible token transfer with pre-post allowance hook") + + print("Transfer Transaction Hooks Example Complete!") + } +} diff --git a/Package.swift b/Package.swift index f3c9b322..abd29b9e 100644 --- a/Package.swift +++ b/Package.swift @@ -25,11 +25,13 @@ import PackageDescription let exampleTargets = [ "AccountAlias", "AccountAllowance", + "AccountHooks", "AddNftAllowance", "BatchTransaction", "ConsensusPubSub", "ConsensusPubSubChunked", "ConsensusPubSubWithSubmitKey", + "ContractHooks", "CreateAccount", "CreateAccountThresholdKey", "CreateAccountWithAlias", @@ -50,6 +52,7 @@ let exampleTargets = [ "GetExchangeRates", "GetFileContents", "InitializeClientWithMirrorNetwork", + "LambdaSStore", "LongTermScheduledTransaction", "MirrorNodeContractQuery", "ModifyTokenKeys", @@ -66,6 +69,7 @@ let exampleTargets = [ "TopicWithAdminKey", "TransferCrypto", "TransferTokens", + "TransferWithHooks", "UpdateAccountPublicKey", "ValidateChecksum", "TokenUpdateMetadata", diff --git a/Sources/Hiero/Account/AccountCreateTransaction.swift b/Sources/Hiero/Account/AccountCreateTransaction.swift index c113b78b..453f90f8 100644 --- a/Sources/Hiero/Account/AccountCreateTransaction.swift +++ b/Sources/Hiero/Account/AccountCreateTransaction.swift @@ -31,6 +31,7 @@ public final class AccountCreateTransaction: Transaction { self.stakedAccountId = stakedAccountId self.stakedNodeId = stakedNodeId self.declineStakingReward = declineStakingReward + self.hookCreationDetails = [] super.init() } @@ -56,6 +57,7 @@ public final class AccountCreateTransaction: Transaction { self.declineStakingReward = data.declineReward self.alias = !data.alias.isEmpty ? try EvmAddress(data.alias) : nil + self.hookCreationDetails = try data.hookCreationDetails.map { try HookCreationDetails.fromProtobuf($0) } try super.init(protobuf: proto) } @@ -289,6 +291,26 @@ public final class AccountCreateTransaction: Transaction { return self } + public var hookCreationDetails: [HookCreationDetails] { + willSet { + ensureNotFrozen() + } + } + + @discardableResult + public func addHook(_ hook: HookCreationDetails) -> Self { + self.hookCreationDetails.append(hook) + + return self + } + + @discardableResult + public func setHooks(_ hooks: [HookCreationDetails]) -> Self { + self.hookCreationDetails = hooks + + return self + } + internal override func validateChecksums(on ledgerId: LedgerId) throws { try stakedAccountId?.validateChecksums(on: ledgerId) try autoRenewAccountId?.validateChecksums(on: ledgerId) @@ -336,6 +358,8 @@ extension AccountCreateTransaction: ToProtobuf { } proto.declineReward = declineStakingReward + + proto.hookCreationDetails = hookCreationDetails.map { $0.toProtobuf() } } } } diff --git a/Sources/Hiero/Account/AccountUpdateTransaction.swift b/Sources/Hiero/Account/AccountUpdateTransaction.swift index 354f2d27..40533d67 100644 --- a/Sources/Hiero/Account/AccountUpdateTransaction.swift +++ b/Sources/Hiero/Account/AccountUpdateTransaction.swift @@ -15,6 +15,8 @@ import SwiftProtobuf public final class AccountUpdateTransaction: Transaction { /// Create a new `AccountCreateTransaction` ready for configuration. public override init() { + self.hookCreationDetails = [] + self.hooksToDelete = [] super.init() } @@ -59,6 +61,8 @@ public final class AccountUpdateTransaction: Transaction { self.stakedAccountId = stakedAccountId self.stakedNodeId = stakedNodeId self.declineStakingReward = data.hasDeclineReward ? data.declineReward.value : nil + self.hookCreationDetails = try data.hookCreationDetails.map { try HookCreationDetails.fromProtobuf($0) } + self.hooksToDelete = data.hookIdsToDelete try super.init(protobuf: proto) } @@ -288,6 +292,46 @@ public final class AccountUpdateTransaction: Transaction { return self } + public var hookCreationDetails: [HookCreationDetails] { + willSet { + ensureNotFrozen() + } + } + + @discardableResult + public func addHookToCreate(_ hook: HookCreationDetails) -> Self { + self.hookCreationDetails.append(hook) + + return self + } + + @discardableResult + public func setHooksToCreate(_ hooks: [HookCreationDetails]) -> Self { + self.hookCreationDetails = hooks + + return self + } + + public var hooksToDelete: [Int64] { + willSet { + ensureNotFrozen() + } + } + + @discardableResult + public func addHookToDelete(_ hook: Int64) -> Self { + self.hooksToDelete.append(hook) + + return self + } + + @discardableResult + public func setHooksToDelete(_ hooks: [Int64]) -> Self { + self.hooksToDelete = hooks + + return self + } + internal override func validateChecksums(on ledgerId: LedgerId) throws { try accountId?.validateChecksums(on: ledgerId) try stakedAccountId?.validateChecksums(on: ledgerId) @@ -347,6 +391,12 @@ extension AccountUpdateTransaction: ToProtobuf { if let declineStakingReward = declineStakingReward { proto.declineReward = Google_Protobuf_BoolValue(declineStakingReward) } + + for hook in hookCreationDetails { + proto.hookCreationDetails.append(hook.toProtobuf()) + } + + proto.hookIdsToDelete = hooksToDelete } } } diff --git a/Sources/Hiero/AnyTransaction.swift b/Sources/Hiero/AnyTransaction.swift index a532e402..88813f50 100644 --- a/Sources/Hiero/AnyTransaction.swift +++ b/Sources/Hiero/AnyTransaction.swift @@ -63,6 +63,8 @@ internal enum ServicesTransactionDataList { case hintsPartialSignature([Com_Hedera_Hapi_Services_Auxiliary_Hints_HintsPartialSignatureTransactionBody]) case crsPublication([Com_Hedera_Hapi_Services_Auxiliary_Hints_CrsPublicationTransactionBody]) case atomicBatch([Proto_AtomicBatchTransactionBody]) + case lambdaSstore([Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody]) + case hookDispatch([Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody]) internal mutating func append(_ transaction: Proto_TransactionBody.OneOf_Data) throws { switch (self, transaction) { @@ -370,6 +372,8 @@ extension ServicesTransactionDataList: TryFromProtobuf { case .tokenCancelAirdrop(let data): value = .tokenCancelAirdrop([data]) case .tokenClaimAirdrop(let data): value = .tokenClaimAirdrop([data]) case .atomicBatch(let data): value = .atomicBatch([data]) + case .lambdaSstore(let data): value = .lambdaSstore([data]) + case .hookDispatch(let data): value = .hookDispatch([data]) case .stateSignatureTransaction: throw HError.fromProtobuf("Unsupported transaction `StateSignatureTransaction`") case .hintsPreprocessingVote: diff --git a/Sources/Hiero/Contract/ContractCreateTransaction.swift b/Sources/Hiero/Contract/ContractCreateTransaction.swift index 468b6673..8b0d6d50 100644 --- a/Sources/Hiero/Contract/ContractCreateTransaction.swift +++ b/Sources/Hiero/Contract/ContractCreateTransaction.swift @@ -72,6 +72,7 @@ public final class ContractCreateTransaction: Transaction { self.stakedNodeId = stakedNodeId } self.declineStakingReward = declineStakingReward + self.hookCreationDetails = [] super.init() } @@ -104,6 +105,7 @@ public final class ContractCreateTransaction: Transaction { self.stakedAccountId = stakedAccountId self.stakedNodeId = stakedNodeId self.declineStakingReward = data.declineReward + self.hookCreationDetails = try data.hookCreationDetails.map { try HookCreationDetails.fromProtobuf($0) } try super.init(protobuf: proto) } @@ -343,6 +345,26 @@ public final class ContractCreateTransaction: Transaction { return self } + public var hookCreationDetails: [HookCreationDetails] { + willSet { + ensureNotFrozen() + } + } + + @discardableResult + public func addHook(_ hook: HookCreationDetails) -> Self { + self.hookCreationDetails.append(hook) + + return self + } + + @discardableResult + public func setHooks(_ hooks: [HookCreationDetails]) -> Self { + self.hookCreationDetails = hooks + + return self + } + internal override func validateChecksums(on ledgerId: LedgerId) throws { try bytecodeFileId?.validateChecksums(on: ledgerId) try autoRenewAccountId?.validateChecksums(on: ledgerId) @@ -391,6 +413,7 @@ extension ContractCreateTransaction: ToProtobuf { } proto.declineReward = declineStakingReward + proto.hookCreationDetails = hookCreationDetails.map { $0.toProtobuf() } } } } diff --git a/Sources/Hiero/Contract/ContractUpdateTransaction.swift b/Sources/Hiero/Contract/ContractUpdateTransaction.swift index 3bb62099..ef28f5ed 100644 --- a/Sources/Hiero/Contract/ContractUpdateTransaction.swift +++ b/Sources/Hiero/Contract/ContractUpdateTransaction.swift @@ -32,6 +32,8 @@ public final class ContractUpdateTransaction: Transaction { self.stakedAccountId = stakedAccountId self.stakedNodeId = stakedNodeId self.declineStakingReward = declineStakingReward + self.hookCreationDetails = [] + self.hooksToDelete = [] super.init() } @@ -75,6 +77,8 @@ public final class ContractUpdateTransaction: Transaction { self.stakedAccountId = stakedAccountId self.stakedNodeId = stakedNodeId self.declineStakingReward = data.hasDeclineReward ? data.declineReward.value : nil + self.hookCreationDetails = try data.hookCreationDetails.map { try HookCreationDetails.fromProtobuf($0) } + self.hooksToDelete = data.hookIdsToDelete try super.init(protobuf: proto) } @@ -289,6 +293,46 @@ public final class ContractUpdateTransaction: Transaction { return self } + public var hookCreationDetails: [HookCreationDetails] { + willSet { + ensureNotFrozen() + } + } + + @discardableResult + public func addHookToCreate(_ hook: HookCreationDetails) -> Self { + self.hookCreationDetails.append(hook) + + return self + } + + @discardableResult + public func setHooksToCreate(_ hooks: [HookCreationDetails]) -> Self { + self.hookCreationDetails = hooks + + return self + } + + public var hooksToDelete: [Int64] { + willSet { + ensureNotFrozen() + } + } + + @discardableResult + public func addHookToDelete(_ hook: Int64) -> Self { + self.hooksToDelete.append(hook) + + return self + } + + @discardableResult + public func setHooksToDelete(_ hooks: [Int64]) -> Self { + self.hooksToDelete = hooks + + return self + } + internal override func transactionExecute(_ channel: GRPCChannel, _ request: Proto_Transaction) async throws -> Proto_TransactionResponse { @@ -344,6 +388,9 @@ extension ContractUpdateTransaction: ToProtobuf { if let contractMemo = contractMemo { proto.memoWrapper = .init(contractMemo) } + + proto.hookCreationDetails = hookCreationDetails.map { $0.toProtobuf() } + proto.hookIdsToDelete = hooksToDelete } } } diff --git a/Sources/Hiero/Data+Extensions.swift b/Sources/Hiero/Data+Extensions.swift index 6aa62a50..1044f6a4 100644 --- a/Sources/Hiero/Data+Extensions.swift +++ b/Sources/Hiero/Data+Extensions.swift @@ -36,7 +36,7 @@ extension Data { }) } - internal init?(hexEncoded: S) { + public init?(hexEncoded: S) { let chars = Array(hexEncoded.utf8) // note: hex check is done character by character let count = chars.count diff --git a/Sources/Hiero/FeeSchedule/RequestType.swift b/Sources/Hiero/FeeSchedule/RequestType.swift index ea89f8fe..ef1de2d8 100644 --- a/Sources/Hiero/FeeSchedule/RequestType.swift +++ b/Sources/Hiero/FeeSchedule/RequestType.swift @@ -278,6 +278,12 @@ public enum RequestType { /// Atomic batch transaction case atomicBatch + /// Lambda Sstore transaction + case lambdaSstore + + /// Hook dispatch + case hookDispatch + // swiftlint:disable:next function_body_length internal init?(protobuf proto: Proto_HederaFunctionality) throws { switch proto { @@ -372,6 +378,8 @@ public enum RequestType { case .historyProofVote: self = .historyProofVote case .crsPublication: self = .crsPublication case .atomicBatch: self = .atomicBatch + case .lambdaSstore: self = .lambdaSstore + case .hookDispatch: self = .hookDispatch case .UNRECOGNIZED(let code): throw HError.fromProtobuf("unrecognized RequestType: `\(code)`") } @@ -469,6 +477,8 @@ public enum RequestType { case .historyProofVote: return .historyProofVote case .crsPublication: return .crsPublication case .atomicBatch: return .atomicBatch + case .lambdaSstore: return .lambdaSstore + case .hookDispatch: return .hookDispatch } } } diff --git a/Sources/Hiero/Hooks/EvmHookCall.swift b/Sources/Hiero/Hooks/EvmHookCall.swift new file mode 100644 index 00000000..1f7d7701 --- /dev/null +++ b/Sources/Hiero/Hooks/EvmHookCall.swift @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// Specifies the details of a call to an EVM hook. +public struct EvmHookCall { + /// The call data to pass to the hook. + public var data: Data + + /// The gas limit to use. + public var gasLimit: UInt64 + + public init(data: Data = Data(), gasLimit: UInt64 = 0) { + self.data = data + self.gasLimit = gasLimit + } + + /// Set the call data to pass to the hook. + @discardableResult + public mutating func data(_ callData: Data) -> Self { + self.data = callData + return self + } + + /// Set the gas limit for the hook. + @discardableResult + public mutating func gasLimit(_ gasLimit: UInt64) -> Self { + self.gasLimit = gasLimit + return self + } +} + +extension EvmHookCall: TryProtobufCodable { + internal typealias Protobuf = Proto_EvmHookCall + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + self.data = Data(proto.data) + self.gasLimit = UInt64(proto.gasLimit) + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + .with { proto in + proto.data = data + proto.gasLimit = UInt64(gasLimit) + } + } +} + +extension EvmHookCall: ValidateChecksums { + internal func validateChecksums(on ledgerId: LedgerId) throws { + // EvmHookCall only contains Data and UInt64, no checksums to validate + } +} diff --git a/Sources/Hiero/Hooks/EvmHookSpec.swift b/Sources/Hiero/Hooks/EvmHookSpec.swift new file mode 100644 index 00000000..ebe28d30 --- /dev/null +++ b/Sources/Hiero/Hooks/EvmHookSpec.swift @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// Shared specifications of an EVM hook. May be used for any extension point. +public struct EvmHookSpec { + /// The source of the EVM bytecode for the hook. + public var contractId: ContractId? + + public init(contractId: ContractId? = nil) { + self.contractId = contractId + } + + /// Set the contract that contains the hook EVM bytecode. Resets other bytecode sources. + @discardableResult + public mutating func contractId(_ contractId: ContractId) -> Self { + self.contractId = contractId + return self + } +} + +extension EvmHookSpec: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_EvmHookSpec + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + // Handle the case where contractID might not be set + if case .contractID(let contractID)? = proto.bytecodeSource { + self.contractId = try ContractId.fromProtobuf(contractID) + } else { + self.contractId = nil + } + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + .with { proto in + if let id = contractId { + proto.bytecodeSource = .contractID(id.toProtobuf()) + } + } + } +} diff --git a/Sources/Hiero/Hooks/FungibleHookCall.swift b/Sources/Hiero/Hooks/FungibleHookCall.swift new file mode 100644 index 00000000..a24bbcfa --- /dev/null +++ b/Sources/Hiero/Hooks/FungibleHookCall.swift @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// Specifies a call to a hook from within a transaction that interacts with fungible tokens (HBAR included). +public struct FungibleHookCall { + /// The underlying hook call. + public var hookCall: HookCall + + /// The type of the fungible hook to call. + public var hookType: FungibleHookType + + public init(hookCall: HookCall = HookCall(), hookType: FungibleHookType = .uninitialized) { + self.hookCall = hookCall + self.hookType = hookType + } + + /// Set the underlying hook call. + @discardableResult + public mutating func hookCall(_ hookCall: HookCall) -> Self { + self.hookCall = hookCall + return self + } + + /// Set the type of the fungible hook to call. + @discardableResult + public mutating func hookType(_ hookType: FungibleHookType) -> Self { + self.hookType = hookType + return self + } + + /// Set the full hook ID. + @discardableResult + public mutating func fullHookId(_ hookId: HookId) -> Self { + self.hookCall.fullHookId(hookId) + return self + } + + /// Set the hook ID. + @discardableResult + public mutating func hookId(_ hookId: Int64) -> Self { + self.hookCall.hookId(hookId) + return self + } + + /// Set the EVM hook call. + @discardableResult + public mutating func evmHookCall(_ evmHookCall: EvmHookCall) -> Self { + self.hookCall.evmHookCall(evmHookCall) + return self + } +} + +extension FungibleHookCall: TryProtobufCodable { + internal typealias Protobuf = Proto_HookCall + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + self.hookCall = try HookCall(protobuf: proto) + // Note: The hook type is not stored in the protobuf, so we default to uninitialized + // The caller should set the appropriate hook type based on context + self.hookType = .uninitialized + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + return hookCall.toProtobuf() + } +} + +extension FungibleHookCall: ValidateChecksums { + internal func validateChecksums(on ledgerId: LedgerId) throws { + try hookCall.validateChecksums(on: ledgerId) + } +} diff --git a/Sources/Hiero/Hooks/FungibleHookType.swift b/Sources/Hiero/Hooks/FungibleHookType.swift new file mode 100644 index 00000000..9b46e32a --- /dev/null +++ b/Sources/Hiero/Hooks/FungibleHookType.swift @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation + +/// Enumeration specifying the different types of hooks for fungible tokens (including HBAR). +public enum FungibleHookType: CaseIterable { + /// Execute the allowance hook before the transaction. + case preTxAllowanceHook + + /// Execute the allowance hook before and after the transaction. + case prePostTxAllowanceHook + + /// Hook type not set. + case uninitialized +} + +extension FungibleHookType: CustomStringConvertible { + public var description: String { + switch self { + case .preTxAllowanceHook: + return "PRE_TX_ALLOWANCE_HOOK" + case .prePostTxAllowanceHook: + return "PRE_POST_TX_ALLOWANCE_HOOK" + case .uninitialized: + return "UNINITIALIZED" + } + } +} + +extension FungibleHookType: Equatable { + public static func == (lhs: FungibleHookType, rhs: FungibleHookType) -> Bool { + switch (lhs, rhs) { + case (.preTxAllowanceHook, .preTxAllowanceHook), + (.prePostTxAllowanceHook, .prePostTxAllowanceHook), + (.uninitialized, .uninitialized): + return true + default: + return false + } + } +} + +extension FungibleHookType: Hashable { + public func hash(into hasher: inout Hasher) { + switch self { + case .preTxAllowanceHook: + hasher.combine(0) + case .prePostTxAllowanceHook: + hasher.combine(1) + case .uninitialized: + hasher.combine(2) + } + } +} diff --git a/Sources/Hiero/Hooks/HookCall.swift b/Sources/Hiero/Hooks/HookCall.swift new file mode 100644 index 00000000..76e79014 --- /dev/null +++ b/Sources/Hiero/Hooks/HookCall.swift @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +public struct HookCall { + public var fullHookId: HookId? + public var hookId: Int64? + public var evmHookCall: EvmHookCall? + + public init() {} + + public init(fullHookId: HookId? = nil, evmHookCall: EvmHookCall? = nil) { + self.fullHookId = fullHookId + self.hookId = nil + self.evmHookCall = evmHookCall + } + + public init(hookId: Int64? = nil, evmHookCall: EvmHookCall? = nil) { + self.fullHookId = nil + self.hookId = hookId + self.evmHookCall = evmHookCall + } + + @discardableResult + public mutating func fullHookId(_ hookId: HookId) -> Self { + self.fullHookId = hookId + self.hookId = nil + return self + } + + @discardableResult + public mutating func hookId(_ hookId: Int64) -> Self { + self.hookId = hookId + self.fullHookId = nil + return self + } + + @discardableResult + public mutating func evmHookCall(_ evmHookCall: EvmHookCall) -> Self { + self.evmHookCall = evmHookCall + return self + } +} + +extension HookCall: TryProtobufCodable { + internal typealias Protobuf = Proto_HookCall + + internal init(protobuf proto: Protobuf) throws { + // Map the `oneof id` + switch proto.id { + case .fullHookID(let v): + self.fullHookId = try HookId(protobuf: v) + self.hookId = nil + case .hookID(let v): + self.fullHookId = nil + self.hookId = v + case nil: + self.fullHookId = nil + self.hookId = nil + } + + // Map the `oneof callSpec` + switch proto.callSpec { + case .evmHookCall(let v): + self.evmHookCall = try EvmHookCall(protobuf: v) + case nil: + self.evmHookCall = nil + } + } + + internal func toProtobuf() -> Protobuf { + var proto = Protobuf() + + // Set the `oneof id` + if let full = fullHookId { + proto.id = .fullHookID(full.toProtobuf()) + } else if let id = hookId { + proto.id = .hookID(id) + } else { + proto.id = nil + } + + // Set the `oneof callSpec` + if let evm = evmHookCall { + proto.callSpec = .evmHookCall(evm.toProtobuf()) + } else { + proto.callSpec = nil + } + + return proto + } +} + +extension HookCall: ValidateChecksums { + internal func validateChecksums(on ledgerId: LedgerId) throws { + try fullHookId?.validateChecksums(on: ledgerId) + try evmHookCall?.validateChecksums(on: ledgerId) + } +} diff --git a/Sources/Hiero/Hooks/HookCreationDetails.swift b/Sources/Hiero/Hooks/HookCreationDetails.swift new file mode 100644 index 00000000..927ecd5c --- /dev/null +++ b/Sources/Hiero/Hooks/HookCreationDetails.swift @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +public struct HookCreationDetails { + public var hookExtensionPoint: HookExtensionPoint + public var hookId: Int64 + public var lambdaEvmHook: LambdaEvmHook? + public var adminKey: Key? + + public init( + hookExtensionPoint: HookExtensionPoint, + hookId: Int64 = 0, + lambdaEvmHook: LambdaEvmHook? = nil, + adminKey: Key? = nil + ) { + self.hookExtensionPoint = hookExtensionPoint + self.hookId = hookId + self.lambdaEvmHook = lambdaEvmHook + self.adminKey = adminKey + } + + @discardableResult + public mutating func hookExtensionPoint(_ hookExtensionPoint: HookExtensionPoint) -> Self { + self.hookExtensionPoint = hookExtensionPoint + return self + } + + @discardableResult + public mutating func hookId(_ hookId: Int64) -> Self { + self.hookId = hookId + return self + } + + @discardableResult + public mutating func lambdaEvmHook(_ hook: LambdaEvmHook) -> Self { + self.lambdaEvmHook = hook + return self + } + + @discardableResult + public mutating func adminKey(_ key: Key?) -> Self { + self.adminKey = key + return self + } +} + +extension HookCreationDetails: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_HookCreationDetails + + internal init(protobuf proto: Protobuf) throws { + self.hookExtensionPoint = try HookExtensionPoint(protobuf: proto.extensionPoint) + self.hookId = proto.hookID + + // Only accept lambda; anything else => nil + switch proto.hook { + case .lambdaEvmHook(let v): + self.lambdaEvmHook = try LambdaEvmHook(protobuf: v) + default: + self.lambdaEvmHook = nil + } + + self.adminKey = proto.hasAdminKey ? try Key(protobuf: proto.adminKey) : nil + } + + internal func toProtobuf() -> Protobuf { + var proto = Protobuf() + proto.extensionPoint = hookExtensionPoint.toProtobuf() + proto.hookID = hookId + + // Only encode lambda; otherwise leave the oneof unset (nil) + if let lambda = lambdaEvmHook { + proto.hook = .lambdaEvmHook(lambda.toProtobuf()) + } else { + proto.hook = nil + } + + if let key = adminKey { + proto.adminKey = key.toProtobuf() + } // else: leave unset + + return proto + } +} diff --git a/Sources/Hiero/Hooks/HookEntityId.swift b/Sources/Hiero/Hooks/HookEntityId.swift new file mode 100644 index 00000000..23b173df --- /dev/null +++ b/Sources/Hiero/Hooks/HookEntityId.swift @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 + +import HieroProtobufs + +public final class HookEntityId { + /// ID of the account that owns a hook. + public var accountId: AccountId? = nil + + public init() {} + + public init(_ accountId: AccountId) { + self.accountId = accountId + } + + /// Sets the ID of the account that owns a hook. + @discardableResult + public func accountId(_ accountId: AccountId) -> Self { + self.accountId = accountId + return self + } +} + +extension HookEntityId: TryProtobufCodable { + internal typealias Protobuf = Proto_HookEntityId + + /// Creates a hook entity ID from a hook entity ID protobuf. + /// + /// - Parameters: + /// - proto: the hook entity ID protobuf. + internal convenience init(protobuf proto: Protobuf) throws { + let id = try AccountId.fromProtobuf(proto.accountID) + self.init(id) + } + + /// Converts this hook entity ID to a protobuf. + internal func toProtobuf() -> Protobuf { + .with { proto in + if let id = accountId { + proto.accountID = id.toProtobuf() + } + } + } +} + +extension HookEntityId: ValidateChecksums { + internal func validateChecksums(on ledgerId: LedgerId) throws { + try accountId?.validateChecksums(on: ledgerId) + } +} diff --git a/Sources/Hiero/Hooks/HookExtensionPoint.swift b/Sources/Hiero/Hooks/HookExtensionPoint.swift new file mode 100644 index 00000000..2888e166 --- /dev/null +++ b/Sources/Hiero/Hooks/HookExtensionPoint.swift @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +public enum HookExtensionPoint { + case accountAllowanceHook +} + +extension HookExtensionPoint: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_HookExtensionPoint + + internal init(protobuf proto: Protobuf) throws { + switch proto { + case .accountAllowanceHook: + self = .accountAllowanceHook + case .UNRECOGNIZED: + // Default / fallback + self = .accountAllowanceHook + } + } + + internal func toProtobuf() -> Protobuf { + switch self { + case .accountAllowanceHook: + return .accountAllowanceHook + } + } +} diff --git a/Sources/Hiero/Hooks/HookId.swift b/Sources/Hiero/Hooks/HookId.swift new file mode 100644 index 00000000..3cb961ae --- /dev/null +++ b/Sources/Hiero/Hooks/HookId.swift @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// An ID for a hook. +public struct HookId { + /// The hook's owning entity ID. + public var entityId: HookEntityId + + /// The ID for the hook. + public var hookId: Int64 + + public init(entityId: HookEntityId = HookEntityId(), hookId: Int64 = 0) { + self.entityId = entityId + self.hookId = hookId + } + + /// Set the ID of the owning entity. + @discardableResult + public mutating func entityId(_ entityId: HookEntityId) -> Self { + self.entityId = entityId + return self + } + + /// Set the ID of the hook. + @discardableResult + public mutating func hookId(_ hookId: Int64) -> Self { + self.hookId = hookId + return self + } +} + +extension HookId: TryProtobufCodable { + internal typealias Protobuf = Proto_HookId + + /// Construct a `HookId` from protobuf. + internal init(protobuf proto: Protobuf) throws { + self.entityId = try HookEntityId(protobuf: proto.entityID) + self.hookId = proto.hookID + } + + /// Convert this `HookId` to protobuf. + internal func toProtobuf() -> Protobuf { + .with { proto in + proto.entityID = entityId.toProtobuf() + proto.hookID = hookId + } + } +} + +extension HookId: ValidateChecksums { + internal func validateChecksums(on ledgerId: LedgerId) throws { + try entityId.validateChecksums(on: ledgerId) + } +} diff --git a/Sources/Hiero/Hooks/HookType.swift b/Sources/Hiero/Hooks/HookType.swift new file mode 100644 index 00000000..468b77b3 --- /dev/null +++ b/Sources/Hiero/Hooks/HookType.swift @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// Enumeration specifying the different types of hooks. +public enum HookType { + /// Execute the hook before the transaction. + case preHook + + /// Execute the hook before and after the transaction. + case prePostHook + + /// Execute the hook for the sender before the transaction. + case preHookSender + + /// Execute the hook for the sender before and after the transaction. + case prePostHookSender + + /// Execute the hook for the receiver before the transaction. + case preHookReceiver + + /// Execute the hook for the receiver before and after the transaction. + case prePostHookReceiver +} diff --git a/Sources/Hiero/Hooks/LambdaEvmHook.swift b/Sources/Hiero/Hooks/LambdaEvmHook.swift new file mode 100644 index 00000000..7f35cdd0 --- /dev/null +++ b/Sources/Hiero/Hooks/LambdaEvmHook.swift @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// Definition of a lambda EVM hook. +public struct LambdaEvmHook { + /// Shared EVM hook spec (e.g., contract housing the hook bytecode). + public var spec: EvmHookSpec + + /// The initial storage updates for the lambda, if any. + public var storageUpdates: [LambdaStorageUpdate] + + public init(spec: EvmHookSpec = .init(), storageUpdates: [LambdaStorageUpdate] = []) { + self.spec = spec + self.storageUpdates = storageUpdates + } + + /// Add a storage update to this hook. + @discardableResult + public mutating func addStorageUpdate(_ storageUpdate: LambdaStorageUpdate) -> Self { + storageUpdates.append(storageUpdate) + return self + } + + /// Set the storage updates for this hook. + @discardableResult + public mutating func setStorageUpdates(_ storageUpdates: [LambdaStorageUpdate]) -> Self { + self.storageUpdates = storageUpdates + return self + } + + /// Clear the storage updates for this hook. + @discardableResult + public mutating func clearStorageUpdates() -> Self { + storageUpdates.removeAll() + return self + } +} + +extension LambdaEvmHook: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + // Assuming the generated message has `spec` and `storageUpdates` fields. + self.spec = try EvmHookSpec(protobuf: proto.spec) + self.storageUpdates = try proto.storageUpdates.map { try LambdaStorageUpdate(protobuf: $0) } + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + var proto = Protobuf() + proto.spec = spec.toProtobuf() + proto.storageUpdates = storageUpdates.map { $0.toProtobuf() } + return proto + } +} diff --git a/Sources/Hiero/Hooks/LambdaMappingEntries.swift b/Sources/Hiero/Hooks/LambdaMappingEntries.swift new file mode 100644 index 00000000..c36f3a7a --- /dev/null +++ b/Sources/Hiero/Hooks/LambdaMappingEntries.swift @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// Specifies storage slot updates via indirection into a Solidity mapping. +/// +/// Concretely, if the Solidity mapping is itself at slot `mapping_slot`, then the +/// storage slot for key `key` in the mapping is defined by: +/// `key_storage_slot = keccak256(abi.encodePacked(mapping_slot, key))`. +/// +/// This message lets a metaprotocol be specified in terms of changes to a Solidity +/// mapping's entries. If only raw slots could be updated, then a block stream consumer +/// following the metaprotocol would have to invert the Keccak256 hash to determine +/// which mapping entry was being updated, which is not possible. +public struct LambdaMappingEntries { + /// The slot corresponding to the Solidity mapping. + public var mappingSlot: Data + + /// The mapping entries for this mapping slot. + public var entries: [LambdaMappingEntry] + + public init(mappingSlot: Data = Data(), entries: [LambdaMappingEntry] = []) { + self.mappingSlot = mappingSlot + self.entries = entries + } + + /// Set the Solidity mapping slot. + @discardableResult + public mutating func mappingSlot(_ mappingSlot: Data) -> Self { + self.mappingSlot = mappingSlot + return self + } + + /// Add a mapping entry. + @discardableResult + public mutating func addEntry(_ entry: LambdaMappingEntry) -> Self { + self.entries.append(entry) + return self + } + + /// Set all mapping entries. + @discardableResult + public mutating func setEntries(_ entries: [LambdaMappingEntry]) -> Self { + self.entries = entries + return self + } + + /// Clear the mapping entries. + @discardableResult + public mutating func clearEntries() -> Self { + self.entries.removeAll() + return self + } +} + +extension LambdaMappingEntries: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + self.mappingSlot = proto.mappingSlot + self.entries = try proto.entries.map { try LambdaMappingEntry(protobuf: $0) } + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + var proto = Protobuf() + proto.mappingSlot = mappingSlot + proto.entries = entries.map { $0.toProtobuf() } + return proto + } +} diff --git a/Sources/Hiero/Hooks/LambdaMappingEntry.swift b/Sources/Hiero/Hooks/LambdaMappingEntry.swift new file mode 100644 index 00000000..99840245 --- /dev/null +++ b/Sources/Hiero/Hooks/LambdaMappingEntry.swift @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// An implicit storage slot specified as a Solidity mapping entry. +public struct LambdaMappingEntry { + /// The 32-byte key of the mapping entry. + public var key: Data? + + /// The slot corresponding to the Solidity mapping. + public var preimage: Data? + + /// The 32-byte value of the mapping entry (leave empty to delete). + public var value: Data + + public init(key: Data = Data(), preimage: Data = Data(), value: Data = Data()) { + self.key = key + self.preimage = preimage + self.value = value + } + + /// Set the key for the mapping entry. + @discardableResult + public mutating func key(_ key: Data) -> Self { + self.key = key + self.preimage = nil // Reset preimage when setting key + return self + } + + /// Set the Solidity preimage. + @discardableResult + public mutating func preimage(_ preimage: Data) -> Self { + self.preimage = preimage + self.key = nil // Reset key when setting preimage + return self + } + + /// Set the value for the mapping entry. + @discardableResult + public mutating func value(_ value: Data) -> Self { + self.value = value + return self + } +} + +extension LambdaMappingEntry: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + self.key = proto.key + self.preimage = proto.preimage + self.value = proto.value + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + var proto = Protobuf() + + if let key = key { + proto.key = key + } + + if let preimage = preimage { + proto.preimage = preimage + } + + proto.value = value + return proto + } +} diff --git a/Sources/Hiero/Hooks/LambdaSStoreTransaction.swift b/Sources/Hiero/Hooks/LambdaSStoreTransaction.swift new file mode 100644 index 00000000..b3941365 --- /dev/null +++ b/Sources/Hiero/Hooks/LambdaSStoreTransaction.swift @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import GRPC +import HieroProtobufs + +/// Updates storage for a Lambda EVM hook. +public final class LambdaSStoreTransaction: Transaction { + /// Create a new `LambdaSStoreTransaction` ready for configuration. + public override init() { + super.init() + } + + /// The ID of the hook to update. + public var hookId: HookId? { + willSet { ensureNotFrozen() } + } + + /// The storage updates to apply to the hook. + public var storageUpdates: [LambdaStorageUpdate] = [] { + willSet { ensureNotFrozen() } + } + + /// Sets the ID of the hook to update. + @discardableResult + public func hookId(_ hookId: HookId) -> Self { + self.hookId = hookId + return self + } + + /// Adds a storage update. + @discardableResult + public func addStorageUpdate(_ update: LambdaStorageUpdate) -> Self { + storageUpdates.append(update) + return self + } + + /// Sets all storage updates. + @discardableResult + public func storageUpdates(_ updates: [LambdaStorageUpdate]) -> Self { + self.storageUpdates = updates + return self + } + + /// Clears all storage updates. + @discardableResult + public func clearStorageUpdates() -> Self { + storageUpdates.removeAll() + return self + } + + /// Construct from `TransactionBody` and the concrete Lambda SSTORE body. + internal init( + protobuf proto: Proto_TransactionBody, + _ data: Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody + ) throws { + // hookID (message presence); treat default instance as nil if your SDK does that. + self.hookId = try HookId(protobuf: data.hookID) + + // storageUpdates (repeated) + self.storageUpdates = try data.storageUpdates.map { try LambdaStorageUpdate(protobuf: $0) } + + try super.init(protobuf: proto) + } + + internal override func validateChecksums(on ledgerId: LedgerId) throws { + // If HookId/HookEntityId/AccountId supports checksum validation, invoke it here. + // Example (adjust to your actual API): + // try hookId?.entityId.accountId?.validateChecksums(on: ledgerId) + try super.validateChecksums(on: ledgerId) + } + + internal override func transactionExecute( + _ channel: GRPCChannel, + _ request: Proto_Transaction + ) async throws -> Proto_TransactionResponse { + // TODO: Adjust the service/type name to your generated gRPC client and method. + // This is the conventional naming SwiftProtobuf generates for a `HooksService` with rpc `lambdaSStore`. + return try await Proto_SmartContractServiceAsyncClient(channel: channel) + .lambdaSStore(request, callOptions: applyGrpcHeader()) + } + + internal override func toTransactionDataProtobuf(_ chunkInfo: ChunkInfo) -> Proto_TransactionBody.OneOf_Data { + _ = chunkInfo.assertSingleTransaction() + return .lambdaSstore(toProtobuf()) + } +} + +extension LambdaSStoreTransaction: ToProtobuf { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody + + internal func toProtobuf() -> Protobuf { + .with { proto in + if let id = hookId { + proto.hookID = id.toProtobuf() + } + proto.storageUpdates = storageUpdates.map { $0.toProtobuf() } + } + } +} diff --git a/Sources/Hiero/Hooks/LambdaStorageSlot.swift b/Sources/Hiero/Hooks/LambdaStorageSlot.swift new file mode 100644 index 00000000..3f4a995f --- /dev/null +++ b/Sources/Hiero/Hooks/LambdaStorageSlot.swift @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// An explicit storage slot update. +public struct LambdaStorageSlot { + /// The 32-byte storage slot key. + public var key: Data + + /// The 32-byte storage slot value (leave empty to delete). + public var value: Data + + public init(key: Data = Data(), value: Data = Data()) { + self.key = key + self.value = value + } + + /// Set the storage slot key. + @discardableResult + public mutating func key(_ key: Data) -> Self { + self.key = key + return self + } + + /// Set the storage slot value. + @discardableResult + public mutating func value(_ value: Data) -> Self { + self.value = value + return self + } +} + +extension LambdaStorageSlot: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + self.key = proto.key + self.value = proto.value + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + var proto = Protobuf() + proto.key = key + proto.value = value + return proto + } +} diff --git a/Sources/Hiero/Hooks/LambdaStorageUpdate.swift b/Sources/Hiero/Hooks/LambdaStorageUpdate.swift new file mode 100644 index 00000000..a9510dfb --- /dev/null +++ b/Sources/Hiero/Hooks/LambdaStorageUpdate.swift @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// 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. +public struct LambdaStorageUpdate { + /// An explicit storage slot update. + public var storageSlot: LambdaStorageSlot? + + /// An implicit storage slot update specified as Solidity mapping entries. + public var mappingEntries: LambdaMappingEntries? + + public init(storageSlot: LambdaStorageSlot? = nil, mappingEntries: LambdaMappingEntries? = nil) { + self.storageSlot = storageSlot + self.mappingEntries = mappingEntries + } + + /// Set an update for an explicit storage slot. Resets any mapping-based update. + @discardableResult + public mutating func setStorageSlot(_ storageSlot: LambdaStorageSlot) -> Self { + self.storageSlot = storageSlot + self.mappingEntries = nil + return self + } + + /// Set an update for Solidity-mapped entries. Resets any explicit-slot update. + @discardableResult + public mutating func setMappingEntries(_ mappingEntries: LambdaMappingEntries) -> Self { + self.mappingEntries = mappingEntries + self.storageSlot = nil + return self + } +} + +extension LambdaStorageUpdate: TryProtobufCodable { + internal typealias Protobuf = Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate + + internal init(protobuf proto: Protobuf) throws { + // Default to nils + self.storageSlot = nil + self.mappingEntries = nil + + // Handle the `oneof` for which update is present. + if let which = proto.update { + switch which { + case .storageSlot(let slot): + self.storageSlot = try LambdaStorageSlot(protobuf: slot) + case .mappingEntries(let entries): + self.mappingEntries = try LambdaMappingEntries(protobuf: entries) + } + } + } + + internal func toProtobuf() -> Protobuf { + var proto = Protobuf() + + // Set exactly one branch of the oneof (or leave nil if neither is set). + if let slot = storageSlot { + proto.update = .storageSlot(slot.toProtobuf()) + } else if let entries = mappingEntries { + proto.update = .mappingEntries(entries.toProtobuf()) + } else { + proto.update = nil + } + + return proto + } +} diff --git a/Sources/Hiero/Hooks/NftHookCall.swift b/Sources/Hiero/Hooks/NftHookCall.swift new file mode 100644 index 00000000..5570eb88 --- /dev/null +++ b/Sources/Hiero/Hooks/NftHookCall.swift @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs + +/// Specifies a call to a hook from within a transaction that interacts with NFTs. +public struct NftHookCall { + /// The underlying hook call. + public var hookCall: HookCall + + /// The type of the NFT hook to call. + public var hookType: NftHookType + + public init(hookCall: HookCall = HookCall(), hookType: NftHookType = .uninitialized) { + self.hookCall = hookCall + self.hookType = hookType + } + + /// Set the underlying hook call. + @discardableResult + public mutating func hookCall(_ hookCall: HookCall) -> Self { + self.hookCall = hookCall + return self + } + + /// Set the type of the NFT hook to call. + @discardableResult + public mutating func hookType(_ hookType: NftHookType) -> Self { + self.hookType = hookType + return self + } + + /// Set the full hook ID. + @discardableResult + public mutating func fullHookId(_ hookId: HookId) -> Self { + self.hookCall.fullHookId(hookId) + return self + } + + /// Set the hook ID. + @discardableResult + public mutating func hookId(_ hookId: Int64) -> Self { + self.hookCall.hookId(hookId) + return self + } + + /// Set the EVM hook call. + @discardableResult + public mutating func evmHookCall(_ evmHookCall: EvmHookCall) -> Self { + self.hookCall.evmHookCall(evmHookCall) + return self + } +} + +extension NftHookCall: TryProtobufCodable { + internal typealias Protobuf = Proto_HookCall + + /// Construct from protobuf. + internal init(protobuf proto: Protobuf) throws { + self.hookCall = try HookCall(protobuf: proto) + // Note: The hook type is not stored in the protobuf, so we default to uninitialized + // The caller should set the appropriate hook type based on context + self.hookType = .uninitialized + } + + /// Convert to protobuf. + internal func toProtobuf() -> Protobuf { + return hookCall.toProtobuf() + } +} + +extension NftHookCall: ValidateChecksums { + internal func validateChecksums(on ledgerId: LedgerId) throws { + try hookCall.validateChecksums(on: ledgerId) + } +} diff --git a/Sources/Hiero/Hooks/NftHookType.swift b/Sources/Hiero/Hooks/NftHookType.swift new file mode 100644 index 00000000..97aae5ae --- /dev/null +++ b/Sources/Hiero/Hooks/NftHookType.swift @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation + +/// Enumeration specifying the different types of hooks for NFTs. +public enum NftHookType: CaseIterable { + /// Execute the hook before the transaction. + case preHook + + /// Execute the hook before and after the transaction. + case prePostHook + + /// Hook type uninitialized. + case uninitialized +} + +extension NftHookType: CustomStringConvertible { + public var description: String { + switch self { + case .preHook: + return "PRE_HOOK" + case .prePostHook: + return "PRE_POST_HOOK" + case .uninitialized: + return "UNINITIALIZED" + } + } +} + +extension NftHookType: Equatable { + public static func == (lhs: NftHookType, rhs: NftHookType) -> Bool { + switch (lhs, rhs) { + case (.preHook, .preHook), + (.prePostHook, .prePostHook), + (.uninitialized, .uninitialized): + return true + default: + return false + } + } +} + +extension NftHookType: Hashable { + public func hash(into hasher: inout Hasher) { + switch self { + case .preHook: + hasher.combine(0) + case .prePostHook: + hasher.combine(1) + case .uninitialized: + hasher.combine(2) + } + } +} diff --git a/Sources/Hiero/Status.swift b/Sources/Hiero/Status.swift index da1dcdb2..dd0813bd 100644 --- a/Sources/Hiero/Status.swift +++ b/Sources/Hiero/Status.swift @@ -1160,6 +1160,84 @@ public enum Status: Equatable { /// The GRPC proxy endpoint is set in the NodeCreate or NodeUpdate transaction, which the network does not support. case grpcWebProxyNotSupported // = 399 + /// An NFT transfers list referenced a token type other than NON_FUNGIBLE_UNIQUE. + case nftTransersOnlyAllowedForNonFungibleUnique // = 400 + + /// A HAPI client cannot set the SignedTransaction#use_serialized_tx_message_hash_algorithm field. + case invalidSerializedTxMessageHashAlgorithm // = 401 + + /// An EVM hook execution was throttled due to high network gas utilization. + case evmHookGasThrottled // = 500 + + /// A user tried to create a hook with an id already in use. + case hookIdInUse // = 501 + + /// A transaction tried to execute a hook that did not match the specified type or was malformed in some other way. + case badHookRequest // = 502 + + /// A CryptoTransfer relying on a ACCOUNT_ALLOWANCE hook was rejected. + case rejectedByAccountAllowanceHook // = 503 + + /// A hook id was not found. + case hookNotFound // = 504 + + /// A lambda mapping slot, storage key, or storage value exceeded 32 bytes. + case lambdaStorageUpdateBytesTooLong // = 505 + + /// A lambda mapping slot, storage key, or storage value failed to use the minimal representation (i.e., no leading zeros). + case lambdaStorageUpdateBytesMustUseMinimalRepresentation // = 506 + + /// A hook id was invalid. + case invalidHookId // = 507 + + /// A lambda storage update had no contents. + case emptyLambdaStorageUpdate // = 508 + + /// A user repeated the same hook id in a creation details list. + case hookIdRepeatedInCreationDetails // = 509 + + /// Hooks are not not enabled on the target Hiero network. + case hooksNotEnabled // = 510 + + /// The target hook is not a lambda. + case hookIsNotALambda // = 511 + + /// A hook was deleted. + case hookDeleted // = 512 + + /// The LambdaSStore tried to update too many storage slots in a single transaction. + case tooManyLambdaStorageUpdates // = 513 + + /// A lambda mapping slot, storage key, or storage value failed to use the minimal representation (i.e., no leading zeros). + case hookCreationBytesNotUsingMinimalRepresenation // = 514 + + /// A lambda mapping slot, storage key, or storage value exceeded 32 bytes. + case hookCreationBytesTooLong // = 515 + + /// A hook creation spec was not found. + case invalidHookCreationSpec // = 516 + + /// A hook extension point was empty. + case hookExtensionEmpty // = 517 + + /// A hook admin key was invalid. + case invalidHookAdminKey // = 518 + + /// The hook deletion requires the hook to have zero storage slots. + case hookDeletionRequiresZeroStorageSlots // = 519 + + /// Cannot set both a hook call and an approval on the same AccountAmount or NftTransfer message. + case cannotSetHooksAndApproval // = 520 + + /// The attempted operation is invalid until all the target entity's hooks have been deleted. + case transactionRequiresZeroHooks // = 521 + + /// The HookCall set in the transaction is invalid. + case invalidHookCall // = 522 + + /// Hooks are not supported to be used in TokenAirdrop transactions + case hooksAreNotSupportedInAirdrops // = 523 + /// swift-format-ignore: AlwaysUseLowerCamelCase case unrecognized(Int32) @@ -1522,6 +1600,32 @@ public enum Status: Equatable { case 397: self = .throttleGroupLcmOverflow case 398: self = .airdropContainsMultipleSendersForAToken case 399: self = .grpcWebProxyNotSupported + case 400: self = .nftTransersOnlyAllowedForNonFungibleUnique + case 401: self = .invalidSerializedTxMessageHashAlgorithm + case 500: self = .evmHookGasThrottled + case 501: self = .hookIdInUse + case 502: self = .badHookRequest + case 503: self = .rejectedByAccountAllowanceHook + case 504: self = .hookNotFound + case 505: self = .lambdaStorageUpdateBytesTooLong + case 506: self = .lambdaStorageUpdateBytesMustUseMinimalRepresentation + case 507: self = .invalidHookId + case 508: self = .emptyLambdaStorageUpdate + case 509: self = .hookIdRepeatedInCreationDetails + case 510: self = .hooksNotEnabled + case 511: self = .hookIsNotALambda + case 512: self = .hookDeleted + case 513: self = .tooManyLambdaStorageUpdates + case 514: self = .hookCreationBytesNotUsingMinimalRepresenation + case 515: self = .hookCreationBytesTooLong + case 516: self = .invalidHookCreationSpec + case 517: self = .hookExtensionEmpty + case 518: self = .invalidHookAdminKey + case 519: self = .hookDeletionRequiresZeroStorageSlots + case 520: self = .cannotSetHooksAndApproval + case 521: self = .transactionRequiresZeroHooks + case 522: self = .invalidHookCall + case 523: self = .hooksAreNotSupportedInAirdrops default: self = .unrecognized(rawValue) } } @@ -1886,6 +1990,32 @@ public enum Status: Equatable { case .throttleGroupLcmOverflow: return 397 case .airdropContainsMultipleSendersForAToken: return 398 case .grpcWebProxyNotSupported: return 399 + case .nftTransersOnlyAllowedForNonFungibleUnique: return 400 + case .invalidSerializedTxMessageHashAlgorithm: return 401 + case .evmHookGasThrottled: return 500 + case .hookIdInUse: return 501 + case .badHookRequest: return 502 + case .rejectedByAccountAllowanceHook: return 503 + case .hookNotFound: return 504 + case .lambdaStorageUpdateBytesTooLong: return 505 + case .lambdaStorageUpdateBytesMustUseMinimalRepresentation: return 506 + case .invalidHookId: return 507 + case .emptyLambdaStorageUpdate: return 508 + case .hookIdRepeatedInCreationDetails: return 509 + case .hooksNotEnabled: return 510 + case .hookIsNotALambda: return 511 + case .hookDeleted: return 512 + case .tooManyLambdaStorageUpdates: return 513 + case .hookCreationBytesNotUsingMinimalRepresenation: return 514 + case .hookCreationBytesTooLong: return 515 + case .invalidHookCreationSpec: return 516 + case .hookExtensionEmpty: return 517 + case .invalidHookAdminKey: return 518 + case .hookDeletionRequiresZeroStorageSlots: return 519 + case .cannotSetHooksAndApproval: return 520 + case .transactionRequiresZeroHooks: return 521 + case .invalidHookCall: return 522 + case .hooksAreNotSupportedInAirdrops: return 523 case .unrecognized(let i): return i } } @@ -2248,6 +2378,37 @@ extension Status: CaseIterable { .missingBatchKey, .batchKeySetOnNonInnerTransaction, .invalidBatchKey, + .scheduleExpiryNotConfigurable, + .creatingSystemEntities, + .throttleGroupLcmOverflow, + .airdropContainsMultipleSendersForAToken, + .grpcWebProxyNotSupported, + .nftTransersOnlyAllowedForNonFungibleUnique, + .invalidSerializedTxMessageHashAlgorithm, + .evmHookGasThrottled, + .hookIdInUse, + .badHookRequest, + .rejectedByAccountAllowanceHook, + .hookNotFound, + .lambdaStorageUpdateBytesTooLong, + .lambdaStorageUpdateBytesMustUseMinimalRepresentation, + .invalidHookId, + .emptyLambdaStorageUpdate, + .hookIdRepeatedInCreationDetails, + .hooksNotEnabled, + .hookIsNotALambda, + .hookDeleted, + .tooManyLambdaStorageUpdates, + .hookCreationBytesNotUsingMinimalRepresenation, + .hookCreationBytesTooLong, + .invalidHookCreationSpec, + .hookExtensionEmpty, + .invalidHookAdminKey, + .hookDeletionRequiresZeroStorageSlots, + .cannotSetHooksAndApproval, + .transactionRequiresZeroHooks, + .invalidHookCall, + .hooksAreNotSupportedInAirdrops, ] } @@ -2608,6 +2769,37 @@ extension Status { 392: "MISSING_BATCH_KEY", 393: "BATCH_KEY_SET_ON_NON_INNER_TRANSACTION", 394: "INVALID_BATCH_KEY", + 395: "SCHEDULE_EXPIRY_NOT_CONFIGURABLE", + 396: "CREATING_SYSTEM_ENTITIES", + 397: "THROTTLE_GROUP_LCM_OVERFLOW", + 398: "AIRDROP_CONTAINS_MULTIPLE_SENDERS_FOR_A_TOKEN", + 399: "GRPC_WEB_PROXY_NOT_SUPPORTED", + 400: "NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE", + 401: "INVALID_SERIALIZED_TX_MESSAGE_HASH_ALGORITHM", + 500: "EVM_HOOK_GAS_THROTTLED", + 501: "HOOK_ID_IN_USE", + 502: "BAD_HOOK_REQUEST", + 503: "REJECTED_BY_ACCOUNT_ALLOWANCE_HOOK", + 504: "HOOK_NOT_FOUND", + 505: "LAMBDA_STORAGE_UPDATE_BYTES_TOO_LONG", + 506: "LAMBDA_STORAGE_UPDATE_BYTES_MUST_USE_MINIMAL_REPRESENTATION", + 507: "INVALID_HOOK_ID", + 508: "EMPTY_LAMBDA_STORAGE_UPDATE", + 509: "HOOK_ID_REPEATED_IN_CREATION_DETAILS", + 510: "HOOKS_NOT_ENABLED", + 511: "HOOK_IS_NOT_A_LAMBDA", + 512: "HOOK_DELETED", + 513: "TOO_MANY_LAMBDA_STORAGE_UPDATES", + 514: "HOOK_CREATION_BYTES_MUST_USE_MINIMAL_REPRESENTATION", + 515: "HOOK_CREATION_BYTES_TOO_LONG", + 516: "INVALID_HOOK_CREATION_SPEC", + 517: "HOOK_EXTENSION_EMPTY", + 518: "INVALID_HOOK_ADMIN_KEY", + 519: "HOOK_DELETION_REQUIRES_ZERO_STORAGE_SLOTS", + 520: "CANNOT_SET_HOOKS_AND_APPROVAL", + 521: "TRANSACTION_REQUIRES_ZERO_HOOKS", + 522: "INVALID_HOOK_CALL", + 523: "HOOKS_ARE_NOT_SUPPORTED_IN_AIRDROPS", ] } diff --git a/Sources/Hiero/Token/AbstractTokenTransferTransaction.swift b/Sources/Hiero/Token/AbstractTokenTransferTransaction.swift index 8ea0df71..5756d826 100644 --- a/Sources/Hiero/Token/AbstractTokenTransferTransaction.swift +++ b/Sources/Hiero/Token/AbstractTokenTransferTransaction.swift @@ -12,9 +12,11 @@ public class AbstractTokenTransferTransaction: Transaction { let accountId: AccountId var amount: Int64 let isApproval: Bool + var hookCall: FungibleHookCall? internal func validateChecksums(on ledgerId: LedgerId) throws { try accountId.validateChecksums(on: ledgerId) + try hookCall?.hookCall.validateChecksums(on: ledgerId) } } @@ -36,10 +38,14 @@ public class AbstractTokenTransferTransaction: Transaction { let receiverAccountId: AccountId let serial: UInt64 let isApproval: Bool + var senderHookCall: NftHookCall? + var receiverHookCall: NftHookCall? internal func validateChecksums(on ledgerId: LedgerId) throws { try senderAccountId.validateChecksums(on: ledgerId) try receiverAccountId.validateChecksums(on: ledgerId) + try senderHookCall?.validateChecksums(on: ledgerId) + try receiverHookCall?.validateChecksums(on: ledgerId) } } @@ -147,6 +153,14 @@ public class AbstractTokenTransferTransaction: Transaction { doTokenTransferWithDecimals(tokenId, accountId, amount, true, expectedDecimals) } + /// Add a fungible token transfer with a hook to be submitted as part of this TransferTransaction. + @discardableResult + public func tokenTransferWithHook( + _ tokenId: TokenId, _ accountId: AccountId, _ amount: Int64, _ hookCall: FungibleHookCall + ) -> Self { + doTokenTransferWithHook(tokenId, accountId, amount, false, hookCall) + } + /// Add a non-approved nft transfer to the transaction. @discardableResult public func nftTransfer(_ nftId: NftId, _ senderAccountId: AccountId, _ receiverAccountId: AccountId) @@ -163,13 +177,27 @@ public class AbstractTokenTransferTransaction: Transaction { doNftTransfer(nftId, senderAccountId, receiverAccountId, true) } + /// Add an NFT transfer with separate sender and receiver hook calls to this TransferTransaction. + @discardableResult + public func nftTransferWithHooks( + _ nftId: NftId, + _ senderAccountId: AccountId, + _ receiverAccountId: AccountId, + _ senderHookCall: NftHookCall, + _ receiverHookCall: NftHookCall + ) -> Self { + doNftTransferWithHooks( + nftId, senderAccountId, receiverAccountId, false, senderHookCall, receiverHookCall) + } + private func doTokenTransfer( _ tokenId: TokenId, _ accountId: AccountId, _ amount: Int64, _ approved: Bool ) -> Self { - let transfer = Transfer(accountId: accountId, amount: amount, isApproval: approved) + let transfer = Transfer( + accountId: accountId, amount: amount, isApproval: approved, hookCall: nil) if let firstIndex = tokenTransfersInner.firstIndex(where: { (tokenTransfer) in tokenTransfer.tokenId == tokenId }) { @@ -203,7 +231,8 @@ public class AbstractTokenTransferTransaction: Transaction { _ approved: Bool, _ expectedDecimals: UInt32 ) -> Self { - let transfer = Transfer(accountId: accountId, amount: amount, isApproval: approved) + let transfer = Transfer( + accountId: accountId, amount: amount, isApproval: approved, hookCall: nil) if let firstIndex = tokenTransfersInner.firstIndex(where: { (tokenTransfer) in tokenTransfer.tokenId == tokenId }) { @@ -239,6 +268,42 @@ public class AbstractTokenTransferTransaction: Transaction { return self } + private func doTokenTransferWithHook( + _ tokenId: TokenId, + _ accountId: AccountId, + _ amount: Int64, + _ approved: Bool, + _ hookCall: FungibleHookCall + ) -> Self { + let transfer = Transfer( + accountId: accountId, amount: amount, isApproval: approved, hookCall: hookCall) + + if let firstIndex = tokenTransfersInner.firstIndex(where: { (tokenTransfer) in tokenTransfer.tokenId == tokenId + }) { + let existingTransfers = tokenTransfersInner[firstIndex].transfers + + if let existingTransferIndex = existingTransfers.firstIndex(where: { + $0.accountId == accountId && $0.isApproval == approved + }) { + tokenTransfersInner[firstIndex].transfers[existingTransferIndex].amount += amount + tokenTransfersInner[firstIndex].transfers[existingTransferIndex].hookCall = hookCall + } else { + tokenTransfersInner[firstIndex].expectedDecimals = nil + tokenTransfersInner[firstIndex].transfers.append(transfer) + } + } else { + tokenTransfersInner.append( + TokenTransfer( + tokenId: tokenId, + transfers: [transfer], + nftTransfers: [], + expectedDecimals: nil + )) + } + + return self + } + private func doNftTransfer( _ nftId: NftId, _ senderAccountId: AccountId, @@ -249,7 +314,44 @@ public class AbstractTokenTransferTransaction: Transaction { senderAccountId: senderAccountId, receiverAccountId: receiverAccountId, serial: nftId.serial, - isApproval: approved + isApproval: approved, + senderHookCall: nil, + receiverHookCall: nil + ) + + if let index = tokenTransfersInner.firstIndex(where: { transfer in transfer.tokenId == nftId.tokenId }) { + var tmp = tokenTransfersInner[index] + tmp.nftTransfers.append(transfer) + tokenTransfersInner[index] = tmp + } else { + tokenTransfersInner.append( + TokenTransfer( + tokenId: nftId.tokenId, + transfers: [], + nftTransfers: [transfer], + expectedDecimals: nil + ) + ) + } + + return self + } + + private func doNftTransferWithHooks( + _ nftId: NftId, + _ senderAccountId: AccountId, + _ receiverAccountId: AccountId, + _ approved: Bool, + _ senderHookCall: NftHookCall, + _ receiverHookCall: NftHookCall + ) -> Self { + let transfer = NftTransfer( + senderAccountId: senderAccountId, + receiverAccountId: receiverAccountId, + serial: nftId.serial, + isApproval: approved, + senderHookCall: senderHookCall, + receiverHookCall: receiverHookCall ) if let index = tokenTransfersInner.firstIndex(where: { transfer in transfer.tokenId == nftId.tokenId }) { @@ -319,8 +421,19 @@ extension AbstractTokenTransferTransaction.Transfer: TryProtobufCodable { self.init( accountId: try .fromProtobuf(proto.accountID), amount: proto.amount, - isApproval: proto.isApproval + isApproval: proto.isApproval, + hookCall: nil ) + + // Handle hook calls + switch proto.hookCall { + case .preTxAllowanceHook(let hookCall): + self.hookCall = try FungibleHookCall(protobuf: hookCall) + case .prePostTxAllowanceHook(let hookCall): + self.hookCall = try FungibleHookCall(protobuf: hookCall) + case nil: + break + } } internal func toProtobuf() -> Protobuf { @@ -328,6 +441,15 @@ extension AbstractTokenTransferTransaction.Transfer: TryProtobufCodable { proto.accountID = accountId.toProtobuf() proto.amount = amount proto.isApproval = isApproval + + // Handle hook calls + if let hookCall = hookCall { + if hookCall.hookType == .preTxAllowanceHook { + proto.hookCall = .preTxAllowanceHook(hookCall.toProtobuf()) + } else if hookCall.hookType == .prePostTxAllowanceHook { + proto.hookCall = .prePostTxAllowanceHook(hookCall.toProtobuf()) + } + } } } } @@ -366,8 +488,30 @@ extension AbstractTokenTransferTransaction.NftTransfer: TryProtobufCodable { senderAccountId: try .fromProtobuf(proto.senderAccountID), receiverAccountId: try .fromProtobuf(proto.receiverAccountID), serial: UInt64(proto.serialNumber), - isApproval: proto.isApproval + isApproval: proto.isApproval, + senderHookCall: nil, + receiverHookCall: nil ) + + // Handle sender allowance hook calls + switch proto.senderAllowanceHookCall { + case .preTxSenderAllowanceHook(let hookCall): + self.senderHookCall = try NftHookCall(protobuf: hookCall) + case .prePostTxSenderAllowanceHook(let hookCall): + self.senderHookCall = try NftHookCall(protobuf: hookCall) + case nil: + break + } + + // Handle receiver allowance hook calls + switch proto.receiverAllowanceHookCall { + case .preTxReceiverAllowanceHook(let hookCall): + self.receiverHookCall = try NftHookCall(protobuf: hookCall) + case .prePostTxReceiverAllowanceHook(let hookCall): + self.receiverHookCall = try NftHookCall(protobuf: hookCall) + case nil: + break + } } internal func toProtobuf() -> Protobuf { @@ -376,6 +520,20 @@ extension AbstractTokenTransferTransaction.NftTransfer: TryProtobufCodable { proto.receiverAccountID = receiverAccountId.toProtobuf() proto.serialNumber = Int64(bitPattern: serial) proto.isApproval = isApproval + + // Handle sender allowance hook calls + if let senderHookCall = senderHookCall { + proto.senderAllowanceHookCall = .preTxSenderAllowanceHook(senderHookCall.toProtobuf()) + } else if let senderHookCall = senderHookCall { + proto.senderAllowanceHookCall = .prePostTxSenderAllowanceHook(senderHookCall.toProtobuf()) + } + + // Handle receiver allowance hook calls + if let receiverHookCall = receiverHookCall { + proto.receiverAllowanceHookCall = .preTxReceiverAllowanceHook(receiverHookCall.toProtobuf()) + } else if let receiverHookCall = receiverHookCall { + proto.receiverAllowanceHookCall = .prePostTxReceiverAllowanceHook(receiverHookCall.toProtobuf()) + } } } } diff --git a/Sources/Hiero/Transaction/Transaction+FromProtobuf.swift b/Sources/Hiero/Transaction/Transaction+FromProtobuf.swift index 2d0fee28..b75d97f4 100644 --- a/Sources/Hiero/Transaction/Transaction+FromProtobuf.swift +++ b/Sources/Hiero/Transaction/Transaction+FromProtobuf.swift @@ -216,29 +216,12 @@ extension Transaction { let value = try intoOnlyValue(value) return try BatchTransaction(protobuf: firstBody, value) - case .stateSignatureTransaction(let code): - throw HError.fromProtobuf("unrecognized: stateSignatureTransaction `\(code)`") - - case .hintsPreprocessingVote(let code): - throw HError.fromProtobuf("unrecognized: hintsPreprocessingVote `\(code)`") - - case .hintsKeyPublication(let code): - throw HError.fromProtobuf("unrecognized: hintsKeyPublication `\(code)`") - - case .hintsPartialSignature(let code): - throw HError.fromProtobuf("unrecognized: hintsPartialSignature `\(code)`") - - case .historyProofSignature(let code): - throw HError.fromProtobuf("unrecognized: historyProofSignature `\(code)`") - - case .historyProofKeyPublication(let code): - throw HError.fromProtobuf("unrecognized: historyProofKeyPublication `\(code)`") - - case .historyProofVote(let code): - throw HError.fromProtobuf("unrecognized: historyProofVote `\(code)`") + case .lambdaSstore(let value): + let value = try intoOnlyValue(value) + return try LambdaSStoreTransaction(protobuf: firstBody, value) - case .crsPublication(let code): - throw HError.fromProtobuf("unrecognized: crsPublication `\(code)`") + default: + throw HError.fromProtobuf("unrecognized code") } } } diff --git a/Sources/Hiero/TransferTransaction.swift b/Sources/Hiero/TransferTransaction.swift index 0db89049..a9ba9988 100644 --- a/Sources/Hiero/TransferTransaction.swift +++ b/Sources/Hiero/TransferTransaction.swift @@ -76,6 +76,42 @@ public final class TransferTransaction: AbstractTokenTransferTransaction { return self } + /// Add an Hbar transfer with a hook to be submitted as part of this TransferTransaction. + @discardableResult + public func hbarTransferWithHook(_ accountId: AccountId, _ amount: Hbar, _ hookCall: FungibleHookCall) -> Self { + doHbarTransferWithHook(accountId, amount.toTinybars(), false, hookCall) + } + + private func doHbarTransferWithHook( + _ accountId: AccountId, + _ amount: Int64, + _ approved: Bool, + _ hookCall: FungibleHookCall + ) -> Self { + for (index, transfer) in transfers.enumerated() + where transfer.accountId == accountId && transfer.isApproval == approved { + let newTinybars = transfer.amount + amount + if newTinybars == 0 { + transfers.remove(at: index) + } else { + transfers[index].amount = newTinybars + transfers[index].hookCall = hookCall + } + + return self + } + + transfers.append( + Transfer( + accountId: accountId, + amount: amount, + isApproval: approved, + hookCall: hookCall + )) + + return self + } + internal override func validateChecksums(on ledgerId: LedgerId) throws { try transfers.validateChecksums(on: ledgerId) try tokenTransfersInner.validateChecksums(on: ledgerId) diff --git a/Sources/Hiero/Version.swift b/Sources/Hiero/Version.swift index 4ad512c9..5b882521 100644 --- a/Sources/Hiero/Version.swift +++ b/Sources/Hiero/Version.swift @@ -1,4 +1,4 @@ // SPDX-License-Identifier: Apache-2.0 public enum VersionInfo { - public static let version = "v0.43.0" + public static let version = "v0.43.0-dev" } diff --git a/Sources/HieroProtobufs/Generated/services/basic_types.pb.swift b/Sources/HieroProtobufs/Generated/services/basic_types.pb.swift index da08698e..7f450867 100644 --- a/Sources/HieroProtobufs/Generated/services/basic_types.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/basic_types.pb.swift @@ -973,6 +973,14 @@ public enum Proto_HederaFunctionality: SwiftProtobuf.Enum, Swift.CaseIterable { ///* /// Submit a batch of transactions to run atomically case atomicBatch // = 108 + + ///* + /// Update one or more storage slots in an lambda EVM hook. + case lambdaSstore // = 109 + + ///* + /// (Internal-only) Dispatch a hook action. + case hookDispatch // = 110 case UNRECOGNIZED(Int) public init() { @@ -1072,6 +1080,8 @@ public enum Proto_HederaFunctionality: SwiftProtobuf.Enum, Swift.CaseIterable { case 106: self = .historyProofVote case 107: self = .crsPublication case 108: self = .atomicBatch + case 109: self = .lambdaSstore + case 110: self = .hookDispatch default: self = .UNRECOGNIZED(rawValue) } } @@ -1169,6 +1179,8 @@ public enum Proto_HederaFunctionality: SwiftProtobuf.Enum, Swift.CaseIterable { case .historyProofVote: return 106 case .crsPublication: return 107 case .atomicBatch: return 108 + case .lambdaSstore: return 109 + case .hookDispatch: return 110 case .UNRECOGNIZED(let i): return i } } @@ -1266,6 +1278,8 @@ public enum Proto_HederaFunctionality: SwiftProtobuf.Enum, Swift.CaseIterable { .historyProofVote, .crsPublication, .atomicBatch, + .lambdaSstore, + .hookDispatch, ] } @@ -1769,6 +1783,175 @@ public struct Proto_TransactionID: Sendable { fileprivate var _accountID: Proto_AccountID? = nil } +///* +/// Once a hook is created, its full id. +///

+/// A composite of its creating entity's id and an arbitrary 64-bit hook id +/// (which need not be sequential). +public struct Proto_HookId: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The hook's creating entity id. + public var entityID: Proto_HookEntityId { + get {return _entityID ?? Proto_HookEntityId()} + set {_entityID = newValue} + } + /// Returns true if `entityID` has been explicitly set. + public var hasEntityID: Bool {return self._entityID != nil} + /// Clears the value of `entityID`. Subsequent reads from it will return its default value. + public mutating func clearEntityID() {self._entityID = nil} + + ///* + /// An arbitrary 64-bit identifier. + public var hookID: Int64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _entityID: Proto_HookEntityId? = nil +} + +///* +/// The id of an entity using a hook. +public struct Proto_HookEntityId: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var entityID: Proto_HookEntityId.OneOf_EntityID? = nil + + ///* + /// An account using a hook. + public var accountID: Proto_AccountID { + get { + if case .accountID(let v)? = entityID {return v} + return Proto_AccountID() + } + set {entityID = .accountID(newValue)} + } + + ///* + /// A contract using a hook. + public var contractID: Proto_ContractID { + get { + if case .contractID(let v)? = entityID {return v} + return Proto_ContractID() + } + set {entityID = .contractID(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_EntityID: Equatable, Sendable { + ///* + /// An account using a hook. + case accountID(Proto_AccountID) + ///* + /// A contract using a hook. + case contractID(Proto_ContractID) + + } + + public init() {} +} + +///* +/// Specifies a call to a hook from within a transaction. +///

+/// Often the hook's entity is implied by the nature of the call site. For example, when using an account allowance hook +/// inside a crypto transfer, the hook's entity is necessarily the account whose authorization is required. +///

+/// For future extension points where the hook owner is not forced by the context, we include the option to fully +/// specify the hook id for the call. +public struct Proto_HookCall: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var id: Proto_HookCall.OneOf_ID? = nil + + ///* + /// The full id of the hook to call, when the owning entity is not forced by the call site. + public var fullHookID: Proto_HookId { + get { + if case .fullHookID(let v)? = id {return v} + return Proto_HookId() + } + set {id = .fullHookID(newValue)} + } + + ///* + /// The numeric id of the hook to call, when the owning entity is forced by the call site. + public var hookID: Int64 { + get { + if case .hookID(let v)? = id {return v} + return 0 + } + set {id = .hookID(newValue)} + } + + ///* + /// Specifies details of the call. + public var callSpec: Proto_HookCall.OneOf_CallSpec? = nil + + ///* + /// Specification of how to call an EVM hook. + public var evmHookCall: Proto_EvmHookCall { + get { + if case .evmHookCall(let v)? = callSpec {return v} + return Proto_EvmHookCall() + } + set {callSpec = .evmHookCall(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_ID: Equatable, Sendable { + ///* + /// The full id of the hook to call, when the owning entity is not forced by the call site. + case fullHookID(Proto_HookId) + ///* + /// The numeric id of the hook to call, when the owning entity is forced by the call site. + case hookID(Int64) + + } + + ///* + /// Specifies details of the call. + public enum OneOf_CallSpec: Equatable, Sendable { + ///* + /// Specification of how to call an EVM hook. + case evmHookCall(Proto_EvmHookCall) + + } + + public init() {} +} + +///* +/// Specifies details of a call to an EVM hook. +public struct Proto_EvmHookCall: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// Call data to pass to the hook via the IHieroHook.HookContext#data field. + public var data: Data = Data() + + ///* + /// The gas limit to use. + public var gasLimit: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + ///* /// An account, and the amount that it sends or receives during a token transfer. /// @@ -1809,8 +1992,60 @@ public struct Proto_AccountAmount: Sendable { /// The default value SHALL be false (unset). public var isApproval: Bool = false + ///* + /// If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` on scoped + /// account; the hook's invoked methods must not revert and must return + /// true for the containing CryptoTransfer to succeed. + ///

+ /// Cannot be set if `is_approval` is true. + public var hookCall: Proto_AccountAmount.OneOf_HookCall? = nil + + ///* + /// A single call made before attempting the CryptoTransfer, to a + /// method with logical signature allow(HookContext, ProposedTransfers) + public var preTxAllowanceHook: Proto_HookCall { + get { + if case .preTxAllowanceHook(let v)? = hookCall {return v} + return Proto_HookCall() + } + set {hookCall = .preTxAllowanceHook(newValue)} + } + + ///* + /// Two calls, the first call before attempting the CryptoTransfer, to a + /// method with logical signature allowPre(HookContext, ProposedTransfers); + /// and the second call after attempting the CryptoTransfer, to a method + /// with logical signature allowPost(HookContext, ProposedTransfers). + public var prePostTxAllowanceHook: Proto_HookCall { + get { + if case .prePostTxAllowanceHook(let v)? = hookCall {return v} + return Proto_HookCall() + } + set {hookCall = .prePostTxAllowanceHook(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() + ///* + /// If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` on scoped + /// account; the hook's invoked methods must not revert and must return + /// true for the containing CryptoTransfer to succeed. + ///

+ /// Cannot be set if `is_approval` is true. + public enum OneOf_HookCall: Equatable, Sendable { + ///* + /// A single call made before attempting the CryptoTransfer, to a + /// method with logical signature allow(HookContext, ProposedTransfers) + case preTxAllowanceHook(Proto_HookCall) + ///* + /// Two calls, the first call before attempting the CryptoTransfer, to a + /// method with logical signature allowPre(HookContext, ProposedTransfers); + /// and the second call after attempting the CryptoTransfer, to a method + /// with logical signature allowPost(HookContext, ProposedTransfers). + case prePostTxAllowanceHook(Proto_HookCall) + + } + public init() {} fileprivate var _accountID: Proto_AccountID? = nil @@ -1850,7 +2085,7 @@ public struct Proto_TransferList: Sendable { /// Each `NftTransfer` SHALL be contained in another message (typically /// `TokenTransferList`) that details which `Token` type applies to this NFT /// transfer. -public struct Proto_NftTransfer: Sendable { +public struct Proto_NftTransfer: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1858,28 +2093,31 @@ public struct Proto_NftTransfer: Sendable { ///* /// An Account identifier for the sender. public var senderAccountID: Proto_AccountID { - get {return _senderAccountID ?? Proto_AccountID()} - set {_senderAccountID = newValue} + get {return _storage._senderAccountID ?? Proto_AccountID()} + set {_uniqueStorage()._senderAccountID = newValue} } /// Returns true if `senderAccountID` has been explicitly set. - public var hasSenderAccountID: Bool {return self._senderAccountID != nil} + public var hasSenderAccountID: Bool {return _storage._senderAccountID != nil} /// Clears the value of `senderAccountID`. Subsequent reads from it will return its default value. - public mutating func clearSenderAccountID() {self._senderAccountID = nil} + public mutating func clearSenderAccountID() {_uniqueStorage()._senderAccountID = nil} ///* /// An Account identifier for the receiver. public var receiverAccountID: Proto_AccountID { - get {return _receiverAccountID ?? Proto_AccountID()} - set {_receiverAccountID = newValue} + get {return _storage._receiverAccountID ?? Proto_AccountID()} + set {_uniqueStorage()._receiverAccountID = newValue} } /// Returns true if `receiverAccountID` has been explicitly set. - public var hasReceiverAccountID: Bool {return self._receiverAccountID != nil} + public var hasReceiverAccountID: Bool {return _storage._receiverAccountID != nil} /// Clears the value of `receiverAccountID`. Subsequent reads from it will return its default value. - public mutating func clearReceiverAccountID() {self._receiverAccountID = nil} + public mutating func clearReceiverAccountID() {_uniqueStorage()._receiverAccountID = nil} ///* /// A serial number for the NFT to transfer. - public var serialNumber: Int64 = 0 + public var serialNumber: Int64 { + get {return _storage._serialNumber} + set {_uniqueStorage()._serialNumber = newValue} + } ///* /// An approved allowance flag.
@@ -1890,14 +2128,128 @@ public struct Proto_NftTransfer: Sendable { /// If set, the `senderAccountID` MUST be the "payer" account for /// the transaction
/// The default value SHALL be false (unset). - public var isApproval: Bool = false + public var isApproval: Bool { + get {return _storage._isApproval} + set {_uniqueStorage()._isApproval = newValue} + } + + ///* + /// If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` installed on + /// senderAccountID that must succeed for the transaction to occur. + ///

+ /// Cannot be set if `is_approval` is true. + public var senderAllowanceHookCall: OneOf_SenderAllowanceHookCall? { + get {return _storage._senderAllowanceHookCall} + set {_uniqueStorage()._senderAllowanceHookCall = newValue} + } + + ///* + /// A single call made before attempting the CryptoTransfer, to a + /// method with logical signature allow(HookContext, ProposedTransfers) + public var preTxSenderAllowanceHook: Proto_HookCall { + get { + if case .preTxSenderAllowanceHook(let v)? = _storage._senderAllowanceHookCall {return v} + return Proto_HookCall() + } + set {_uniqueStorage()._senderAllowanceHookCall = .preTxSenderAllowanceHook(newValue)} + } + + ///* + /// Two calls, the first call before attempting the CryptoTransfer, to a + /// method with logical signature allowPre(HookContext, ProposedTransfers); + /// and the second call after attempting the CryptoTransfer, to a method + /// with logical signature allowPost(HookContext, ProposedTransfers). + public var prePostTxSenderAllowanceHook: Proto_HookCall { + get { + if case .prePostTxSenderAllowanceHook(let v)? = _storage._senderAllowanceHookCall {return v} + return Proto_HookCall() + } + set {_uniqueStorage()._senderAllowanceHookCall = .prePostTxSenderAllowanceHook(newValue)} + } + + ///* + /// If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` installed on + /// receiverAccountID that must succeed for the transaction to occur. + ///

+ /// May be set even if `is_approval` is true. In this case, the approval applies + /// to the sender authorization, and the hook applies to the receiver authorization + /// (if needed, e.g. because of a fallback royalty fee or receiver signature + /// requirement). + public var receiverAllowanceHookCall: OneOf_ReceiverAllowanceHookCall? { + get {return _storage._receiverAllowanceHookCall} + set {_uniqueStorage()._receiverAllowanceHookCall = newValue} + } + + ///* + /// A single call made before attempting the CryptoTransfer, to a + /// method with logical signature allow(HookContext, ProposedTransfers) + public var preTxReceiverAllowanceHook: Proto_HookCall { + get { + if case .preTxReceiverAllowanceHook(let v)? = _storage._receiverAllowanceHookCall {return v} + return Proto_HookCall() + } + set {_uniqueStorage()._receiverAllowanceHookCall = .preTxReceiverAllowanceHook(newValue)} + } + + ///* + /// Two calls, the first call before attempting the CryptoTransfer, to a + /// method with logical signature allowPre(HookContext, ProposedTransfers); + /// and the second call after attempting the CryptoTransfer, to a method + /// with logical signature allowPost(HookContext, ProposedTransfers). + public var prePostTxReceiverAllowanceHook: Proto_HookCall { + get { + if case .prePostTxReceiverAllowanceHook(let v)? = _storage._receiverAllowanceHookCall {return v} + return Proto_HookCall() + } + set {_uniqueStorage()._receiverAllowanceHookCall = .prePostTxReceiverAllowanceHook(newValue)} + } public var unknownFields = SwiftProtobuf.UnknownStorage() + ///* + /// If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` installed on + /// senderAccountID that must succeed for the transaction to occur. + ///

+ /// Cannot be set if `is_approval` is true. + public enum OneOf_SenderAllowanceHookCall: Equatable, Sendable { + ///* + /// A single call made before attempting the CryptoTransfer, to a + /// method with logical signature allow(HookContext, ProposedTransfers) + case preTxSenderAllowanceHook(Proto_HookCall) + ///* + /// Two calls, the first call before attempting the CryptoTransfer, to a + /// method with logical signature allowPre(HookContext, ProposedTransfers); + /// and the second call after attempting the CryptoTransfer, to a method + /// with logical signature allowPost(HookContext, ProposedTransfers). + case prePostTxSenderAllowanceHook(Proto_HookCall) + + } + + ///* + /// If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` installed on + /// receiverAccountID that must succeed for the transaction to occur. + ///

+ /// May be set even if `is_approval` is true. In this case, the approval applies + /// to the sender authorization, and the hook applies to the receiver authorization + /// (if needed, e.g. because of a fallback royalty fee or receiver signature + /// requirement). + public enum OneOf_ReceiverAllowanceHookCall: Equatable, Sendable { + ///* + /// A single call made before attempting the CryptoTransfer, to a + /// method with logical signature allow(HookContext, ProposedTransfers) + case preTxReceiverAllowanceHook(Proto_HookCall) + ///* + /// Two calls, the first call before attempting the CryptoTransfer, to a + /// method with logical signature allowPre(HookContext, ProposedTransfers); + /// and the second call after attempting the CryptoTransfer, to a method + /// with logical signature allowPost(HookContext, ProposedTransfers). + case prePostTxReceiverAllowanceHook(Proto_HookCall) + + } + public init() {} - fileprivate var _senderAccountID: Proto_AccountID? = nil - fileprivate var _receiverAccountID: Proto_AccountID? = nil + fileprivate var _storage = _StorageClass.defaultInstance } ///* @@ -3781,6 +4133,8 @@ extension Proto_HederaFunctionality: SwiftProtobuf._ProtoNameProviding { 106: .same(proto: "HistoryProofVote"), 107: .same(proto: "CrsPublication"), 108: .same(proto: "AtomicBatch"), + 109: .same(proto: "LambdaSStore"), + 110: .same(proto: "HookDispatch"), ] } @@ -4270,12 +4624,247 @@ extension Proto_TransactionID: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl } } +extension Proto_HookId: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HookId" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "entity_id"), + 2: .standard(proto: "hook_id"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._entityID) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self.hookID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._entityID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if self.hookID != 0 { + try visitor.visitSingularInt64Field(value: self.hookID, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Proto_HookId, rhs: Proto_HookId) -> Bool { + if lhs._entityID != rhs._entityID {return false} + if lhs.hookID != rhs.hookID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Proto_HookEntityId: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HookEntityId" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "account_id"), + 2: .standard(proto: "contract_id"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Proto_AccountID? + var hadOneofValue = false + if let current = self.entityID { + hadOneofValue = true + if case .accountID(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.entityID = .accountID(v) + } + }() + case 2: try { + var v: Proto_ContractID? + var hadOneofValue = false + if let current = self.entityID { + hadOneofValue = true + if case .contractID(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.entityID = .contractID(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.entityID { + case .accountID?: try { + guard case .accountID(let v)? = self.entityID else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .contractID?: try { + guard case .contractID(let v)? = self.entityID else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Proto_HookEntityId, rhs: Proto_HookEntityId) -> Bool { + if lhs.entityID != rhs.entityID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Proto_HookCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HookCall" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "full_hook_id"), + 2: .standard(proto: "hook_id"), + 3: .standard(proto: "evm_hook_call"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Proto_HookId? + var hadOneofValue = false + if let current = self.id { + hadOneofValue = true + if case .fullHookID(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.id = .fullHookID(v) + } + }() + case 2: try { + var v: Int64? + try decoder.decodeSingularInt64Field(value: &v) + if let v = v { + if self.id != nil {try decoder.handleConflictingOneOf()} + self.id = .hookID(v) + } + }() + case 3: try { + var v: Proto_EvmHookCall? + var hadOneofValue = false + if let current = self.callSpec { + hadOneofValue = true + if case .evmHookCall(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.callSpec = .evmHookCall(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.id { + case .fullHookID?: try { + guard case .fullHookID(let v)? = self.id else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .hookID?: try { + guard case .hookID(let v)? = self.id else { preconditionFailure() } + try visitor.visitSingularInt64Field(value: v, fieldNumber: 2) + }() + case nil: break + } + try { if case .evmHookCall(let v)? = self.callSpec { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Proto_HookCall, rhs: Proto_HookCall) -> Bool { + if lhs.id != rhs.id {return false} + if lhs.callSpec != rhs.callSpec {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Proto_EvmHookCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EvmHookCall" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "data"), + 2: .standard(proto: "gas_limit"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.data) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.gasLimit) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.data.isEmpty { + try visitor.visitSingularBytesField(value: self.data, fieldNumber: 1) + } + if self.gasLimit != 0 { + try visitor.visitSingularUInt64Field(value: self.gasLimit, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Proto_EvmHookCall, rhs: Proto_EvmHookCall) -> Bool { + if lhs.data != rhs.data {return false} + if lhs.gasLimit != rhs.gasLimit {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Proto_AccountAmount: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".AccountAmount" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "accountID"), 2: .same(proto: "amount"), 3: .standard(proto: "is_approval"), + 4: .standard(proto: "pre_tx_allowance_hook"), + 5: .standard(proto: "pre_post_tx_allowance_hook"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -4287,6 +4876,32 @@ extension Proto_AccountAmount: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl case 1: try { try decoder.decodeSingularMessageField(value: &self._accountID) }() case 2: try { try decoder.decodeSingularSInt64Field(value: &self.amount) }() case 3: try { try decoder.decodeSingularBoolField(value: &self.isApproval) }() + case 4: try { + var v: Proto_HookCall? + var hadOneofValue = false + if let current = self.hookCall { + hadOneofValue = true + if case .preTxAllowanceHook(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.hookCall = .preTxAllowanceHook(v) + } + }() + case 5: try { + var v: Proto_HookCall? + var hadOneofValue = false + if let current = self.hookCall { + hadOneofValue = true + if case .prePostTxAllowanceHook(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.hookCall = .prePostTxAllowanceHook(v) + } + }() default: break } } @@ -4306,6 +4921,17 @@ extension Proto_AccountAmount: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if self.isApproval != false { try visitor.visitSingularBoolField(value: self.isApproval, fieldNumber: 3) } + switch self.hookCall { + case .preTxAllowanceHook?: try { + guard case .preTxAllowanceHook(let v)? = self.hookCall else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() + case .prePostTxAllowanceHook?: try { + guard case .prePostTxAllowanceHook(let v)? = self.hookCall else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + }() + case nil: break + } try unknownFields.traverse(visitor: &visitor) } @@ -4313,6 +4939,7 @@ extension Proto_AccountAmount: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if lhs._accountID != rhs._accountID {return false} if lhs.amount != rhs.amount {return false} if lhs.isApproval != rhs.isApproval {return false} + if lhs.hookCall != rhs.hookCall {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -4357,48 +4984,178 @@ extension Proto_NftTransfer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 2: .same(proto: "receiverAccountID"), 3: .same(proto: "serialNumber"), 4: .standard(proto: "is_approval"), + 5: .standard(proto: "pre_tx_sender_allowance_hook"), + 6: .standard(proto: "pre_post_tx_sender_allowance_hook"), + 7: .standard(proto: "pre_tx_receiver_allowance_hook"), + 8: .standard(proto: "pre_post_tx_receiver_allowance_hook"), ] + fileprivate class _StorageClass { + var _senderAccountID: Proto_AccountID? = nil + var _receiverAccountID: Proto_AccountID? = nil + var _serialNumber: Int64 = 0 + var _isApproval: Bool = false + var _senderAllowanceHookCall: Proto_NftTransfer.OneOf_SenderAllowanceHookCall? + var _receiverAllowanceHookCall: Proto_NftTransfer.OneOf_ReceiverAllowanceHookCall? + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _senderAccountID = source._senderAccountID + _receiverAccountID = source._receiverAccountID + _serialNumber = source._serialNumber + _isApproval = source._isApproval + _senderAllowanceHookCall = source._senderAllowanceHookCall + _receiverAllowanceHookCall = source._receiverAllowanceHookCall + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._senderAccountID) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._receiverAccountID) }() - case 3: try { try decoder.decodeSingularInt64Field(value: &self.serialNumber) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.isApproval) }() - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &_storage._senderAccountID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._receiverAccountID) }() + case 3: try { try decoder.decodeSingularInt64Field(value: &_storage._serialNumber) }() + case 4: try { try decoder.decodeSingularBoolField(value: &_storage._isApproval) }() + case 5: try { + var v: Proto_HookCall? + var hadOneofValue = false + if let current = _storage._senderAllowanceHookCall { + hadOneofValue = true + if case .preTxSenderAllowanceHook(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._senderAllowanceHookCall = .preTxSenderAllowanceHook(v) + } + }() + case 6: try { + var v: Proto_HookCall? + var hadOneofValue = false + if let current = _storage._senderAllowanceHookCall { + hadOneofValue = true + if case .prePostTxSenderAllowanceHook(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._senderAllowanceHookCall = .prePostTxSenderAllowanceHook(v) + } + }() + case 7: try { + var v: Proto_HookCall? + var hadOneofValue = false + if let current = _storage._receiverAllowanceHookCall { + hadOneofValue = true + if case .preTxReceiverAllowanceHook(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._receiverAllowanceHookCall = .preTxReceiverAllowanceHook(v) + } + }() + case 8: try { + var v: Proto_HookCall? + var hadOneofValue = false + if let current = _storage._receiverAllowanceHookCall { + hadOneofValue = true + if case .prePostTxReceiverAllowanceHook(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._receiverAllowanceHookCall = .prePostTxReceiverAllowanceHook(v) + } + }() + default: break + } } } } public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._senderAccountID { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._receiverAccountID { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if self.serialNumber != 0 { - try visitor.visitSingularInt64Field(value: self.serialNumber, fieldNumber: 3) - } - if self.isApproval != false { - try visitor.visitSingularBoolField(value: self.isApproval, fieldNumber: 4) + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = _storage._senderAccountID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._receiverAccountID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if _storage._serialNumber != 0 { + try visitor.visitSingularInt64Field(value: _storage._serialNumber, fieldNumber: 3) + } + if _storage._isApproval != false { + try visitor.visitSingularBoolField(value: _storage._isApproval, fieldNumber: 4) + } + switch _storage._senderAllowanceHookCall { + case .preTxSenderAllowanceHook?: try { + guard case .preTxSenderAllowanceHook(let v)? = _storage._senderAllowanceHookCall else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + }() + case .prePostTxSenderAllowanceHook?: try { + guard case .prePostTxSenderAllowanceHook(let v)? = _storage._senderAllowanceHookCall else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + }() + case nil: break + } + switch _storage._receiverAllowanceHookCall { + case .preTxReceiverAllowanceHook?: try { + guard case .preTxReceiverAllowanceHook(let v)? = _storage._receiverAllowanceHookCall else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + }() + case .prePostTxReceiverAllowanceHook?: try { + guard case .prePostTxReceiverAllowanceHook(let v)? = _storage._receiverAllowanceHookCall else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 8) + }() + case nil: break + } } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Proto_NftTransfer, rhs: Proto_NftTransfer) -> Bool { - if lhs._senderAccountID != rhs._senderAccountID {return false} - if lhs._receiverAccountID != rhs._receiverAccountID {return false} - if lhs.serialNumber != rhs.serialNumber {return false} - if lhs.isApproval != rhs.isApproval {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._senderAccountID != rhs_storage._senderAccountID {return false} + if _storage._receiverAccountID != rhs_storage._receiverAccountID {return false} + if _storage._serialNumber != rhs_storage._serialNumber {return false} + if _storage._isApproval != rhs_storage._isApproval {return false} + if _storage._senderAllowanceHookCall != rhs_storage._senderAllowanceHookCall {return false} + if _storage._receiverAllowanceHookCall != rhs_storage._receiverAllowanceHookCall {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/HieroProtobufs/Generated/services/contract_create.pb.swift b/Sources/HieroProtobufs/Generated/services/contract_create.pb.swift index 097dea26..f3db0fe2 100644 --- a/Sources/HieroProtobufs/Generated/services/contract_create.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/contract_create.pb.swift @@ -366,6 +366,13 @@ public struct Proto_ContractCreateTransactionBody: @unchecked Sendable { set {_uniqueStorage()._declineReward = newValue} } + ///* + /// Details of hooks to add immediately after creating this contract. + public var hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] { + get {return _storage._hookCreationDetails} + set {_uniqueStorage()._hookCreationDetails = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_InitcodeSource: Equatable, @unchecked Sendable { @@ -441,6 +448,7 @@ extension Proto_ContractCreateTransactionBody: SwiftProtobuf.Message, SwiftProto 17: .standard(proto: "staked_account_id"), 18: .standard(proto: "staked_node_id"), 19: .standard(proto: "decline_reward"), + 20: .standard(proto: "hook_creation_details"), ] fileprivate class _StorageClass { @@ -459,6 +467,7 @@ extension Proto_ContractCreateTransactionBody: SwiftProtobuf.Message, SwiftProto var _autoRenewAccountID: Proto_AccountID? = nil var _stakedID: Proto_ContractCreateTransactionBody.OneOf_StakedID? var _declineReward: Bool = false + var _hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] = [] #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -488,6 +497,7 @@ extension Proto_ContractCreateTransactionBody: SwiftProtobuf.Message, SwiftProto _autoRenewAccountID = source._autoRenewAccountID _stakedID = source._stakedID _declineReward = source._declineReward + _hookCreationDetails = source._hookCreationDetails } } @@ -561,6 +571,7 @@ extension Proto_ContractCreateTransactionBody: SwiftProtobuf.Message, SwiftProto } }() case 19: try { try decoder.decodeSingularBoolField(value: &_storage._declineReward) }() + case 20: try { try decoder.decodeRepeatedMessageField(value: &_storage._hookCreationDetails) }() default: break } } @@ -629,6 +640,9 @@ extension Proto_ContractCreateTransactionBody: SwiftProtobuf.Message, SwiftProto if _storage._declineReward != false { try visitor.visitSingularBoolField(value: _storage._declineReward, fieldNumber: 19) } + if !_storage._hookCreationDetails.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._hookCreationDetails, fieldNumber: 20) + } } try unknownFields.traverse(visitor: &visitor) } @@ -653,6 +667,7 @@ extension Proto_ContractCreateTransactionBody: SwiftProtobuf.Message, SwiftProto if _storage._autoRenewAccountID != rhs_storage._autoRenewAccountID {return false} if _storage._stakedID != rhs_storage._stakedID {return false} if _storage._declineReward != rhs_storage._declineReward {return false} + if _storage._hookCreationDetails != rhs_storage._hookCreationDetails {return false} return true } if !storagesAreEqual {return false} diff --git a/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift b/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift index 2359cb85..89db36ea 100644 --- a/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift @@ -33,6 +33,9 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP ///* /// Context of an internal call in an EVM transaction that is not otherwise externalized.
+/// This message does not say anything about whether an EVM transaction is itself a logical +/// transaction in a Hiero transactional unit. It simply provides context on an internal +/// message call within an EVM transaction. public struct Proto_InternalCallContext: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -56,7 +59,7 @@ public struct Proto_InternalCallContext: @unchecked Sendable { } ///* -/// Results of executing EVM transaction.
+/// Results of executing a EVM transaction.
public struct Proto_EvmTransactionResult: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -99,7 +102,8 @@ public struct Proto_EvmTransactionResult: @unchecked Sendable { public var gasUsed: UInt64 = 0 ///* - /// If not already externalized, the context of the internal call producing this result. + /// If not already externalized in a transaction body, the context of the + /// internal call producing this result. public var internalCallContext: Proto_InternalCallContext { get {return _internalCallContext ?? Proto_InternalCallContext()} set {_internalCallContext = newValue} diff --git a/Sources/HieroProtobufs/Generated/services/contract_update.pb.swift b/Sources/HieroProtobufs/Generated/services/contract_update.pb.swift index e3f7ec94..5b540d3e 100644 --- a/Sources/HieroProtobufs/Generated/services/contract_update.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/contract_update.pb.swift @@ -276,6 +276,20 @@ public struct Proto_ContractUpdateTransactionBody: @unchecked Sendable { /// Clears the value of `declineReward`. Subsequent reads from it will return its default value. public mutating func clearDeclineReward() {_uniqueStorage()._declineReward = nil} + ///* + /// The ids the hooks to delete from the contract. + public var hookIdsToDelete: [Int64] { + get {return _storage._hookIdsToDelete} + set {_uniqueStorage()._hookIdsToDelete = newValue} + } + + ///* + /// The hooks to create for the contract. + public var hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] { + get {return _storage._hookCreationDetails} + set {_uniqueStorage()._hookCreationDetails = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// This should be condensed to just a field instead of a oneof and field 9 reserved. @@ -351,6 +365,8 @@ extension Proto_ContractUpdateTransactionBody: SwiftProtobuf.Message, SwiftProto 13: .standard(proto: "staked_account_id"), 14: .standard(proto: "staked_node_id"), 15: .standard(proto: "decline_reward"), + 16: .standard(proto: "hook_ids_to_delete"), + 17: .standard(proto: "hook_creation_details"), ] fileprivate class _StorageClass { @@ -365,6 +381,8 @@ extension Proto_ContractUpdateTransactionBody: SwiftProtobuf.Message, SwiftProto var _autoRenewAccountID: Proto_AccountID? = nil var _stakedID: Proto_ContractUpdateTransactionBody.OneOf_StakedID? var _declineReward: SwiftProtobuf.Google_Protobuf_BoolValue? = nil + var _hookIdsToDelete: [Int64] = [] + var _hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] = [] #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -390,6 +408,8 @@ extension Proto_ContractUpdateTransactionBody: SwiftProtobuf.Message, SwiftProto _autoRenewAccountID = source._autoRenewAccountID _stakedID = source._stakedID _declineReward = source._declineReward + _hookIdsToDelete = source._hookIdsToDelete + _hookCreationDetails = source._hookCreationDetails } } @@ -459,6 +479,8 @@ extension Proto_ContractUpdateTransactionBody: SwiftProtobuf.Message, SwiftProto } }() case 15: try { try decoder.decodeSingularMessageField(value: &_storage._declineReward) }() + case 16: try { try decoder.decodeRepeatedInt64Field(value: &_storage._hookIdsToDelete) }() + case 17: try { try decoder.decodeRepeatedMessageField(value: &_storage._hookCreationDetails) }() default: break } } @@ -520,6 +542,12 @@ extension Proto_ContractUpdateTransactionBody: SwiftProtobuf.Message, SwiftProto try { if let v = _storage._declineReward { try visitor.visitSingularMessageField(value: v, fieldNumber: 15) } }() + if !_storage._hookIdsToDelete.isEmpty { + try visitor.visitPackedInt64Field(value: _storage._hookIdsToDelete, fieldNumber: 16) + } + if !_storage._hookCreationDetails.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._hookCreationDetails, fieldNumber: 17) + } } try unknownFields.traverse(visitor: &visitor) } @@ -540,6 +568,8 @@ extension Proto_ContractUpdateTransactionBody: SwiftProtobuf.Message, SwiftProto if _storage._autoRenewAccountID != rhs_storage._autoRenewAccountID {return false} if _storage._stakedID != rhs_storage._stakedID {return false} if _storage._declineReward != rhs_storage._declineReward {return false} + if _storage._hookIdsToDelete != rhs_storage._hookIdsToDelete {return false} + if _storage._hookCreationDetails != rhs_storage._hookCreationDetails {return false} return true } if !storagesAreEqual {return false} diff --git a/Sources/HieroProtobufs/Generated/services/crypto_create.pb.swift b/Sources/HieroProtobufs/Generated/services/crypto_create.pb.swift index b282756e..6df603ff 100644 --- a/Sources/HieroProtobufs/Generated/services/crypto_create.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/crypto_create.pb.swift @@ -282,6 +282,13 @@ public struct Proto_CryptoCreateTransactionBody: @unchecked Sendable { set {_uniqueStorage()._alias = newValue} } + ///* + /// Details of hooks to add immediately after creating this account. + public var hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] { + get {return _storage._hookCreationDetails} + set {_uniqueStorage()._hookCreationDetails = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_StakedID: Equatable, Sendable { @@ -331,6 +338,7 @@ extension Proto_CryptoCreateTransactionBody: SwiftProtobuf.Message, SwiftProtobu 16: .standard(proto: "staked_node_id"), 17: .standard(proto: "decline_reward"), 18: .same(proto: "alias"), + 19: .standard(proto: "hook_creation_details"), ] fileprivate class _StorageClass { @@ -349,6 +357,7 @@ extension Proto_CryptoCreateTransactionBody: SwiftProtobuf.Message, SwiftProtobu var _stakedID: Proto_CryptoCreateTransactionBody.OneOf_StakedID? var _declineReward: Bool = false var _alias: Data = Data() + var _hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] = [] #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -378,6 +387,7 @@ extension Proto_CryptoCreateTransactionBody: SwiftProtobuf.Message, SwiftProtobu _stakedID = source._stakedID _declineReward = source._declineReward _alias = source._alias + _hookCreationDetails = source._hookCreationDetails } } @@ -431,6 +441,7 @@ extension Proto_CryptoCreateTransactionBody: SwiftProtobuf.Message, SwiftProtobu }() case 17: try { try decoder.decodeSingularBoolField(value: &_storage._declineReward) }() case 18: try { try decoder.decodeSingularBytesField(value: &_storage._alias) }() + case 19: try { try decoder.decodeRepeatedMessageField(value: &_storage._hookCreationDetails) }() default: break } } @@ -496,6 +507,9 @@ extension Proto_CryptoCreateTransactionBody: SwiftProtobuf.Message, SwiftProtobu if !_storage._alias.isEmpty { try visitor.visitSingularBytesField(value: _storage._alias, fieldNumber: 18) } + if !_storage._hookCreationDetails.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._hookCreationDetails, fieldNumber: 19) + } } try unknownFields.traverse(visitor: &visitor) } @@ -520,6 +534,7 @@ extension Proto_CryptoCreateTransactionBody: SwiftProtobuf.Message, SwiftProtobu if _storage._stakedID != rhs_storage._stakedID {return false} if _storage._declineReward != rhs_storage._declineReward {return false} if _storage._alias != rhs_storage._alias {return false} + if _storage._hookCreationDetails != rhs_storage._hookCreationDetails {return false} return true } if !storagesAreEqual {return false} diff --git a/Sources/HieroProtobufs/Generated/services/crypto_update.pb.swift b/Sources/HieroProtobufs/Generated/services/crypto_update.pb.swift index 890efec0..ffa4628e 100644 --- a/Sources/HieroProtobufs/Generated/services/crypto_update.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/crypto_update.pb.swift @@ -335,6 +335,20 @@ public struct Proto_CryptoUpdateTransactionBody: @unchecked Sendable { /// Clears the value of `declineReward`. Subsequent reads from it will return its default value. public mutating func clearDeclineReward() {_uniqueStorage()._declineReward = nil} + ///* + /// The ids the hooks to delete from the account. + public var hookIdsToDelete: [Int64] { + get {return _storage._hookIdsToDelete} + set {_uniqueStorage()._hookIdsToDelete = newValue} + } + + ///* + /// The hooks to create for the account. + public var hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] { + get {return _storage._hookCreationDetails} + set {_uniqueStorage()._hookCreationDetails = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// This entire oneOf is deprecated, and the concept is not implemented. @@ -447,6 +461,8 @@ extension Proto_CryptoUpdateTransactionBody: SwiftProtobuf.Message, SwiftProtobu 16: .standard(proto: "staked_account_id"), 17: .standard(proto: "staked_node_id"), 18: .standard(proto: "decline_reward"), + 19: .standard(proto: "hook_ids_to_delete"), + 20: .standard(proto: "hook_creation_details"), ] fileprivate class _StorageClass { @@ -463,6 +479,8 @@ extension Proto_CryptoUpdateTransactionBody: SwiftProtobuf.Message, SwiftProtobu var _maxAutomaticTokenAssociations: SwiftProtobuf.Google_Protobuf_Int32Value? = nil var _stakedID: Proto_CryptoUpdateTransactionBody.OneOf_StakedID? var _declineReward: SwiftProtobuf.Google_Protobuf_BoolValue? = nil + var _hookIdsToDelete: [Int64] = [] + var _hookCreationDetails: [Com_Hedera_Hapi_Node_Hooks_HookCreationDetails] = [] #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -490,6 +508,8 @@ extension Proto_CryptoUpdateTransactionBody: SwiftProtobuf.Message, SwiftProtobu _maxAutomaticTokenAssociations = source._maxAutomaticTokenAssociations _stakedID = source._stakedID _declineReward = source._declineReward + _hookIdsToDelete = source._hookIdsToDelete + _hookCreationDetails = source._hookCreationDetails } } @@ -601,6 +621,8 @@ extension Proto_CryptoUpdateTransactionBody: SwiftProtobuf.Message, SwiftProtobu } }() case 18: try { try decoder.decodeSingularMessageField(value: &_storage._declineReward) }() + case 19: try { try decoder.decodeRepeatedInt64Field(value: &_storage._hookIdsToDelete) }() + case 20: try { try decoder.decodeRepeatedMessageField(value: &_storage._hookCreationDetails) }() default: break } } @@ -669,6 +691,12 @@ extension Proto_CryptoUpdateTransactionBody: SwiftProtobuf.Message, SwiftProtobu try { if let v = _storage._declineReward { try visitor.visitSingularMessageField(value: v, fieldNumber: 18) } }() + if !_storage._hookIdsToDelete.isEmpty { + try visitor.visitPackedInt64Field(value: _storage._hookIdsToDelete, fieldNumber: 19) + } + if !_storage._hookCreationDetails.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._hookCreationDetails, fieldNumber: 20) + } } try unknownFields.traverse(visitor: &visitor) } @@ -691,6 +719,8 @@ extension Proto_CryptoUpdateTransactionBody: SwiftProtobuf.Message, SwiftProtobu if _storage._maxAutomaticTokenAssociations != rhs_storage._maxAutomaticTokenAssociations {return false} if _storage._stakedID != rhs_storage._stakedID {return false} if _storage._declineReward != rhs_storage._declineReward {return false} + if _storage._hookIdsToDelete != rhs_storage._hookIdsToDelete {return false} + if _storage._hookCreationDetails != rhs_storage._hookCreationDetails {return false} return true } if !storagesAreEqual {return false} diff --git a/Sources/HieroProtobufs/Generated/services/hook_dispatch.pb.swift b/Sources/HieroProtobufs/Generated/services/hook_dispatch.pb.swift new file mode 100644 index 00000000..e014fa71 --- /dev/null +++ b/Sources/HieroProtobufs/Generated/services/hook_dispatch.pb.swift @@ -0,0 +1,249 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: services/hook_dispatch.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +///* +/// Dispatches a hook action to an appropriate service. +public struct Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var action: Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody.OneOf_Action? = nil + + ///* + /// The id of the hook to delete. + public var hookIDToDelete: Proto_HookId { + get { + if case .hookIDToDelete(let v)? = action {return v} + return Proto_HookId() + } + set {action = .hookIDToDelete(newValue)} + } + + ///* + /// Creation details for a new hook. + public var creation: Com_Hedera_Hapi_Node_Hooks_HookCreation { + get { + if case .creation(let v)? = action {return v} + return Com_Hedera_Hapi_Node_Hooks_HookCreation() + } + set {action = .creation(newValue)} + } + + ///* + /// Execution details of an existing hook. + public var execution: Com_Hedera_Hapi_Node_Hooks_HookExecution { + get { + if case .execution(let v)? = action {return v} + return Com_Hedera_Hapi_Node_Hooks_HookExecution() + } + set {action = .execution(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Action: Equatable, Sendable { + ///* + /// The id of the hook to delete. + case hookIDToDelete(Proto_HookId) + ///* + /// Creation details for a new hook. + case creation(Com_Hedera_Hapi_Node_Hooks_HookCreation) + ///* + /// Execution details of an existing hook. + case execution(Com_Hedera_Hapi_Node_Hooks_HookExecution) + + } + + public init() {} +} + +///* +/// Details the execution of a hook. +public struct Com_Hedera_Hapi_Node_Hooks_HookExecution: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The id of the hook's entity. + public var hookEntityID: Proto_HookEntityId { + get {return _hookEntityID ?? Proto_HookEntityId()} + set {_hookEntityID = newValue} + } + /// Returns true if `hookEntityID` has been explicitly set. + public var hasHookEntityID: Bool {return self._hookEntityID != nil} + /// Clears the value of `hookEntityID`. Subsequent reads from it will return its default value. + public mutating func clearHookEntityID() {self._hookEntityID = nil} + + ///* + /// The details of the call, including which hook id to call. + public var call: Proto_HookCall { + get {return _call ?? Proto_HookCall()} + set {_call = newValue} + } + /// Returns true if `call` has been explicitly set. + public var hasCall: Bool {return self._call != nil} + /// Clears the value of `call`. Subsequent reads from it will return its default value. + public mutating func clearCall() {self._call = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _hookEntityID: Proto_HookEntityId? = nil + fileprivate var _call: Proto_HookCall? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "com.hedera.hapi.node.hooks" + +extension Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HookDispatchTransactionBody" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "hook_id_to_delete"), + 2: .same(proto: "creation"), + 3: .same(proto: "execution"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Proto_HookId? + var hadOneofValue = false + if let current = self.action { + hadOneofValue = true + if case .hookIDToDelete(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.action = .hookIDToDelete(v) + } + }() + case 2: try { + var v: Com_Hedera_Hapi_Node_Hooks_HookCreation? + var hadOneofValue = false + if let current = self.action { + hadOneofValue = true + if case .creation(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.action = .creation(v) + } + }() + case 3: try { + var v: Com_Hedera_Hapi_Node_Hooks_HookExecution? + var hadOneofValue = false + if let current = self.action { + hadOneofValue = true + if case .execution(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.action = .execution(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.action { + case .hookIDToDelete?: try { + guard case .hookIDToDelete(let v)? = self.action else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .creation?: try { + guard case .creation(let v)? = self.action else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .execution?: try { + guard case .execution(let v)? = self.action else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody, rhs: Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody) -> Bool { + if lhs.action != rhs.action {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_HookExecution: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HookExecution" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "hook_entity_id"), + 2: .same(proto: "call"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._hookEntityID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._call) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._hookEntityID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._call { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_HookExecution, rhs: Com_Hedera_Hapi_Node_Hooks_HookExecution) -> Bool { + if lhs._hookEntityID != rhs._hookEntityID {return false} + if lhs._call != rhs._call {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/HieroProtobufs/Generated/services/hook_types.pb.swift b/Sources/HieroProtobufs/Generated/services/hook_types.pb.swift new file mode 100644 index 00000000..509e5af8 --- /dev/null +++ b/Sources/HieroProtobufs/Generated/services/hook_types.pb.swift @@ -0,0 +1,819 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: services/hook_types.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +///** +/// The Hiero extension points that accept a hook. +public enum Com_Hedera_Hapi_Node_Hooks_HookExtensionPoint: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + ///* + /// Used to customize an account's allowances during a CryptoTransfer transaction. + case accountAllowanceHook // = 0 + case UNRECOGNIZED(Int) + + public init() { + self = .accountAllowanceHook + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .accountAllowanceHook + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .accountAllowanceHook: return 0 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Com_Hedera_Hapi_Node_Hooks_HookExtensionPoint] = [ + .accountAllowanceHook, + ] + +} + +///* +/// Specifies the creation of a new hook at the given id for the given entity. +public struct Com_Hedera_Hapi_Node_Hooks_HookCreation: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The id of the hook's entity. + public var entityID: Proto_HookEntityId { + get {return _entityID ?? Proto_HookEntityId()} + set {_entityID = newValue} + } + /// Returns true if `entityID` has been explicitly set. + public var hasEntityID: Bool {return self._entityID != nil} + /// Clears the value of `entityID`. Subsequent reads from it will return its default value. + public mutating func clearEntityID() {self._entityID = nil} + + ///* + /// The creation details. + public var details: Com_Hedera_Hapi_Node_Hooks_HookCreationDetails { + get {return _details ?? Com_Hedera_Hapi_Node_Hooks_HookCreationDetails()} + set {_details = newValue} + } + /// Returns true if `details` has been explicitly set. + public var hasDetails: Bool {return self._details != nil} + /// Clears the value of `details`. Subsequent reads from it will return its default value. + public mutating func clearDetails() {self._details = nil} + + ///* + /// If set, the id of the hook following this one in the owner's + /// doubly-linked list of hooks. + public var nextHookID: SwiftProtobuf.Google_Protobuf_Int64Value { + get {return _nextHookID ?? SwiftProtobuf.Google_Protobuf_Int64Value()} + set {_nextHookID = newValue} + } + /// Returns true if `nextHookID` has been explicitly set. + public var hasNextHookID: Bool {return self._nextHookID != nil} + /// Clears the value of `nextHookID`. Subsequent reads from it will return its default value. + public mutating func clearNextHookID() {self._nextHookID = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _entityID: Proto_HookEntityId? = nil + fileprivate var _details: Com_Hedera_Hapi_Node_Hooks_HookCreationDetails? = nil + fileprivate var _nextHookID: SwiftProtobuf.Google_Protobuf_Int64Value? = nil +} + +///** +/// The details of a hook's creation. +public struct Com_Hedera_Hapi_Node_Hooks_HookCreationDetails: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The extension point for the hook. + public var extensionPoint: Com_Hedera_Hapi_Node_Hooks_HookExtensionPoint = .accountAllowanceHook + + ///* + /// The id to create the hook at. + public var hookID: Int64 = 0 + + ///* + /// The hook implementation. + public var hook: Com_Hedera_Hapi_Node_Hooks_HookCreationDetails.OneOf_Hook? = nil + + ///* + /// A hook programmed in EVM bytecode that may access state or interact with + /// external contracts. + public var lambdaEvmHook: Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook { + get { + if case .lambdaEvmHook(let v)? = hook {return v} + return Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook() + } + set {hook = .lambdaEvmHook(newValue)} + } + + ///* + /// If set, a key that that can be used to remove or replace the hook; or (if + /// applicable, as with a lambda EVM hook) perform transactions that customize + /// the hook. + public var adminKey: Proto_Key { + get {return _adminKey ?? Proto_Key()} + set {_adminKey = newValue} + } + /// Returns true if `adminKey` has been explicitly set. + public var hasAdminKey: Bool {return self._adminKey != nil} + /// Clears the value of `adminKey`. Subsequent reads from it will return its default value. + public mutating func clearAdminKey() {self._adminKey = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + ///* + /// The hook implementation. + public enum OneOf_Hook: Equatable, Sendable { + ///* + /// A hook programmed in EVM bytecode that may access state or interact with + /// external contracts. + case lambdaEvmHook(Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook) + + } + + public init() {} + + fileprivate var _adminKey: Proto_Key? = nil +} + +///* +/// Definition of a lambda EVM hook. +public struct Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The specification for the hook. + public var spec: Com_Hedera_Hapi_Node_Hooks_EvmHookSpec { + get {return _spec ?? Com_Hedera_Hapi_Node_Hooks_EvmHookSpec()} + set {_spec = newValue} + } + /// Returns true if `spec` has been explicitly set. + public var hasSpec: Bool {return self._spec != nil} + /// Clears the value of `spec`. Subsequent reads from it will return its default value. + public mutating func clearSpec() {self._spec = nil} + + ///* + /// Initial storage updates for the lambda, if any. + public var storageUpdates: [Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _spec: Com_Hedera_Hapi_Node_Hooks_EvmHookSpec? = nil +} + +///* +/// Shared specifications for an EVM hook. May be used for any extension point. +public struct Com_Hedera_Hapi_Node_Hooks_EvmHookSpec: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The source of the EVM bytecode for the hook. + public var bytecodeSource: Com_Hedera_Hapi_Node_Hooks_EvmHookSpec.OneOf_BytecodeSource? = nil + + ///* + /// The id of a contract that implements the extension point API with EVM bytecode. + public var contractID: Proto_ContractID { + get { + if case .contractID(let v)? = bytecodeSource {return v} + return Proto_ContractID() + } + set {bytecodeSource = .contractID(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + ///* + /// The source of the EVM bytecode for the hook. + public enum OneOf_BytecodeSource: Equatable, Sendable { + ///* + /// The id of a contract that implements the extension point API with EVM bytecode. + case contractID(Proto_ContractID) + + } + + public init() {} +} + +///* +/// 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. +public struct Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var update: Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate.OneOf_Update? = nil + + ///* + /// An explicit storage slot update. + public var storageSlot: Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot { + get { + if case .storageSlot(let v)? = update {return v} + return Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot() + } + set {update = .storageSlot(newValue)} + } + + ///* + /// Implicit storage slot updates specified as Solidity mapping entries. + public var mappingEntries: Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries { + get { + if case .mappingEntries(let v)? = update {return v} + return Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries() + } + set {update = .mappingEntries(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Update: Equatable, Sendable { + ///* + /// An explicit storage slot update. + case storageSlot(Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot) + ///* + /// Implicit storage slot updates specified as Solidity mapping entries. + case mappingEntries(Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries) + + } + + public init() {} +} + +///* +/// Specifies storage slot updates via indirection into a Solidity mapping. +///

+/// Concretely, if the Solidity mapping is itself at slot `mapping_slot`, then +/// the * storage slot for key `key` in the mapping is defined by the relationship +/// `key_storage_slot = keccak256(abi.encodePacked(mapping_slot, key))`. +///

+/// This message lets a metaprotocol be specified in terms of changes to a +/// Solidity mapping's entries. If only raw slots could be updated, then a block +/// stream consumer following the metaprotocol would have to invert the Keccak256 +/// hash to determine which mapping entry was being updated, which is not possible. +public struct Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The slot that corresponds to the Solidity mapping itself. Must use a + /// minimal byte representation (no leading zeros). + public var mappingSlot: Data = Data() + + ///* + /// The entries in the mapping at the given slot. + public var entries: [Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +///* +/// An entry in a Solidity mapping. Very helpful for protocols that apply +/// `LambdaSStore` to manage the entries of a hook contract's mapping instead +/// its raw storage slots. +///

+/// This is especially attractive when the mapping value itself fits in a single +/// word; for more complicated value storage layouts it becomes necessary to +/// combine the mapping update with additional `LambdaStorageSlot` updates that +/// specify the complete storage slots of the value type. +public struct Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var entryKey: Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry.OneOf_EntryKey? = nil + + ///* + /// The explicit bytes of the mapping entry. Must use a minimal byte representation; + /// may not exceed 32 bytes in length. + public var key: Data { + get { + if case .key(let v)? = entryKey {return v} + return Data() + } + set {entryKey = .key(newValue)} + } + + ///* + /// The bytes that are the preimage of the Keccak256 hash that forms the mapping key. + /// May be longer or shorter than 32 bytes and may have leading zeros, since Solidity + /// supports variable-length keys in mappings. + public var preimage: Data { + get { + if case .preimage(let v)? = entryKey {return v} + return Data() + } + set {entryKey = .preimage(newValue)} + } + + ///* + /// If the mapping entry is present and non-zero, its value. May not be longer than + /// 32 bytes in length; must use a minimal byte representation (no leading zeros). + /// Leaving this field empty in an update removes the entry from the mapping. + public var value: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_EntryKey: Equatable, @unchecked Sendable { + ///* + /// The explicit bytes of the mapping entry. Must use a minimal byte representation; + /// may not exceed 32 bytes in length. + case key(Data) + ///* + /// The bytes that are the preimage of the Keccak256 hash that forms the mapping key. + /// May be longer or shorter than 32 bytes and may have leading zeros, since Solidity + /// supports variable-length keys in mappings. + case preimage(Data) + + } + + public init() {} +} + +///* +/// A slot in the storage of a lambda EVM hook. +public struct Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The key of the slot. Must use a minimal byte representation (no + /// leading zeros); may not exceed 32 bytes in length. + public var key: Data = Data() + + ///* + /// If the slot is present and non-zero, its value. Must use a minimal + /// byte representation (no leading zeros); may not exceed 32 bytes in + /// length Leaving this field empty in an update removes the slot from + /// storage. + public var value: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "com.hedera.hapi.node.hooks" + +extension Com_Hedera_Hapi_Node_Hooks_HookExtensionPoint: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "ACCOUNT_ALLOWANCE_HOOK"), + ] +} + +extension Com_Hedera_Hapi_Node_Hooks_HookCreation: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HookCreation" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "entity_id"), + 2: .same(proto: "details"), + 9: .standard(proto: "next_hook_id"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._entityID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._details) }() + case 9: try { try decoder.decodeSingularMessageField(value: &self._nextHookID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._entityID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._details { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._nextHookID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_HookCreation, rhs: Com_Hedera_Hapi_Node_Hooks_HookCreation) -> Bool { + if lhs._entityID != rhs._entityID {return false} + if lhs._details != rhs._details {return false} + if lhs._nextHookID != rhs._nextHookID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_HookCreationDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HookCreationDetails" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "extension_point"), + 2: .standard(proto: "hook_id"), + 3: .standard(proto: "lambda_evm_hook"), + 4: .standard(proto: "admin_key"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.extensionPoint) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self.hookID) }() + case 3: try { + var v: Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook? + var hadOneofValue = false + if let current = self.hook { + hadOneofValue = true + if case .lambdaEvmHook(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.hook = .lambdaEvmHook(v) + } + }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._adminKey) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.extensionPoint != .accountAllowanceHook { + try visitor.visitSingularEnumField(value: self.extensionPoint, fieldNumber: 1) + } + if self.hookID != 0 { + try visitor.visitSingularInt64Field(value: self.hookID, fieldNumber: 2) + } + try { if case .lambdaEvmHook(let v)? = self.hook { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = self._adminKey { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_HookCreationDetails, rhs: Com_Hedera_Hapi_Node_Hooks_HookCreationDetails) -> Bool { + if lhs.extensionPoint != rhs.extensionPoint {return false} + if lhs.hookID != rhs.hookID {return false} + if lhs.hook != rhs.hook {return false} + if lhs._adminKey != rhs._adminKey {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LambdaEvmHook" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "spec"), + 2: .standard(proto: "storage_updates"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._spec) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.storageUpdates) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._spec { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.storageUpdates.isEmpty { + try visitor.visitRepeatedMessageField(value: self.storageUpdates, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook, rhs: Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook) -> Bool { + if lhs._spec != rhs._spec {return false} + if lhs.storageUpdates != rhs.storageUpdates {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_EvmHookSpec: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EvmHookSpec" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "contract_id"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Proto_ContractID? + var hadOneofValue = false + if let current = self.bytecodeSource { + hadOneofValue = true + if case .contractID(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.bytecodeSource = .contractID(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if case .contractID(let v)? = self.bytecodeSource { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_EvmHookSpec, rhs: Com_Hedera_Hapi_Node_Hooks_EvmHookSpec) -> Bool { + if lhs.bytecodeSource != rhs.bytecodeSource {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LambdaStorageUpdate" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "storage_slot"), + 2: .standard(proto: "mapping_entries"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot? + var hadOneofValue = false + if let current = self.update { + hadOneofValue = true + if case .storageSlot(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.update = .storageSlot(v) + } + }() + case 2: try { + var v: Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries? + var hadOneofValue = false + if let current = self.update { + hadOneofValue = true + if case .mappingEntries(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.update = .mappingEntries(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.update { + case .storageSlot?: try { + guard case .storageSlot(let v)? = self.update else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .mappingEntries?: try { + guard case .mappingEntries(let v)? = self.update else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate, rhs: Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate) -> Bool { + if lhs.update != rhs.update {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LambdaMappingEntries" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "mapping_slot"), + 2: .same(proto: "entries"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.mappingSlot) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.entries) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.mappingSlot.isEmpty { + try visitor.visitSingularBytesField(value: self.mappingSlot, fieldNumber: 1) + } + if !self.entries.isEmpty { + try visitor.visitRepeatedMessageField(value: self.entries, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries, rhs: Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries) -> Bool { + if lhs.mappingSlot != rhs.mappingSlot {return false} + if lhs.entries != rhs.entries {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LambdaMappingEntry" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "key"), + 2: .same(proto: "preimage"), + 3: .same(proto: "value"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Data? + try decoder.decodeSingularBytesField(value: &v) + if let v = v { + if self.entryKey != nil {try decoder.handleConflictingOneOf()} + self.entryKey = .key(v) + } + }() + case 2: try { + var v: Data? + try decoder.decodeSingularBytesField(value: &v) + if let v = v { + if self.entryKey != nil {try decoder.handleConflictingOneOf()} + self.entryKey = .preimage(v) + } + }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.entryKey { + case .key?: try { + guard case .key(let v)? = self.entryKey else { preconditionFailure() } + try visitor.visitSingularBytesField(value: v, fieldNumber: 1) + }() + case .preimage?: try { + guard case .preimage(let v)? = self.entryKey else { preconditionFailure() } + try visitor.visitSingularBytesField(value: v, fieldNumber: 2) + }() + case nil: break + } + if !self.value.isEmpty { + try visitor.visitSingularBytesField(value: self.value, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry, rhs: Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry) -> Bool { + if lhs.entryKey != rhs.entryKey {return false} + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LambdaStorageSlot" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "key"), + 2: .same(proto: "value"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.key) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.key.isEmpty { + try visitor.visitSingularBytesField(value: self.key, fieldNumber: 1) + } + if !self.value.isEmpty { + try visitor.visitSingularBytesField(value: self.value, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot, rhs: Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot) -> Bool { + if lhs.key != rhs.key {return false} + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/HieroProtobufs/Generated/services/lambda_sstore.pb.swift b/Sources/HieroProtobufs/Generated/services/lambda_sstore.pb.swift new file mode 100644 index 00000000..6337bb44 --- /dev/null +++ b/Sources/HieroProtobufs/Generated/services/lambda_sstore.pb.swift @@ -0,0 +1,96 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: services/lambda_sstore.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +///* +/// Adds or removes key/value pairs in the storage of a lambda. The lambda's owning key must sign the transaction. +public struct Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + ///* + /// The id of the lambda EVM hook whose storage is being updated. + public var hookID: Proto_HookId { + get {return _hookID ?? Proto_HookId()} + set {_hookID = newValue} + } + /// Returns true if `hookID` has been explicitly set. + public var hasHookID: Bool {return self._hookID != nil} + /// Clears the value of `hookID`. Subsequent reads from it will return its default value. + public mutating func clearHookID() {self._hookID = nil} + + ///* + /// The updates to the storage of the lambda. + public var storageUpdates: [Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _hookID: Proto_HookId? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "com.hedera.hapi.node.hooks" + +extension Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LambdaSStoreTransactionBody" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "hook_id"), + 2: .standard(proto: "storage_updates"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._hookID) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.storageUpdates) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._hookID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.storageUpdates.isEmpty { + try visitor.visitRepeatedMessageField(value: self.storageUpdates, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody, rhs: Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody) -> Bool { + if lhs._hookID != rhs._hookID {return false} + if lhs.storageUpdates != rhs.storageUpdates {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/HieroProtobufs/Generated/services/node_update.pb.swift b/Sources/HieroProtobufs/Generated/services/node_update.pb.swift index 58e12046..d39d992d 100644 --- a/Sources/HieroProtobufs/Generated/services/node_update.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/node_update.pb.swift @@ -208,7 +208,9 @@ public struct Com_Hedera_Hapi_Node_Addressbook_NodeUpdateTransactionBody: Sendab /// This endpoint MUST use a valid port and SHALL be reachable over TLS.
/// This field MAY be omitted if the node does not support gRPC-Web access.
/// This field MUST be updated if the gRPC-Web endpoint changes.
- /// This field SHALL enable frontend clients to avoid hard-coded proxy endpoints. + /// This field SHALL enable frontend clients to avoid hard-coded proxy endpoints.
+ /// This field MAY be set to `ServiceEndpoint.DEFAULT` to remove a previously-valid + /// web proxy. public var grpcProxyEndpoint: Proto_ServiceEndpoint { get {return _grpcProxyEndpoint ?? Proto_ServiceEndpoint()} set {_grpcProxyEndpoint = newValue} diff --git a/Sources/HieroProtobufs/Generated/services/response_code.pb.swift b/Sources/HieroProtobufs/Generated/services/response_code.pb.swift index 23a7c499..aa047658 100644 --- a/Sources/HieroProtobufs/Generated/services/response_code.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/response_code.pb.swift @@ -1546,6 +1546,113 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { /// The GRPC proxy endpoint is set in the NodeCreate or NodeUpdate transaction, /// which the network does not support. case grpcWebProxyNotSupported // = 399 + + ///* + /// An NFT transfers list referenced a token type other than NON_FUNGIBLE_UNIQUE. + case nftTransfersOnlyAllowedForNonFungibleUnique // = 400 + + ///* + /// A HAPI client cannot set the SignedTransaction#use_serialized_tx_message_hash_algorithm field. + case invalidSerializedTxMessageHashAlgorithm // = 401 + + ///* + /// An EVM hook execution was throttled due to high network gas utilization. + case evmHookGasThrottled // = 500 + + ///* + /// A user tried to create a hook with an id already in use. + case hookIDInUse // = 501 + + ///* + /// A transaction tried to execute a hook that did not match the specified + /// type or was malformed in some other way. + case badHookRequest // = 502 + + ///* + /// A CryptoTransfer relying on a ACCOUNT_ALLOWANCE hook was rejected. + case rejectedByAccountAllowanceHook // = 503 + + ///* + /// A hook id was not found. + case hookNotFound // = 504 + + ///* + /// A lambda mapping slot, storage key, or storage value exceeded 32 bytes. + case lambdaStorageUpdateBytesTooLong // = 505 + + ///* + /// A lambda mapping slot, storage key, or storage value failed to use the + /// minimal representation (i.e., no leading zeros). + case lambdaStorageUpdateBytesMustUseMinimalRepresentation // = 506 + + ///* + /// A hook id was invalid. + case invalidHookID // = 507 + + ///* + /// A lambda storage update had no contents. + case emptyLambdaStorageUpdate // = 508 + + ///* + /// A user repeated the same hook id in a creation details list. + case hookIDRepeatedInCreationDetails // = 509 + + ///* + /// Hooks are not not enabled on the target Hiero network. + case hooksNotEnabled // = 510 + + ///* + /// The target hook is not a lambda. + case hookIsNotALambda // = 511 + + ///* + /// A hook was deleted. + case hookDeleted // = 512 + + ///* + /// The LambdaSStore tried to update too many storage slots in a single transaction. + case tooManyLambdaStorageUpdates // = 513 + + ///* + /// A lambda mapping slot, storage key, or storage value failed to use the + /// minimal representation (i.e., no leading zeros). + case hookCreationBytesMustUseMinimalRepresentation // = 514 + + ///* + /// A lambda mapping slot, storage key, or storage value exceeded 32 bytes. + case hookCreationBytesTooLong // = 515 + + ///* + /// A hook creation spec was not found. + case invalidHookCreationSpec // = 516 + + ///* + /// A hook extension point was empty. + case hookExtensionEmpty // = 517 + + ///* + /// A hook admin key was invalid. + case invalidHookAdminKey // = 518 + + ///* + /// The hook deletion requires the hook to have zero storage slots. + case hookDeletionRequiresZeroStorageSlots // = 519 + + ///* + /// Cannot set both a hook call and an approval on the same AccountAmount or NftTransfer message. + case cannotSetHooksAndApproval // = 520 + + ///* + /// The attempted operation is invalid until all the target entity's hooks have been deleted. + case transactionRequiresZeroHooks // = 521 + + ///* + /// The HookCall set in the transaction is invalid + case invalidHookCall // = 522 + + ///* + /// Hooks are not supported to be used in TokenAirdrop transactions + case hooksAreNotSupportedInAirdrops // = 523 case UNRECOGNIZED(Int) public init() { @@ -1912,6 +2019,32 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { case 397: self = .throttleGroupLcmOverflow case 398: self = .airdropContainsMultipleSendersForAToken case 399: self = .grpcWebProxyNotSupported + case 400: self = .nftTransfersOnlyAllowedForNonFungibleUnique + case 401: self = .invalidSerializedTxMessageHashAlgorithm + case 500: self = .evmHookGasThrottled + case 501: self = .hookIDInUse + case 502: self = .badHookRequest + case 503: self = .rejectedByAccountAllowanceHook + case 504: self = .hookNotFound + case 505: self = .lambdaStorageUpdateBytesTooLong + case 506: self = .lambdaStorageUpdateBytesMustUseMinimalRepresentation + case 507: self = .invalidHookID + case 508: self = .emptyLambdaStorageUpdate + case 509: self = .hookIDRepeatedInCreationDetails + case 510: self = .hooksNotEnabled + case 511: self = .hookIsNotALambda + case 512: self = .hookDeleted + case 513: self = .tooManyLambdaStorageUpdates + case 514: self = .hookCreationBytesMustUseMinimalRepresentation + case 515: self = .hookCreationBytesTooLong + case 516: self = .invalidHookCreationSpec + case 517: self = .hookExtensionEmpty + case 518: self = .invalidHookAdminKey + case 519: self = .hookDeletionRequiresZeroStorageSlots + case 520: self = .cannotSetHooksAndApproval + case 521: self = .transactionRequiresZeroHooks + case 522: self = .invalidHookCall + case 523: self = .hooksAreNotSupportedInAirdrops default: self = .UNRECOGNIZED(rawValue) } } @@ -2276,6 +2409,32 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { case .throttleGroupLcmOverflow: return 397 case .airdropContainsMultipleSendersForAToken: return 398 case .grpcWebProxyNotSupported: return 399 + case .nftTransfersOnlyAllowedForNonFungibleUnique: return 400 + case .invalidSerializedTxMessageHashAlgorithm: return 401 + case .evmHookGasThrottled: return 500 + case .hookIDInUse: return 501 + case .badHookRequest: return 502 + case .rejectedByAccountAllowanceHook: return 503 + case .hookNotFound: return 504 + case .lambdaStorageUpdateBytesTooLong: return 505 + case .lambdaStorageUpdateBytesMustUseMinimalRepresentation: return 506 + case .invalidHookID: return 507 + case .emptyLambdaStorageUpdate: return 508 + case .hookIDRepeatedInCreationDetails: return 509 + case .hooksNotEnabled: return 510 + case .hookIsNotALambda: return 511 + case .hookDeleted: return 512 + case .tooManyLambdaStorageUpdates: return 513 + case .hookCreationBytesMustUseMinimalRepresentation: return 514 + case .hookCreationBytesTooLong: return 515 + case .invalidHookCreationSpec: return 516 + case .hookExtensionEmpty: return 517 + case .invalidHookAdminKey: return 518 + case .hookDeletionRequiresZeroStorageSlots: return 519 + case .cannotSetHooksAndApproval: return 520 + case .transactionRequiresZeroHooks: return 521 + case .invalidHookCall: return 522 + case .hooksAreNotSupportedInAirdrops: return 523 case .UNRECOGNIZED(let i): return i } } @@ -2640,6 +2799,32 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { .throttleGroupLcmOverflow, .airdropContainsMultipleSendersForAToken, .grpcWebProxyNotSupported, + .nftTransfersOnlyAllowedForNonFungibleUnique, + .invalidSerializedTxMessageHashAlgorithm, + .evmHookGasThrottled, + .hookIDInUse, + .badHookRequest, + .rejectedByAccountAllowanceHook, + .hookNotFound, + .lambdaStorageUpdateBytesTooLong, + .lambdaStorageUpdateBytesMustUseMinimalRepresentation, + .invalidHookID, + .emptyLambdaStorageUpdate, + .hookIDRepeatedInCreationDetails, + .hooksNotEnabled, + .hookIsNotALambda, + .hookDeleted, + .tooManyLambdaStorageUpdates, + .hookCreationBytesMustUseMinimalRepresentation, + .hookCreationBytesTooLong, + .invalidHookCreationSpec, + .hookExtensionEmpty, + .invalidHookAdminKey, + .hookDeletionRequiresZeroStorageSlots, + .cannotSetHooksAndApproval, + .transactionRequiresZeroHooks, + .invalidHookCall, + .hooksAreNotSupportedInAirdrops, ] } @@ -3006,5 +3191,31 @@ extension Proto_ResponseCodeEnum: SwiftProtobuf._ProtoNameProviding { 397: .same(proto: "THROTTLE_GROUP_LCM_OVERFLOW"), 398: .same(proto: "AIRDROP_CONTAINS_MULTIPLE_SENDERS_FOR_A_TOKEN"), 399: .same(proto: "GRPC_WEB_PROXY_NOT_SUPPORTED"), + 400: .same(proto: "NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE"), + 401: .same(proto: "INVALID_SERIALIZED_TX_MESSAGE_HASH_ALGORITHM"), + 500: .same(proto: "EVM_HOOK_GAS_THROTTLED"), + 501: .same(proto: "HOOK_ID_IN_USE"), + 502: .same(proto: "BAD_HOOK_REQUEST"), + 503: .same(proto: "REJECTED_BY_ACCOUNT_ALLOWANCE_HOOK"), + 504: .same(proto: "HOOK_NOT_FOUND"), + 505: .same(proto: "LAMBDA_STORAGE_UPDATE_BYTES_TOO_LONG"), + 506: .same(proto: "LAMBDA_STORAGE_UPDATE_BYTES_MUST_USE_MINIMAL_REPRESENTATION"), + 507: .same(proto: "INVALID_HOOK_ID"), + 508: .same(proto: "EMPTY_LAMBDA_STORAGE_UPDATE"), + 509: .same(proto: "HOOK_ID_REPEATED_IN_CREATION_DETAILS"), + 510: .same(proto: "HOOKS_NOT_ENABLED"), + 511: .same(proto: "HOOK_IS_NOT_A_LAMBDA"), + 512: .same(proto: "HOOK_DELETED"), + 513: .same(proto: "TOO_MANY_LAMBDA_STORAGE_UPDATES"), + 514: .same(proto: "HOOK_CREATION_BYTES_MUST_USE_MINIMAL_REPRESENTATION"), + 515: .same(proto: "HOOK_CREATION_BYTES_TOO_LONG"), + 516: .same(proto: "INVALID_HOOK_CREATION_SPEC"), + 517: .same(proto: "HOOK_EXTENSION_EMPTY"), + 518: .same(proto: "INVALID_HOOK_ADMIN_KEY"), + 519: .same(proto: "HOOK_DELETION_REQUIRES_ZERO_STORAGE_SLOTS"), + 520: .same(proto: "CANNOT_SET_HOOKS_AND_APPROVAL"), + 521: .same(proto: "TRANSACTION_REQUIRES_ZERO_HOOKS"), + 522: .same(proto: "INVALID_HOOK_CALL"), + 523: .same(proto: "HOOKS_ARE_NOT_SUPPORTED_IN_AIRDROPS"), ] } diff --git a/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift b/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift index 3461ffcb..bc587e53 100644 --- a/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift @@ -602,6 +602,17 @@ public struct Proto_SchedulableTransactionBody: @unchecked Sendable { set {_uniqueStorage()._data = .tokenAirdrop(newValue)} } + ///* + /// A list of maximum custom fees that the users are willing to pay. + ///

+ /// This field is OPTIONAL.
+ /// If left empty, the users are accepting to pay any custom fee.
+ /// If used with a transaction type that does not support custom fee limits, the transaction will fail. + public var maxCustomFees: [Proto_CustomFeeLimit] { + get {return _storage._maxCustomFees} + set {_uniqueStorage()._maxCustomFees = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Data: Equatable, Sendable { @@ -878,12 +889,14 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf 46: .same(proto: "tokenCancelAirdrop"), 47: .same(proto: "tokenClaimAirdrop"), 48: .same(proto: "tokenAirdrop"), + 1001: .standard(proto: "max_custom_fees"), ] fileprivate class _StorageClass { var _transactionFee: UInt64 = 0 var _memo: String = String() var _data: Proto_SchedulableTransactionBody.OneOf_Data? + var _maxCustomFees: [Proto_CustomFeeLimit] = [] #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -901,6 +914,7 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf _transactionFee = source._transactionFee _memo = source._memo _data = source._data + _maxCustomFees = source._maxCustomFees } } @@ -1519,6 +1533,7 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf _storage._data = .tokenAirdrop(v) } }() + case 1001: try { try decoder.decodeRepeatedMessageField(value: &_storage._maxCustomFees) }() default: break } } @@ -1724,6 +1739,9 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf }() case nil: break } + if !_storage._maxCustomFees.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._maxCustomFees, fieldNumber: 1001) + } } try unknownFields.traverse(visitor: &visitor) } @@ -1736,6 +1754,7 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf if _storage._transactionFee != rhs_storage._transactionFee {return false} if _storage._memo != rhs_storage._memo {return false} if _storage._data != rhs_storage._data {return false} + if _storage._maxCustomFees != rhs_storage._maxCustomFees {return false} return true } if !storagesAreEqual {return false} diff --git a/Sources/HieroProtobufs/Generated/services/smart_contract_service.grpc.swift b/Sources/HieroProtobufs/Generated/services/smart_contract_service.grpc.swift index 087ae242..6c852b43 100644 --- a/Sources/HieroProtobufs/Generated/services/smart_contract_service.grpc.swift +++ b/Sources/HieroProtobufs/Generated/services/smart_contract_service.grpc.swift @@ -81,6 +81,11 @@ public protocol Proto_SmartContractServiceClientProtocol: GRPCClient { _ request: Proto_Transaction, callOptions: CallOptions? ) -> UnaryCall + + func lambdaSStore( + _ request: Proto_Transaction, + callOptions: CallOptions? + ) -> UnaryCall } extension Proto_SmartContractServiceClientProtocol { @@ -397,6 +402,25 @@ extension Proto_SmartContractServiceClientProtocol { interceptors: self.interceptors?.makecallEthereumInterceptors() ?? [] ) } + + ///* + /// Update zero or more slots of a lambda. + /// + /// - Parameters: + /// - request: Request to send to lambdaSStore. + /// - callOptions: Call options. + /// - Returns: A `UnaryCall` with futures for the metadata, status and response. + public func lambdaSStore( + _ request: Proto_Transaction, + callOptions: CallOptions? = nil + ) -> UnaryCall { + return self.makeUnaryCall( + path: Proto_SmartContractServiceClientMetadata.Methods.lambdaSStore.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makelambdaSStoreInterceptors() ?? [] + ) + } } @available(*, deprecated) @@ -525,6 +549,11 @@ public protocol Proto_SmartContractServiceAsyncClientProtocol: GRPCClient { _ request: Proto_Transaction, callOptions: CallOptions? ) -> GRPCAsyncUnaryCall + + func makeLambdaSstoreCall( + _ request: Proto_Transaction, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -680,6 +709,28 @@ extension Proto_SmartContractServiceAsyncClientProtocol { interceptors: self.interceptors?.makecallEthereumInterceptors() ?? [] ) } + + public func makeLambdaSstoreCall( + _ request: Proto_Transaction, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: Proto_SmartContractServiceClientMetadata.Methods.lambdaSStore.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makelambdaSStoreInterceptors() ?? [] + ) + } + + public func makeLambdaSStoreCall( + _ request: Proto_Transaction, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeLambdaSstoreCall( + request, + callOptions: callOptions + ) + } } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -827,6 +878,18 @@ extension Proto_SmartContractServiceAsyncClientProtocol { interceptors: self.interceptors?.makecallEthereumInterceptors() ?? [] ) } + + public func lambdaSStore( + _ request: Proto_Transaction, + callOptions: CallOptions? = nil + ) async throws -> Proto_TransactionResponse { + return try await self.performAsyncUnaryCall( + path: Proto_SmartContractServiceClientMetadata.Methods.lambdaSStore.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makelambdaSStoreInterceptors() ?? [] + ) + } } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -883,6 +946,9 @@ public protocol Proto_SmartContractServiceClientInterceptorFactoryProtocol: Send /// - Returns: Interceptors to use when invoking 'callEthereum'. func makecallEthereumInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'lambdaSStore'. + func makelambdaSStoreInterceptors() -> [ClientInterceptor] } public enum Proto_SmartContractServiceClientMetadata { @@ -902,6 +968,7 @@ public enum Proto_SmartContractServiceClientMetadata { Proto_SmartContractServiceClientMetadata.Methods.systemDelete, Proto_SmartContractServiceClientMetadata.Methods.systemUndelete, Proto_SmartContractServiceClientMetadata.Methods.callEthereum, + Proto_SmartContractServiceClientMetadata.Methods.lambdaSStore, ] ) @@ -977,6 +1044,12 @@ public enum Proto_SmartContractServiceClientMetadata { path: "/proto.SmartContractService/callEthereum", type: GRPCCallType.unary ) + + public static let lambdaSStore = GRPCMethodDescriptor( + name: "lambdaSStore", + path: "/proto.SmartContractService/lambdaSStore", + type: GRPCCallType.unary + ) } } @@ -1119,6 +1192,10 @@ public protocol Proto_SmartContractServiceProvider: CallHandlerProvider { /// data, but MAY be charged up to 80% of that value if the amount required /// is less than this "floor" amount. func callEthereum(request: Proto_Transaction, context: StatusOnlyCallContext) -> EventLoopFuture + + ///* + /// Update zero or more slots of a lambda. + func lambdaSStore(request: Proto_Transaction, context: StatusOnlyCallContext) -> EventLoopFuture } extension Proto_SmartContractServiceProvider { @@ -1241,6 +1318,15 @@ extension Proto_SmartContractServiceProvider { userFunction: self.callEthereum(request:context:) ) + case "lambdaSStore": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makelambdaSStoreInterceptors() ?? [], + userFunction: self.lambdaSStore(request:context:) + ) + default: return nil } @@ -1424,6 +1510,13 @@ public protocol Proto_SmartContractServiceAsyncProvider: CallHandlerProvider, Se request: Proto_Transaction, context: GRPCAsyncServerCallContext ) async throws -> Proto_TransactionResponse + + ///* + /// Update zero or more slots of a lambda. + func lambdaSStore( + request: Proto_Transaction, + context: GRPCAsyncServerCallContext + ) async throws -> Proto_TransactionResponse } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -1553,6 +1646,15 @@ extension Proto_SmartContractServiceAsyncProvider { wrapping: { try await self.callEthereum(request: $0, context: $1) } ) + case "lambdaSStore": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makelambdaSStoreInterceptors() ?? [], + wrapping: { try await self.lambdaSStore(request: $0, context: $1) } + ) + default: return nil } @@ -1608,6 +1710,10 @@ public protocol Proto_SmartContractServiceServerInterceptorFactoryProtocol: Send /// - Returns: Interceptors to use when handling 'callEthereum'. /// Defaults to calling `self.makeInterceptors()`. func makecallEthereumInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'lambdaSStore'. + /// Defaults to calling `self.makeInterceptors()`. + func makelambdaSStoreInterceptors() -> [ServerInterceptor] } public enum Proto_SmartContractServiceServerMetadata { @@ -1627,6 +1733,7 @@ public enum Proto_SmartContractServiceServerMetadata { Proto_SmartContractServiceServerMetadata.Methods.systemDelete, Proto_SmartContractServiceServerMetadata.Methods.systemUndelete, Proto_SmartContractServiceServerMetadata.Methods.callEthereum, + Proto_SmartContractServiceServerMetadata.Methods.lambdaSStore, ] ) @@ -1702,5 +1809,11 @@ public enum Proto_SmartContractServiceServerMetadata { path: "/proto.SmartContractService/callEthereum", type: GRPCCallType.unary ) + + public static let lambdaSStore = GRPCMethodDescriptor( + name: "lambdaSStore", + path: "/proto.SmartContractService/lambdaSStore", + type: GRPCCallType.unary + ) } } diff --git a/Sources/HieroProtobufs/Generated/services/transaction.pb.swift b/Sources/HieroProtobufs/Generated/services/transaction.pb.swift index b5843bf9..6b3c446d 100644 --- a/Sources/HieroProtobufs/Generated/services/transaction.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/transaction.pb.swift @@ -965,6 +965,26 @@ public struct Proto_TransactionBody: @unchecked Sendable { set {_uniqueStorage()._data = .atomicBatch(newValue)} } + ///* + /// A transaction body for updating the storage of a EVM lambda hook. + public var lambdaSstore: Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody { + get { + if case .lambdaSstore(let v)? = _storage._data {return v} + return Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody() + } + set {_uniqueStorage()._data = .lambdaSstore(newValue)} + } + + ///* + /// An internal-only transaction body for dispatching a hook CRUD operation. + public var hookDispatch: Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody { + get { + if case .hookDispatch(let v)? = _storage._data {return v} + return Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody() + } + set {_uniqueStorage()._data = .hookDispatch(newValue)} + } + ///* /// A list of maximum custom fees that the users are willing to pay. ///

@@ -1265,6 +1285,12 @@ public struct Proto_TransactionBody: @unchecked Sendable { ///* /// A transaction body for handling a set of transactions atomically. case atomicBatch(Proto_AtomicBatchTransactionBody) + ///* + /// A transaction body for updating the storage of a EVM lambda hook. + case lambdaSstore(Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody) + ///* + /// An internal-only transaction body for dispatching a hook CRUD operation. + case hookDispatch(Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody) } @@ -1425,6 +1451,8 @@ extension Proto_TransactionBody: SwiftProtobuf.Message, SwiftProtobuf._MessageIm 71: .standard(proto: "history_proof_vote"), 72: .standard(proto: "crs_publication"), 74: .standard(proto: "atomic_batch"), + 75: .standard(proto: "lambda_sstore"), + 76: .standard(proto: "hook_dispatch"), 1001: .standard(proto: "max_custom_fees"), ] @@ -2292,6 +2320,32 @@ extension Proto_TransactionBody: SwiftProtobuf.Message, SwiftProtobuf._MessageIm _storage._data = .atomicBatch(v) } }() + case 75: try { + var v: Com_Hedera_Hapi_Node_Hooks_LambdaSStoreTransactionBody? + var hadOneofValue = false + if let current = _storage._data { + hadOneofValue = true + if case .lambdaSstore(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._data = .lambdaSstore(v) + } + }() + case 76: try { + var v: Com_Hedera_Hapi_Node_Hooks_HookDispatchTransactionBody? + var hadOneofValue = false + if let current = _storage._data { + hadOneofValue = true + if case .hookDispatch(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._data = .hookDispatch(v) + } + }() case 1001: try { try decoder.decodeRepeatedMessageField(value: &_storage._maxCustomFees) }() default: break } @@ -2573,9 +2627,21 @@ extension Proto_TransactionBody: SwiftProtobuf.Message, SwiftProtobuf._MessageIm try { if let v = _storage._batchKey { try visitor.visitSingularMessageField(value: v, fieldNumber: 73) } }() - try { if case .atomicBatch(let v)? = _storage._data { + switch _storage._data { + case .atomicBatch?: try { + guard case .atomicBatch(let v)? = _storage._data else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 74) - } }() + }() + case .lambdaSstore?: try { + guard case .lambdaSstore(let v)? = _storage._data else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 75) + }() + case .hookDispatch?: try { + guard case .hookDispatch(let v)? = _storage._data else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 76) + }() + default: break + } if !_storage._maxCustomFees.isEmpty { try visitor.visitRepeatedMessageField(value: _storage._maxCustomFees, fieldNumber: 1001) } diff --git a/Sources/HieroProtobufs/Generated/services/transaction_contents.pb.swift b/Sources/HieroProtobufs/Generated/services/transaction_contents.pb.swift index 6150e82b..d21d7fe6 100644 --- a/Sources/HieroProtobufs/Generated/services/transaction_contents.pb.swift +++ b/Sources/HieroProtobufs/Generated/services/transaction_contents.pb.swift @@ -67,6 +67,15 @@ public struct Proto_SignedTransaction: @unchecked Sendable { /// Clears the value of `sigMap`. Subsequent reads from it will return its default value. public mutating func clearSigMap() {self._sigMap = nil} + ///* + /// If false then the hash of this transaction is the SHA-384 hash of the + /// serialization of this SignedTransaction message as it arrived on the wire. + ///

+ /// If true then the hash of this transaction is the SHA-384 hash of the + /// ascending field order serialization of the Transaction whose `bodyBytes` + /// and sigMap fields are deserialized from the contents of this message. + public var useSerializedTxMessageHashAlgorithm: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -83,6 +92,7 @@ extension Proto_SignedTransaction: SwiftProtobuf.Message, SwiftProtobuf._Message public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "bodyBytes"), 2: .same(proto: "sigMap"), + 3: .standard(proto: "use_serialized_tx_message_hash_algorithm"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -93,6 +103,7 @@ extension Proto_SignedTransaction: SwiftProtobuf.Message, SwiftProtobuf._Message switch fieldNumber { case 1: try { try decoder.decodeSingularBytesField(value: &self.bodyBytes) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._sigMap) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.useSerializedTxMessageHashAlgorithm) }() default: break } } @@ -109,12 +120,16 @@ extension Proto_SignedTransaction: SwiftProtobuf.Message, SwiftProtobuf._Message try { if let v = self._sigMap { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + if self.useSerializedTxMessageHashAlgorithm != false { + try visitor.visitSingularBoolField(value: self.useSerializedTxMessageHashAlgorithm, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Proto_SignedTransaction, rhs: Proto_SignedTransaction) -> Bool { if lhs.bodyBytes != rhs.bodyBytes {return false} if lhs._sigMap != rhs._sigMap {return false} + if lhs.useSerializedTxMessageHashAlgorithm != rhs.useSerializedTxMessageHashAlgorithm {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/HieroProtobufs/Protos/services/basic_types.proto b/Sources/HieroProtobufs/Protos/services/basic_types.proto index df1ea135..c71a2f65 100644 --- a/Sources/HieroProtobufs/Protos/services/basic_types.proto +++ b/Sources/HieroProtobufs/Protos/services/basic_types.proto @@ -435,6 +435,87 @@ message TransactionID { int32 nonce = 4; } +/** + * Once a hook is created, its full id. + *

+ * A composite of its creating entity's id and an arbitrary 64-bit hook id + * (which need not be sequential). + */ +message HookId { + /** + * The hook's creating entity id. + */ + HookEntityId entity_id = 1; + + /** + * An arbitrary 64-bit identifier. + */ + int64 hook_id = 2; +} + +/** + * The id of an entity using a hook. + */ +message HookEntityId { + oneof entity_id { + /** + * An account using a hook. + */ + AccountID account_id = 1; + /** + * A contract using a hook. + */ + ContractID contract_id = 2; + } +} + +/** + * Specifies a call to a hook from within a transaction. + *

+ * Often the hook's entity is implied by the nature of the call site. For example, when using an account allowance hook + * inside a crypto transfer, the hook's entity is necessarily the account whose authorization is required. + *

+ * For future extension points where the hook owner is not forced by the context, we include the option to fully + * specify the hook id for the call. + */ +message HookCall { + oneof id { + /** + * The full id of the hook to call, when the owning entity is not forced by the call site. + */ + HookId full_hook_id = 1; + /** + * The numeric id of the hook to call, when the owning entity is forced by the call site. + */ + int64 hook_id = 2; + } + + /** + * Specifies details of the call. + */ + oneof call_spec { + /** + * Specification of how to call an EVM hook. + */ + EvmHookCall evm_hook_call = 3; + } +} + +/** + * Specifies details of a call to an EVM hook. + */ +message EvmHookCall { + /** + * Call data to pass to the hook via the IHieroHook.HookContext#data field. + */ + bytes data = 1; + + /** + * The gas limit to use. + */ + uint64 gas_limit = 2; +} + /** * An account, and the amount that it sends or receives during a token transfer. * @@ -467,6 +548,28 @@ message AccountAmount { * The default value SHALL be false (unset). */ bool is_approval = 3; + + /** + * If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` on scoped + * account; the hook's invoked methods must not revert and must return + * true for the containing CryptoTransfer to succeed. + *

+ * Cannot be set if `is_approval` is true. + */ + oneof hook_call { + /** + * A single call made before attempting the CryptoTransfer, to a + * method with logical signature allow(HookContext, ProposedTransfers) + */ + HookCall pre_tx_allowance_hook = 4; + /** + * Two calls, the first call before attempting the CryptoTransfer, to a + * method with logical signature allowPre(HookContext, ProposedTransfers); + * and the second call after attempting the CryptoTransfer, to a method + * with logical signature allowPost(HookContext, ProposedTransfers). + */ + HookCall pre_post_tx_allowance_hook = 5; + } } /** @@ -525,6 +628,51 @@ message NftTransfer { * The default value SHALL be false (unset). */ bool is_approval = 4; + + /** + * If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` installed on + * senderAccountID that must succeed for the transaction to occur. + *

+ * Cannot be set if `is_approval` is true. + */ + oneof sender_allowance_hook_call { + /** + * A single call made before attempting the CryptoTransfer, to a + * method with logical signature allow(HookContext, ProposedTransfers) + */ + HookCall pre_tx_sender_allowance_hook = 5; + /** + * Two calls, the first call before attempting the CryptoTransfer, to a + * method with logical signature allowPre(HookContext, ProposedTransfers); + * and the second call after attempting the CryptoTransfer, to a method + * with logical signature allowPost(HookContext, ProposedTransfers). + */ + HookCall pre_post_tx_sender_allowance_hook = 6; + } + + /** + * If set, a call to a hook of type `ACCOUNT_ALLOWANCE_HOOK` installed on + * receiverAccountID that must succeed for the transaction to occur. + *

+ * May be set even if `is_approval` is true. In this case, the approval applies + * to the sender authorization, and the hook applies to the receiver authorization + * (if needed, e.g. because of a fallback royalty fee or receiver signature + * requirement). + */ + oneof receiver_allowance_hook_call { + /** + * A single call made before attempting the CryptoTransfer, to a + * method with logical signature allow(HookContext, ProposedTransfers) + */ + HookCall pre_tx_receiver_allowance_hook = 7; + /** + * Two calls, the first call before attempting the CryptoTransfer, to a + * method with logical signature allowPre(HookContext, ProposedTransfers); + * and the second call after attempting the CryptoTransfer, to a method + * with logical signature allowPost(HookContext, ProposedTransfers). + */ + HookCall pre_post_tx_receiver_allowance_hook = 8; + } } /** @@ -1689,6 +1837,16 @@ enum HederaFunctionality { * Submit a batch of transactions to run atomically */ AtomicBatch = 108; + + /** + * Update one or more storage slots in an lambda EVM hook. + */ + LambdaSStore = 109; + + /** + * (Internal-only) Dispatch a hook action. + */ + HookDispatch = 110; } /** diff --git a/Sources/HieroProtobufs/Protos/services/contract_create.proto b/Sources/HieroProtobufs/Protos/services/contract_create.proto index 14f05d27..abb03f93 100644 --- a/Sources/HieroProtobufs/Protos/services/contract_create.proto +++ b/Sources/HieroProtobufs/Protos/services/contract_create.proto @@ -45,6 +45,7 @@ option java_multiple_files = true; import "services/basic_types.proto"; import "services/duration.proto"; +import "services/hook_types.proto"; /** * Create a new smart contract. @@ -272,4 +273,9 @@ message ContractCreateTransactionBody { * without reward. */ bool decline_reward = 19; + + /** + * Details of hooks to add immediately after creating this contract. + */ + repeated com.hedera.hapi.node.hooks.HookCreationDetails hook_creation_details = 20; } diff --git a/Sources/HieroProtobufs/Protos/services/contract_types.proto b/Sources/HieroProtobufs/Protos/services/contract_types.proto index 7202ea7b..fcc48211 100644 --- a/Sources/HieroProtobufs/Protos/services/contract_types.proto +++ b/Sources/HieroProtobufs/Protos/services/contract_types.proto @@ -23,6 +23,9 @@ import "google/protobuf/wrappers.proto"; /** * Context of an internal call in an EVM transaction that is not otherwise externalized.
+ * This message does not say anything about whether an EVM transaction is itself a logical + * transaction in a Hiero transactional unit. It simply provides context on an internal + * message call within an EVM transaction. */ message InternalCallContext { /** @@ -42,7 +45,7 @@ message InternalCallContext { } /** - * Results of executing EVM transaction.
+ * Results of executing a EVM transaction.
*/ message EvmTransactionResult { /** @@ -73,7 +76,8 @@ message EvmTransactionResult { uint64 gas_used = 5; /** - * If not already externalized, the context of the internal call producing this result. + * If not already externalized in a transaction body, the context of the + * internal call producing this result. */ InternalCallContext internal_call_context = 6; } diff --git a/Sources/HieroProtobufs/Protos/services/contract_update.proto b/Sources/HieroProtobufs/Protos/services/contract_update.proto index deb4cd1e..3d699023 100644 --- a/Sources/HieroProtobufs/Protos/services/contract_update.proto +++ b/Sources/HieroProtobufs/Protos/services/contract_update.proto @@ -22,6 +22,7 @@ option java_multiple_files = true; import "services/basic_types.proto"; import "services/duration.proto"; +import "services/hook_types.proto"; import "services/timestamp.proto"; import "google/protobuf/wrappers.proto"; @@ -178,4 +179,14 @@ message ContractUpdateTransactionBody { * without reward. */ google.protobuf.BoolValue decline_reward = 15; + + /** + * The ids the hooks to delete from the contract. + */ + repeated int64 hook_ids_to_delete = 16; + + /** + * The hooks to create for the contract. + */ + repeated com.hedera.hapi.node.hooks.HookCreationDetails hook_creation_details = 17; } diff --git a/Sources/HieroProtobufs/Protos/services/crypto_create.proto b/Sources/HieroProtobufs/Protos/services/crypto_create.proto index af46c47b..108b8b0d 100644 --- a/Sources/HieroProtobufs/Protos/services/crypto_create.proto +++ b/Sources/HieroProtobufs/Protos/services/crypto_create.proto @@ -20,6 +20,7 @@ option java_multiple_files = true; import "services/basic_types.proto"; import "services/duration.proto"; +import "services/hook_types.proto"; /* * Create a new account. @@ -198,4 +199,9 @@ message CryptoCreateTransactionBody { * Once set, an account alias is immutable and MUST NOT be changed. */ bytes alias = 18; + + /** + * Details of hooks to add immediately after creating this account. + */ + repeated com.hedera.hapi.node.hooks.HookCreationDetails hook_creation_details = 19; } diff --git a/Sources/HieroProtobufs/Protos/services/crypto_update.proto b/Sources/HieroProtobufs/Protos/services/crypto_update.proto index f551a42d..539b7126 100644 --- a/Sources/HieroProtobufs/Protos/services/crypto_update.proto +++ b/Sources/HieroProtobufs/Protos/services/crypto_update.proto @@ -20,6 +20,7 @@ option java_multiple_files = true; import "services/basic_types.proto"; import "services/duration.proto"; +import "services/hook_types.proto"; import "services/timestamp.proto"; import "google/protobuf/wrappers.proto"; @@ -216,4 +217,13 @@ message CryptoUpdateTransactionBody { */ google.protobuf.BoolValue decline_reward = 18; + /** + * The ids the hooks to delete from the account. + */ + repeated int64 hook_ids_to_delete = 19; + + /** + * The hooks to create for the account. + */ + repeated com.hedera.hapi.node.hooks.HookCreationDetails hook_creation_details = 20; } diff --git a/Sources/HieroProtobufs/Protos/services/hook_dispatch.proto b/Sources/HieroProtobufs/Protos/services/hook_dispatch.proto new file mode 100644 index 00000000..bfe8cc2f --- /dev/null +++ b/Sources/HieroProtobufs/Protos/services/hook_dispatch.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package com.hedera.hapi.node.hooks; + +// SPDX-License-Identifier: Apache-2.0 +option java_package = "com.hedera.hapi.node.hooks.legacy"; +// <<>> This comment is special code for setting PBJ Compiler java package +option java_multiple_files = true; + +import "services/basic_types.proto"; +import "services/hook_types.proto"; + +/** + * Dispatches a hook action to an appropriate service. + */ +message HookDispatchTransactionBody { + oneof action { + /** + * The id of the hook to delete. + */ + proto.HookId hook_id_to_delete = 1; + + /** + * Creation details for a new hook. + */ + HookCreation creation = 2; + + /** + * Execution details of an existing hook. + */ + HookExecution execution = 3; + } +} + +/** + * Details the execution of a hook. + */ +message HookExecution { + /** + * The id of the hook's entity. + */ + proto.HookEntityId hook_entity_id = 1; + + /** + * The details of the call, including which hook id to call. + */ + proto.HookCall call = 2; +} + diff --git a/Sources/HieroProtobufs/Protos/services/hook_types.proto b/Sources/HieroProtobufs/Protos/services/hook_types.proto new file mode 100644 index 00000000..c1fcccec --- /dev/null +++ b/Sources/HieroProtobufs/Protos/services/hook_types.proto @@ -0,0 +1,200 @@ +syntax = "proto3"; + +package com.hedera.hapi.node.hooks; + +import "services/basic_types.proto"; +import "google/protobuf/wrappers.proto"; + +// SPDX-License-Identifier: Apache-2.0 +option java_package = "com.hedera.hapi.node.hooks.legacy"; +// <<>> This comment is special code for setting PBJ Compiler java package +option java_multiple_files = true; + +/*** + * The Hiero extension points that accept a hook. + */ +enum HookExtensionPoint { + /** + * Used to customize an account's allowances during a CryptoTransfer transaction. + */ + ACCOUNT_ALLOWANCE_HOOK = 0; +} + +/** + * Specifies the creation of a new hook at the given id for the given entity. + */ +message HookCreation { + /** + * The id of the hook's entity. + */ + proto.HookEntityId entity_id = 1; + + /** + * The creation details. + */ + HookCreationDetails details = 2; + + /** + * If set, the id of the hook following this one in the owner's + * doubly-linked list of hooks. + */ + google.protobuf.Int64Value next_hook_id = 9; +} + +/*** + * The details of a hook's creation. + */ +message HookCreationDetails { + /** + * The extension point for the hook. + */ + HookExtensionPoint extension_point = 1; + + /** + * The id to create the hook at. + */ + int64 hook_id = 2; + + /** + * The hook implementation. + */ + oneof hook { + /** + * A hook programmed in EVM bytecode that may access state or interact with + * external contracts. + */ + LambdaEvmHook lambda_evm_hook = 3; + } + + /** + * If set, a key that that can be used to remove or replace the hook; or (if + * applicable, as with a lambda EVM hook) perform transactions that customize + * the hook. + */ + proto.Key admin_key = 4; +} + +/** + * Definition of a lambda EVM hook. + */ +message LambdaEvmHook { + /** + * The specification for the hook. + */ + EvmHookSpec spec = 1; + + /** + * Initial storage updates for the lambda, if any. + */ + repeated LambdaStorageUpdate storage_updates = 2; +} + +/** + * Shared specifications for an EVM hook. May be used for any extension point. + */ +message EvmHookSpec { + /** + * The source of the EVM bytecode for the hook. + */ + oneof bytecode_source { + /** + * The id of a contract that implements the extension point API with EVM bytecode. + */ + proto.ContractID contract_id = 1; + } +} + +/** + * 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. + */ +message LambdaStorageUpdate { + oneof update { + /** + * An explicit storage slot update. + */ + LambdaStorageSlot storage_slot = 1; + /** + * Implicit storage slot updates specified as Solidity mapping entries. + */ + LambdaMappingEntries mapping_entries = 2; + } +} + +/** + * Specifies storage slot updates via indirection into a Solidity mapping. + *

+ * Concretely, if the Solidity mapping is itself at slot `mapping_slot`, then + * the * storage slot for key `key` in the mapping is defined by the relationship + * `key_storage_slot = keccak256(abi.encodePacked(mapping_slot, key))`. + *

+ * This message lets a metaprotocol be specified in terms of changes to a + * Solidity mapping's entries. If only raw slots could be updated, then a block + * stream consumer following the metaprotocol would have to invert the Keccak256 + * hash to determine which mapping entry was being updated, which is not possible. + */ +message LambdaMappingEntries { + /** + * The slot that corresponds to the Solidity mapping itself. Must use a + * minimal byte representation (no leading zeros). + */ + bytes mapping_slot = 1; + + /** + * The entries in the mapping at the given slot. + */ + repeated LambdaMappingEntry entries = 2; +} + +/** + * An entry in a Solidity mapping. Very helpful for protocols that apply + * `LambdaSStore` to manage the entries of a hook contract's mapping instead + * its raw storage slots. + *

+ * This is especially attractive when the mapping value itself fits in a single + * word; for more complicated value storage layouts it becomes necessary to + * combine the mapping update with additional `LambdaStorageSlot` updates that + * specify the complete storage slots of the value type. + */ +message LambdaMappingEntry { + oneof entry_key { + /** + * The explicit bytes of the mapping entry. Must use a minimal byte representation; + * may not exceed 32 bytes in length. + */ + bytes key = 1; + /** + * The bytes that are the preimage of the Keccak256 hash that forms the mapping key. + * May be longer or shorter than 32 bytes and may have leading zeros, since Solidity + * supports variable-length keys in mappings. + */ + bytes preimage = 2; + } + + /** + * If the mapping entry is present and non-zero, its value. May not be longer than + * 32 bytes in length; must use a minimal byte representation (no leading zeros). + * Leaving this field empty in an update removes the entry from the mapping. + */ + bytes value = 3; +} + +/** + * A slot in the storage of a lambda EVM hook. + */ +message LambdaStorageSlot { + /** + * The key of the slot. Must use a minimal byte representation (no + * leading zeros); may not exceed 32 bytes in length. + */ + bytes key = 1; + + /** + * If the slot is present and non-zero, its value. Must use a minimal + * byte representation (no leading zeros); may not exceed 32 bytes in + * length Leaving this field empty in an update removes the slot from + * storage. + */ + bytes value = 2; +} diff --git a/Sources/HieroProtobufs/Protos/services/lambda_sstore.proto b/Sources/HieroProtobufs/Protos/services/lambda_sstore.proto new file mode 100644 index 00000000..f67676cb --- /dev/null +++ b/Sources/HieroProtobufs/Protos/services/lambda_sstore.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package com.hedera.hapi.node.hooks; + +// SPDX-License-Identifier: Apache-2.0 +option java_package = "com.hedera.hapi.node.hooks.legacy"; +// <<>> This comment is special code for setting PBJ Compiler java package +option java_multiple_files = true; + +import "services/basic_types.proto"; +import "services/hook_types.proto"; + +/** + * Adds or removes key/value pairs in the storage of a lambda. The lambda's owning key must sign the transaction. + */ +message LambdaSStoreTransactionBody { + /** + * The id of the lambda EVM hook whose storage is being updated. + */ + proto.HookId hook_id = 1; + + /** + * The updates to the storage of the lambda. + */ + repeated LambdaStorageUpdate storage_updates = 2; +} diff --git a/Sources/HieroProtobufs/Protos/services/node_update.proto b/Sources/HieroProtobufs/Protos/services/node_update.proto index d2a80350..7a0ceb28 100644 --- a/Sources/HieroProtobufs/Protos/services/node_update.proto +++ b/Sources/HieroProtobufs/Protos/services/node_update.proto @@ -162,7 +162,9 @@ message NodeUpdateTransactionBody { * This endpoint MUST use a valid port and SHALL be reachable over TLS.
* This field MAY be omitted if the node does not support gRPC-Web access.
* This field MUST be updated if the gRPC-Web endpoint changes.
- * This field SHALL enable frontend clients to avoid hard-coded proxy endpoints. + * This field SHALL enable frontend clients to avoid hard-coded proxy endpoints.
+ * This field MAY be set to `ServiceEndpoint.DEFAULT` to remove a previously-valid + * web proxy. */ proto.ServiceEndpoint grpc_proxy_endpoint = 10; } diff --git a/Sources/HieroProtobufs/Protos/services/response_code.proto b/Sources/HieroProtobufs/Protos/services/response_code.proto index 6607588d..2d3b8649 100644 --- a/Sources/HieroProtobufs/Protos/services/response_code.proto +++ b/Sources/HieroProtobufs/Protos/services/response_code.proto @@ -1760,4 +1760,136 @@ enum ResponseCodeEnum { * which the network does not support. */ GRPC_WEB_PROXY_NOT_SUPPORTED = 399; + + /** + * An NFT transfers list referenced a token type other than NON_FUNGIBLE_UNIQUE. + */ + NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE = 400; + + /** + * A HAPI client cannot set the SignedTransaction#use_serialized_tx_message_hash_algorithm field. + */ + INVALID_SERIALIZED_TX_MESSAGE_HASH_ALGORITHM = 401; + + /** + * An EVM hook execution was throttled due to high network gas utilization. + */ + EVM_HOOK_GAS_THROTTLED = 500; + + /** + * A user tried to create a hook with an id already in use. + */ + HOOK_ID_IN_USE = 501; + + /** + * A transaction tried to execute a hook that did not match the specified + * type or was malformed in some other way. + */ + BAD_HOOK_REQUEST = 502; + + /** + * A CryptoTransfer relying on a ACCOUNT_ALLOWANCE hook was rejected. + */ + REJECTED_BY_ACCOUNT_ALLOWANCE_HOOK = 503; + + /** + * A hook id was not found. + */ + HOOK_NOT_FOUND = 504; + + /** + * A lambda mapping slot, storage key, or storage value exceeded 32 bytes. + */ + LAMBDA_STORAGE_UPDATE_BYTES_TOO_LONG = 505; + + /** + * A lambda mapping slot, storage key, or storage value failed to use the + * minimal representation (i.e., no leading zeros). + */ + LAMBDA_STORAGE_UPDATE_BYTES_MUST_USE_MINIMAL_REPRESENTATION = 506; + + /** + * A hook id was invalid. + */ + INVALID_HOOK_ID = 507; + + /** + * A lambda storage update had no contents. + */ + EMPTY_LAMBDA_STORAGE_UPDATE = 508; + + /** + * A user repeated the same hook id in a creation details list. + */ + HOOK_ID_REPEATED_IN_CREATION_DETAILS = 509; + + /** + * Hooks are not not enabled on the target Hiero network. + */ + HOOKS_NOT_ENABLED = 510; + + /** + * The target hook is not a lambda. + */ + HOOK_IS_NOT_A_LAMBDA = 511; + + /** + * A hook was deleted. + */ + HOOK_DELETED = 512; + + /** + * The LambdaSStore tried to update too many storage slots in a single transaction. + */ + TOO_MANY_LAMBDA_STORAGE_UPDATES = 513; + + /** + * A lambda mapping slot, storage key, or storage value failed to use the + * minimal representation (i.e., no leading zeros). + */ + HOOK_CREATION_BYTES_MUST_USE_MINIMAL_REPRESENTATION = 514; + + /** + * A lambda mapping slot, storage key, or storage value exceeded 32 bytes. + */ + HOOK_CREATION_BYTES_TOO_LONG = 515; + + /** + * A hook creation spec was not found. + */ + INVALID_HOOK_CREATION_SPEC = 516; + + /** + * A hook extension point was empty. + */ + HOOK_EXTENSION_EMPTY = 517; + + /** + * A hook admin key was invalid. + */ + INVALID_HOOK_ADMIN_KEY = 518; + + /** + * The hook deletion requires the hook to have zero storage slots. + */ + HOOK_DELETION_REQUIRES_ZERO_STORAGE_SLOTS = 519; + + /** + * Cannot set both a hook call and an approval on the same AccountAmount or NftTransfer message. + */ + CANNOT_SET_HOOKS_AND_APPROVAL = 520; + + /** + * The attempted operation is invalid until all the target entity's hooks have been deleted. + */ + TRANSACTION_REQUIRES_ZERO_HOOKS = 521; + + /** + * The HookCall set in the transaction is invalid + */ + INVALID_HOOK_CALL = 522; + /** + * Hooks are not supported to be used in TokenAirdrop transactions + */ + HOOKS_ARE_NOT_SUPPORTED_IN_AIRDROPS = 523; } diff --git a/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto b/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto index 7928e9a4..e9f6851e 100644 --- a/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto +++ b/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto @@ -70,6 +70,7 @@ import "services/token_airdrop.proto"; import "services/schedule_delete.proto"; import "services/util_prng.proto"; +import "services/custom_fees.proto"; import "services/node_create.proto"; import "services/node_update.proto"; @@ -403,4 +404,13 @@ message SchedulableTransactionBody { */ TokenAirdropTransactionBody tokenAirdrop = 48; } + + /** + * A list of maximum custom fees that the users are willing to pay. + *

+ * This field is OPTIONAL.
+ * If left empty, the users are accepting to pay any custom fee.
+ * If used with a transaction type that does not support custom fee limits, the transaction will fail. + */ + repeated CustomFeeLimit max_custom_fees = 1001; } diff --git a/Sources/HieroProtobufs/Protos/services/smart_contract_service.proto b/Sources/HieroProtobufs/Protos/services/smart_contract_service.proto index cf64ced4..208034ac 100644 --- a/Sources/HieroProtobufs/Protos/services/smart_contract_service.proto +++ b/Sources/HieroProtobufs/Protos/services/smart_contract_service.proto @@ -171,4 +171,9 @@ service SmartContractService { * is less than this "floor" amount. */ rpc callEthereum (Transaction) returns (TransactionResponse); + + /** + * Update zero or more slots of a lambda. + */ + rpc lambdaSStore (Transaction) returns (TransactionResponse); } diff --git a/Sources/HieroProtobufs/Protos/services/transaction.proto b/Sources/HieroProtobufs/Protos/services/transaction.proto index 42e994d2..fe031736 100644 --- a/Sources/HieroProtobufs/Protos/services/transaction.proto +++ b/Sources/HieroProtobufs/Protos/services/transaction.proto @@ -98,6 +98,9 @@ import "services/auxiliary/history/history_proof_signature.proto"; import "services/auxiliary/history/history_proof_key_publication.proto"; import "services/auxiliary/history/history_proof_vote.proto"; +import "services/lambda_sstore.proto"; +import "services/hook_dispatch.proto"; + /** * A wrapper around signed transaction bytes.
* This was originally a transaction with body, signatures, and/or bytes, @@ -648,6 +651,16 @@ message TransactionBody { * A transaction body for handling a set of transactions atomically. */ AtomicBatchTransactionBody atomic_batch = 74; + + /** + * A transaction body for updating the storage of a EVM lambda hook. + */ + com.hedera.hapi.node.hooks.LambdaSStoreTransactionBody lambda_sstore = 75; + + /** + * An internal-only transaction body for dispatching a hook CRUD operation. + */ + com.hedera.hapi.node.hooks.HookDispatchTransactionBody hook_dispatch = 76; } /** diff --git a/Sources/HieroProtobufs/Protos/services/transaction_contents.proto b/Sources/HieroProtobufs/Protos/services/transaction_contents.proto index 1d9198ea..52f7e88a 100644 --- a/Sources/HieroProtobufs/Protos/services/transaction_contents.proto +++ b/Sources/HieroProtobufs/Protos/services/transaction_contents.proto @@ -47,4 +47,14 @@ message SignedTransaction { * This set MAY contain additional signatures. */ SignatureMap sigMap = 2; + + /** + * If false then the hash of this transaction is the SHA-384 hash of the + * serialization of this SignedTransaction message as it arrived on the wire. + *

+ * If true then the hash of this transaction is the SHA-384 hash of the + * ascending field order serialization of the Transaction whose `bodyBytes` + * and sigMap fields are deserialized from the contents of this message. + */ + bool use_serialized_tx_message_hash_algorithm = 3; } diff --git a/Sources/HieroProtobufs/update_protos.py b/Sources/HieroProtobufs/update_protos.py index f2802798..50da30ed 100644 --- a/Sources/HieroProtobufs/update_protos.py +++ b/Sources/HieroProtobufs/update_protos.py @@ -177,6 +177,9 @@ def run_protoc_grpc(proto_files: List[str]): "services/get_account_details.proto", "services/get_by_key.proto", "services/get_by_solidity_id.proto", + "services/hook_dispatch.proto", + "services/hook_types.proto", + "services/lambda_sstore.proto", "services/network_get_execution_time.proto", "services/network_get_version_info.proto", "services/network_service.proto", diff --git a/Tests/HieroE2ETests/Account/AccountCreate.swift b/Tests/HieroE2ETests/Account/AccountCreate.swift index 8bf4fc65..51e65901 100644 --- a/Tests/HieroE2ETests/Account/AccountCreate.swift +++ b/Tests/HieroE2ETests/Account/AccountCreate.swift @@ -4,6 +4,9 @@ import Hiero import XCTest internal final class AccountCreate: XCTestCase { + + private let testContractId = ContractId(shard: 1, realm: 2, num: 3) + internal func testInitialBalanceAndKey() async throws { let testEnv = try TestEnvironment.nonFree @@ -98,41 +101,6 @@ internal final class AccountCreate: XCTestCase { XCTAssertEqual(info.aliasKey, key.publicKey) } - // there's a disagreement between Java and Swift here. - // internal func testManagesExpiration() async throws { - // let testEnv = try TestEnvironment.nonFree - - // let key = PrivateKey.generateEd25519() - - // let receipt = try await AccountCreateTransaction() - // .keyWithoutAlias(.single(key.publicKey)) - // .transactionId( - // .withValidStart( - // testEnv.operator.accountId, - // .now - .seconds(40) - // ) - // ) - // .transactionValidDuration(.seconds(30)) - // .freezeWith(testEnv.client) - // .execute(testEnv.client) - // .getReceipt(testEnv.client) - - // let accountId = try XCTUnwrap(receipt.accountId) - - // addTeardownBlock { try await Account(id: accountId, key: key).delete(testEnv) } - - // let info = try await AccountInfoQuery(accountId: accountId).execute(testEnv.client) - - // XCTAssertEqual(info.accountId, accountId) - // XCTAssertFalse(info.isDeleted) - // XCTAssertEqual(info.key, .single(key.publicKey)) - // XCTAssertEqual(info.balance, 0) - // XCTAssertEqual(info.autoRenewPeriod, .days(90)) - // // fixme: ensure no warning gets emitted. - // // XCTAssertNil(info.proxyAccountId) - // XCTAssertEqual(info.proxyReceived, 0) - // } - internal func testAliasFromAdminKey() async throws { // Tests the third row of this table // https://github.com/hashgraph/hedera-improvement-proposal/blob/d39f740021d7da592524cffeaf1d749803798e9a/HIP/hip-583.md#signatures @@ -473,4 +441,165 @@ internal final class AccountCreate: XCTestCase { return true } + internal func test_CreateTransactionWithLambdaHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let ecdsaPrivateKey = PrivateKey.generateEcdsa() + + // Create a real contract first + let contractResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + let contractReceipt = try await contractResponse.getReceipt(testEnv.client) + let contractId = try XCTUnwrap(contractReceipt.contractId) + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = contractId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, hookId: 1, lambdaEvmHook: lambdaEvmHook) + + // When / Then + var txReceipt: Hiero.TransactionReceipt! + do { + txReceipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(ecdsaPrivateKey.publicKey)) + .addHook(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(ecdsaPrivateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + } catch { + XCTFail("Unexpected throw: \(error)") + } + + XCTAssertNotNil(txReceipt.accountId) + } + + func test_CreateTransactionWithLambdaHookAndStorageUpdates() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let ecdsaPrivateKey = PrivateKey.generateEcdsa() + + // Create a real contract first + let contractResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + let contractReceipt = try await contractResponse.getReceipt(testEnv.client) + let contractId = try XCTUnwrap(contractReceipt.contractId) + + print("contractId: \(contractId)") + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = contractId + + var lambdaStorageSlot = LambdaStorageSlot() + lambdaStorageSlot.key = Data([0x01, 0x23, 0x45]) + lambdaStorageSlot.value = Data([0x67, 0x89, 0xAB]) + + var lambdaStorageUpdate = LambdaStorageUpdate() + lambdaStorageUpdate.storageSlot = lambdaStorageSlot + + lambdaEvmHook.addStorageUpdate(lambdaStorageUpdate) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, hookId: 1, lambdaEvmHook: lambdaEvmHook) + + // When / Then + do { + let txResponse = try await AccountCreateTransaction() + .keyWithoutAlias(.single(ecdsaPrivateKey.publicKey)) + .addHook(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(ecdsaPrivateKey) + .execute(testEnv.client) + + let txReceipt = try await txResponse.getReceipt(testEnv.client) + XCTAssertNotNil(txReceipt.accountId) + } catch { + XCTFail("Unexpected throw: \(error)") + } + } + + func test_CreateTransactionWithLambdaHookWithNoContractId() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let ecdsaPrivateKey = PrivateKey.generateEcdsa() + + var lambdaEvmHook = LambdaEvmHook() + + var lambdaStorageSlot = LambdaStorageSlot() + lambdaStorageSlot.key = Data([0x01, 0x23, 0x45]) + lambdaStorageSlot.value = Data([0x67, 0x89, 0xAB]) + + var lambdaStorageUpdate = LambdaStorageUpdate() + lambdaStorageUpdate.storageSlot = lambdaStorageSlot + + lambdaEvmHook.addStorageUpdate(lambdaStorageUpdate) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, hookId: 1, lambdaEvmHook: lambdaEvmHook) + + // When / Then (expecting a precheck failure) + await assertThrowsHErrorAsync( + try await AccountCreateTransaction() + .keyWithoutAlias(.single(ecdsaPrivateKey.publicKey)) + .addHook(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client), "expected error creating account" + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.receiptStatus(status: _)`") + return + } + + XCTAssertEqual(status, .invalidHookCreationSpec) + } + } + + func test_CreateTransactionWithSameLambdaHookIds() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let ecdsaPrivateKey = PrivateKey.generateEcdsa() + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = testContractId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, hookId: 1, lambdaEvmHook: lambdaEvmHook) + + // When / Then — expect precheck error when duplicate hook IDs supplied + await assertThrowsHErrorAsync( + try await AccountCreateTransaction() + .keyWithoutAlias(.single(ecdsaPrivateKey.publicKey)) + .addHook(hookCreationDetails) + .addHook(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .transactionPreCheckStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.transactionPreCheckStatus(status: _)`") + return + } + + XCTAssertEqual(status, .hookIdRepeatedInCreationDetails) + } + } + } diff --git a/Tests/HieroE2ETests/Account/AccountUpdate.swift b/Tests/HieroE2ETests/Account/AccountUpdate.swift index 573a7a15..6c50bcfd 100644 --- a/Tests/HieroE2ETests/Account/AccountUpdate.swift +++ b/Tests/HieroE2ETests/Account/AccountUpdate.swift @@ -122,4 +122,352 @@ internal final class AccountUpdate: XCTestCase { XCTAssertEqual(status, .existingAutomaticAssociationsExceedGivenLimit) } } + + internal func test_CanAddHookToCreateToAccount() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let privateKey = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + addTeardownBlock { + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(testEnv.operator.accountId) + .sign(privateKey) + .execute(testEnv.client) + } + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = ContractId(shard: 1, realm: 2, num: 3) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + do { + _ = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + } catch { + XCTFail("Unexpected throw: \(error)") + } + } + + internal func test_CannotUpdateWithMultipleOfSameHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let privateKey = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + addTeardownBlock { + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(testEnv.operator.accountId) + .sign(privateKey) + .execute(testEnv.client) + } + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = ContractId(shard: 1, realm: 2, num: 3) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + await assertThrowsHErrorAsync( + try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .transactionPreCheckStatus(let status, transactionId: _) = error.kind else { + XCTFail("Expected `.transactionPreCheckStatus`, got \(error.kind)") + return + } + XCTAssertEqual(status, .hookIdRepeatedInCreationDetails) + } + } + + internal func test_CannotUpdateWithHookAlreadyInUse() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let privateKey = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + addTeardownBlock { + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(testEnv.operator.accountId) + .sign(privateKey) + .execute(testEnv.client) + } + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = ContractId(shard: 1, realm: 2, num: 3) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // Add once + _ = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + // Add again should fail + await assertThrowsHErrorAsync( + try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("Expected `.receiptStatus`, got \(error.kind)") + return + } + XCTAssertEqual(status, .hookIdInUse) + } + } + + internal func test_CanAddHookToCreateToAccountWithStorageUpdates() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let privateKey = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + addTeardownBlock { + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(testEnv.operator.accountId) + .sign(privateKey) + .execute(testEnv.client) + } + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = ContractId(shard: 1, realm: 2, num: 3) + + var slot = LambdaStorageSlot() + slot.key = Data([0x01, 0x23, 0x45]) + slot.value = Data([0x67, 0x89, 0xAB]) + + var update = LambdaStorageUpdate() + update.storageSlot = slot + + lambdaEvmHook.addStorageUpdate(update) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + do { + _ = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + } catch { + XCTFail("Unexpected throw: \(error)") + } + } + + internal func test_CanDeleteHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let privateKey = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + addTeardownBlock { + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(testEnv.operator.accountId) + .sign(privateKey) + .execute(testEnv.client) + } + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = ContractId(shard: 1, realm: 2, num: 3) + + let hookId: Int64 = 1 + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: hookId, + lambdaEvmHook: lambdaEvmHook + ) + + // Add + _ = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + // Delete + do { + _ = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToDelete(hookId) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + } catch { + XCTFail("Unexpected throw: \(error)") + } + } + + internal func test_CannotDeleteNonExistentHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let privateKey = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + addTeardownBlock { + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(testEnv.operator.accountId) + .sign(privateKey) + .execute(testEnv.client) + } + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = ContractId(shard: 1, realm: 2, num: 3) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // Add + _ = try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + // Delete non-existent hook + await assertThrowsHErrorAsync( + try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToDelete(999) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("Expected `.receiptStatus`, got \(error.kind)") + return + } + XCTAssertEqual(status, .hookNotFound) + } + } + + internal func test_CannotAddAndDeleteSameHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let privateKey = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + addTeardownBlock { + _ = try await AccountDeleteTransaction() + .accountId(accountId) + .transferAccountId(testEnv.operator.accountId) + .sign(privateKey) + .execute(testEnv.client) + } + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = ContractId(shard: 1, realm: 2, num: 3) + + let hookId: Int64 = 1 + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: hookId, + lambdaEvmHook: lambdaEvmHook + ) + + await assertThrowsHErrorAsync( + try await AccountUpdateTransaction() + .accountId(accountId) + .addHookToCreate(hookCreationDetails) + .addHookToDelete(hookId) + .freezeWith(testEnv.client) + .sign(privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("Expected `.receiptStatus`, got \(error.kind)") + return + } + XCTAssertEqual(status, .hookNotFound) + } + } } diff --git a/Tests/HieroE2ETests/Contract/Contract.swift b/Tests/HieroE2ETests/Contract/Contract.swift index 170a756f..365dd8b8 100644 --- a/Tests/HieroE2ETests/Contract/Contract.swift +++ b/Tests/HieroE2ETests/Contract/Contract.swift @@ -47,7 +47,7 @@ extension ContractHelpers { let receipt = try await ContractCreateTransaction( bytecodeFileId: file.fileId, adminKey: operatorAdminKey ? .single(testEnv.operator.privateKey.publicKey) : nil, - gas: 200000, + gas: 300000, constructorParameters: ContractFunctionParameters().addString("Hello from Hiero.").toBytes(), contractMemo: "[e2e::ContractCreateTransaction]" ) diff --git a/Tests/HieroE2ETests/Contract/ContractCreate.swift b/Tests/HieroE2ETests/Contract/ContractCreate.swift index 7e64d011..35e274f8 100644 --- a/Tests/HieroE2ETests/Contract/ContractCreate.swift +++ b/Tests/HieroE2ETests/Contract/ContractCreate.swift @@ -4,6 +4,7 @@ import Hiero import XCTest internal final class ContractCreate: XCTestCase { + internal func testBasic() async throws { let testEnv = try TestEnvironment.nonFree @@ -11,7 +12,7 @@ internal final class ContractCreate: XCTestCase { let receipt = try await ContractCreateTransaction() .adminKey(.single(testEnv.operator.privateKey.publicKey)) - .gas(200000) + .gas(300000) .constructorParameters(ContractFunctionParameters().addString("Hello from Hiero.")) .bytecodeFileId(bytecode.fileId) .contractMemo("[e2e::ContractCreateTransaction]") @@ -42,7 +43,7 @@ internal final class ContractCreate: XCTestCase { let bytecode = try await File.forContent(ContractHelpers.bytecode, testEnv) let receipt = try await ContractCreateTransaction() - .gas(200000) + .gas(300000) .constructorParameters(ContractFunctionParameters().addString("Hello from Hiero.")) .bytecodeFileId(bytecode.fileId) .contractMemo("[e2e::ContractCreateTransaction]") @@ -129,4 +130,224 @@ internal final class ContractCreate: XCTestCase { XCTAssertEqual(status, .invalidFileID) } } + + internal func test_CreateContractWithHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300_000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When + let txResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300_000) + .addHook(hookCreationDetails) + .execute(testEnv.client) + + // Then + let txReceipt = try await txResponse.getReceipt(testEnv.client) + XCTAssertNotNil(txReceipt.contractId) + } + + internal func test_CreateContractWithHookWithStorageUpdates() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + var slot = LambdaStorageSlot() + slot.key = Data([0x01, 0x23, 0x45]) + slot.value = Data([0x67, 0x89, 0xAB]) + + var update = LambdaStorageUpdate() + update.storageSlot = slot + + lambdaEvmHook.addStorageUpdate(update) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When + let txResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300_000) + .addHook(hookCreationDetails) + .execute(testEnv.client) + + // Then + let txReceipt = try await txResponse.getReceipt(testEnv.client) + XCTAssertNotNil(txReceipt.contractId) + } + + internal func test_CannotCreateContractWithNoContractIdForHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let lambdaEvmHook = LambdaEvmHook() + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + await assertThrowsHErrorAsync( + try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .addHook(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.receiptStatus(status: _)`") + return + } + XCTAssertEqual(status, .invalidHookCreationSpec) + } + } + + internal func test_CannotCreateContractWithDuplicateHookId() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + let contractReceipt = try await contractResponse.getReceipt(testEnv.client) + let contractId = try XCTUnwrap(contractReceipt.contractId) + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = contractId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + await assertThrowsHErrorAsync( + try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .addHook(hookCreationDetails) + .addHook(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .transactionPreCheckStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.transactionPreCheckStatus(status: _)`") + return + } + XCTAssertEqual(status, .hookIdRepeatedInCreationDetails) + } + } + + internal func test_CreateContractWithHookWithAdminKey() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let adminKey = PrivateKey.generateEcdsa() + + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook, + adminKey: .single(adminKey.publicKey) + ) + + // When + let txResponse = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .addHook(hookCreationDetails) + .execute(testEnv.client) + + // Then + let txReceipt = try await txResponse.getReceipt(testEnv.client) + XCTAssertNotNil(txReceipt.contractId) + } } diff --git a/Tests/HieroE2ETests/Contract/ContractCreateFlow.swift b/Tests/HieroE2ETests/Contract/ContractCreateFlow.swift index 8870412e..48f0d6db 100644 --- a/Tests/HieroE2ETests/Contract/ContractCreateFlow.swift +++ b/Tests/HieroE2ETests/Contract/ContractCreateFlow.swift @@ -17,7 +17,7 @@ internal final class ContractCreateFlow: XCTestCase { let receipt = try await Hiero.ContractCreateFlow() .bytecode(ContractHelpers.bytecodeString) .adminKey(.single(testEnv.operator.privateKey.publicKey)) - .gas(200000) + .gas(300000) .constructorParameters(ContractFunctionParameters().addString("Hello from Hiero.")) .contractMemo("[e2e::ContractCreateFlow]") .execute(testEnv.client) @@ -84,7 +84,7 @@ internal final class ContractCreateFlow: XCTestCase { let receipt = try await Hiero.ContractCreateFlow() .bytecode(ContractHelpers.bytecodeString) .adminKey(.single(adminKey.publicKey)) - .gas(200000) + .gas(300000) .constructorParameters(ContractFunctionParameters().addString("Hello from Hiero.")) .contractMemo("[e2e::ContractCreateFlow]") .sign(adminKey) @@ -124,7 +124,7 @@ internal final class ContractCreateFlow: XCTestCase { let receipt = try await Hiero.ContractCreateFlow() .bytecode(ContractHelpers.bytecodeString) .adminKey(.single(adminKey.publicKey)) - .gas(200000) + .gas(300000) .constructorParameters(ContractFunctionParameters().addString("Hello from Hiero.")) .contractMemo("[e2e::ContractCreateFlow]") .signWith(adminKey.publicKey, adminKey.sign(_:)) diff --git a/Tests/HieroE2ETests/Contract/ContractUpdate.swift b/Tests/HieroE2ETests/Contract/ContractUpdate.swift index cf21597d..58e2848d 100644 --- a/Tests/HieroE2ETests/Contract/ContractUpdate.swift +++ b/Tests/HieroE2ETests/Contract/ContractUpdate.swift @@ -4,6 +4,7 @@ import Hiero import XCTest internal final class ContractUpdate: XCTestCase { + internal func testBasic() async throws { let testEnv = try TestEnvironment.nonFree @@ -65,4 +66,384 @@ internal final class ContractUpdate: XCTestCase { XCTAssertEqual(status, .modifyingImmutableContract) } } + + internal func test_CanAddHookToContract() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + do { + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + } catch { + XCTFail("Unexpected throw: \(error)") + } + } + + internal func test_CannotAddDuplicateHooksToContract() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + await assertThrowsHErrorAsync( + try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .addHookToCreate(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .transactionPreCheckStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.transactionPreCheckStatus(status: _)`") + return + } + XCTAssertEqual(status, .hookIdRepeatedInCreationDetails) + } + } + + internal func test_CannotAddHookToContractThatAlreadyExists() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(testEnv.operator.privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + // When / Then + await assertThrowsHErrorAsync( + try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.receiptStatus(status: _)`") + return + } + XCTAssertEqual(status, .hookIdInUse) + } + } + + internal func test_CanAddHookToContractWithStorageUpdates() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + var slot = LambdaStorageSlot() + slot.key = Data([0x01, 0x23, 0x45]) + slot.value = Data([0x67, 0x89, 0xAB]) + + var update = LambdaStorageUpdate() + update.storageSlot = slot + lambdaEvmHook.addStorageUpdate(update) + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + do { + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .execute(testEnv.client) + .getReceipt(testEnv.client) + } catch { + XCTFail("Unexpected throw: \(error)") + } + } + + internal func test_CanDeleteHookFromContract() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookId: Int64 = 1 + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: hookId, + lambdaEvmHook: lambdaEvmHook + ) + + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(testEnv.operator.privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + // When / Then + do { + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToDelete(hookId) + .freezeWith(testEnv.client) + .execute(testEnv.client) + .getReceipt(testEnv.client) + } catch { + XCTFail("Unexpected throw: \(error)") + } + } + + internal func test_CannotDeleteNonExistentHookFromContract() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookId: Int64 = 1 + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: hookId, + lambdaEvmHook: lambdaEvmHook + ) + + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + // When / Then + await assertThrowsHErrorAsync( + try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToDelete(999) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.receiptStatus(status: _)`") + return + } + XCTAssertEqual(status, .hookNotFound) + } + } + + internal func test_CannotAddAndDeleteSameHookFromContract() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookId: Int64 = 1 + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: hookId, + lambdaEvmHook: lambdaEvmHook + ) + + // When / Then + await assertThrowsHErrorAsync( + try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .addHookToDelete(hookId) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.receiptStatus(status: _)`") + return + } + XCTAssertEqual(status, .hookNotFound) + } + } + + internal func test_CannotDeleteAlreadyDeletedHookFromContract() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let contractId = try await ContractHelpers.makeContract(testEnv, operatorAdminKey: true) + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookId: Int64 = 1 + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: hookId, + lambdaEvmHook: lambdaEvmHook + ) + + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToCreate(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(testEnv.operator.privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + _ = try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToDelete(hookId) + .freezeWith(testEnv.client) + .sign(testEnv.operator.privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + // When / Then + await assertThrowsHErrorAsync( + try await ContractUpdateTransaction() + .contractId(contractId) + .addHookToDelete(hookId) + .freezeWith(testEnv.client) + .sign(testEnv.operator.privateKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + ) { error in + guard case .receiptStatus(let status, transactionId: _) = error.kind else { + XCTFail("\(error.kind) is not `.receiptStatus(status: _)`") + return + } + XCTAssertEqual(status, .hookNotFound) + } + } } diff --git a/Tests/HieroE2ETests/TransferTransactionHooks.swift b/Tests/HieroE2ETests/TransferTransactionHooks.swift new file mode 100644 index 00000000..e8c67b2b --- /dev/null +++ b/Tests/HieroE2ETests/TransferTransactionHooks.swift @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import Hiero +import XCTest + +internal final class TransferTransactionHooks: XCTestCase { + + internal func testHbarTransferWithPreTxHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 2, + lambdaEvmHook: lambdaEvmHook + ) + + let key = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(key.publicKey)) + .initialBalance(1) + .addHook(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + let hookCall = FungibleHookCall( + hookCall: HookCall(hookId: 2, evmHookCall: EvmHookCall(gasLimit: 25000)), + hookType: .preTxAllowanceHook + ) + + // When + let transferTx = TransferTransaction() + .hbarTransferWithHook(accountId, Hbar(1), hookCall) + .hbarTransfer(testEnv.operator.accountId, Hbar(-1)) + + // Then + let response = try await transferTx.execute(testEnv.client) + let transferReceipt = try await response.getReceipt(testEnv.client) + + XCTAssertEqual(transferReceipt.status, .success) + } + + internal func testHbarTransferWithPrePostTxHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 2, + lambdaEvmHook: lambdaEvmHook + ) + + let key = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(key.publicKey)) + .initialBalance(1) + .addHook(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + let hookCall = FungibleHookCall( + hookCall: HookCall(hookId: 2, evmHookCall: EvmHookCall(gasLimit: 25000)), + hookType: .prePostTxAllowanceHook + ) + + // When + let transferTx = TransferTransaction() + .hbarTransferWithHook(accountId, Hbar(1), hookCall) + .hbarTransfer(testEnv.operator.accountId, Hbar(-1)) + + // Then + let response = try await transferTx.execute(testEnv.client) + let transferReceipt = try await response.getReceipt(testEnv.client) + + XCTAssertEqual(transferReceipt.status, .success) + } + + internal func testTokenTransferWithPreTxHook() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 2, + lambdaEvmHook: lambdaEvmHook + ) + + let key = PrivateKey.generateEd25519() + let receipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(key.publicKey)) + .initialBalance(1) + .addHook(hookCreationDetails) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let accountId = try XCTUnwrap(receipt.accountId) + + let tokenId = try await TokenCreateTransaction() + .name("Test Token") + .symbol("TT") + .decimals(2) + .initialSupply(1000) + .treasuryAccountId(testEnv.operator.accountId) + .adminKey(.single(testEnv.operator.privateKey.publicKey)) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .tokenId! + + _ = try await TokenAssociateTransaction(accountId: accountId, tokenIds: [tokenId]) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + let hookCall = FungibleHookCall( + hookCall: HookCall(hookId: 2, evmHookCall: EvmHookCall(gasLimit: 25000)), + hookType: .preTxAllowanceHook + ) + + // When + let transferTx = TransferTransaction() + .tokenTransferWithHook(tokenId, accountId, 1000, hookCall) + .tokenTransfer(tokenId, testEnv.operator.accountId, -1000) + + // Then + let response = try await transferTx.execute(testEnv.client) + let transferReceipt = try await response.getReceipt(testEnv.client) + + XCTAssertEqual(transferReceipt.status, .success) + } + + internal func disable_testNftTransferWithSenderAndReceiverHooks() async throws { + let testEnv = try TestEnvironment.nonFree + + // Given + let lambdaId = try await ContractCreateTransaction() + .bytecode( + Data( + hexEncoded: + "608060405234801561001057600080fd5b50610167806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632f570a2314610030575b600080fd5b61004a600480360381019061004591906100b6565b610060565b604051610057919061010a565b60405180910390f35b60006001905092915050565b60008083601f84011261007e57600080fd5b8235905067ffffffffffffffff81111561009757600080fd5b6020830191508360018202830111156100af57600080fd5b9250929050565b600080602083850312156100c957600080fd5b600083013567ffffffffffffffff8111156100e357600080fd5b6100ef8582860161006c565b92509250509250929050565b61010481610125565b82525050565b600060208201905061011f60008301846100fb565b92915050565b6000811515905091905056fea264697066735822122097fc0c3ac3155b53596be3af3b4d2c05eb5e273c020ee447f01b72abc3416e1264736f6c63430008000033" + )! + ) + .gas(300000) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .contractId! + + var lambdaEvmHook = LambdaEvmHook() + lambdaEvmHook.spec.contractId = lambdaId + + let hookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 1, + lambdaEvmHook: lambdaEvmHook + ) + + let senderKey = PrivateKey.generateEd25519() + let senderReceipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(senderKey.publicKey)) + .initialBalance(2) + .addHook(hookCreationDetails) + .freezeWith(testEnv.client) + .sign(senderKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let senderAccountId = try XCTUnwrap(senderReceipt.accountId) + + var receiverLambdaEvmHook = LambdaEvmHook() + receiverLambdaEvmHook.spec.contractId = lambdaId + + let receiverHookCreationDetails = HookCreationDetails( + hookExtensionPoint: .accountAllowanceHook, + hookId: 2, + lambdaEvmHook: receiverLambdaEvmHook + ) + + let receiverKey = PrivateKey.generateEd25519() + let receiverReceipt = try await AccountCreateTransaction() + .keyWithoutAlias(.single(receiverKey.publicKey)) + .initialBalance(2) + .addHook(receiverHookCreationDetails) + .freezeWith(testEnv.client) + .sign(receiverKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + let receiverAccountId = try XCTUnwrap(receiverReceipt.accountId) + + let tokenId = try await TokenCreateTransaction() + .name("Test NFT") + .symbol("TNFT") + .tokenType(.nonFungibleUnique) + .initialSupply(0) + .treasuryAccountId(senderAccountId) + .adminKey(.single(senderKey.publicKey)) + .supplyKey(.single(senderKey.publicKey)) + .freezeWith(testEnv.client) + .sign(senderKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + .tokenId! + + let _ = try await TokenMintTransaction(tokenId: tokenId) + .metadata([Data("NFT Metadata".utf8)]) + .freezeWith(testEnv.client) + .sign(senderKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + _ = try await TokenAssociateTransaction(accountId: receiverAccountId, tokenIds: [tokenId]) + .freezeWith(testEnv.client) + .sign(receiverKey) + .execute(testEnv.client) + .getReceipt(testEnv.client) + + let senderCall = NftHookCall( + hookCall: HookCall(hookId: 1, evmHookCall: EvmHookCall(gasLimit: 25000)), + hookType: .preHook + ) + + let receiverCall = NftHookCall( + hookCall: HookCall(hookId: 2, evmHookCall: EvmHookCall(gasLimit: 25000)), + hookType: .preHook + ) + + let nftId = NftId(tokenId: tokenId, serial: 1) + + // When + let transferTx = TransferTransaction() + .nftTransferWithHooks(nftId, senderAccountId, receiverAccountId, senderCall, receiverCall) + + // Then + let response = try await transferTx.execute(testEnv.client) + let transferReceipt = try await response.getReceipt(testEnv.client) + + XCTAssertEqual(transferReceipt.status, .success) + } +} diff --git a/Tests/HieroTests/EvmHookCallTests.swift b/Tests/HieroTests/EvmHookCallTests.swift new file mode 100644 index 00000000..a5a541fb --- /dev/null +++ b/Tests/HieroTests/EvmHookCallTests.swift @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class EvmHookCallUnitTests: XCTestCase { + + private let testCallData = Data([0x01, 0x23, 0x45]) + private let testGasLimit: UInt64 = 1_000_000 + + func test_GetSetCallData() { + // Given + var evmHookCall = EvmHookCall() + + // When + evmHookCall.data = testCallData + + // Then + XCTAssertEqual(evmHookCall.data, testCallData) + } + + func test_GetSetGasLimit() { + // Given + var evmHookCall = EvmHookCall() + + // When + evmHookCall.gasLimit = testGasLimit + + // Then + XCTAssertEqual(evmHookCall.gasLimit, testGasLimit) + } + + // MARK: - TEST_F(EvmHookCallUnitTests, FromProtobuf) + func test_FromProtobuf() throws { + // Given + var protoMsg = Proto_EvmHookCall() + protoMsg.data = testCallData + protoMsg.gasLimit = testGasLimit + + // When + let evmHookCall = try EvmHookCall.fromProtobuf(protoMsg) + + // Then + XCTAssertEqual(evmHookCall.data, testCallData) + XCTAssertEqual(evmHookCall.gasLimit, testGasLimit) + } + + // MARK: - TEST_F(EvmHookCallUnitTests, ToProtobuf) + func test_ToProtobuf() { + // Given + var evmHookCall = EvmHookCall() + evmHookCall.data = testCallData + evmHookCall.gasLimit = testGasLimit + + // When + let protoMsg = evmHookCall.toProtobuf() + + // Then + XCTAssertEqual(protoMsg.data, testCallData) + XCTAssertEqual(protoMsg.gasLimit, testGasLimit) + } +} diff --git a/Tests/HieroTests/EvmHookSpecTests.swift b/Tests/HieroTests/EvmHookSpecTests.swift new file mode 100644 index 00000000..5460d09d --- /dev/null +++ b/Tests/HieroTests/EvmHookSpecTests.swift @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class EvmHookSpecUnitTests: XCTestCase { + + private let testContractId = ContractId(shard: 1, realm: 2, num: 3) + + func test_GetSetContractId() { + // Given + var evmHookSpec = EvmHookSpec() + + // When + evmHookSpec.contractId = testContractId + + // Then + XCTAssertEqual(evmHookSpec.contractId, testContractId) + } + + // TEST_F(EvmHookSpecUnitTests, FromProtobuf) + func test_FromProtobuf() throws { + // Given + var protoSpec = Com_Hedera_Hapi_Node_Hooks_EvmHookSpec() + protoSpec.bytecodeSource = .contractID(testContractId.toProtobuf()) + + // When + let evmHookSpec = try EvmHookSpec.fromProtobuf(protoSpec) + + // Then + XCTAssertEqual(evmHookSpec.contractId, testContractId) + } + + func test_ToProtobuf() { + // Given + var evmHookSpec = EvmHookSpec() + evmHookSpec.contractId = testContractId + + // When + let protoSpec = evmHookSpec.toProtobuf() + + // Then + guard case .contractID(let contractID)? = protoSpec.bytecodeSource else { + XCTFail("Expected bytecodeSource to be .contractID") + return + } + XCTAssertEqual(UInt64(truncatingIfNeeded: contractID.shardNum), testContractId.shard) + XCTAssertEqual(UInt64(truncatingIfNeeded: contractID.realmNum), testContractId.realm) + XCTAssertEqual(UInt64(truncatingIfNeeded: contractID.contractNum), testContractId.num) + } +} diff --git a/Tests/HieroTests/FungibleHookCallTests.swift b/Tests/HieroTests/FungibleHookCallTests.swift new file mode 100644 index 00000000..de489c7c --- /dev/null +++ b/Tests/HieroTests/FungibleHookCallTests.swift @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class FungibleHookCallUnitTests: XCTestCase { + + // Fixture-equivalent constants + private let testAccountId = AccountId(shard: 1, realm: 2, num: 3) + private let testHookId: Int64 = 4 + private let testCallData = Data([0x56, 0x78, 0x9A]) + private let testGasLimit: UInt64 = 11 + + private var testHookEntityId: HookEntityId { + return HookEntityId(testAccountId) + } + + private var testFullHookId: HookId { + return HookId(entityId: testHookEntityId, hookId: testHookId) + } + + private var testEvmHookCall: EvmHookCall { + var c = EvmHookCall() + c.data = testCallData + c.gasLimit = testGasLimit + return c + } + + func test_DefaultInitialization() { + // Given & When + let fungibleHookCall = FungibleHookCall() + + // Then + XCTAssertEqual(fungibleHookCall.hookType, .uninitialized) + XCTAssertNil(fungibleHookCall.hookCall.fullHookId) + XCTAssertNil(fungibleHookCall.hookCall.hookId) + XCTAssertNil(fungibleHookCall.hookCall.evmHookCall) + } + + func test_CustomInitialization() { + // Given + let hookCall = HookCall() + let hookType = FungibleHookType.preTxAllowanceHook + + // When + let fungibleHookCall = FungibleHookCall(hookCall: hookCall, hookType: hookType) + + // Then + XCTAssertEqual(fungibleHookCall.hookType, hookType) + XCTAssertNil(fungibleHookCall.hookCall.fullHookId) + XCTAssertNil(fungibleHookCall.hookCall.hookId) + XCTAssertNil(fungibleHookCall.hookCall.evmHookCall) + } + + func test_SetHookCall() { + // Given + var fungibleHookCall = FungibleHookCall() + let hookCall = HookCall() + + // When + fungibleHookCall.hookCall(hookCall) + + // Then + XCTAssertNil(fungibleHookCall.hookCall.fullHookId) + XCTAssertNil(fungibleHookCall.hookCall.hookId) + XCTAssertNil(fungibleHookCall.hookCall.evmHookCall) + } + + func test_SetHookType() { + // Given + var fungibleHookCall = FungibleHookCall() + + // When + fungibleHookCall.hookType(.preTxAllowanceHook) + + // Then + XCTAssertEqual(fungibleHookCall.hookType, .preTxAllowanceHook) + } + + func test_SetFullHookId() { + // Given + var fungibleHookCall = FungibleHookCall() + + // When + fungibleHookCall.fullHookId(testFullHookId) + + // Then + XCTAssertNotNil(fungibleHookCall.hookCall.fullHookId?.entityId.accountId) + XCTAssertEqual(fungibleHookCall.hookCall.fullHookId?.entityId.accountId, testAccountId) + XCTAssertEqual(fungibleHookCall.hookCall.fullHookId?.hookId, testHookId) + } + + func test_SetHookId() { + // Given + var fungibleHookCall = FungibleHookCall() + + // When + fungibleHookCall.hookId(testHookId) + + // Then + XCTAssertEqual(fungibleHookCall.hookCall.hookId, testHookId) + } + + func test_SetEvmHookCall() { + // Given + var fungibleHookCall = FungibleHookCall() + + // When + fungibleHookCall.evmHookCall(testEvmHookCall) + + // Then + XCTAssertNotNil(fungibleHookCall.hookCall.evmHookCall) + XCTAssertEqual(fungibleHookCall.hookCall.evmHookCall?.data, testCallData) + XCTAssertEqual(fungibleHookCall.hookCall.evmHookCall?.gasLimit, testGasLimit) + } + + func test_FromProtobuf() throws { + // Given + var protoFull = Proto_HookCall() + var protoHookIdOnly = Proto_HookCall() + + // full_hook_id + evm_hook_call + protoFull.fullHookID = testFullHookId.toProtobuf() + protoFull.evmHookCall = testEvmHookCall.toProtobuf() + + // hook_id only + protoHookIdOnly.hookID = testHookId + + // When + let fungibleHookCallFull = try FungibleHookCall.fromProtobuf(protoFull) + let fungibleHookCallHookOnly = try FungibleHookCall.fromProtobuf(protoHookIdOnly) + + // Then + XCTAssertNotNil(fungibleHookCallFull.hookCall.fullHookId?.entityId.accountId) + XCTAssertEqual(fungibleHookCallFull.hookCall.fullHookId?.entityId.accountId, testAccountId) + XCTAssertEqual(fungibleHookCallFull.hookCall.fullHookId?.hookId, testHookId) + + XCTAssertNotNil(fungibleHookCallFull.hookCall.evmHookCall) + XCTAssertEqual(fungibleHookCallFull.hookCall.evmHookCall?.data, testCallData) + XCTAssertEqual(fungibleHookCallFull.hookCall.evmHookCall?.gasLimit, testGasLimit) + + XCTAssertEqual(fungibleHookCallHookOnly.hookCall.hookId, testHookId) + + // Hook type should be uninitialized since it's not stored in protobuf + XCTAssertEqual(fungibleHookCallFull.hookType, .uninitialized) + XCTAssertEqual(fungibleHookCallHookOnly.hookType, .uninitialized) + } + + func test_ToProtobuf() { + // Given + var fungibleHookCallFull = FungibleHookCall() + var fungibleHookCallHookOnly = FungibleHookCall() + + fungibleHookCallFull.hookCall.fullHookId = testFullHookId + fungibleHookCallFull.hookCall.evmHookCall = testEvmHookCall + fungibleHookCallFull.hookType = .preTxAllowanceHook + + fungibleHookCallHookOnly.hookCall.hookId = testHookId + fungibleHookCallHookOnly.hookType = .prePostTxAllowanceHook + + // When + let protoFull = fungibleHookCallFull.toProtobuf() + let protoHookOnly = fungibleHookCallHookOnly.toProtobuf() + + // Then + XCTAssertTrue(protoFull.fullHookID.hasEntityID) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.shardNum), testAccountId.shard) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.realmNum), testAccountId.realm) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.accountNum), testAccountId.num) + XCTAssertEqual(protoFull.fullHookID.hookID, testHookId) + + XCTAssertEqual(protoFull.evmHookCall.data, testCallData) + XCTAssertEqual(protoFull.evmHookCall.gasLimit, testGasLimit) + + XCTAssertEqual(protoHookOnly.hookID, testHookId) + } +} diff --git a/Tests/HieroTests/FungibleHookTypeTests.swift b/Tests/HieroTests/FungibleHookTypeTests.swift new file mode 100644 index 00000000..5a326b14 --- /dev/null +++ b/Tests/HieroTests/FungibleHookTypeTests.swift @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import XCTest + +@testable import Hiero + +final class FungibleHookTypeUnitTests: XCTestCase { + + func test_AllCases() { + // Given & When + let allCases = FungibleHookType.allCases + + // Then + XCTAssertEqual(allCases.count, 3) + XCTAssertTrue(allCases.contains(.preTxAllowanceHook)) + XCTAssertTrue(allCases.contains(.prePostTxAllowanceHook)) + XCTAssertTrue(allCases.contains(.uninitialized)) + } + + func test_Description() { + // Given & When & Then + XCTAssertEqual(FungibleHookType.preTxAllowanceHook.description, "PRE_TX_ALLOWANCE_HOOK") + XCTAssertEqual(FungibleHookType.prePostTxAllowanceHook.description, "PRE_POST_TX_ALLOWANCE_HOOK") + XCTAssertEqual(FungibleHookType.uninitialized.description, "UNINITIALIZED") + } + + func test_Equality() { + // Given & When & Then + XCTAssertEqual(FungibleHookType.preTxAllowanceHook, FungibleHookType.preTxAllowanceHook) + XCTAssertEqual(FungibleHookType.prePostTxAllowanceHook, FungibleHookType.prePostTxAllowanceHook) + XCTAssertEqual(FungibleHookType.uninitialized, FungibleHookType.uninitialized) + + XCTAssertNotEqual(FungibleHookType.preTxAllowanceHook, FungibleHookType.prePostTxAllowanceHook) + XCTAssertNotEqual(FungibleHookType.preTxAllowanceHook, FungibleHookType.uninitialized) + XCTAssertNotEqual(FungibleHookType.prePostTxAllowanceHook, FungibleHookType.uninitialized) + } + + func test_Hashable() { + // Given + let set: Set = [.preTxAllowanceHook, .prePostTxAllowanceHook, .uninitialized] + + // When & Then + XCTAssertEqual(set.count, 3) + XCTAssertTrue(set.contains(.preTxAllowanceHook)) + XCTAssertTrue(set.contains(.prePostTxAllowanceHook)) + XCTAssertTrue(set.contains(.uninitialized)) + } +} diff --git a/Tests/HieroTests/HookCallTests.swift b/Tests/HieroTests/HookCallTests.swift new file mode 100644 index 00000000..83df9af5 --- /dev/null +++ b/Tests/HieroTests/HookCallTests.swift @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class HookCallUnitTests: XCTestCase { + + // Fixture-equivalent constants + private let testAccountId = AccountId(shard: 1, realm: 2, num: 3) + private let testHookId: Int64 = 4 + private let testCallData = Data([0x56, 0x78, 0x9A]) + private let testGasLimit: UInt64 = 11 + + private var testHookEntityId: HookEntityId { + return HookEntityId(testAccountId) + } + + private var testFullHookId: HookId { + return HookId(entityId: testHookEntityId, hookId: testHookId) + } + + private var testEvmHookCall: EvmHookCall { + var c = EvmHookCall() + c.data = testCallData + c.gasLimit = testGasLimit + return c + } + + func test_GetSetFullHookId() { + // Given + var hookCall = HookCall() + + // When + hookCall.fullHookId(testFullHookId) + + // Then + XCTAssertNotNil(hookCall.fullHookId?.entityId.accountId) + XCTAssertEqual(hookCall.fullHookId?.entityId.accountId, testAccountId) + XCTAssertEqual(hookCall.fullHookId?.hookId, testHookId) + } + + func test_GetSetFullHookIdResetsHookId() { + // Given + var hookCall = HookCall() + + // When + hookCall.hookId(testHookId) + hookCall.fullHookId(testFullHookId) + + // Then + XCTAssertNil(hookCall.hookId) + } + + func test_GetSetHookId() { + // Given + var hookCall = HookCall() + + // When + hookCall.hookId(testHookId) + + // Then + XCTAssertEqual(hookCall.hookId, testHookId) + } + + func test_GetSetHookIdResetsFullHookId() { + // Given + var hookCall = HookCall() + + // When + hookCall.fullHookId(testFullHookId) + hookCall.hookId(testHookId) + + // Then + XCTAssertNil(hookCall.fullHookId) + } + + func test_GetSetEvmHookCall() { + // Given + var hookCall = HookCall() + + // When + hookCall.evmHookCall(testEvmHookCall) + + // Then + XCTAssertNotNil(hookCall.evmHookCall) + XCTAssertEqual(hookCall.evmHookCall?.data, testCallData) + XCTAssertEqual(hookCall.evmHookCall?.gasLimit, testGasLimit) + } + + func test_FromProtobuf() throws { + // Given + var protoFull = Proto_HookCall() + var protoHookIdOnly = Proto_HookCall() + + // full_hook_id + evm_hook_call + protoFull.fullHookID = testFullHookId.toProtobuf() + protoFull.evmHookCall = testEvmHookCall.toProtobuf() + + // hook_id only + protoHookIdOnly.hookID = testHookId + + // When + let hookCallFull = try HookCall.fromProtobuf(protoFull) + let hookCallHookOnly = try HookCall.fromProtobuf(protoHookIdOnly) + + // Then + XCTAssertNotNil(hookCallFull.fullHookId?.entityId.accountId) + XCTAssertEqual(hookCallFull.fullHookId?.entityId.accountId, testAccountId) + XCTAssertEqual(hookCallFull.fullHookId?.hookId, testHookId) + + XCTAssertNotNil(hookCallFull.evmHookCall) + XCTAssertEqual(hookCallFull.evmHookCall?.data, testCallData) + XCTAssertEqual(hookCallFull.evmHookCall?.gasLimit, testGasLimit) + + XCTAssertEqual(hookCallHookOnly.hookId, testHookId) + } + + func test_ToProtobuf() { + // Given + var hookCallFull = HookCall() + var hookCallHookOnly = HookCall() + + hookCallFull.fullHookId = testFullHookId + hookCallFull.evmHookCall = testEvmHookCall + + hookCallHookOnly.hookId = testHookId + + // When + let protoFull = hookCallFull.toProtobuf() + let protoHookOnly = hookCallHookOnly.toProtobuf() + + // Then + XCTAssertTrue(protoFull.fullHookID.hasEntityID) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.shardNum), testAccountId.shard) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.realmNum), testAccountId.realm) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.accountNum), testAccountId.num) + XCTAssertEqual(protoFull.fullHookID.hookID, testHookId) + + XCTAssertEqual(protoFull.evmHookCall.data, testCallData) + XCTAssertEqual(protoFull.evmHookCall.gasLimit, testGasLimit) + + XCTAssertEqual(protoHookOnly.hookID, testHookId) + } +} diff --git a/Tests/HieroTests/HookCreationDetailsTests.swift b/Tests/HieroTests/HookCreationDetailsTests.swift new file mode 100644 index 00000000..d9ea628c --- /dev/null +++ b/Tests/HieroTests/HookCreationDetailsTests.swift @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class HookCreationDetailsUnitTests: XCTestCase { + + private func makeStorageSlot(_ key: Data, _ value: Data) -> LambdaStorageSlot { + var s = LambdaStorageSlot() + s.key(key) + s.value(value) + return s + } + + private func makeEntryWithKey(_ key: Data, _ value: Data) -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.key(key) + e.value(value) + return e + } + + private func makeEntryWithPreimage(_ preimage: Data, _ value: Data) -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.preimage(preimage) + e.value(value) + return e + } + + private func makeMappingEntries(_ mappingSlot: Data, _ entries: [LambdaMappingEntry]) -> LambdaMappingEntries { + var me = LambdaMappingEntries() + me.setEntries(entries) + me.mappingSlot(mappingSlot) + return me + } + + private func makeStorageUpdateStorageSlot(_ slot: LambdaStorageSlot) -> LambdaStorageUpdate { + var u = LambdaStorageUpdate() + u.setStorageSlot(slot) + return u + } + + private func makeStorageUpdateMappingEntries(_ entries: LambdaMappingEntries) -> LambdaStorageUpdate { + var u = LambdaStorageUpdate() + u.setMappingEntries(entries) + return u + } + + private func makeLambdaEvmHook() -> LambdaEvmHook { + let key1 = Data([0x01, 0x23, 0x45]) + let key2 = Data([0x67, 0x89, 0xAB]) + let preimage = Data([0xCD, 0xEF, 0x02]) + + let value1 = Data([0x04, 0x06, 0x08]) + let value2 = Data([0x0A, 0x0C, 0x0E]) + let value3 = Data([0x11, 0x13, 0x15]) + + let mappingSlot = Data([0x17, 0x19, 0x1B]) + + let slot = makeStorageSlot(key1, value1) + let update1 = makeStorageUpdateStorageSlot(slot) + + let e1 = makeEntryWithKey(key2, value2) + let e2 = makeEntryWithPreimage(preimage, value3) + let me = makeMappingEntries(mappingSlot, [e1, e2]) + let update2 = makeStorageUpdateMappingEntries(me) + + var hook = LambdaEvmHook() + hook.addStorageUpdate(update1) + hook.addStorageUpdate(update2) + return hook + } + + private func makeAdminKey() throws -> Key { + let priv = PrivateKey.generateEcdsa() + return .single(priv.publicKey) + } + + func test_GetSetHookId() throws { + // Given + var details = HookCreationDetails(hookExtensionPoint: .accountAllowanceHook) + + // When + details.hookId(1) + + // Then + XCTAssertEqual(details.hookId, 1) + } + + func test_GetSetLambdaEvmHook() { + // Given + var details = HookCreationDetails(hookExtensionPoint: .accountAllowanceHook) + let lambda = makeLambdaEvmHook() + + // When + details.lambdaEvmHook(lambda) + + // Then + XCTAssertNotNil(details.lambdaEvmHook) + XCTAssertEqual(details.lambdaEvmHook?.storageUpdates.count, lambda.storageUpdates.count) + } + + func test_GetSetAdminKey() throws { + // Given + var details = HookCreationDetails(hookExtensionPoint: .accountAllowanceHook) + let key = try makeAdminKey() + + // When + details.adminKey(key) + + // Then + // Round-trip bytes check if your Key exposes them; otherwise just assert non-nil. + XCTAssertNotNil(details.adminKey) + XCTAssertEqual(details.adminKey?.toBytes(), key.toBytes()) + } + + func test_FromProtobuf() throws { + // Given + var proto = Com_Hedera_Hapi_Node_Hooks_HookCreationDetails() + proto.extensionPoint = HookExtensionPoint.accountAllowanceHook.toProtobuf() + proto.hookID = 1 + proto.lambdaEvmHook = makeLambdaEvmHook().toProtobuf() + + let key = try makeAdminKey() + proto.adminKey = key.toProtobuf() + + // When + let decoded = try HookCreationDetails.fromProtobuf(proto) + + // Then + XCTAssertEqual(decoded.hookExtensionPoint, .accountAllowanceHook) + XCTAssertEqual(decoded.hookId, 1) + XCTAssertNotNil(decoded.lambdaEvmHook) + XCTAssertNotNil(decoded.adminKey) + } + + func test_ToProtobuf() throws { + // Given + var details = HookCreationDetails(hookExtensionPoint: .accountAllowanceHook) + details.hookId(1) + details.lambdaEvmHook(makeLambdaEvmHook()) + + let key = try makeAdminKey() + details.adminKey(key) + + // When + let proto = details.toProtobuf() + + // Then + XCTAssertEqual(proto.extensionPoint, HookExtensionPoint.accountAllowanceHook.toProtobuf()) + XCTAssertEqual(proto.hookID, 1) + XCTAssertEqual(proto.hook, .lambdaEvmHook(proto.lambdaEvmHook)) + XCTAssertTrue(proto.hasAdminKey) + } +} diff --git a/Tests/HieroTests/HookEntityIdTests.swift b/Tests/HieroTests/HookEntityIdTests.swift new file mode 100644 index 00000000..e9437ab7 --- /dev/null +++ b/Tests/HieroTests/HookEntityIdTests.swift @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 + +import HieroProtobufs +import XCTest + +@testable import Hiero // TODO: Replace with your Swift target/module name + +final class HookEntityIdUnitTests: XCTestCase { + + private let testAccountId = AccountId(shard: 1, realm: 2, num: 3) + + func test_GetSetAccountId() { + // Given + let hookEntityId = HookEntityId(AccountId(num: 0)) + + // When + hookEntityId.accountId(testAccountId) + + // Then + XCTAssertEqual(hookEntityId.accountId, testAccountId) + } + + func test_FromProtobuf() throws { + // Given + var protoMsg = Proto_HookEntityId() + protoMsg.accountID = testAccountId.toProtobuf() + + // When + let hookEntityId = try HookEntityId.fromProtobuf(protoMsg) + + // Then + XCTAssertEqual(hookEntityId.accountId, testAccountId) + } + + func test_ToProtobuf() { + // Given + let hookEntityId = HookEntityId(testAccountId) + + // When + let protoMsg = hookEntityId.toProtobuf() + + // Then + XCTAssertEqual(UInt64(truncatingIfNeeded: protoMsg.accountID.shardNum), testAccountId.shard) + XCTAssertEqual(UInt64(truncatingIfNeeded: protoMsg.accountID.realmNum), testAccountId.realm) + XCTAssertEqual(UInt64(truncatingIfNeeded: protoMsg.accountID.accountNum), testAccountId.num) + } +} diff --git a/Tests/HieroTests/HookIdTests.swift b/Tests/HieroTests/HookIdTests.swift new file mode 100644 index 00000000..f4dbb746 --- /dev/null +++ b/Tests/HieroTests/HookIdTests.swift @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 + +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class HookIdUnitTests: XCTestCase { + + private let testAccountId = AccountId(shard: 1, realm: 2, num: 3) + private let testHookId: Int64 = 4 + private var testHookEntityId: HookEntityId { + return HookEntityId(testAccountId) + } + + func test_GetSetEntityId() { + // Given + var hookId = HookId() + + // When + hookId.entityId(testHookEntityId) + + // Then + XCTAssertNotNil(hookId.entityId.accountId) + XCTAssertEqual(hookId.entityId.accountId, testAccountId) + } + + func test_GetSetHookId() { + // Given + var hookId = HookId() + + // When + hookId.hookId(testHookId) + + // Then + XCTAssertEqual(hookId.hookId, testHookId) + } + + func test_FromProtobuf() throws { + // Given + var proto = Proto_HookId() + proto.entityID = testHookEntityId.toProtobuf() + proto.hookID = testHookId + + // When + let hookId = try HookId.fromProtobuf(proto) + + // Then + XCTAssertNotNil(hookId.entityId.accountId) + XCTAssertEqual(hookId.entityId.accountId, testAccountId) + XCTAssertEqual(hookId.hookId, testHookId) + } + + func test_ToProtobuf() { + // Given + let hookId = HookId(entityId: testHookEntityId, hookId: testHookId) + + // When + let proto = hookId.toProtobuf() + + // Then + XCTAssertTrue(proto.hasEntityID) + XCTAssertEqual(UInt64(truncatingIfNeeded: proto.entityID.accountID.shardNum), testAccountId.shard) + XCTAssertEqual(UInt64(truncatingIfNeeded: proto.entityID.accountID.realmNum), testAccountId.realm) + XCTAssertEqual(UInt64(truncatingIfNeeded: proto.entityID.accountID.accountNum), testAccountId.num) + } +} diff --git a/Tests/HieroTests/LambdaEvmHookTests.swift b/Tests/HieroTests/LambdaEvmHookTests.swift new file mode 100644 index 00000000..fa802713 --- /dev/null +++ b/Tests/HieroTests/LambdaEvmHookTests.swift @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class LambdaEvmHookUnitTests: XCTestCase { + + private func makeStorageSlot(key: Data, value: Data) -> LambdaStorageSlot { + var s = LambdaStorageSlot() + s.key(key) + s.value(value) + return s + } + + private func makeEntryWithKey(_ key: Data, value: Data) -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.key(key) + e.value(value) + return e + } + + private func makeEntryWithPreimage(_ preimage: Data, value: Data) -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.preimage(preimage) + e.value(value) + return e + } + + private func makeMappingEntries(mappingSlot: Data, _ entries: [LambdaMappingEntry]) -> LambdaMappingEntries { + var me = LambdaMappingEntries() + me.mappingSlot(mappingSlot) + me.setEntries(entries) + return me + } + + private func makeStorageUpdateStorageSlot(_ slot: LambdaStorageSlot) -> LambdaStorageUpdate { + var u = LambdaStorageUpdate() + u.setStorageSlot(slot) + return u + } + + private func makeStorageUpdateMappingEntries(_ entries: LambdaMappingEntries) -> LambdaStorageUpdate { + var u = LambdaStorageUpdate() + u.setMappingEntries(entries) + return u + } + + private func makeUpdates() -> [LambdaStorageUpdate] { + // test bytes + let key1 = Data([0x01, 0x23, 0x45]) + let key2 = Data([0x67, 0x89, 0xAB]) + let preimage = Data([0xCD, 0xEF, 0x02]) + + let value1 = Data([0x04, 0x06, 0x08]) + let value2 = Data([0x0A, 0x0C, 0x0E]) + let value3 = Data([0x11, 0x13, 0x15]) + + let mappingSlot = Data([0x17, 0x19, 0x1B]) + + let slot = makeStorageSlot(key: key1, value: value1) + let update1 = makeStorageUpdateStorageSlot(slot) + + let e1 = makeEntryWithKey(key2, value: value2) + let e2 = makeEntryWithPreimage(preimage, value: value3) + let me = makeMappingEntries(mappingSlot: mappingSlot, [e1, e2]) + let update2 = makeStorageUpdateMappingEntries(me) + + return [update1, update2] + } + + func test_GetSetStorageUpdates() { + // Given + var hook = LambdaEvmHook() + let updates = makeUpdates() + + // When + hook.setStorageUpdates(updates) + + // Then + XCTAssertEqual(hook.storageUpdates.count, updates.count) + } + + func test_AddStorageUpdate() { + // Given + var hook = LambdaEvmHook() + let updates = makeUpdates() + + // When + for u in updates { + hook.addStorageUpdate(u) + } + + // Then + XCTAssertEqual(hook.storageUpdates.count, updates.count) + } + + func test_ClearStorageUpdates() { + // Given + var hook = LambdaEvmHook() + hook.setStorageUpdates(makeUpdates()) + + // When + hook.clearStorageUpdates() + + // Then + XCTAssertTrue(hook.storageUpdates.isEmpty) + } + + func test_FromProtobuf() throws { + // Given + var proto = Com_Hedera_Hapi_Node_Hooks_LambdaEvmHook() + proto.storageUpdates = makeUpdates().map { $0.toProtobuf() } + + // When + let decoded = try LambdaEvmHook.fromProtobuf(proto) + + // Then + XCTAssertEqual(decoded.storageUpdates.count, proto.storageUpdates.count) + } + + func test_ToProtobuf() { + // Given + var hook = LambdaEvmHook() + let updates = makeUpdates() + hook.setStorageUpdates(updates) + + // When + let proto = hook.toProtobuf() + + // Then + XCTAssertEqual(proto.storageUpdates.count, updates.count) + } +} diff --git a/Tests/HieroTests/LambdaMappingEntriesTests.swift b/Tests/HieroTests/LambdaMappingEntriesTests.swift new file mode 100644 index 00000000..95152f02 --- /dev/null +++ b/Tests/HieroTests/LambdaMappingEntriesTests.swift @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class LambdaMappingEntriesUnitTests: XCTestCase { + + // Fixture-equivalent constants + private let mappingSlot = Data([0x17, 0x19, 0x1B]) + + private let key1 = Data([0x01, 0x23, 0x45]) + private let key3 = Data([0x67, 0x89, 0xAB]) + private let preimage2 = Data([0xCD, 0xEF, 0x02]) + + private let value1 = Data([0x04, 0x06, 0x08]) + private let value2 = Data([0x0A, 0x0C, 0x0E]) + private let value3 = Data([0x11, 0x13, 0x15]) + + private func makeEntry1() -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.key(key1) + e.value(value1) + return e + } + + private func makeEntry2() -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.preimage(preimage2) + e.value(value2) + return e + } + + private func makeEntry3() -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.key(key3) + e.value(value3) + return e + } + + private func makeEntries() -> [LambdaMappingEntry] { + [makeEntry1(), makeEntry2(), makeEntry3()] + } + + func test_GetSetMappingSlot() { + // Given + var entries = LambdaMappingEntries() + + // When + entries.mappingSlot(mappingSlot) + + // Then + XCTAssertEqual(entries.mappingSlot, mappingSlot) + } + + func test_GetSetEntries() { + // Given + var entries = LambdaMappingEntries() + + // When + entries.setEntries(makeEntries()) + + // Then + XCTAssertEqual(entries.entries.count, makeEntries().count) + } + + func test_AddEntry() { + // Given + var entries = LambdaMappingEntries() + let e1 = makeEntry1() + + // When + entries.addEntry(e1) + + // Then + XCTAssertEqual(entries.entries.count, 1) + XCTAssertNotNil(entries.entries[0].key) + XCTAssertEqual(entries.entries[0].key, key1) + XCTAssertEqual(entries.entries[0].value, value1) + } + + func test_ClearEntries() { + // Given + var entries = LambdaMappingEntries() + entries.setEntries(makeEntries()) + + // When + entries.clearEntries() + + // Then + XCTAssertTrue(entries.entries.isEmpty) + } + + func test_FromProtobuf() throws { + // Given + var proto = Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntries() + proto.mappingSlot = mappingSlot + proto.entries = makeEntries().map { $0.toProtobuf() } + + // When + let decoded = try LambdaMappingEntries.fromProtobuf(proto) + + // Then + XCTAssertEqual(decoded.mappingSlot, mappingSlot) + XCTAssertEqual(decoded.entries.count, makeEntries().count) + } + + func test_ToProtobuf() { + // Given + var entries = LambdaMappingEntries() + entries.mappingSlot(mappingSlot) + entries.setEntries(makeEntries()) + + // When + let proto = entries.toProtobuf() + + // Then + XCTAssertEqual(proto.mappingSlot, mappingSlot) + XCTAssertEqual(proto.entries.count, makeEntries().count) + } +} diff --git a/Tests/HieroTests/LambdaMappingEntryTests.swift b/Tests/HieroTests/LambdaMappingEntryTests.swift new file mode 100644 index 00000000..c9a9b278 --- /dev/null +++ b/Tests/HieroTests/LambdaMappingEntryTests.swift @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class LambdaMappingEntryUnitTests: XCTestCase { + + // Fixture-equivalent constants + private let testKey = Data([0x01, 0x23, 0x45]) + private let testPreimage = Data([0x67, 0x89, 0xAB]) + private let testValue = Data([0xCD, 0xEF, 0x02]) + + func test_GetSetKey() { + // Given + var entry = LambdaMappingEntry() + + // When + entry.key(testKey) + + // Then + XCTAssertNotNil(entry.key) + XCTAssertEqual(entry.key, testKey) + } + + func test_GetSetKeyResetPreimage() { + // Given + var entry = LambdaMappingEntry() + + // When + entry.key(testKey) + entry.preimage(testPreimage) + + // Then + XCTAssertNil(entry.key) + } + + func test_GetSetPreimage() { + // Given + var entry = LambdaMappingEntry() + + // When + entry.preimage(testPreimage) + + // Then + XCTAssertNotNil(entry.preimage) + XCTAssertEqual(entry.preimage, testPreimage) + } + + func test_GetSetPreimageResetsKey() { + // Given + var entry = LambdaMappingEntry() + + // When + entry.preimage(testPreimage) + entry.key(testKey) + + // Then + XCTAssertNil(entry.preimage) + } + + func test_GetSetValue() { + // Given + var entry = LambdaMappingEntry() + + // When + entry.value(testValue) + + // Then + XCTAssertEqual(entry.value, testValue) + } + + func test_FromProtobuf() throws { + // Given + var protoKey = Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry() + var protoPreimage = Com_Hedera_Hapi_Node_Hooks_LambdaMappingEntry() + + protoKey.key = testKey + protoKey.value = testValue + + protoPreimage.preimage = testPreimage + + // When + let entryKey = try LambdaMappingEntry.fromProtobuf(protoKey) + let entryPreimage = try LambdaMappingEntry.fromProtobuf(protoPreimage) + + // Then + XCTAssertNotNil(entryKey.key) + XCTAssertEqual(entryKey.key, testKey) + XCTAssertEqual(entryKey.value, testValue) + + XCTAssertNotNil(entryPreimage.preimage) + XCTAssertEqual(entryPreimage.preimage, testPreimage) + } + + func test_ToProtobuf() { + // Given + var entryKey = LambdaMappingEntry() + var entryPreimage = LambdaMappingEntry() + + entryKey.key(testKey) + entryKey.value(testValue) + + entryPreimage.preimage(testPreimage) + + // When + let protoKey = entryKey.toProtobuf() + let protoPreimage = entryPreimage.toProtobuf() + + // Then + // key path + XCTAssertEqual(protoKey.key, testKey) + XCTAssertEqual(protoKey.value, testValue) + + // preimage path + XCTAssertEqual(protoPreimage.preimage, testPreimage) + } +} diff --git a/Tests/HieroTests/LambdaStorageSlotTests.swift b/Tests/HieroTests/LambdaStorageSlotTests.swift new file mode 100644 index 00000000..97efcaac --- /dev/null +++ b/Tests/HieroTests/LambdaStorageSlotTests.swift @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class LambdaStorageSlotUnitTests: XCTestCase { + + // Fixture-equivalent constants + private let testKey = Data([0x01, 0x23, 0x45]) + private let testValue = Data([0x67, 0x89, 0xAB]) + + func test_GetSetKey() { + // Given + var slot = LambdaStorageSlot() + + // When + slot.key(testKey) + + // Then + XCTAssertEqual(slot.key, testKey) + } + + func test_GetSetValue() { + // Given + var slot = LambdaStorageSlot() + + // When + slot.value(testValue) + + // Then + XCTAssertEqual(slot.value, testValue) + } + + func test_FromProtobuf() throws { + // Given + var proto = Com_Hedera_Hapi_Node_Hooks_LambdaStorageSlot() + proto.key = testKey + proto.value = testValue + + // When + let slot = try LambdaStorageSlot.fromProtobuf(proto) + + // Then + XCTAssertEqual(slot.key, testKey) + XCTAssertEqual(slot.value, testValue) + } + + func test_ToProtobuf() { + // Given + var slot = LambdaStorageSlot() + slot.key(testKey) + slot.value(testValue) + + // When + let proto = slot.toProtobuf() + + // Then + XCTAssertEqual(proto.key, testKey) + XCTAssertEqual(proto.value, testValue) + } +} diff --git a/Tests/HieroTests/LambdaStorageUpdateTests.swift b/Tests/HieroTests/LambdaStorageUpdateTests.swift new file mode 100644 index 00000000..a2913c60 --- /dev/null +++ b/Tests/HieroTests/LambdaStorageUpdateTests.swift @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class LambdaStorageUpdateUnitTests: XCTestCase { + + // Fixture-equivalent constants + private let mappingSlot = Data([0x17, 0x19, 0x1B]) + + private let key1 = Data([0x01, 0x23, 0x45]) + private let key3 = Data([0x67, 0x89, 0xAB]) + + private let preimage2 = Data([0xCD, 0xEF, 0x02]) + + private let value1 = Data([0x04, 0x06, 0x08]) + private let value2 = Data([0x0A, 0x0C, 0x0E]) + private let value3 = Data([0x11, 0x13, 0x15]) + + private func makeEntry1() -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.key(key1) + e.value(value1) + return e + } + + private func makeEntry2() -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.preimage(preimage2) + e.value(value2) + return e + } + + private func makeEntry3() -> LambdaMappingEntry { + var e = LambdaMappingEntry() + e.key(key3) + e.value(value3) + return e + } + + private func makeEntries() -> LambdaMappingEntries { + var le = LambdaMappingEntries() + le.setEntries([makeEntry1(), makeEntry2(), makeEntry3()]) + le.mappingSlot(mappingSlot) + return le + } + + private func makeStorageSlot() -> LambdaStorageSlot { + var s = LambdaStorageSlot() + s.key(key1) + s.value(value1) + return s + } + + func test_GetSetStorageSlot() { + // Given + var update = LambdaStorageUpdate() + + // When + update.setStorageSlot(makeStorageSlot()) + + // Then + XCTAssertNotNil(update.storageSlot) + XCTAssertEqual(update.storageSlot?.key, key1) + XCTAssertEqual(update.storageSlot?.value, value1) + } + + func test_GetSetMappingEntries() { + // Given + var update = LambdaStorageUpdate() + let entries = makeEntries() + + // When + update.setMappingEntries(entries) + + // Then + XCTAssertNotNil(update.mappingEntries) + XCTAssertEqual(update.mappingEntries?.entries.count, entries.entries.count) + } + + func test_SetStorageSlotResetsMappingEntries() { + // Given + var update = LambdaStorageUpdate() + + // When + update.setMappingEntries(makeEntries()) + update.setStorageSlot(makeStorageSlot()) + + // Then + XCTAssertNil(update.mappingEntries) + } + + func test_SetMappingEntriesResetsStorageSlot() { + // Given + var update = LambdaStorageUpdate() + + // When + update.setStorageSlot(makeStorageSlot()) + update.setMappingEntries(makeEntries()) + + // Then + XCTAssertNil(update.storageSlot) + } + + func test_FromProtobuf() throws { + // Given + var protoSlot = Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate() + protoSlot.storageSlot = makeStorageSlot().toProtobuf() + + var protoEntries = Com_Hedera_Hapi_Node_Hooks_LambdaStorageUpdate() + protoEntries.mappingEntries = makeEntries().toProtobuf() + + // When + let decodedSlot = try LambdaStorageUpdate.fromProtobuf(protoSlot) + let decodedEntries = try LambdaStorageUpdate.fromProtobuf(protoEntries) + + // Then + XCTAssertNotNil(decodedSlot.storageSlot) + XCTAssertEqual(decodedSlot.storageSlot?.key, key1) + XCTAssertEqual(decodedSlot.storageSlot?.value, value1) + + XCTAssertNotNil(decodedEntries.mappingEntries) + XCTAssertEqual(decodedEntries.mappingEntries?.entries.count, 3) + } + + func test_ToProtobuf() { + // Given + var updateSlot = LambdaStorageUpdate() + var updateEntries = LambdaStorageUpdate() + + updateSlot.setStorageSlot(makeStorageSlot()) + updateEntries.setMappingEntries(makeEntries()) + + // When + let protoSlot = updateSlot.toProtobuf() + let protoEntries = updateEntries.toProtobuf() + + // Then + // storageSlot path + XCTAssertEqual(protoSlot.storageSlot.key, key1) + XCTAssertEqual(protoSlot.storageSlot.value, value1) + + // mappingEntries path + XCTAssertEqual(protoEntries.mappingEntries.mappingSlot, mappingSlot) + XCTAssertEqual(protoEntries.mappingEntries.entries.count, 3) + } +} diff --git a/Tests/HieroTests/NFTHookCallTests.swift b/Tests/HieroTests/NFTHookCallTests.swift new file mode 100644 index 00000000..8ffa97a3 --- /dev/null +++ b/Tests/HieroTests/NFTHookCallTests.swift @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import HieroProtobufs +import XCTest + +@testable import Hiero + +final class NFTHookCallUnitTests: XCTestCase { + + // Fixture-equivalent constants + private let testAccountId = AccountId(shard: 1, realm: 2, num: 3) + private let testHookId: Int64 = 4 + private let testCallData = Data([0x56, 0x78, 0x9A]) + private let testGasLimit: UInt64 = 11 + + private var testHookEntityId: HookEntityId { + return HookEntityId(testAccountId) + } + + private var testFullHookId: HookId { + return HookId(entityId: testHookEntityId, hookId: testHookId) + } + + private var testEvmHookCall: EvmHookCall { + var c = EvmHookCall() + c.data = testCallData + c.gasLimit = testGasLimit + return c + } + + func test_DefaultInitialization() { + // Given & When + let nftHookCall = NftHookCall() + + // Then + XCTAssertEqual(nftHookCall.hookType, .uninitialized) + XCTAssertNil(nftHookCall.hookCall.fullHookId) + XCTAssertNil(nftHookCall.hookCall.hookId) + XCTAssertNil(nftHookCall.hookCall.evmHookCall) + } + + func test_CustomInitialization() { + // Given + let hookCall = HookCall() + let hookType = NftHookType.preHook + + // When + let nftHookCall = NftHookCall(hookCall: hookCall, hookType: hookType) + + // Then + XCTAssertEqual(nftHookCall.hookType, hookType) + XCTAssertNil(nftHookCall.hookCall.fullHookId) + XCTAssertNil(nftHookCall.hookCall.hookId) + XCTAssertNil(nftHookCall.hookCall.evmHookCall) + } + + func test_SetHookCall() { + // Given + var nftHookCall = NftHookCall() + let hookCall = HookCall() + + // When + nftHookCall.hookCall(hookCall) + + // Then + XCTAssertNil(nftHookCall.hookCall.fullHookId) + XCTAssertNil(nftHookCall.hookCall.hookId) + XCTAssertNil(nftHookCall.hookCall.evmHookCall) + } + + func test_SetHookType() { + // Given + var nftHookCall = NftHookCall() + + // When + nftHookCall.hookType(.preHook) + + // Then + XCTAssertEqual(nftHookCall.hookType, .preHook) + } + + func test_SetFullHookId() { + // Given + var nftHookCall = NftHookCall() + + // When + nftHookCall.fullHookId(testFullHookId) + + // Then + XCTAssertNotNil(nftHookCall.hookCall.fullHookId?.entityId.accountId) + XCTAssertEqual(nftHookCall.hookCall.fullHookId?.entityId.accountId, testAccountId) + XCTAssertEqual(nftHookCall.hookCall.fullHookId?.hookId, testHookId) + } + + func test_SetHookId() { + // Given + var nftHookCall = NftHookCall() + + // When + nftHookCall.hookId(testHookId) + + // Then + XCTAssertEqual(nftHookCall.hookCall.hookId, testHookId) + } + + func test_SetEvmHookCall() { + // Given + var nftHookCall = NftHookCall() + + // When + nftHookCall.evmHookCall(testEvmHookCall) + + // Then + XCTAssertNotNil(nftHookCall.hookCall.evmHookCall) + XCTAssertEqual(nftHookCall.hookCall.evmHookCall?.data, testCallData) + XCTAssertEqual(nftHookCall.hookCall.evmHookCall?.gasLimit, testGasLimit) + } + + func test_FromProtobuf() throws { + // Given + var protoFull = Proto_HookCall() + var protoHookIdOnly = Proto_HookCall() + + // full_hook_id + evm_hook_call + protoFull.fullHookID = testFullHookId.toProtobuf() + protoFull.evmHookCall = testEvmHookCall.toProtobuf() + + // hook_id only + protoHookIdOnly.hookID = testHookId + + // When + let nftHookCallFull = try NftHookCall.fromProtobuf(protoFull) + let nftHookCallHookOnly = try NftHookCall.fromProtobuf(protoHookIdOnly) + + // Then + XCTAssertNotNil(nftHookCallFull.hookCall.fullHookId?.entityId.accountId) + XCTAssertEqual(nftHookCallFull.hookCall.fullHookId?.entityId.accountId, testAccountId) + XCTAssertEqual(nftHookCallFull.hookCall.fullHookId?.hookId, testHookId) + + XCTAssertNotNil(nftHookCallFull.hookCall.evmHookCall) + XCTAssertEqual(nftHookCallFull.hookCall.evmHookCall?.data, testCallData) + XCTAssertEqual(nftHookCallFull.hookCall.evmHookCall?.gasLimit, testGasLimit) + + XCTAssertEqual(nftHookCallHookOnly.hookCall.hookId, testHookId) + + // Hook type should be uninitialized since it's not stored in protobuf + XCTAssertEqual(nftHookCallFull.hookType, .uninitialized) + XCTAssertEqual(nftHookCallHookOnly.hookType, .uninitialized) + } + + func test_ToProtobuf() { + // Given + var nftHookCallFull = NftHookCall() + var nftHookCallHookOnly = NftHookCall() + + nftHookCallFull.hookCall.fullHookId = testFullHookId + nftHookCallFull.hookCall.evmHookCall = testEvmHookCall + nftHookCallFull.hookType = .preHook + + nftHookCallHookOnly.hookCall.hookId = testHookId + nftHookCallHookOnly.hookType = .prePostHook + + // When + let protoFull = nftHookCallFull.toProtobuf() + let protoHookOnly = nftHookCallHookOnly.toProtobuf() + + // Then + XCTAssertTrue(protoFull.fullHookID.hasEntityID) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.shardNum), testAccountId.shard) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.realmNum), testAccountId.realm) + XCTAssertEqual( + UInt64(truncatingIfNeeded: protoFull.fullHookID.entityID.accountID.accountNum), testAccountId.num) + XCTAssertEqual(protoFull.fullHookID.hookID, testHookId) + + XCTAssertEqual(protoFull.evmHookCall.data, testCallData) + XCTAssertEqual(protoFull.evmHookCall.gasLimit, testGasLimit) + + XCTAssertEqual(protoHookOnly.hookID, testHookId) + } +} diff --git a/Tests/HieroTests/NFTHookTypeTests.swift b/Tests/HieroTests/NFTHookTypeTests.swift new file mode 100644 index 00000000..e8fb2bc3 --- /dev/null +++ b/Tests/HieroTests/NFTHookTypeTests.swift @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import XCTest + +@testable import Hiero + +final class NFTHookTypeUnitTests: XCTestCase { + + func test_AllCases() { + // Given & When + let allCases = NftHookType.allCases + + // Then + XCTAssertEqual(allCases.count, 3) + XCTAssertTrue(allCases.contains(.preHook)) + XCTAssertTrue(allCases.contains(.prePostHook)) + XCTAssertTrue(allCases.contains(.uninitialized)) + } + + func test_Description() { + // Given & When & Then + XCTAssertEqual(NftHookType.preHook.description, "PRE_HOOK") + XCTAssertEqual(NftHookType.prePostHook.description, "PRE_POST_HOOK") + XCTAssertEqual(NftHookType.uninitialized.description, "UNINITIALIZED") + } + + func test_Equality() { + // Given & When & Then + XCTAssertEqual(NftHookType.preHook, NftHookType.preHook) + XCTAssertEqual(NftHookType.prePostHook, NftHookType.prePostHook) + XCTAssertEqual(NftHookType.uninitialized, NftHookType.uninitialized) + + XCTAssertNotEqual(NftHookType.preHook, NftHookType.prePostHook) + XCTAssertNotEqual(NftHookType.preHook, NftHookType.uninitialized) + XCTAssertNotEqual(NftHookType.prePostHook, NftHookType.uninitialized) + } + + func test_Hashable() { + // Given + let set: Set = [.preHook, .prePostHook, .uninitialized] + + // When & Then + XCTAssertEqual(set.count, 3) + XCTAssertTrue(set.contains(.preHook)) + XCTAssertTrue(set.contains(.prePostHook)) + XCTAssertTrue(set.contains(.uninitialized)) + } +} diff --git a/protobufs b/protobufs index c4f62d04..3fd256e2 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c4f62d047ec898519710feab6882beebe70a124d +Subproject commit 3fd256e226c24e3d7fea459e6bd2361704010515