From d4313f8ca81462ee09af555c6ab8307d4d67b13c Mon Sep 17 00:00:00 2001 From: Bartosz Rybarski Date: Fri, 16 Jun 2023 15:29:26 +0200 Subject: [PATCH 1/7] Added simulateTransaction endpoint --- Sources/Starknet/Data/Events.swift | 10 + .../Data/Transaction/TransactionTrace.swift | 173 ++++++++++++++++++ .../StarknetProvider/JsonRpcMethod.swift | 1 + .../StarknetProvider/JsonRpcParams.swift | 41 ++++- .../StarknetProvider/StarknetProvider.swift | 9 + .../Providers/StarknetProviderProtocol.swift | 11 +- .../Providers/ProviderTests.swift | 25 +++ .../Utils/DevnetClient/DevnetClient.swift | 6 + requirements.txt | 2 +- 9 files changed, 267 insertions(+), 11 deletions(-) create mode 100644 Sources/Starknet/Data/Transaction/TransactionTrace.swift diff --git a/Sources/Starknet/Data/Events.swift b/Sources/Starknet/Data/Events.swift index e908ad80a..669e3fa39 100644 --- a/Sources/Starknet/Data/Events.swift +++ b/Sources/Starknet/Data/Events.swift @@ -56,3 +56,13 @@ public struct StarknetEvent: Decodable, Equatable { case data } } + +public struct StarknetEventContent: Decodable, Equatable { + public let keys: [Felt] + public let data: [Felt] + + enum CodingKeys: String, CodingKey { + case keys + case data + } +} diff --git a/Sources/Starknet/Data/Transaction/TransactionTrace.swift b/Sources/Starknet/Data/Transaction/TransactionTrace.swift new file mode 100644 index 000000000..80d9ae045 --- /dev/null +++ b/Sources/Starknet/Data/Transaction/TransactionTrace.swift @@ -0,0 +1,173 @@ +import Foundation + +public enum StarknetEntryPointType: String, Decodable { + case external = "EXTERNAL" + case l1Handler = "L1_HANDLER" + case constructor = "CONSTRUCTOR" +} + +public enum StarknetCallType: String, Decodable { + case call = "CALL" + case libraryCall = "LIBRARY_CALL" +} + +public struct StarknetFunctionInvocation: Decodable, Equatable { + public let contractAddress: Felt + public let entrypoint: Felt + public let calldata: StarknetCalldata + public let callerAddress: Felt + public let codeAddress: Felt + public let entryPointType: StarknetEntryPointType + public let callType: StarknetCallType + public let result: [Felt] + public let calls: [StarknetFunctionInvocation] + public let events: [StarknetEventContent] + public let messages: [MessageToL1] + + private enum CodingKeys: String, CodingKey { + case contractAddress = "contract_address" + case entrypoint + case calldata + case callerAddress = "caller_address" + case codeAddress = "code_address" + case entryPointType = "entry_point_type" + case callType = "call_type" + case result + case calls + case events + case messages + } +} + +public protocol StarknetTransactionTrace: Decodable, Equatable {} + +public struct StarknetInvokeTransactionTrace: StarknetTransactionTrace { + public let validateInvocation: StarknetFunctionInvocation? + public let executeInvocation: StarknetFunctionInvocation? + public let feeTransferInvocation: StarknetFunctionInvocation? + + private enum CodingKeys: String, CodingKey { + case validateInvocation = "validate_invocation" + case executeInvocation = "execute_invocation" + case feeTransferInvocation = "fee_transfer_invocation" + } +} + +public struct StarknetDeclareTransactionTrace: StarknetTransactionTrace { + public let validateInvocation: StarknetFunctionInvocation? + public let feeTransferInvocation: StarknetFunctionInvocation? + + private enum CodingKeys: String, CodingKey { + case validateInvocation = "validate_invocation" + case feeTransferInvocation = "fee_transfer_invocation" + } +} + +public struct StarknetDeployAccountTransactionTrace: StarknetTransactionTrace { + public let validateInvocation: StarknetFunctionInvocation? + public let constructorInvocation: StarknetFunctionInvocation? + public let feeTransferInvocation: StarknetFunctionInvocation? + + private enum CodingKeys: String, CodingKey { + case validateInvocation = "validate_invocation" + case constructorInvocation = "constructor_invocation" + case feeTransferInvocation = "fee_transfer_invocation" + } +} + +public struct StarknetL1HandlerTransactionTrace: StarknetTransactionTrace { + public let functionInvocation: StarknetFunctionInvocation? + + private enum CodingKeys: String, CodingKey { + case functionInvocation = "function_invocation" + } +} + +enum StarknetTransactionTraceWrapper: Decodable { + fileprivate enum Keys: String, CodingKey { + case validateInvocation = "validate_invocation" + case executeInvocation = "execute_invocation" + case feeTransferInvocation = "fee_transfer_invocation" + case constructorInvocation = "constructor_invocation" + case functionInvocation = "function_invocation" + } + + case invoke(StarknetInvokeTransactionTrace) + case declare(StarknetDeclareTransactionTrace) + case deployAccount(StarknetDeployAccountTransactionTrace) + case l1Handler(StarknetL1HandlerTransactionTrace) + + public var transactionTrace: any StarknetTransactionTrace { + switch self { + case let .invoke(txTrace): + return txTrace + case let .declare(txTrace): + return txTrace + case let .deployAccount(txTrace): + return txTrace + case let .l1Handler(txTrace): + return txTrace + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Keys.self) + + if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), + let executeInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .executeInvocation), + let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) { + self = .invoke(StarknetInvokeTransactionTrace( + validateInvocation: validateInvocation, + executeInvocation: executeInvocation, + feeTransferInvocation: feeTransferInvocation + )) + } else if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), + let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) { + self = .declare(StarknetDeclareTransactionTrace( + validateInvocation: validateInvocation, + feeTransferInvocation: feeTransferInvocation + )) + } else if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), + let constructorInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .constructorInvocation), + let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) { + self = .deployAccount(StarknetDeployAccountTransactionTrace( + validateInvocation: validateInvocation, + constructorInvocation: constructorInvocation, + feeTransferInvocation: feeTransferInvocation + )) + } else if let functionInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .functionInvocation) { + self = .l1Handler(StarknetL1HandlerTransactionTrace( + functionInvocation: functionInvocation + )) + } else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Invalid transaction trace wrapper" + ) + ) + } + } +} + +public struct StarknetSimulatedTransaction: Decodable { + public let transactionTrace: any StarknetTransactionTrace + public let feeEstimation: StarknetFeeEstimate + + enum CodingKeys: String, CodingKey { + case transactionTrace = "transaction_trace" + case feeEstimation = "fee_estimation" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + transactionTrace = try container.decode(StarknetTransactionTraceWrapper.self, forKey: .transactionTrace).transactionTrace + feeEstimation = try container.decode(StarknetFeeEstimate.self, forKey: .feeEstimation) + } +} + +public enum StarknetSimulationFlag: String, Codable { + case skipValidate = "SKIP_VALIDATE" + case skipExecute = "SKIP_EXECUTE" +} diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift index 25d78f3bf..964332a07 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift @@ -13,4 +13,5 @@ enum JsonRpcMethod: String, Encodable { case getTransactionByHash = "starknet_getTransactionByHash" case getTransactionByBlockIdAndIndex = "starknet_getTransactionByBlockIdAndIndex" case getTransactionReceipt = "starknet_getTransactionReceipt" + case simulateTransaction = "starknet_simulateTransaction" } diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift index 69f7fa176..c06b9ea20 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift @@ -28,19 +28,20 @@ struct AddInvokeTransactionParams: Encodable { } } +// Walkaround to allow encoding polymorphic array +struct WrappedSequencerTransaction: Encodable { + let transaction: any StarknetSequencerTransaction + + func encode(to encoder: Encoder) throws { + try transaction.encode(to: encoder) + } +} + + struct EstimateFeeParams: Encodable { let request: [any StarknetSequencerTransaction] let blockId: StarknetBlockId - // Walkaround to allow encoding polymorphic array - struct WrappedSequencerTransaction: Encodable { - let transaction: any StarknetSequencerTransaction - - func encode(to encoder: Encoder) throws { - try transaction.encode(to: encoder) - } - } - func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -104,4 +105,26 @@ struct GetTransactionReceiptPayload: Encodable { } } +struct SimulateTransactionsParams: Encodable { + let transactions: [any StarknetSequencerTransaction] + let blockId: StarknetBlockId + let simulationFlags: Set + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + let wrappedTransactions = transactions.map { WrappedSequencerTransaction(transaction: $0) } + + try container.encode(wrappedTransactions, forKey: .transactions) + try container.encode(blockId, forKey: .blockId) + try container.encode(simulationFlags, forKey: .simulationFlags) + } + + enum CodingKeys: String, CodingKey { + case transactions + case blockId = "block_id" + case simulationFlags = "simulation_flags" + } +} + struct EmptyParams: Encodable {} diff --git a/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift b/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift index 609de17dd..fe731aa40 100644 --- a/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift +++ b/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift @@ -142,4 +142,13 @@ public class StarknetProvider: StarknetProviderProtocol { return result.transactionReceipt } + + public func simulateTransactions(_ transactions: [any StarknetSequencerTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] { + + let params = SimulateTransactionsParams(transactions: transactions, blockId: blockId, simulationFlags: simulationFlags) + + let result = try await makeRequest(method: .simulateTransaction, params: params, receive: [StarknetSimulatedTransaction].self) + + return result + } } diff --git a/Sources/Starknet/Providers/StarknetProviderProtocol.swift b/Sources/Starknet/Providers/StarknetProviderProtocol.swift index 3dbf3202d..690e970e3 100644 --- a/Sources/Starknet/Providers/StarknetProviderProtocol.swift +++ b/Sources/Starknet/Providers/StarknetProviderProtocol.swift @@ -95,9 +95,18 @@ public protocol StarknetProviderProtocol { /// - txHash : the hash of the requested transaction /// - Returns: receipt of a transaction identified by given hash func getTransactionReceiptBy(hash: Felt) async throws -> StarknetTransactionReceipt + + /// Simulate a given transaction on the requested state, and generate the execution trace + /// + /// - Parameters: + /// - transactions: list of transactions to simulate + /// - blockId: at which block to run the simulation + /// - skipValidatte: a flag whether signature validation should be bypassed + /// - skipValidatte: a flag whether signature validation should be bypassed + func simulateTransactions(_ transactions: [any StarknetSequencerTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] } -private let defaultBlockId = StarknetBlockId.tag(.pending) +let defaultBlockId = StarknetBlockId.tag(.pending) public extension StarknetProviderProtocol { /// Call starknet contract in the pending block. diff --git a/Tests/StarknetTests/Providers/ProviderTests.swift b/Tests/StarknetTests/Providers/ProviderTests.swift index 631844020..249e165ea 100644 --- a/Tests/StarknetTests/Providers/ProviderTests.swift +++ b/Tests/StarknetTests/Providers/ProviderTests.swift @@ -9,6 +9,7 @@ final class ProviderTests: XCTestCase { To run, make sure you're running starknet-devnet on port 5050, with seed 0 */ static var devnetClient: DevnetClientProtocol! + var provider: StarknetProviderProtocol! override class func setUp() { @@ -151,4 +152,28 @@ final class ProviderTests: XCTestCase { XCTAssertEqual(fees.count, 2) } + + func testSimulateTransactions() async throws { + do { + let acc = try await ProviderTests.devnetClient.deployAccount(name: "test_simulate_transactions") + let signer = StarkCurveSigner(privateKey: acc.details.privateKey)! + let contract = try await ProviderTests.devnetClient.deployContract(contractName: "balance", deprecated: true) + let account = StarknetAccount(address: acc.details.address, signer: signer, provider: provider) + + let nonce = try await account.getNonce() + + let call = StarknetCall(contractAddress: contract.address, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) + let params = StarknetExecutionParams(nonce: nonce, maxFee: 1000000000000) + + let tx = try account.sign(calls: [call], params: params, forFeeEstimation: true) + + let simulation = try await provider.simulateTransactions([tx], at: .tag(.latest), simulationFlags: [.skipValidate]) + + print(simulation) + } catch let error { + print(error) + + throw error + } + } } diff --git a/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift b/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift index 35d4ef923..97fa2c7a5 100644 --- a/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift +++ b/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift @@ -59,6 +59,8 @@ func makeDevnetClient() -> DevnetClientProtocol { private let devnetPath: String private let starknetPath: String + private var deployedContracts: [String: TransactionResult] = [:] + let gatewayUrl: String let feederGatewayUrl: String let rpcUrl: String @@ -220,6 +222,10 @@ func makeDevnetClient() -> DevnetClientProtocol { public func deployContract(contractName: String, deprecated: Bool) async throws -> TransactionResult { try guardDevnetIsRunning() + if let transactionResult = deployedContracts["contractName"] { + return transactionResult + } + let classHash = try await declareContract(contractName: contractName, deprecated: deprecated) let params = [ diff --git a/requirements.txt b/requirements.txt index 60a71997c..6a36d7472 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -starknet-devnet==0.5.2 +starknet-devnet==0.5.3 cairo-lang==0.11.1.1 From 26c4ff4f2cea479dbc014e5f302a96bf8f415ff0 Mon Sep 17 00:00:00 2001 From: Bartosz Rybarski Date: Fri, 16 Jun 2023 15:30:04 +0200 Subject: [PATCH 2/7] Formatted files --- Sources/Starknet/Data/Transaction/TransactionTrace.swift | 9 ++++++--- .../Providers/StarknetProvider/JsonRpcParams.swift | 1 - .../Providers/StarknetProvider/StarknetProvider.swift | 1 - Tests/StarknetTests/Providers/ProviderTests.swift | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Sources/Starknet/Data/Transaction/TransactionTrace.swift b/Sources/Starknet/Data/Transaction/TransactionTrace.swift index 80d9ae045..492c56546 100644 --- a/Sources/Starknet/Data/Transaction/TransactionTrace.swift +++ b/Sources/Starknet/Data/Transaction/TransactionTrace.swift @@ -115,21 +115,24 @@ enum StarknetTransactionTraceWrapper: Decodable { if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), let executeInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .executeInvocation), - let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) { + let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) + { self = .invoke(StarknetInvokeTransactionTrace( validateInvocation: validateInvocation, executeInvocation: executeInvocation, feeTransferInvocation: feeTransferInvocation )) } else if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), - let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) { + let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) + { self = .declare(StarknetDeclareTransactionTrace( validateInvocation: validateInvocation, feeTransferInvocation: feeTransferInvocation )) } else if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), let constructorInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .constructorInvocation), - let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) { + let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) + { self = .deployAccount(StarknetDeployAccountTransactionTrace( validateInvocation: validateInvocation, constructorInvocation: constructorInvocation, diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift index c06b9ea20..e083f592d 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift @@ -37,7 +37,6 @@ struct WrappedSequencerTransaction: Encodable { } } - struct EstimateFeeParams: Encodable { let request: [any StarknetSequencerTransaction] let blockId: StarknetBlockId diff --git a/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift b/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift index fe731aa40..dd46d7a37 100644 --- a/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift +++ b/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift @@ -144,7 +144,6 @@ public class StarknetProvider: StarknetProviderProtocol { } public func simulateTransactions(_ transactions: [any StarknetSequencerTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] { - let params = SimulateTransactionsParams(transactions: transactions, blockId: blockId, simulationFlags: simulationFlags) let result = try await makeRequest(method: .simulateTransaction, params: params, receive: [StarknetSimulatedTransaction].self) diff --git a/Tests/StarknetTests/Providers/ProviderTests.swift b/Tests/StarknetTests/Providers/ProviderTests.swift index 249e165ea..ae2b3c37d 100644 --- a/Tests/StarknetTests/Providers/ProviderTests.swift +++ b/Tests/StarknetTests/Providers/ProviderTests.swift @@ -163,14 +163,14 @@ final class ProviderTests: XCTestCase { let nonce = try await account.getNonce() let call = StarknetCall(contractAddress: contract.address, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) - let params = StarknetExecutionParams(nonce: nonce, maxFee: 1000000000000) + let params = StarknetExecutionParams(nonce: nonce, maxFee: 1_000_000_000_000) let tx = try account.sign(calls: [call], params: params, forFeeEstimation: true) let simulation = try await provider.simulateTransactions([tx], at: .tag(.latest), simulationFlags: [.skipValidate]) print(simulation) - } catch let error { + } catch { print(error) throw error From 390e0f6e872f2fb3b1323776388d15b7a667ae35 Mon Sep 17 00:00:00 2001 From: Bartosz Rybarski Date: Thu, 29 Jun 2023 15:21:03 +0200 Subject: [PATCH 3/7] Updated simulateTransaction endpoint --- .../Data/Transaction/TransactionTrace.swift | 85 +++++++++---------- .../Providers/ProviderTests.swift | 62 ++++++++++---- 2 files changed, 85 insertions(+), 62 deletions(-) diff --git a/Sources/Starknet/Data/Transaction/TransactionTrace.swift b/Sources/Starknet/Data/Transaction/TransactionTrace.swift index 492c56546..45b35f943 100644 --- a/Sources/Starknet/Data/Transaction/TransactionTrace.swift +++ b/Sources/Starknet/Data/Transaction/TransactionTrace.swift @@ -15,21 +15,21 @@ public struct StarknetFunctionInvocation: Decodable, Equatable { public let contractAddress: Felt public let entrypoint: Felt public let calldata: StarknetCalldata - public let callerAddress: Felt - public let codeAddress: Felt - public let entryPointType: StarknetEntryPointType - public let callType: StarknetCallType - public let result: [Felt] - public let calls: [StarknetFunctionInvocation] - public let events: [StarknetEventContent] - public let messages: [MessageToL1] + public let callerAddress: Felt? + public let classHash: Felt? + public let entryPointType: StarknetEntryPointType? + public let callType: StarknetCallType? + public let result: [Felt]? + public let calls: [StarknetFunctionInvocation]? + public let events: [StarknetEventContent]? + public let messages: [MessageToL1]? private enum CodingKeys: String, CodingKey { case contractAddress = "contract_address" - case entrypoint + case entrypoint = "entry_point_selector" case calldata case callerAddress = "caller_address" - case codeAddress = "code_address" + case classHash = "class_hash" case entryPointType = "entry_point_type" case callType = "call_type" case result @@ -53,16 +53,6 @@ public struct StarknetInvokeTransactionTrace: StarknetTransactionTrace { } } -public struct StarknetDeclareTransactionTrace: StarknetTransactionTrace { - public let validateInvocation: StarknetFunctionInvocation? - public let feeTransferInvocation: StarknetFunctionInvocation? - - private enum CodingKeys: String, CodingKey { - case validateInvocation = "validate_invocation" - case feeTransferInvocation = "fee_transfer_invocation" - } -} - public struct StarknetDeployAccountTransactionTrace: StarknetTransactionTrace { public let validateInvocation: StarknetFunctionInvocation? public let constructorInvocation: StarknetFunctionInvocation? @@ -93,7 +83,6 @@ enum StarknetTransactionTraceWrapper: Decodable { } case invoke(StarknetInvokeTransactionTrace) - case declare(StarknetDeclareTransactionTrace) case deployAccount(StarknetDeployAccountTransactionTrace) case l1Handler(StarknetL1HandlerTransactionTrace) @@ -101,8 +90,6 @@ enum StarknetTransactionTraceWrapper: Decodable { switch self { case let .invoke(txTrace): return txTrace - case let .declare(txTrace): - return txTrace case let .deployAccount(txTrace): return txTrace case let .l1Handler(txTrace): @@ -113,43 +100,47 @@ enum StarknetTransactionTraceWrapper: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) - if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), - let executeInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .executeInvocation), - let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) - { + // Invocations can be null, so if let = try? syntax won't work here. + do { + let validateInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .validateInvocation) + let executeInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .executeInvocation) + let feeTransferInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .feeTransferInvocation) + self = .invoke(StarknetInvokeTransactionTrace( validateInvocation: validateInvocation, executeInvocation: executeInvocation, feeTransferInvocation: feeTransferInvocation )) - } else if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), - let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) - { - self = .declare(StarknetDeclareTransactionTrace( - validateInvocation: validateInvocation, - feeTransferInvocation: feeTransferInvocation - )) - } else if let validateInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .validateInvocation), - let constructorInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .constructorInvocation), - let feeTransferInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .feeTransferInvocation) - { + return + } catch {} + + do { + let validateInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .validateInvocation) + let constructorInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .constructorInvocation) + let feeTransferInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .feeTransferInvocation) + self = .deployAccount(StarknetDeployAccountTransactionTrace( validateInvocation: validateInvocation, constructorInvocation: constructorInvocation, feeTransferInvocation: feeTransferInvocation )) - } else if let functionInvocation = try container.decodeIfPresent(StarknetFunctionInvocation.self, forKey: .functionInvocation) { + return + } catch {} + + do { + let functionInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .functionInvocation) + self = .l1Handler(StarknetL1HandlerTransactionTrace( functionInvocation: functionInvocation )) - } else { - throw DecodingError.dataCorrupted( - DecodingError.Context( - codingPath: container.codingPath, - debugDescription: "Invalid transaction trace wrapper" - ) - ) - } + return + } catch {} + + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Invalid transaction trace" + )) } } diff --git a/Tests/StarknetTests/Providers/ProviderTests.swift b/Tests/StarknetTests/Providers/ProviderTests.swift index ae2b3c37d..ba0635763 100644 --- a/Tests/StarknetTests/Providers/ProviderTests.swift +++ b/Tests/StarknetTests/Providers/ProviderTests.swift @@ -154,26 +154,58 @@ final class ProviderTests: XCTestCase { } func testSimulateTransactions() async throws { - do { - let acc = try await ProviderTests.devnetClient.deployAccount(name: "test_simulate_transactions") - let signer = StarkCurveSigner(privateKey: acc.details.privateKey)! - let contract = try await ProviderTests.devnetClient.deployContract(contractName: "balance", deprecated: true) - let account = StarknetAccount(address: acc.details.address, signer: signer, provider: provider) + let acc = try await ProviderTests.devnetClient.deployAccount(name: "test_simulate_transactions") + let signer = StarkCurveSigner(privateKey: acc.details.privateKey)! + let contract = try await ProviderTests.devnetClient.deployContract(contractName: "balance", deprecated: true) + let account = StarknetAccount(address: acc.details.address, signer: signer, provider: provider) - let nonce = try await account.getNonce() + let nonce = try await account.getNonce() + + let call = StarknetCall(contractAddress: contract.address, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) + let params = StarknetExecutionParams(nonce: nonce, maxFee: 1_000_000_000_000) - let call = StarknetCall(contractAddress: contract.address, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) - let params = StarknetExecutionParams(nonce: nonce, maxFee: 1_000_000_000_000) + let invokeTx = try account.sign(calls: [call], params: params, forFeeEstimation: true) - let tx = try account.sign(calls: [call], params: params, forFeeEstimation: true) + let accountClassHash = try await provider.getClassHashAt(account.address) + let newSigner = StarkCurveSigner(privateKey: 1234)! + let newPublicKey = newSigner.publicKey + let newAccountAddress = StarknetContractAddressCalculator.calculateFrom(classHash: accountClassHash, calldata: [newPublicKey], salt: .zero) + let newAccount = StarknetAccount(address: newAccountAddress, signer: newSigner, provider: provider) - let simulation = try await provider.simulateTransactions([tx], at: .tag(.latest), simulationFlags: [.skipValidate]) + try await Self.devnetClient.prefundAccount(address: newAccountAddress) - print(simulation) - } catch { - print(error) + let newAccountParams = StarknetExecutionParams(nonce: 0, maxFee: 0) + let deployAccountTx = try newAccount.signDeployAccount(classHash: accountClassHash, calldata: [newPublicKey], salt: .zero, params: newAccountParams, forFeeEstimation: true) - throw error - } + let simulations = try await provider.simulateTransactions([invokeTx, deployAccountTx], at: .tag(.latest), simulationFlags: []) + + XCTAssertEqual(simulations.count, 2) + XCTAssertTrue(simulations[0].transactionTrace is StarknetInvokeTransactionTrace) + XCTAssertTrue(simulations[1].transactionTrace is StarknetDeployAccountTransactionTrace) + + let invokeWithoutSignature = StarknetSequencerInvokeTransaction( + senderAddress: invokeTx.senderAddress, + calldata: invokeTx.calldata, + signature: [], + maxFee: invokeTx.maxFee, + nonce: invokeTx.nonce, + version: invokeTx.version + ) + + let deployAccountWithoutSignature = StarknetSequencerDeployAccountTransaction( + signature: [], + maxFee: deployAccountTx.maxFee, + nonce: deployAccountTx.nonce, + contractAddressSalt: deployAccountTx.contractAddressSalt, + constructorCalldata: deployAccountTx.constructorCalldata, + classHash: deployAccountTx.classHash, + version: deployAccountTx.version + ) + + let simulations2 = try await provider.simulateTransactions([invokeWithoutSignature, deployAccountWithoutSignature], at: .tag(.latest), simulationFlags: [.skipValidate]) + + XCTAssertEqual(simulations2.count, 2) + XCTAssertTrue(simulations2[0].transactionTrace is StarknetInvokeTransactionTrace) + XCTAssertTrue(simulations2[1].transactionTrace is StarknetDeployAccountTransactionTrace) } } From 5d3e1ecc231c4ea12b0320ed3cbaadded65068dd Mon Sep 17 00:00:00 2001 From: Bartosz Rybarski Date: Thu, 29 Jun 2023 18:16:12 +0200 Subject: [PATCH 4/7] Cosmetic changes --- .../Starknet/Data/Transaction/TransactionTrace.swift | 12 ++++++------ .../Providers/StarknetProvider/JsonRpcParams.swift | 2 +- requirements.txt | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Starknet/Data/Transaction/TransactionTrace.swift b/Sources/Starknet/Data/Transaction/TransactionTrace.swift index 45b35f943..7685764ce 100644 --- a/Sources/Starknet/Data/Transaction/TransactionTrace.swift +++ b/Sources/Starknet/Data/Transaction/TransactionTrace.swift @@ -11,6 +11,11 @@ public enum StarknetCallType: String, Decodable { case libraryCall = "LIBRARY_CALL" } +public enum StarknetSimulationFlag: String, Codable { + case skipValidate = "SKIP_VALIDATE" + case skipExecute = "SKIP_EXECUTE" +} + public struct StarknetFunctionInvocation: Decodable, Equatable { public let contractAddress: Felt public let entrypoint: Felt @@ -100,7 +105,7 @@ enum StarknetTransactionTraceWrapper: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) - // Invocations can be null, so if let = try? syntax won't work here. + // Invocations can be null, so `if let = try?` syntax won't work here. do { let validateInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .validateInvocation) let executeInvocation = try container.decode(StarknetFunctionInvocation?.self, forKey: .executeInvocation) @@ -160,8 +165,3 @@ public struct StarknetSimulatedTransaction: Decodable { feeEstimation = try container.decode(StarknetFeeEstimate.self, forKey: .feeEstimation) } } - -public enum StarknetSimulationFlag: String, Codable { - case skipValidate = "SKIP_VALIDATE" - case skipExecute = "SKIP_EXECUTE" -} diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift index e083f592d..6c8193cd7 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift @@ -28,7 +28,7 @@ struct AddInvokeTransactionParams: Encodable { } } -// Walkaround to allow encoding polymorphic array +// Workaround to allow encoding polymorphic array struct WrappedSequencerTransaction: Encodable { let transaction: any StarknetSequencerTransaction diff --git a/requirements.txt b/requirements.txt index 6a36d7472..eacca2cb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -starknet-devnet==0.5.3 +starknet-devnet==0.5.4 cairo-lang==0.11.1.1 From 4ef3a9af00ce3363e27021ce89fc7319cb8a6c6d Mon Sep 17 00:00:00 2001 From: Bartosz Rybarski Date: Thu, 29 Jun 2023 18:40:11 +0200 Subject: [PATCH 5/7] Added additional method to StarknetProvider --- .../Providers/StarknetProviderProtocol.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/Starknet/Providers/StarknetProviderProtocol.swift b/Sources/Starknet/Providers/StarknetProviderProtocol.swift index 690e970e3..1c832e3c0 100644 --- a/Sources/Starknet/Providers/StarknetProviderProtocol.swift +++ b/Sources/Starknet/Providers/StarknetProviderProtocol.swift @@ -96,13 +96,12 @@ public protocol StarknetProviderProtocol { /// - Returns: receipt of a transaction identified by given hash func getTransactionReceiptBy(hash: Felt) async throws -> StarknetTransactionReceipt - /// Simulate a given transaction on the requested state, and generate the execution trace + /// Simulate running a given list of transactions, and generate the execution trace /// /// - Parameters: /// - transactions: list of transactions to simulate - /// - blockId: at which block to run the simulation - /// - skipValidatte: a flag whether signature validation should be bypassed - /// - skipValidatte: a flag whether signature validation should be bypassed + /// - blockId: block used to run the simulation + /// - simulationFlags: a set of simulation flags func simulateTransactions(_ transactions: [any StarknetSequencerTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] } @@ -166,4 +165,13 @@ public extension StarknetProviderProtocol { func getClassHashAt(_ address: Felt) async throws -> Felt { try await getClassHashAt(address, at: defaultBlockId) } + + /// Simulate running a given list of transactions in the latest block, and generate the execution trace + /// + /// - Parameters: + /// - transactions: list of transactions to simulate + /// - simulationFlags: a set of simulation flags + func simulateTransactions(_ transactions: [any StarknetSequencerTransaction], simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] { + try await simulateTransactions(transactions, at: defaultBlockId, simulationFlags: simulationFlags) + } } From 065b83d46cec9588b2904151fe0ed675915efd82 Mon Sep 17 00:00:00 2001 From: Bartosz Rybarski Date: Wed, 5 Jul 2023 15:18:44 +0200 Subject: [PATCH 6/7] Updated devnet version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eacca2cb9..563f4465b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -starknet-devnet==0.5.4 +starknet-devnet==0.5.5 cairo-lang==0.11.1.1 From eab7393915896786cd3e4d67ce7469538ea9f5d9 Mon Sep 17 00:00:00 2001 From: Bartosz Rybarski Date: Wed, 5 Jul 2023 15:28:25 +0200 Subject: [PATCH 7/7] Updated cairo-lang version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 563f4465b..9be79adc0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ starknet-devnet==0.5.5 -cairo-lang==0.11.1.1 +cairo-lang==0.12.0