From 403b9b5c3928cb267ba3e9102349c891263b3af7 Mon Sep 17 00:00:00 2001 From: Christian Rogobete Date: Tue, 4 Jun 2024 17:43:54 +0200 Subject: [PATCH] soroban server improvements: getAccount, getContractData --- stellarsdk/stellarsdk/sdk/Account.swift | 9 +++ stellarsdk/stellarsdk/sdk/MuxedAccount.swift | 2 +- .../stellarsdk/soroban/SorobanServer.swift | 69 +++++++++++++++++++ .../soroban/SorobanAtomicSwapTest.swift | 15 ++-- .../soroban/SorobanAuthTest.swift | 20 +++--- .../soroban/SorobanEventsTest.swift | 10 +-- .../stellarsdkTests/soroban/SorobanTest.swift | 35 +++++++--- 7 files changed, 128 insertions(+), 32 deletions(-) diff --git a/stellarsdk/stellarsdk/sdk/Account.swift b/stellarsdk/stellarsdk/sdk/Account.swift index 30379f31..bd91faba 100644 --- a/stellarsdk/stellarsdk/sdk/Account.swift +++ b/stellarsdk/stellarsdk/sdk/Account.swift @@ -24,6 +24,11 @@ public class Account: TransactionAccount self.sequenceNumber = sequenceNumber } + public init(accountId: String, sequenceNumber: Int64) throws { + self.keyPair = try KeyPair(accountId: accountId) + self.sequenceNumber = sequenceNumber + } + /// Returns sequence number incremented by one, but does not increment internal counter. public func incrementedSequenceNumber() -> Int64 { return sequenceNumber + 1 @@ -37,4 +42,8 @@ public class Account: TransactionAccount public func decrementSequenceNumber() { sequenceNumber -= 1 } + + public var accountId:String { + return keyPair.accountId + } } diff --git a/stellarsdk/stellarsdk/sdk/MuxedAccount.swift b/stellarsdk/stellarsdk/sdk/MuxedAccount.swift index 6e9d3523..4c3795c0 100644 --- a/stellarsdk/stellarsdk/sdk/MuxedAccount.swift +++ b/stellarsdk/stellarsdk/sdk/MuxedAccount.swift @@ -15,7 +15,7 @@ public class MuxedAccount: Account public private (set) var xdr: MuxedAccountXDR /// Human readable Stellar ed25519 or med25519 account ID. - public var accountId: String { + public override var accountId: String { get { return xdr.accountId } diff --git a/stellarsdk/stellarsdk/soroban/SorobanServer.swift b/stellarsdk/stellarsdk/soroban/SorobanServer.swift index 84fb7970..514dad11 100644 --- a/stellarsdk/stellarsdk/soroban/SorobanServer.swift +++ b/stellarsdk/stellarsdk/soroban/SorobanServer.swift @@ -60,6 +60,16 @@ public enum GetContractCodeResponseEnum { case failure(error: SorobanRpcRequestError) } +public enum GetAccountResponseEnum { + case success(response: Account) + case failure(error: SorobanRpcRequestError) +} + +public enum GetContractDataResponseEnum { + case success(response: LedgerEntry) + case failure(error: SorobanRpcRequestError) +} + /// A closure to be called with the response from a post challenge request. public typealias GetHealthResponseClosure = (_ response:GetHealthResponseEnum) -> (Void) public typealias GetNetworkResponseClosure = (_ response:GetNetworkResponseEnum) -> (Void) @@ -71,6 +81,8 @@ public typealias GetTransactionResponseClosure = (_ response:GetTransactionRespo public typealias GetEventsResponseClosure = (_ response:GetEventsResponseEnum) -> (Void) public typealias GetNonceResponseClosure = (_ response:GetNonceResponseEnum) -> (Void) public typealias GetContractCodeResponseClosure = (_ response:GetContractCodeResponseEnum) -> (Void) +public typealias GetAccountResponseClosure = (_ response:GetAccountResponseEnum) -> (Void) +public typealias GetContractDataResponseClosure = (_ response:GetContractDataResponseEnum) -> (Void) /// An enum to diferentiate between succesful and failed responses private enum RpcResult { @@ -300,6 +312,63 @@ public class SorobanServer { } } + /// Fetches a minimal set of current info about a Stellar account. Needed to get the current sequence + /// number for the account, so you can build a successful transaction. Fails if the account was not found or accountiId is invalid + public func getAccount(accountId:String, completion:@escaping GetAccountResponseClosure) { + if let publicKey = try? PublicKey(accountId: accountId) { + let accountKey = LedgerKeyXDR.account(LedgerKeyAccountXDR(accountID: publicKey)) + if let ledgerKeyBase64 = accountKey.xdrEncoded { + self.getLedgerEntries(base64EncodedKeys:[ledgerKeyBase64]) { (response) -> (Void) in + switch response { + case .success(let response): + let data = try? LedgerEntryDataXDR(fromBase64: response.entries[0].xdr) + if let accountData = data?.account { + let account = Account(keyPair: KeyPair(publicKey: accountData.accountID), sequenceNumber: accountData.sequenceNumber); + completion(.success(response: account)) + } + else { + completion(.failure(error: .requestFailed(message: "could not find account"))) + } + case .failure(let error): + completion(.failure(error: error)) + } + } + } else { + completion(.failure(error: .requestFailed(message: "could not create ledger key"))) + } + } else { + completion(.failure(error: .requestFailed(message: "invalid accountId"))) + } + + } + + /// Reads the current value of contract data ledger entries directly. + public func getContractData(contractId: String, key: SCValXDR, durability: ContractDataDurability, completion:@escaping GetContractDataResponseClosure) { + if let contractAddress = try? SCAddressXDR.init(contractId: contractId) { + let contractDataKey = LedgerKeyContractDataXDR(contract: contractAddress, key: key, durability: durability) + let ledgerKey = LedgerKeyXDR.contractData(contractDataKey) + if let ledgerKeyBase64 = ledgerKey.xdrEncoded { + self.getLedgerEntries(base64EncodedKeys:[ledgerKeyBase64]) { (response) -> (Void) in + switch response { + case .success(let response): + if let result = response.entries.first { + completion(.success(response: result)) + } + else { + completion(.failure(error: .requestFailed(message: "could not find contract data"))) + } + case .failure(let error): + completion(.failure(error: error)) + } + } + } else { + completion(.failure(error: .requestFailed(message: "could not create ledger key"))) + } + } else { + completion(.failure(error: .requestFailed(message: "invalid contractId"))) + } + } + /// Submit a trial contract invocation to get back return values, expected ledger footprint, and expected costs. /// See: https://soroban.stellar.org/api/methods/simulateTransaction public func simulateTransaction(simulateTxRequest: SimulateTransactionRequest, completion:@escaping SimulateTransactionResponseClosure) { diff --git a/stellarsdk/stellarsdkTests/soroban/SorobanAtomicSwapTest.swift b/stellarsdk/stellarsdkTests/soroban/SorobanAtomicSwapTest.swift index 95cd1dc9..9c71e98e 100644 --- a/stellarsdk/stellarsdkTests/soroban/SorobanAtomicSwapTest.swift +++ b/stellarsdk/stellarsdkTests/soroban/SorobanAtomicSwapTest.swift @@ -11,9 +11,8 @@ import XCTest import stellarsdk class SorobanAtomicSwapTest: XCTestCase { - // See https://soroban.stellar.org/docs/how-to-guides/atomic-swap - // See https://soroban.stellar.org/docs/learn/authorization - // See https://github.com/StellarCN/py-stellar-base/blob/soroban/examples/soroban_auth_atomic_swap.py + // See https://developers.stellar.org/docs/smart-contracts/example-contracts/atomic-swap + // See https://developers.stellar.org/docs/learn/smart-contract-internals/authorization let sorobanServer = SorobanServer(endpoint: "https://soroban-testnet.stellar.org") // SorobanServer(endpoint: "https://rpc-futurenet.stellar.org") let sdk = StellarSDK.testNet() // StellarSDK.futureNet() @@ -26,7 +25,7 @@ class SorobanAtomicSwapTest: XCTestCase { let tokenBId = "b6d208a3bf0b08ca12d4e1d2a39525caa9e866a0ba396ebe60307f9fbafd451f" let swapFunctionName = "swap" var invokeTransactionId:String? - var submitterAccount:AccountResponse? + var submitterAccount:Account? var latestLedger:UInt32? override func setUp() { @@ -57,11 +56,11 @@ class SorobanAtomicSwapTest: XCTestCase { let expectation = XCTestExpectation(description: "get account response received") let accountId = submitterKeyPair.accountId - sdk.accounts.getAccountDetails(accountId: accountId) { (response) -> (Void) in + sorobanServer.getAccount(accountId: accountId) { (response) -> (Void) in switch response { - case .success(let accResponse): - XCTAssertEqual(accountId, accResponse.accountId) - self.submitterAccount = accResponse + case .success(let account): + XCTAssertEqual(accountId, account.accountId) + self.submitterAccount = account expectation.fulfill() case .failure(_): XCTFail() diff --git a/stellarsdk/stellarsdkTests/soroban/SorobanAuthTest.swift b/stellarsdk/stellarsdkTests/soroban/SorobanAuthTest.swift index 6bded5a2..bab4b4aa 100644 --- a/stellarsdk/stellarsdkTests/soroban/SorobanAuthTest.swift +++ b/stellarsdk/stellarsdkTests/soroban/SorobanAuthTest.swift @@ -22,8 +22,8 @@ class SorobanAuthTest: XCTestCase { var createTransactionId:String? var contractId:String? var invokeTransactionId:String? - var senderAccount:AccountResponse? - var invokerAccount:AccountResponse? + var senderAccount:Account? + var invokerAccount:Account? var latestLedger:UInt32? override func setUp() { @@ -78,11 +78,11 @@ class SorobanAuthTest: XCTestCase { let expectation = XCTestExpectation(description: "current account data received") let accountId = senderKeyPair.accountId - sdk.accounts.getAccountDetails(accountId: accountId) { (response) -> (Void) in + sorobanServer.getAccount(accountId: accountId) { (response) -> (Void) in switch response { - case .success(let accResponse): - XCTAssertEqual(accountId, accResponse.accountId) - self.senderAccount = accResponse + case .success(let account): + XCTAssertEqual(accountId, account.accountId) + self.senderAccount = account expectation.fulfill() case .failure(_): XCTFail() @@ -331,11 +331,11 @@ class SorobanAuthTest: XCTestCase { let expectation = XCTestExpectation(description: "current account data received") let accountId = invokerKeyPair.accountId - sdk.accounts.getAccountDetails(accountId: accountId) { (response) -> (Void) in + sorobanServer.getAccount(accountId: accountId) { (response) -> (Void) in switch response { - case .success(let accResponse): - XCTAssertEqual(accountId, accResponse.accountId) - self.invokerAccount = accResponse + case .success(let account): + XCTAssertEqual(accountId, account.accountId) + self.invokerAccount = account expectation.fulfill() case .failure(_): XCTFail() diff --git a/stellarsdk/stellarsdkTests/soroban/SorobanEventsTest.swift b/stellarsdk/stellarsdkTests/soroban/SorobanEventsTest.swift index 8f1025ec..03067892 100644 --- a/stellarsdk/stellarsdkTests/soroban/SorobanEventsTest.swift +++ b/stellarsdk/stellarsdkTests/soroban/SorobanEventsTest.swift @@ -24,7 +24,7 @@ class SorobanEventsTest: XCTestCase { var createContractFootprint:Footprint? = nil var invokeTransactionId:String? = nil var invokeContractFootprint:Footprint? = nil - var submitterAccount:AccountResponse? + var submitterAccount:Account? var transactionLedger:Int? = nil override func setUp() { @@ -64,11 +64,11 @@ class SorobanEventsTest: XCTestCase { let expectation = XCTestExpectation(description: "current account data received") let accountId = submitterKeyPair.accountId - sdk.accounts.getAccountDetails(accountId: accountId) { (response) -> (Void) in + sorobanServer.getAccount(accountId: accountId) { (response) -> (Void) in switch response { - case .success(let accResponse): - XCTAssertEqual(accountId, accResponse.accountId) - self.submitterAccount = accResponse + case .success(let account): + XCTAssertEqual(accountId, account.accountId) + self.submitterAccount = account expectation.fulfill() case .failure(_): XCTFail() diff --git a/stellarsdk/stellarsdkTests/soroban/SorobanTest.swift b/stellarsdk/stellarsdkTests/soroban/SorobanTest.swift index 0c1036bd..0f810001 100644 --- a/stellarsdk/stellarsdkTests/soroban/SorobanTest.swift +++ b/stellarsdk/stellarsdkTests/soroban/SorobanTest.swift @@ -32,7 +32,7 @@ class SorobanTest: XCTestCase { var asset:Asset? = nil var deployWithAssetTransactionId:String? = nil var deployWithAssetFootprint:Footprint? = nil - var submitterAccount:AccountResponse? + var submitterAccount:Account? override func setUp() { super.setUp() @@ -93,7 +93,7 @@ class SorobanTest: XCTestCase { refreshSubmitterAccount() // test restore contract code footprint - // see: https://soroban.stellar.org/docs/fundamentals-and-concepts/state-expiration + // see: https://developers.stellar.org/docs/learn/smart-contract-internals/state-archival restoreContractCodeFootprint(fileName: "soroban_hello_world_contract") checkTransactionStatusSuccess(transactionId: self.restoreTransactionId!) getTransactionDetails(transactionHash: self.restoreTransactionId!, type:"restore_footprint") @@ -108,7 +108,7 @@ class SorobanTest: XCTestCase { getTransactionStatusError() // test bump contract code footprint - // see: https://soroban.stellar.org/docs/fundamentals-and-concepts/state-expiration + // see: https://developers.stellar.org/docs/learn/smart-contract-internals/state-archival refreshSubmitterAccount() extendContractCodeFootprintTTL(wasmId: self.wasmId!, ledgersToExpire: 10000) checkTransactionStatusSuccess(transactionId: self.bumpTransactionId!) @@ -132,6 +132,7 @@ class SorobanTest: XCTestCase { invokeContract() getInvokeTransactionStatus() getTransactionDetails(transactionHash: self.invokeTransactionId!, type:"HostFunctionTypeHostFunctionTypeInvokeContract") + getContractData() // test SAC with source account refreshSubmitterAccount() @@ -179,17 +180,16 @@ class SorobanTest: XCTestCase { let expectation = XCTestExpectation(description: "current account data received") let accountId = submitterKeyPair.accountId - sdk.accounts.getAccountDetails(accountId: accountId) { (response) -> (Void) in + sorobanServer.getAccount(accountId: accountId) { (response) -> (Void) in switch response { - case .success(let accResponse): - XCTAssertEqual(accountId, accResponse.accountId) - self.submitterAccount = accResponse + case .success(let account): + XCTAssertEqual(accountId, account.accountId) + self.submitterAccount = account expectation.fulfill() case .failure(_): XCTFail() } } - wait(for: [expectation], timeout: 10.0) } } @@ -783,6 +783,25 @@ class SorobanTest: XCTestCase { } } + func getContractData() { + XCTContext.runActivity(named: "getContractData") { activity in + let expectation = XCTestExpectation(description: "get ledger data") + // wait a couple of seconds before checking the status + self.sorobanServer.getContractData(contractId: self.contractId!, key: SCValXDR.ledgerKeyContractInstance, + durability: ContractDataDurability.persistent) { (response) -> (Void) in + switch response { + case .success(let response): + expectation.fulfill() + case .failure(let error): + self.printError(error: error) + XCTFail() + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 10.0) + } + } + func deploySACWithSourceAccount() { XCTContext.runActivity(named: "deploySACWithSourceAccount") { activity in let expectation = XCTestExpectation(description: "contract successfully deployed")