diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b6b4c51c..e361fec4 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -158,7 +158,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "7718b764027c2a4b9f654ed0b712b87ce881348f" + "revision" : "728cede1b36eb24c5c6a41e509a0358c1bd97f9c" } } ], diff --git a/Package.resolved b/Package.resolved index fee874b6..0f56c2ab 100644 --- a/Package.resolved +++ b/Package.resolved @@ -104,7 +104,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "7718b764027c2a4b9f654ed0b712b87ce881348f" + "revision" : "728cede1b36eb24c5c6a41e509a0358c1bd97f9c" } } ], diff --git a/Package.swift b/Package.swift index b29b6954..447013c7 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.14.0"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"), - .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "7718b764027c2a4b9f654ed0b712b87ce881348f") + .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "728cede1b36eb24c5c6a41e509a0358c1bd97f9c") ], targets: [ .target( diff --git a/Sources/ZcashLightClientKit/Error/ZcashError.swift b/Sources/ZcashLightClientKit/Error/ZcashError.swift index 4c3901bb..eb853a9e 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashError.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashError.swift @@ -291,6 +291,9 @@ public enum ZcashError: Equatable, Error { /// - `rustError` contains error generated by the rust layer. /// ZRUST0049 case rustSuggestScanRanges(_ rustError: String) + /// Invalid transaction ID length when calling ZcashRustBackend.getMemo + /// ZRUST0050 + case rustGetMemoInvalidTxIdLength /// SQLite query failed when fetching all accounts from the database. /// - `sqliteError` is error produced by SQLite library. /// ZADAO0001 @@ -624,6 +627,7 @@ public enum ZcashError: Equatable, Error { case .rustPutSaplingSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots" case .rustUpdateChainTip: return "Error from rust layer when calling ZcashRustBackend.updateChainTip" case .rustSuggestScanRanges: return "Error from rust layer when calling ZcashRustBackend.suggestScanRanges" + case .rustGetMemoInvalidTxIdLength: return "txId must be 32 bytes" case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database." case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them." case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database." @@ -786,6 +790,7 @@ public enum ZcashError: Equatable, Error { case .rustPutSaplingSubtreeRoots: return .rustPutSaplingSubtreeRoots case .rustUpdateChainTip: return .rustUpdateChainTip case .rustSuggestScanRanges: return .rustSuggestScanRanges + case .rustGetMemoInvalidTxIdLength: return .rustGetMemoInvalidTxIdLength case .accountDAOGetAll: return .accountDAOGetAll case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode case .accountDAOFindBy: return .accountDAOFindBy diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift index 9648861d..60f12f20 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift @@ -161,6 +161,8 @@ public enum ZcashErrorCode: String { case rustUpdateChainTip = "ZRUST0048" /// Error from rust layer when calling ZcashRustBackend.suggestScanRanges case rustSuggestScanRanges = "ZRUST0049" + /// Invalid transaction ID length when calling ZcashRustBackend.getMemo + case rustGetMemoInvalidTxIdLength = "ZRUST0050" /// SQLite query failed when fetching all accounts from the database. case accountDAOGetAll = "ZADAO0001" /// Fetched accounts from SQLite but can't decode them. diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index 6641b070..9551a4f7 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -70,31 +70,38 @@ actor ZcashRustBackend: ZcashRustBackendWelding { to address: String, value: Int64, memo: MemoBytes? - ) async throws -> Int64 { - let result = usk.bytes.withUnsafeBufferPointer { uskPtr in - zcashlc_create_to_address( - dbData.0, - dbData.1, - uskPtr.baseAddress, - UInt(usk.bytes.count), - [CChar](address.utf8CString), - value, - memo?.bytes, - spendParamsPath.0, - spendParamsPath.1, - outputParamsPath.0, - outputParamsPath.1, - networkType.networkId, - minimumConfirmations, - useZIP317Fees - ) + ) async throws -> Data { + var contiguousTxIdBytes = ContiguousArray([UInt8](repeating: 0x0, count: 32)) + + let success = contiguousTxIdBytes.withUnsafeMutableBufferPointer { txIdBytePtr in + usk.bytes.withUnsafeBufferPointer { uskPtr in + zcashlc_create_to_address( + dbData.0, + dbData.1, + uskPtr.baseAddress, + UInt(usk.bytes.count), + [CChar](address.utf8CString), + value, + memo?.bytes, + spendParamsPath.0, + spendParamsPath.1, + outputParamsPath.0, + outputParamsPath.1, + networkType.networkId, + minimumConfirmations, + useZIP317Fees, + txIdBytePtr.baseAddress + ) + } } - guard result > 0 else { + guard success else { throw ZcashError.rustCreateToAddress(lastErrorMessage(fallback: "`createToAddress` failed with unknown error")) } - return result + return contiguousTxIdBytes.withUnsafeBufferPointer { txIdBytePtr in + Data(txIdBytePtr) + } } func decryptAndStoreTransaction(txBytes: [UInt8], minedHeight: Int32) async throws { @@ -179,25 +186,16 @@ actor ZcashRustBackend: ZcashRustBackendWelding { return UnifiedAddress(validatedEncoding: address, networkType: networkType) } - func getReceivedMemo(idNote: Int64) async -> Memo? { + func getMemo(txId: Data, outputIndex: UInt16) async throws -> Memo? { + guard txId.count == 32 else { + throw ZcashError.rustGetMemoInvalidTxIdLength + } + var contiguousMemoBytes = ContiguousArray(MemoBytes.empty().bytes) var success = false contiguousMemoBytes.withUnsafeMutableBufferPointer { memoBytePtr in - success = zcashlc_get_received_memo(dbData.0, dbData.1, idNote, memoBytePtr.baseAddress, networkType.networkId) - } - - guard success else { return nil } - - return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() } - } - - func getSentMemo(idNote: Int64) async -> Memo? { - var contiguousMemoBytes = ContiguousArray(MemoBytes.empty().bytes) - var success = false - - contiguousMemoBytes.withUnsafeMutableBytes { memoBytePtr in - success = zcashlc_get_sent_memo(dbData.0, dbData.1, idNote, memoBytePtr.baseAddress, networkType.networkId) + success = zcashlc_get_memo(dbData.0, dbData.1, txId.bytes, outputIndex, memoBytePtr.baseAddress, networkType.networkId) } guard success else { return nil } @@ -607,30 +605,37 @@ actor ZcashRustBackend: ZcashRustBackendWelding { usk: UnifiedSpendingKey, memo: MemoBytes?, shieldingThreshold: Zatoshi - ) async throws -> Int64 { - let result = usk.bytes.withUnsafeBufferPointer { uskBuffer in - zcashlc_shield_funds( - dbData.0, - dbData.1, - uskBuffer.baseAddress, - UInt(usk.bytes.count), - memo?.bytes, - UInt64(shieldingThreshold.amount), - spendParamsPath.0, - spendParamsPath.1, - outputParamsPath.0, - outputParamsPath.1, - networkType.networkId, - minimumConfirmations, - useZIP317Fees - ) + ) async throws -> Data { + var contiguousTxIdBytes = ContiguousArray([UInt8](repeating: 0x0, count: 32)) + + let success = contiguousTxIdBytes.withUnsafeMutableBufferPointer { txIdBytePtr in + usk.bytes.withUnsafeBufferPointer { uskBuffer in + zcashlc_shield_funds( + dbData.0, + dbData.1, + uskBuffer.baseAddress, + UInt(usk.bytes.count), + memo?.bytes, + UInt64(shieldingThreshold.amount), + spendParamsPath.0, + spendParamsPath.1, + outputParamsPath.0, + outputParamsPath.1, + networkType.networkId, + minimumConfirmations, + useZIP317Fees, + txIdBytePtr.baseAddress + ) + } } - guard result > 0 else { + guard success else { throw ZcashError.rustShieldFunds(lastErrorMessage(fallback: "`shieldFunds` failed with unknown error")) } - return result + return contiguousTxIdBytes.withUnsafeBufferPointer { txIdBytePtr in + Data(txIdBytePtr) + } } nonisolated func consensusBranchIdFor(height: Int32) throws -> Int32 { diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index 78367fb5..f601dd15 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -50,7 +50,7 @@ protocol ZcashRustBackendWelding { to address: String, value: Int64, memo: MemoBytes? - ) async throws -> Int64 + ) async throws -> Data /// Scans a transaction for any information that can be decrypted by the accounts in the wallet, and saves it to the wallet. /// - parameter tx: the transaction to decrypt @@ -89,14 +89,10 @@ protocol ZcashRustBackendWelding { /// - `rustGetNextAvailableAddressInvalidAddress` if generated unified address isn't valid. func getNextAvailableAddress(account: Int32) async throws -> UnifiedAddress - /// Get received memo from note. - /// - parameter idNote: note_id of note where the memo is located - func getReceivedMemo(idNote: Int64) async -> Memo? - - /// Get sent memo from note. - /// - parameter idNote: note_id of note where the memo is located - /// - Returns: a `Memo` if any - func getSentMemo(idNote: Int64) async -> Memo? + /// Get memo from note. + /// - parameter txId: ID of transaction containing the note + /// - parameter outputIndex: output index of note + func getMemo(txId: Data, outputIndex: UInt16) async throws -> Memo? /// Get the verified cached transparent balance for the given address /// - parameter account; the account index to query @@ -223,7 +219,7 @@ protocol ZcashRustBackendWelding { usk: UnifiedSpendingKey, memo: MemoBytes?, shieldingThreshold: Zatoshi - ) async throws -> Int64 + ) async throws -> Data /// Gets the consensus branch id for the given height /// - Parameter height: the height you what to know the branch id for diff --git a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift index 05f1407e..9665970a 100644 --- a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift @@ -71,7 +71,7 @@ class WalletTransactionEncoder: TransactionEncoder { ) logger.debug("transaction id: \(txId)") - return try await repository.find(id: txId) + return try await repository.find(rawID: txId) } func createSpend( @@ -80,7 +80,7 @@ class WalletTransactionEncoder: TransactionEncoder { to address: String, memoBytes: MemoBytes?, from accountIndex: Int - ) async throws -> Int { + ) async throws -> Data { guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else { throw ZcashError.walletTransEncoderCreateTransactionMissingSaplingParams } @@ -92,7 +92,7 @@ class WalletTransactionEncoder: TransactionEncoder { memo: memoBytes ) - return Int(txId) + return txId } func createShieldingTransaction( @@ -109,7 +109,7 @@ class WalletTransactionEncoder: TransactionEncoder { ) logger.debug("transaction id: \(txId)") - return try await repository.find(id: txId) + return try await repository.find(rawID: txId) } func createShieldingSpend( @@ -117,7 +117,7 @@ class WalletTransactionEncoder: TransactionEncoder { shieldingThreshold: Zatoshi, memo: MemoBytes?, accountIndex: Int - ) async throws -> Int { + ) async throws -> Data { guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else { throw ZcashError.walletTransEncoderShieldFundsMissingSaplingParams } @@ -128,7 +128,7 @@ class WalletTransactionEncoder: TransactionEncoder { shieldingThreshold: shieldingThreshold ) - return Int(txId) + return txId } func submit( diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index a27cf0dc..1db61f80 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -2097,16 +2097,16 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { return createToAddressUskToValueMemoCallsCount > 0 } var createToAddressUskToValueMemoReceivedArguments: (usk: UnifiedSpendingKey, address: String, value: Int64, memo: MemoBytes?)? - var createToAddressUskToValueMemoReturnValue: Int64! - func setCreateToAddressUskToValueMemoReturnValue(_ param: Int64) async { + var createToAddressUskToValueMemoReturnValue: Data! + func setCreateToAddressUskToValueMemoReturnValue(_ param: Data) async { createToAddressUskToValueMemoReturnValue = param } - var createToAddressUskToValueMemoClosure: ((UnifiedSpendingKey, String, Int64, MemoBytes?) async throws -> Int64)? - func setCreateToAddressUskToValueMemoClosure(_ param: ((UnifiedSpendingKey, String, Int64, MemoBytes?) async throws -> Int64)?) async { + var createToAddressUskToValueMemoClosure: ((UnifiedSpendingKey, String, Int64, MemoBytes?) async throws -> Data)? + func setCreateToAddressUskToValueMemoClosure(_ param: ((UnifiedSpendingKey, String, Int64, MemoBytes?) async throws -> Data)?) async { createToAddressUskToValueMemoClosure = param } - func createToAddress(usk: UnifiedSpendingKey, to address: String, value: Int64, memo: MemoBytes?) async throws -> Int64 { + func createToAddress(usk: UnifiedSpendingKey, to address: String, value: Int64, memo: MemoBytes?) async throws -> Data { if let error = createToAddressUskToValueMemoThrowableError { throw error } @@ -2276,55 +2276,36 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { } } - // MARK: - getReceivedMemo + // MARK: - getMemo - var getReceivedMemoIdNoteCallsCount = 0 - var getReceivedMemoIdNoteCalled: Bool { - return getReceivedMemoIdNoteCallsCount > 0 + var getMemoTxIdOutputIndexThrowableError: Error? + func setGetMemoTxIdOutputIndexThrowableError(_ param: Error?) async { + getMemoTxIdOutputIndexThrowableError = param } - var getReceivedMemoIdNoteReceivedIdNote: Int64? - var getReceivedMemoIdNoteReturnValue: Memo? - func setGetReceivedMemoIdNoteReturnValue(_ param: Memo?) async { - getReceivedMemoIdNoteReturnValue = param + var getMemoTxIdOutputIndexCallsCount = 0 + var getMemoTxIdOutputIndexCalled: Bool { + return getMemoTxIdOutputIndexCallsCount > 0 } - var getReceivedMemoIdNoteClosure: ((Int64) async -> Memo?)? - func setGetReceivedMemoIdNoteClosure(_ param: ((Int64) async -> Memo?)?) async { - getReceivedMemoIdNoteClosure = param + var getMemoTxIdOutputIndexReceivedArguments: (txId: Data, outputIndex: UInt16)? + var getMemoTxIdOutputIndexReturnValue: Memo? + func setGetMemoTxIdOutputIndexReturnValue(_ param: Memo?) async { + getMemoTxIdOutputIndexReturnValue = param + } + var getMemoTxIdOutputIndexClosure: ((Data, UInt16) async throws -> Memo?)? + func setGetMemoTxIdOutputIndexClosure(_ param: ((Data, UInt16) async throws -> Memo?)?) async { + getMemoTxIdOutputIndexClosure = param } - func getReceivedMemo(idNote: Int64) async -> Memo? { - getReceivedMemoIdNoteCallsCount += 1 - getReceivedMemoIdNoteReceivedIdNote = idNote - if let closure = getReceivedMemoIdNoteClosure { - return await closure(idNote) - } else { - return getReceivedMemoIdNoteReturnValue + func getMemo(txId: Data, outputIndex: UInt16) async throws -> Memo? { + if let error = getMemoTxIdOutputIndexThrowableError { + throw error } - } - - // MARK: - getSentMemo - - var getSentMemoIdNoteCallsCount = 0 - var getSentMemoIdNoteCalled: Bool { - return getSentMemoIdNoteCallsCount > 0 - } - var getSentMemoIdNoteReceivedIdNote: Int64? - var getSentMemoIdNoteReturnValue: Memo? - func setGetSentMemoIdNoteReturnValue(_ param: Memo?) async { - getSentMemoIdNoteReturnValue = param - } - var getSentMemoIdNoteClosure: ((Int64) async -> Memo?)? - func setGetSentMemoIdNoteClosure(_ param: ((Int64) async -> Memo?)?) async { - getSentMemoIdNoteClosure = param - } - - func getSentMemo(idNote: Int64) async -> Memo? { - getSentMemoIdNoteCallsCount += 1 - getSentMemoIdNoteReceivedIdNote = idNote - if let closure = getSentMemoIdNoteClosure { - return await closure(idNote) + getMemoTxIdOutputIndexCallsCount += 1 + getMemoTxIdOutputIndexReceivedArguments = (txId: txId, outputIndex: outputIndex) + if let closure = getMemoTxIdOutputIndexClosure { + return try await closure(txId, outputIndex) } else { - return getSentMemoIdNoteReturnValue + return getMemoTxIdOutputIndexReturnValue } } @@ -2685,16 +2666,16 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { return shieldFundsUskMemoShieldingThresholdCallsCount > 0 } var shieldFundsUskMemoShieldingThresholdReceivedArguments: (usk: UnifiedSpendingKey, memo: MemoBytes?, shieldingThreshold: Zatoshi)? - var shieldFundsUskMemoShieldingThresholdReturnValue: Int64! - func setShieldFundsUskMemoShieldingThresholdReturnValue(_ param: Int64) async { + var shieldFundsUskMemoShieldingThresholdReturnValue: Data! + func setShieldFundsUskMemoShieldingThresholdReturnValue(_ param: Data) async { shieldFundsUskMemoShieldingThresholdReturnValue = param } - var shieldFundsUskMemoShieldingThresholdClosure: ((UnifiedSpendingKey, MemoBytes?, Zatoshi) async throws -> Int64)? - func setShieldFundsUskMemoShieldingThresholdClosure(_ param: ((UnifiedSpendingKey, MemoBytes?, Zatoshi) async throws -> Int64)?) async { + var shieldFundsUskMemoShieldingThresholdClosure: ((UnifiedSpendingKey, MemoBytes?, Zatoshi) async throws -> Data)? + func setShieldFundsUskMemoShieldingThresholdClosure(_ param: ((UnifiedSpendingKey, MemoBytes?, Zatoshi) async throws -> Data)?) async { shieldFundsUskMemoShieldingThresholdClosure = param } - func shieldFunds(usk: UnifiedSpendingKey, memo: MemoBytes?, shieldingThreshold: Zatoshi) async throws -> Int64 { + func shieldFunds(usk: UnifiedSpendingKey, memo: MemoBytes?, shieldingThreshold: Zatoshi) async throws -> Data { if let error = shieldFundsUskMemoShieldingThresholdThrowableError { throw error } diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 7c15157b..6d9793cd 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -91,24 +91,19 @@ class RustBackendMockHelper { await rustBackendMock.setInitBlockMetadataDbClosure() { } await rustBackendMock.setWriteBlocksMetadataBlocksClosure() { _ in } await rustBackendMock.setInitAccountsTableUfvksClosure() { _ in } - await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1) - await rustBackendMock.setShieldFundsUskMemoShieldingThresholdReturnValue(-1) await rustBackendMock.setGetTransparentBalanceAccountReturnValue(0) await rustBackendMock.setGetVerifiedBalanceAccountReturnValue(0) await rustBackendMock.setListTransparentReceiversAccountReturnValue([]) await rustBackendMock.setGetCurrentAddressAccountThrowableError(ZcashError.rustGetCurrentAddress("mocked error")) await rustBackendMock.setGetNextAvailableAddressAccountThrowableError(ZcashError.rustGetNextAvailableAddress("mocked error")) - await rustBackendMock.setShieldFundsUskMemoShieldingThresholdReturnValue(-1) await rustBackendMock.setCreateAccountSeedThrowableError(ZcashError.rustInitAccountsTableViewingKeyCotainsNullBytes) - await rustBackendMock.setGetReceivedMemoIdNoteReturnValue(nil) - await rustBackendMock.setGetSentMemoIdNoteReturnValue(nil) - await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1) + await rustBackendMock.setGetMemoTxIdOutputIndexReturnValue(nil) await rustBackendMock.setInitDataDbSeedReturnValue(.seedRequired) await rustBackendMock.setGetNearestRewindHeightHeightReturnValue(-1) await rustBackendMock.setInitBlocksTableHeightHashTimeSaplingTreeClosure() { _, _, _, _ in } await rustBackendMock.setPutUnspentTransparentOutputTxidIndexScriptValueHeightClosure() { _, _, _, _, _ in } - await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1) - await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1) + await rustBackendMock.setCreateToAddressUskToValueMemoThrowableError(ZcashError.rustCreateToAddress("mocked error")) + await rustBackendMock.setShieldFundsUskMemoShieldingThresholdThrowableError(ZcashError.rustShieldFunds("mocked error")) await rustBackendMock.setDecryptAndStoreTransactionTxBytesMinedHeightThrowableError(ZcashError.rustDecryptAndStoreTransaction("mock fail")) await rustBackendMock.setInitDataDbSeedClosure() { seed in