diff --git a/Sources/Starknet/Accounts/StarknetAccount.swift b/Sources/Starknet/Accounts/StarknetAccount.swift index bf38a913f..8aff3b7af 100644 --- a/Sources/Starknet/Accounts/StarknetAccount.swift +++ b/Sources/Starknet/Accounts/StarknetAccount.swift @@ -31,7 +31,7 @@ public class StarknetAccount: StarknetAccountProtocol { } private func makeInvokeTransactionV3(calldata: StarknetCalldata, signature: StarknetSignature, params: StarknetInvokeParamsV3, forFeeEstimation: Bool = false) -> StarknetInvokeTransactionV3 { - StarknetInvokeTransactionV3(senderAddress: address, calldata: calldata, signature: signature, l1ResourceBounds: params.resourceBounds.l1Gas, nonce: params.nonce, forFeeEstimation: forFeeEstimation) + StarknetInvokeTransactionV3(senderAddress: address, calldata: calldata, signature: signature, resourceBounds: params.resourceBounds, nonce: params.nonce, forFeeEstimation: forFeeEstimation) } private func makeDeployAccountTransactionV1(classHash: Felt, salt: Felt, calldata: StarknetCalldata, signature: StarknetSignature, params: StarknetDeployAccountParamsV1, forFeeEstimation: Bool) -> StarknetDeployAccountTransactionV1 { @@ -39,7 +39,7 @@ public class StarknetAccount: StarknetAccountProtocol { } private func makeDeployAccountTransactionV3(classHash: Felt, salt: Felt, calldata: StarknetCalldata, signature: StarknetSignature, params: StarknetDeployAccountParamsV3, forFeeEstimation: Bool) -> StarknetDeployAccountTransactionV3 { - StarknetDeployAccountTransactionV3(signature: signature, l1ResourceBounds: params.resourceBounds.l1Gas, nonce: params.nonce, contractAddressSalt: salt, constructorCalldata: calldata, classHash: classHash, forFeeEstimation: forFeeEstimation) + StarknetDeployAccountTransactionV3(signature: signature, resourceBounds: params.resourceBounds, nonce: params.nonce, contractAddressSalt: salt, constructorCalldata: calldata, classHash: classHash, forFeeEstimation: forFeeEstimation) } public func signV1(calls: [StarknetCall], params: StarknetInvokeParamsV1, forFeeEstimation: Bool) throws -> StarknetInvokeTransactionV1 { @@ -126,7 +126,7 @@ public class StarknetAccount: StarknetAccountProtocol { resourceBounds = feeEstimate.toResourceBounds() } - let params = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: resourceBounds.l1Gas) + let params = StarknetInvokeParamsV3(nonce: nonce, resourceBounds: resourceBounds) let signedTransaction = try signV3(calls: calls, params: params, forFeeEstimation: false) return RequestBuilder.addInvokeTransaction(signedTransaction) @@ -148,7 +148,7 @@ public class StarknetAccount: StarknetAccountProtocol { let feeEstimate = try await provider.send(request: estimateFeeV3(calls: calls, nonce: nonce))[0] let resourceBounds = feeEstimate.toResourceBounds(amountMultiplier: estimateAmountMultiplier, unitPriceMultiplier: estimateUnitPriceMultiplier) - let params = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: resourceBounds.l1Gas) + let params = StarknetInvokeParamsV3(nonce: nonce, resourceBounds: resourceBounds) let signedTransaction = try signV3(calls: calls, params: params, forFeeEstimation: false) return RequestBuilder.addInvokeTransaction(signedTransaction) @@ -162,7 +162,7 @@ public class StarknetAccount: StarknetAccountProtocol { } public func estimateFeeV3(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> { - let params = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: .zero) + let params = StarknetInvokeParamsV3(nonce: nonce, resourceBounds: StarknetResourceBoundsMapping.zero) let signedTransaction = try signV3(calls: calls, params: params, forFeeEstimation: true) return RequestBuilder.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) @@ -176,7 +176,7 @@ public class StarknetAccount: StarknetAccountProtocol { } public func estimateDeployAccountFeeV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> { - let params = StarknetDeployAccountParamsV3(nonce: nonce, l1ResourceBounds: .zero) + let params = StarknetDeployAccountParamsV3(nonce: nonce, resourceBounds: StarknetResourceBoundsMapping.zero) let signedTransaction = try signDeployAccountV3(classHash: classHash, calldata: calldata, salt: salt, params: params, forFeeEstimation: true) return RequestBuilder.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) diff --git a/Sources/Starknet/Accounts/StarknetAccountProtocol.swift b/Sources/Starknet/Accounts/StarknetAccountProtocol.swift index 3f1b0f642..1cb59b287 100644 --- a/Sources/Starknet/Accounts/StarknetAccountProtocol.swift +++ b/Sources/Starknet/Accounts/StarknetAccountProtocol.swift @@ -216,11 +216,11 @@ public extension StarknetAccountProtocol { /// - classHash: class hash of account to be deployed /// - calldata: constructor calldata /// - salt: contract salt - /// - l1ResourceBounds: max acceptable l1 resource bounds + /// - resourceBounds: max acceptable l1 and l2 resource bounds /// /// - Returns: Signed deploy account transaction v3 - func signDeployAccountV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, l1ResourceBounds: StarknetResourceBounds) throws -> StarknetDeployAccountTransactionV3 { - try signDeployAccountV3(classHash: classHash, calldata: calldata, salt: salt, params: StarknetDeployAccountParamsV3(nonce: .zero, l1ResourceBounds: l1ResourceBounds), forFeeEstimation: false) + func signDeployAccountV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, resourceBounds: StarknetResourceBoundsMapping) throws -> StarknetDeployAccountTransactionV3 { + try signDeployAccountV3(classHash: classHash, calldata: calldata, salt: salt, params: StarknetDeployAccountParamsV3(nonce: .zero, resourceBounds: resourceBounds), forFeeEstimation: false) } /// Sign a call as invoke transaction v1 diff --git a/Sources/Starknet/Crypto/FeeCalculation.swift b/Sources/Starknet/Crypto/FeeCalculation.swift index ae9beedf4..93d961d88 100644 --- a/Sources/Starknet/Crypto/FeeCalculation.swift +++ b/Sources/Starknet/Crypto/FeeCalculation.swift @@ -4,9 +4,12 @@ import Foundation public extension StarknetFeeEstimate { /// Convert estimated fee to resource bounds with applied multipliers /// - /// Calculates `maxAmount = overallFee / gasPrice`, unless `gasPrice` is 0, then `maxAmount` is 0. - /// Calculates `maxPricePerUnit = gasPrice`. - /// Then multiplies `maxAmount` by **round((amountMultiplier) \* 100)** and `maxPricePerUnit` by **round((unitPriceMultiplier) \* 100)** and performs integer division by 100 on both. + /// Calculates `maxAmountL1 = overallFee / l1GasPrice`, unless `l1GasPrice` is 0, then `maxAmountL1` is 0. + /// Calculates `maxAmountL2 = overallFee / l2GasPrice`, unless `l2GasPrice` is 0, then `maxAmountL2` is 0. + /// Calculates `maxPricePerUnitL1 = gasPriceL1`. + /// Calculates `maxPricePerUnitL2 = gasPriceL2`. + /// Then multiplies `maxAmountL1` and `maxAmountL2` by **round((amountMultiplier) \* 100)** and `maxPricePerUnitL1` and `maxPricePerUnitL2` by **round((unitPriceMultiplier) \* 100)** and performs integer division by 100 on each. + /// /// /// - Parameters: /// - amountMultiplier: multiplier for max amount, defaults to 1.5. @@ -14,12 +17,15 @@ public extension StarknetFeeEstimate { /// /// - Returns: resource bounds with applied multipliers func toResourceBounds(amountMultiplier: Double = 1.5, unitPriceMultiplier: Double = 1.5) -> StarknetResourceBoundsMapping { - let maxAmount = self.gasPrice == .zero ? UInt64AsHex.zero : (self.overallFee.value / self.gasPrice.value).applyMultiplier(amountMultiplier).toUInt64AsHexClamped() + let maxAmountL1 = self.l1GasPrice == .zero ? UInt64AsHex.zero : (self.overallFee.value / self.l1GasPrice.value).applyMultiplier(amountMultiplier).toUInt64AsHexClamped() + let maxAmountL2 = self.l2GasPrice == .zero ? UInt64AsHex.zero : (self.overallFee.value / self.l2GasPrice.value).applyMultiplier(amountMultiplier).toUInt64AsHexClamped() - let maxUnitPrice = self.gasPrice.value.applyMultiplier(unitPriceMultiplier).toUInt128AsHexClamped() + let maxUnitPriceL1 = self.l1GasPrice.value.applyMultiplier(unitPriceMultiplier).toUInt128AsHexClamped() + let maxUnitPriceL2 = self.l2GasPrice.value.applyMultiplier(unitPriceMultiplier).toUInt128AsHexClamped() - let l1Gas = StarknetResourceBounds(maxAmount: maxAmount, maxPricePerUnit: maxUnitPrice) - return StarknetResourceBoundsMapping(l1Gas: l1Gas) + let l1Gas = StarknetResourceBounds(maxAmount: maxAmountL1, maxPricePerUnit: maxUnitPriceL1) + let l2Gas = StarknetResourceBounds(maxAmount: maxAmountL2, maxPricePerUnit: maxUnitPriceL2) + return StarknetResourceBoundsMapping(l1Gas: l1Gas, l2Gas: l2Gas) } /// Convert estimated fee to max fee with applied multiplier. diff --git a/Sources/Starknet/Data/Execution.swift b/Sources/Starknet/Data/Execution.swift index f8d1d785f..d5e5ef170 100644 --- a/Sources/Starknet/Data/Execution.swift +++ b/Sources/Starknet/Data/Execution.swift @@ -32,10 +32,9 @@ public struct StarknetInvokeParamsV3 { public let nonceDataAvailabilityMode: StarknetDAMode public let feeDataAvailabilityMode: StarknetDAMode - public init(nonce: Felt, l1ResourceBounds: StarknetResourceBounds) { + public init(nonce: Felt, resourceBounds: StarknetResourceBoundsMapping) { self.nonce = nonce - // As of Starknet 0.13, most of v3 fields have hardcoded values. - self.resourceBounds = StarknetResourceBoundsMapping(l1Gas: l1ResourceBounds) + self.resourceBounds = resourceBounds self.tip = .zero self.paymasterData = [] self.accountDeploymentData = [] @@ -53,10 +52,9 @@ public struct StarknetOptionalInvokeParamsV3 { public let nonceDataAvailabilityMode: StarknetDAMode public let feeDataAvailabilityMode: StarknetDAMode - public init(nonce: Felt? = nil, l1ResourceBounds: StarknetResourceBounds? = nil) { + public init(nonce: Felt? = nil, resourceBounds: StarknetResourceBoundsMapping? = nil) { self.nonce = nonce - // As of Starknet 0.13, most of v3 fields have hardcoded values. - self.resourceBounds = l1ResourceBounds.map(StarknetResourceBoundsMapping.init) + self.resourceBounds = resourceBounds self.tip = .zero self.paymasterData = [] self.accountDeploymentData = [] @@ -73,18 +71,17 @@ public struct StarknetDeployAccountParamsV3 { public let nonceDataAvailabilityMode: StarknetDAMode public let feeDataAvailabilityMode: StarknetDAMode - public init(nonce: Felt, l1ResourceBounds: StarknetResourceBounds) { + public init(nonce: Felt, resourceBounds: StarknetResourceBoundsMapping) { self.nonce = nonce - // As of Starknet 0.13, most of v3 fields have hardcoded values. - self.resourceBounds = StarknetResourceBoundsMapping(l1Gas: l1ResourceBounds) + self.resourceBounds = resourceBounds self.tip = .zero self.paymasterData = [] self.nonceDataAvailabilityMode = .l1 self.feeDataAvailabilityMode = .l1 } - public init(l1ResourceBounds: StarknetResourceBounds) { - self.init(nonce: .zero, l1ResourceBounds: l1ResourceBounds) + public init(resourceBounds: StarknetResourceBoundsMapping) { + self.init(nonce: .zero, resourceBounds: resourceBounds) } } diff --git a/Sources/Starknet/Data/MessageStatus.swift b/Sources/Starknet/Data/MessageStatus.swift new file mode 100644 index 000000000..d5feb5555 --- /dev/null +++ b/Sources/Starknet/Data/MessageStatus.swift @@ -0,0 +1,11 @@ +public struct MessageStatus: Decodable, Equatable { + public let transactionHash: Felt + public let finalityStatus: StarknetTransactionStatus + public let failureReason: String? + + enum CodingKeys: String, CodingKey { + case transactionHash = "transaction_hash" + case finalityStatus = "finality_status" + case failureReason = "failure_reason" + } +} diff --git a/Sources/Starknet/Data/Responses.swift b/Sources/Starknet/Data/Responses.swift index d1d3084f7..d553c274c 100644 --- a/Sources/Starknet/Data/Responses.swift +++ b/Sources/Starknet/Data/Responses.swift @@ -9,37 +9,45 @@ public struct StarknetInvokeTransactionResponse: Decodable, Equatable { } public struct StarknetFeeEstimate: Decodable, Equatable { - public let gasConsumed: Felt - public let gasPrice: Felt - public let dataGasConsumed: Felt - public let dataGasPrice: Felt + public let l1GasConsumed: Felt + public let l1GasPrice: Felt + public let l2GasConsumed: Felt + public let l2GasPrice: Felt + public let l1DataGasConsumed: Felt + public let l1DataGasPrice: Felt public let overallFee: Felt public let feeUnit: StarknetPriceUnit enum CodingKeys: String, CodingKey { - case gasConsumed = "gas_consumed" - case gasPrice = "gas_price" - case dataGasConsumed = "data_gas_consumed" - case dataGasPrice = "data_gas_price" + case l1GasConsumed = "l1_gas_consumed" + case l1GasPrice = "l1_gas_price" + case l2GasConsumed = "l2_gas_consumed" + case l2GasPrice = "l2_gas_price" + case l1DataGasConsumed = "l1_data_gas_consumed" + case l1DataGasPrice = "l1_data_gas_price" case overallFee = "overall_fee" case feeUnit = "unit" } - public init(gasConsumed: Felt, gasPrice: Felt, dataGasConsumed: Felt, dataGasPrice: Felt, overallFee: Felt, feeUnit: StarknetPriceUnit) { - self.gasConsumed = gasConsumed - self.gasPrice = gasPrice - self.dataGasConsumed = dataGasConsumed - self.dataGasPrice = dataGasPrice + public init(l1GasConsumed: Felt, l1GasPrice: Felt, l2GasConsumed: Felt, l2GasPrice: Felt, l1DataGasConsumed: Felt, l1DataGasPrice: Felt, overallFee: Felt, feeUnit: StarknetPriceUnit) { + self.l1GasConsumed = l1GasConsumed + self.l1GasPrice = l1GasPrice + self.l2GasConsumed = l2GasConsumed + self.l2GasPrice = l2GasPrice + self.l1DataGasConsumed = l1DataGasConsumed + self.l1DataGasPrice = l1DataGasPrice self.overallFee = overallFee self.feeUnit = feeUnit } - public init?(gasConsumed: Felt, gasPrice: Felt, dataGasConsumed: Felt, dataGasPrice: Felt, feeUnit: StarknetPriceUnit) { - self.gasConsumed = gasConsumed - self.gasPrice = gasPrice - self.dataGasConsumed = dataGasConsumed - self.dataGasPrice = dataGasPrice - self.overallFee = Felt(gasPrice.value * gasConsumed.value + dataGasPrice.value * dataGasConsumed.value)! + public init?(l1GasConsumed: Felt, l1GasPrice: Felt, l2GasConsumed: Felt, l2GasPrice: Felt, l1DataGasConsumed: Felt, l1DataGasPrice: Felt, feeUnit: StarknetPriceUnit) { + self.l1GasConsumed = l1GasConsumed + self.l1GasPrice = l1GasPrice + self.l2GasConsumed = l2GasConsumed + self.l2GasPrice = l2GasPrice + self.l1DataGasConsumed = l1DataGasConsumed + self.l1DataGasPrice = l1DataGasPrice + self.overallFee = Felt(l1GasPrice.value * l1GasConsumed.value + l2GasPrice.value * l2GasConsumed.value + l1DataGasPrice.value * l1DataGasConsumed.value)! self.feeUnit = feeUnit } } @@ -74,12 +82,28 @@ public struct StarknetGetEventsResponse: Decodable, Equatable { } } +public struct StarknetGetStorageProofResponse: Decodable, Equatable { + public let classesProof: NodeHashToNodeMapping + public let contractsProof: ContractsProof + public let contractsStorageProof: [NodeHashToNodeMapping] + public let globalRoots: GlobalRoots + + enum CodingKeys: String, CodingKey { + case classesProof = "classes_proof" + case contractsProof = "contracts_proof" + case contractsStorageProof = "contracts_storage_proof" + case globalRoots = "global_roots" + } +} + public struct StarknetGetTransactionStatusResponse: Decodable, Equatable { public let finalityStatus: StarknetTransactionStatus public let executionStatus: StarknetTransactionExecutionStatus? + public let failureReason: String? enum CodingKeys: String, CodingKey { case finalityStatus = "finality_status" case executionStatus = "execution_status" + case failureReason = "failure_reason" } } diff --git a/Sources/Starknet/Data/StorageProof/ContractStorageKey.swift b/Sources/Starknet/Data/StorageProof/ContractStorageKey.swift new file mode 100644 index 000000000..b7f8c6f1d --- /dev/null +++ b/Sources/Starknet/Data/StorageProof/ContractStorageKey.swift @@ -0,0 +1,9 @@ +public struct ContractStorageKeys: Encodable { + let contractAddress: Felt + let storageKeys: [Felt] + + enum CodingKeys: String, CodingKey { + case contractAddress = "contract_address" + case storageKeys = "storage_keys" + } +} diff --git a/Sources/Starknet/Data/StorageProof/ContractsProof.swift b/Sources/Starknet/Data/StorageProof/ContractsProof.swift new file mode 100644 index 000000000..c5ceb689b --- /dev/null +++ b/Sources/Starknet/Data/StorageProof/ContractsProof.swift @@ -0,0 +1,29 @@ +public struct ContractsProof: Decodable, Equatable { + public let nodes: NodeHashToNodeMapping + public let contractLeavesData: [ContractLeafData] + + enum CodingKeys: String, CodingKey { + case nodes + case contractLeavesData = "contract_leaves_data" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + nodes = try container.decode(NodeHashToNodeMapping.self, forKey: .nodes) + contractLeavesData = try container.decode([ContractLeafData].self, forKey: .contractLeavesData) + } + + public static func == (lhs: ContractsProof, rhs: ContractsProof) -> Bool { + lhs.nodes == rhs.nodes && lhs.contractLeavesData == rhs.contractLeavesData + } + + public struct ContractLeafData: Decodable, Equatable { + public let nonce: Felt + public let classHash: Felt + + enum CodingKeys: String, CodingKey { + case nonce + case classHash = "class_hash" + } + } +} diff --git a/Sources/Starknet/Data/StorageProof/GlobalRoots.swift b/Sources/Starknet/Data/StorageProof/GlobalRoots.swift new file mode 100644 index 000000000..4eca4eaa9 --- /dev/null +++ b/Sources/Starknet/Data/StorageProof/GlobalRoots.swift @@ -0,0 +1,11 @@ +public struct GlobalRoots: Decodable, Equatable { + public let contractsTreeRoot: Felt + public let classesTreeRoot: Felt + public let blockHash: Felt + + enum CodingKeys: String, CodingKey { + case contractsTreeRoot = "contracts_tree_root" + case classesTreeRoot = "classes_tree_root" + case blockHash = "block_hash" + } +} diff --git a/Sources/Starknet/Data/StorageProof/MerkleNode.swift b/Sources/Starknet/Data/StorageProof/MerkleNode.swift new file mode 100644 index 000000000..f5ead94f0 --- /dev/null +++ b/Sources/Starknet/Data/StorageProof/MerkleNode.swift @@ -0,0 +1,74 @@ +import Foundation + +public enum MerkleNode: Codable, Equatable { + case binaryNode(BinaryNode) + case edgeNode(EdgeNode) + + public init(from decoder: Decoder) throws { + let binaryNodeKeys = Set(BinaryNode.CodingKeys.allCases.map(\.stringValue)) + let edgeNodeKeys = Set(EdgeNode.CodingKeys.allCases.map(\.stringValue)) + + let binaryNodeContainer = try decoder.container(keyedBy: BinaryNode.CodingKeys.self) + + if Set(binaryNodeContainer.allKeys.map(\.stringValue)) == binaryNodeKeys { + let binaryNode = try BinaryNode(from: decoder) + self = .binaryNode(binaryNode) + } else if let edgeNodeContainer = try? decoder.container(keyedBy: EdgeNode.CodingKeys.self), + Set(edgeNodeContainer.allKeys.map(\.stringValue)) == edgeNodeKeys + { + let edgeNode = try EdgeNode(from: decoder) + self = .edgeNode(edgeNode) + } else { + let context = DecodingError.Context( + codingPath: decoder.codingPath, + // TODO: Improve error message. + debugDescription: "Failed to decode MerkleNode from the given data." + ) + throw DecodingError.dataCorrupted(context) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case let .binaryNode(binaryNode): + try container.encode(binaryNode) + case let .edgeNode(edgeNode): + try container.encode(edgeNode) + } + } + + public static func == (lhs: MerkleNode, rhs: MerkleNode) -> Bool { + switch (lhs, rhs) { + case let (.binaryNode(lhsBinaryNode), .binaryNode(rhsBinaryNode)): + lhsBinaryNode == rhsBinaryNode + case let (.edgeNode(lhsEdgeNode), .edgeNode(rhsEdgeNode)): + lhsEdgeNode == rhsEdgeNode + default: + false + } + } +} + +public struct BinaryNode: Codable, Equatable { + let left: Felt + let right: Felt + + enum CodingKeys: String, CodingKey, CaseIterable { + case left + case right + } +} + +public struct EdgeNode: Codable, Equatable { + let path: NumAsHex + let length: Int + let child: Felt + + enum CodingKeys: String, CodingKey, CaseIterable { + case path + case length + case child + } +} diff --git a/Sources/Starknet/Data/StorageProof/NodeHashToNodeMappingItem.swift b/Sources/Starknet/Data/StorageProof/NodeHashToNodeMappingItem.swift new file mode 100644 index 000000000..12b163ef6 --- /dev/null +++ b/Sources/Starknet/Data/StorageProof/NodeHashToNodeMappingItem.swift @@ -0,0 +1,22 @@ +public typealias NodeHashToNodeMapping = [NodeHashToNodeMappingItem] + +public struct NodeHashToNodeMappingItem: Decodable, Equatable { + public let nodeHash: Felt + public let node: MerkleNode + + enum CodingKeys: String, CodingKey { + case nodeHash = "node_hash" + case node + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + nodeHash = try container.decode(Felt.self, forKey: .nodeHash) + node = try container.decode(MerkleNode.self, forKey: .node) + } + + public static func == (lhs: NodeHashToNodeMappingItem, rhs: NodeHashToNodeMappingItem) -> Bool { + lhs.nodeHash == rhs.nodeHash && lhs.node == rhs.node + } +} diff --git a/Sources/Starknet/Data/Transaction/Data/ExecutionResources.swift b/Sources/Starknet/Data/Transaction/Data/ExecutionResources.swift index e9298b6f3..ca8c3394f 100644 --- a/Sources/Starknet/Data/Transaction/Data/ExecutionResources.swift +++ b/Sources/Starknet/Data/Transaction/Data/ExecutionResources.swift @@ -1,78 +1,28 @@ import Foundation public protocol StarknetResources: Decodable, Equatable { - var steps: Int { get } - var memoryHoles: Int? { get } - var rangeCheckApplications: Int? { get } - var pedersenApplications: Int? { get } - var poseidonApplications: Int? { get } - var ecOpApplications: Int? { get } - var ecdsaApplications: Int? { get } - var bitwiseApplications: Int? { get } - var keccakApplications: Int? { get } - var segmentArena: Int? { get } -} - -public struct StarknetComputationResources: StarknetResources { - public let steps: Int - public let memoryHoles: Int? - public let rangeCheckApplications: Int? - public let pedersenApplications: Int? - public let poseidonApplications: Int? - public let ecOpApplications: Int? - public let ecdsaApplications: Int? - public let bitwiseApplications: Int? - public let keccakApplications: Int? - public let segmentArena: Int? - - enum CodingKeys: String, CodingKey { - case steps - case memoryHoles = "memory_holes" - case rangeCheckApplications = "range_check_builtin_applications" - case pedersenApplications = "pedersen_builtin_applications" - case poseidonApplications = "poseidon_builtin_applications" - case ecOpApplications = "ec_op_builtin_applications" - case ecdsaApplications = "ecdsa_builtin_applications" - case bitwiseApplications = "bitwise_builtin_applications" - case keccakApplications = "keccak_builtin_applications" - case segmentArena = "segment_arena_builtin" - } + var l1Gas: Int { get } + var l2Gas: Int { get } } public struct StarknetExecutionResources: StarknetResources { - public let steps: Int - public let memoryHoles: Int? - public let rangeCheckApplications: Int? - public let pedersenApplications: Int? - public let poseidonApplications: Int? - public let ecOpApplications: Int? - public let ecdsaApplications: Int? - public let bitwiseApplications: Int? - public let keccakApplications: Int? - public let segmentArena: Int? - public let dataAvailability: StarknetDataAvailability + public let l1Gas: Int + public let l1DataGas: Int + public let l2Gas: Int enum CodingKeys: String, CodingKey { - case steps - case memoryHoles = "memory_holes" - case rangeCheckApplications = "range_check_builtin_applications" - case pedersenApplications = "pedersen_builtin_applications" - case poseidonApplications = "poseidon_builtin_applications" - case ecOpApplications = "ec_op_builtin_applications" - case ecdsaApplications = "ecdsa_builtin_applications" - case bitwiseApplications = "bitwise_builtin_applications" - case keccakApplications = "keccak_builtin_applications" - case segmentArena = "segment_arena_builtin" - case dataAvailability = "data_availability" + case l1Gas = "l1_gas" + case l1DataGas = "l1_data_gas" + case l2Gas = "l2_gas" } } -public struct StarknetDataAvailability: Decodable, Equatable { +public struct StarknetInnerCallExecutionResources: StarknetResources { public let l1Gas: Int - public let l1DataGas: Int + public let l2Gas: Int enum CodingKeys: String, CodingKey { case l1Gas = "l1_gas" - case l1DataGas = "l1_data_gas" + case l2Gas = "l2_gas" } } diff --git a/Sources/Starknet/Data/Transaction/Data/ResourceBounds.swift b/Sources/Starknet/Data/Transaction/Data/ResourceBounds.swift index 24ed11fc7..aa178a487 100644 --- a/Sources/Starknet/Data/Transaction/Data/ResourceBounds.swift +++ b/Sources/Starknet/Data/Transaction/Data/ResourceBounds.swift @@ -24,10 +24,10 @@ public struct StarknetResourceBoundsMapping: Codable, Equatable, Hashable { public let l1Gas: StarknetResourceBounds public let l2Gas: StarknetResourceBounds - public init(l1Gas: StarknetResourceBounds) { - self.l1Gas = l1Gas - self.l2Gas = StarknetResourceBounds.zero - } + public static let zero = StarknetResourceBoundsMapping( + l1Gas: StarknetResourceBounds.zero, + l2Gas: StarknetResourceBounds.zero + ) enum CodingKeys: String, CodingKey { case l1Gas = "l1_gas" diff --git a/Sources/Starknet/Data/Transaction/Transaction.swift b/Sources/Starknet/Data/Transaction/Transaction.swift index 8e25b5cab..8c6e74abb 100644 --- a/Sources/Starknet/Data/Transaction/Transaction.swift +++ b/Sources/Starknet/Data/Transaction/Transaction.swift @@ -28,15 +28,14 @@ public struct StarknetInvokeTransactionV3: StarknetInvokeTransaction, StarknetTr public let hash: Felt? - public init(senderAddress: Felt, calldata: StarknetCalldata, signature: StarknetSignature, l1ResourceBounds: StarknetResourceBounds, nonce: Felt, forFeeEstimation: Bool = false, hash: Felt? = nil) { + public init(senderAddress: Felt, calldata: StarknetCalldata, signature: StarknetSignature, resourceBounds: StarknetResourceBoundsMapping, nonce: Felt, forFeeEstimation: Bool = false, hash: Felt? = nil) { self.senderAddress = senderAddress self.calldata = calldata self.signature = signature self.nonce = nonce self.version = forFeeEstimation ? .v3Query : .v3 self.hash = hash - // As of Starknet 0.13, most of v3 fields have hardcoded values. - self.resourceBounds = StarknetResourceBoundsMapping(l1Gas: l1ResourceBounds) + self.resourceBounds = resourceBounds self.tip = .zero self.paymasterData = [] self.accountDeploymentData = [] @@ -209,7 +208,7 @@ public struct StarknetDeployAccountTransactionV3: StarknetDeployAccountTransacti public let hash: Felt? - public init(signature: StarknetSignature, l1ResourceBounds: StarknetResourceBounds, nonce: Felt, contractAddressSalt: Felt, constructorCalldata: StarknetCalldata, classHash: Felt, forFeeEstimation: Bool = false, hash: Felt? = nil) { + public init(signature: StarknetSignature, resourceBounds: StarknetResourceBoundsMapping, nonce: Felt, contractAddressSalt: Felt, constructorCalldata: StarknetCalldata, classHash: Felt, forFeeEstimation: Bool = false, hash: Felt? = nil) { self.signature = signature self.nonce = nonce self.contractAddressSalt = contractAddressSalt @@ -217,8 +216,7 @@ public struct StarknetDeployAccountTransactionV3: StarknetDeployAccountTransacti self.classHash = classHash self.version = forFeeEstimation ? .v3Query : .v3 self.hash = hash - // As of Starknet 0.13, most of v3 fields have hardcoded values. - self.resourceBounds = StarknetResourceBoundsMapping(l1Gas: l1ResourceBounds) + self.resourceBounds = resourceBounds self.tip = .zero self.paymasterData = [] self.nonceDataAvailabilityMode = .l1 @@ -392,15 +390,14 @@ public struct StarknetDeclareTransactionV3: StarknetDeclareTransaction, Starknet public let hash: Felt? - public init(signature: StarknetSignature, l1ResourceBounds: StarknetResourceBounds, nonce: Felt, classHash: Felt, compiledClassHash: Felt, senderAddress: Felt, hash: Felt? = nil) { + public init(signature: StarknetSignature, resourceBounds: StarknetResourceBoundsMapping, nonce: Felt, classHash: Felt, compiledClassHash: Felt, senderAddress: Felt, hash: Felt? = nil) { self.signature = signature self.nonce = nonce self.classHash = classHash self.compiledClassHash = compiledClassHash self.senderAddress = senderAddress self.hash = hash - // As of Starknet 0.13, most of v3 fields have hardcoded values. - self.resourceBounds = StarknetResourceBoundsMapping(l1Gas: l1ResourceBounds) + self.resourceBounds = resourceBounds self.tip = .zero self.paymasterData = [] self.accountDeploymentData = [] diff --git a/Sources/Starknet/Data/Transaction/TransactionTrace.swift b/Sources/Starknet/Data/Transaction/TransactionTrace.swift index 614d4ec36..53b2726de 100644 --- a/Sources/Starknet/Data/Transaction/TransactionTrace.swift +++ b/Sources/Starknet/Data/Transaction/TransactionTrace.swift @@ -33,7 +33,7 @@ public struct StarknetFunctionInvocation: Decodable, Equatable { public let calls: [StarknetFunctionInvocation] public let events: [StarknetOrderedEvent] public let messages: [StarknetOrderedMessageToL1] - public let computationResources: StarknetComputationResources + public let executionResources: StarknetInnerCallExecutionResources private enum CodingKeys: String, CodingKey { case contractAddress = "contract_address" @@ -47,7 +47,7 @@ public struct StarknetFunctionInvocation: Decodable, Equatable { case calls case events case messages - case computationResources = "execution_resources" + case executionResources = "execution_resources" } } diff --git a/Sources/Starknet/Network/StarknetRequest.swift b/Sources/Starknet/Network/StarknetRequest.swift index e96712727..cc8c30130 100644 --- a/Sources/Starknet/Network/StarknetRequest.swift +++ b/Sources/Starknet/Network/StarknetRequest.swift @@ -140,6 +140,12 @@ public enum RequestBuilder { return StarknetRequest(method: .getEvents, params: .getEvents(params)) } + public static func getStorageProof(blockId: StarknetBlockId, classHashes: [Felt]?, contractAddresses: [Felt]?, contractsStorageKeys: [ContractStorageKeys]?) -> StarknetRequest { + let params = GetStorageProofParams(blockId: blockId, classHashes: classHashes, contractAddresses: contractAddresses, contractsStorageKeys: contractsStorageKeys) + + return StarknetRequest(method: .getStorageProof, params: .getStorageProof(params)) + } + /// Get the details and status of a submitted transaction /// /// - Parameters: @@ -189,6 +195,18 @@ public enum RequestBuilder { return StarknetRequest(method: .getTransactionStatus, params: .getTransactionStatus(params)) } + /// Get L1 handler transaction data for all L1 → L2 messages sent by the given L1 transaction. + /// + /// - Parameters: + /// - l1TransactionHash: The hash of the L1 transaction + /// + /// - Returns: The status of the messages + public static func getMessagesStatus(l1TransactionHash: NumAsHex) -> StarknetRequest<[MessageStatus]> { + let params = GetMessagesStatusPayload(l1TransactionHash: l1TransactionHash) + + return StarknetRequest(method: .getMessagesStatus, params: .getMessagesStatus(params)) + } + /// Get the currently configured Starknet chain id /// /// - Returns: The Starknet chain id diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift index 225a37fa5..bf236fd9c 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcMethod.swift @@ -11,10 +11,12 @@ enum JsonRpcMethod: String, Encodable { case getBlockNumber = "starknet_blockNumber" case getBlockHashAndNumber = "starknet_blockHashAndNumber" case getEvents = "starknet_getEvents" + case getStorageProof = "starknet_getStorageProof" case getTransactionByHash = "starknet_getTransactionByHash" case getTransactionByBlockIdAndIndex = "starknet_getTransactionByBlockIdAndIndex" case getTransactionReceipt = "starknet_getTransactionReceipt" case getTransactionStatus = "starknet_getTransactionStatus" + case getMessagesStatus = "starknet_getMessagesStatus" case getChainId = "starknet_chainId" case simulateTransactions = "starknet_simulateTransactions" case estimateMessageFee = "starknet_estimateMessageFee" diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift index e8c3e4eeb..42661c0fa 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift @@ -107,6 +107,20 @@ struct GetEventsPayload: Encodable { let filter: StarknetGetEventsFilter } +struct GetStorageProofParams: Encodable { + let blockId: StarknetBlockId + let classHashes: [Felt]? + let contractAddresses: [Felt]? + let contractsStorageKeys: [ContractStorageKeys]? + + enum CodingKeys: String, CodingKey { + case blockId = "block_id" + case classHashes = "class_hashes" + case contractAddresses = "contract_addresses" + case contractsStorageKeys = "contracts_storage_keys" + } +} + struct GetTransactionByHashParams: Encodable { let hash: Felt @@ -141,6 +155,14 @@ struct GetTransactionStatusPayload: Encodable { } } +struct GetMessagesStatusPayload: Encodable { + let l1TransactionHash: NumAsHex + + enum CodingKeys: String, CodingKey { + case l1TransactionHash = "l1_transaction_hash" + } +} + struct SimulateTransactionsParams: Encodable { let transactions: [any StarknetExecutableTransaction] let blockId: StarknetBlockId @@ -174,10 +196,12 @@ enum JsonRpcParams { case addDeployAccountTransaction(AddDeployAccountTransactionParams) case getClassHashAt(GetClassHashAtParams) case getEvents(GetEventsPayload) + case getStorageProof(GetStorageProofParams) case getTransactionByHash(GetTransactionByHashParams) case getTransactionByBlockIdAndIndex(GetTransactionByBlockIdAndIndex) case getTransactionReceipt(GetTransactionReceiptPayload) case getTransactionStatus(GetTransactionStatusPayload) + case getMessagesStatus(GetMessagesStatusPayload) case simulateTransactions(SimulateTransactionsParams) } @@ -204,6 +228,8 @@ extension JsonRpcParams: Encodable { try params.encode(to: encoder) case let .getEvents(params): try params.encode(to: encoder) + case let .getStorageProof(params): + try params.encode(to: encoder) case let .getTransactionByHash(params): try params.encode(to: encoder) case let .getTransactionByBlockIdAndIndex(params): @@ -212,6 +238,8 @@ extension JsonRpcParams: Encodable { try params.encode(to: encoder) case let .getTransactionStatus(params): try params.encode(to: encoder) + case let .getMessagesStatus(params): + try params.encode(to: encoder) case let .simulateTransactions(params): try params.encode(to: encoder) } diff --git a/Tests/StarknetTests/Accounts/AccountTest.swift b/Tests/StarknetTests/Accounts/AccountTest.swift index 18a551070..eaf2a9d93 100644 --- a/Tests/StarknetTests/Accounts/AccountTest.swift +++ b/Tests/StarknetTests/Accounts/AccountTest.swift @@ -140,7 +140,7 @@ final class AccountTests: XCTestCase { let feeEstimate = try await provider.send(request: account.estimateFeeV3(call: call, nonce: nonce, skipValidate: false))[0] let resourceBounds = feeEstimate.toResourceBounds() - let params = StarknetOptionalInvokeParamsV3(nonce: nonce, l1ResourceBounds: resourceBounds.l1Gas) + let params = StarknetOptionalInvokeParamsV3(nonce: nonce, resourceBounds: resourceBounds) let result = try await provider.send(request: account.executeV3(calls: [call], params: params)) @@ -208,7 +208,7 @@ final class AccountTests: XCTestCase { let feeEstimate = try await provider.send(request: newAccount.estimateDeployAccountFeeV3(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, nonce: nonce))[0] - let params = StarknetDeployAccountParamsV3(nonce: nonce, l1ResourceBounds: feeEstimate.toResourceBounds().l1Gas) + let params = StarknetDeployAccountParamsV3(nonce: nonce, resourceBounds: feeEstimate.toResourceBounds()) let deployAccountTransaction = try newAccount.signDeployAccountV3(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, params: params, forFeeEstimation: false) diff --git a/Tests/StarknetTests/Crypto/FeeEstimateTests.swift b/Tests/StarknetTests/Crypto/FeeEstimateTests.swift index c107c2dc5..9b5f7e7b6 100644 --- a/Tests/StarknetTests/Crypto/FeeEstimateTests.swift +++ b/Tests/StarknetTests/Crypto/FeeEstimateTests.swift @@ -6,16 +6,16 @@ final class FeeEstimateTests: XCTestCase { func testEstimateFeeToResourceBounds() { let cases: [(StarknetFeeEstimate, Double, Double, StarknetResourceBounds)] = [ - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 2138, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 21390, feeUnit: .wei), 1.1, 1.5, StarknetResourceBounds(maxAmount: 11, maxPricePerUnit: 3207)), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 1000, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 10010, feeUnit: .wei), 1.0, 1.0, StarknetResourceBounds(maxAmount: 10, maxPricePerUnit: 1000)), - (StarknetFeeEstimate(gasConsumed: Felt(UInt64AsHex.max.value - 100)!, gasPrice: Felt(UInt128AsHex.max.value - 100)!, dataGasConsumed: Felt.max, dataGasPrice: 10, overallFee: Felt.max, feeUnit: .wei), 1.1, 1.5, StarknetResourceBounds(maxAmount: UInt64AsHex.max, maxPricePerUnit: UInt128AsHex.max)), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 0, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 10, feeUnit: .wei), 1.5, 1.5, StarknetResourceBounds(maxAmount: 0, maxPricePerUnit: 0)), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 2000, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 20010, feeUnit: .wei), 2, 2, StarknetResourceBounds(maxAmount: 20, maxPricePerUnit: 4000)), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 2138, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 21390, feeUnit: .wei), 1.1, 1.5, StarknetResourceBounds(maxAmount: 11, maxPricePerUnit: 3207)), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 1000, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 10010, feeUnit: .wei), 1.0, 1.0, StarknetResourceBounds(maxAmount: 10, maxPricePerUnit: 1000)), + (StarknetFeeEstimate(l1GasConsumed: Felt(UInt64AsHex.max.value - 100)!, l1GasPrice: Felt(UInt128AsHex.max.value - 100)!, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: Felt.max, l1DataGasPrice: 10, overallFee: Felt.max, feeUnit: .wei), 1.1, 1.5, StarknetResourceBounds(maxAmount: UInt64AsHex.max, maxPricePerUnit: UInt128AsHex.max)), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 0, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 10, feeUnit: .wei), 1.5, 1.5, StarknetResourceBounds(maxAmount: 0, maxPricePerUnit: 0)), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 2000, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 20010, feeUnit: .wei), 2, 2, StarknetResourceBounds(maxAmount: 20, maxPricePerUnit: 4000)), ] cases.forEach { let resourceBounds = $0.toResourceBounds(amountMultiplier: $1, unitPriceMultiplier: $2) - let expected = StarknetResourceBoundsMapping(l1Gas: $3) + let expected = StarknetResourceBoundsMapping(l1Gas: $3, l2Gas: StarknetResourceBounds.zero) XCTAssertEqual(resourceBounds.l1Gas.maxAmount, expected.l1Gas.maxAmount) XCTAssertEqual(resourceBounds.l1Gas.maxPricePerUnit, expected.l1Gas.maxPricePerUnit) @@ -28,11 +28,11 @@ final class FeeEstimateTests: XCTestCase { func testEstimateFeeToMaxFee() { let cases: [(StarknetFeeEstimate, Double, Felt)] = [ - (StarknetFeeEstimate(gasConsumed: 1, gasPrice: 2138, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 2148, feeUnit: .wei), 1.1, 2362), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 1000, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 10010, feeUnit: .wei), 1.0, 10010), - (StarknetFeeEstimate(gasConsumed: Felt(UInt64AsHex.max.value - 100)!, gasPrice: Felt(UInt128AsHex.max.value - 100)!, dataGasConsumed: 10, dataGasPrice: 1, overallFee: Felt.max, feeUnit: .wei), 1.1, Felt.max), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 0, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 10, feeUnit: .wei), 1.5, 15), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 2000, dataGasConsumed: 10, dataGasPrice: 1, overallFee: 20010, feeUnit: .wei), 2, 40020), + (StarknetFeeEstimate(l1GasConsumed: 1, l1GasPrice: 2138, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 2148, feeUnit: .wei), 1.1, 2362), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 1000, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 10010, feeUnit: .wei), 1.0, 10010), + (StarknetFeeEstimate(l1GasConsumed: Felt(UInt64AsHex.max.value - 100)!, l1GasPrice: Felt(UInt128AsHex.max.value - 100)!, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: Felt.max, feeUnit: .wei), 1.1, Felt.max), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 0, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 10, feeUnit: .wei), 1.5, 15), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 2000, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, overallFee: 20010, feeUnit: .wei), 2, 40020), ] cases.forEach { @@ -44,10 +44,10 @@ final class FeeEstimateTests: XCTestCase { func testEstimateFeeOverallFeeCalculation() { let cases: [(StarknetFeeEstimate, Felt)] = [ - (StarknetFeeEstimate(gasConsumed: 1, gasPrice: 2138, dataGasConsumed: 10, dataGasPrice: 1, feeUnit: .wei)!, 2148), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 1000, dataGasConsumed: 10, dataGasPrice: 1, feeUnit: .wei)!, 10010), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 0, dataGasConsumed: 10, dataGasPrice: 1, feeUnit: .wei)!, 10), - (StarknetFeeEstimate(gasConsumed: 10, gasPrice: 2000, dataGasConsumed: 10, dataGasPrice: 1, feeUnit: .wei)!, 20010), + (StarknetFeeEstimate(l1GasConsumed: 1, l1GasPrice: 2138, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, feeUnit: .wei)!, 2148), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 1000, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, feeUnit: .wei)!, 10010), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 0, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, feeUnit: .wei)!, 10), + (StarknetFeeEstimate(l1GasConsumed: 10, l1GasPrice: 2000, l2GasConsumed: 0, l2GasPrice: 0, l1DataGasConsumed: 10, l1DataGasPrice: 1, feeUnit: .wei)!, 20010), ] cases.forEach { diff --git a/Tests/StarknetTests/Data/MerkleNodeTests.swift b/Tests/StarknetTests/Data/MerkleNodeTests.swift new file mode 100644 index 000000000..f45d4f9a5 --- /dev/null +++ b/Tests/StarknetTests/Data/MerkleNodeTests.swift @@ -0,0 +1,85 @@ +import XCTest + +@testable import Starknet + +final class MerkleNodeTests: XCTestCase { + func testBinaryNode() throws { + let json = """ + { + "left": "0x123", + "right": "0x456" + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + + let node = try decoder.decode(MerkleNode.self, from: json) + + if case let .binaryNode(binaryNode) = node { + XCTAssertEqual(binaryNode.left, Felt(0x123)) + XCTAssertEqual(binaryNode.right, Felt(0x456)) + } else { + XCTFail("Expected a binaryNode, but got \(node)") + } + } + + func testInvalidBinaryNode() throws { + let json = """ + { + "left": "0x123" + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + + XCTAssertThrowsError(try decoder.decode(MerkleNode.self, from: json)) + } + + func testEdgeNode() throws { + let json = """ + { + "path": "0x123", + "length": 456, + "child": "0x789" + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + + let node = try decoder.decode(MerkleNode.self, from: json) + if case let .edgeNode(edgeNode) = node { + XCTAssertEqual(edgeNode.path, 123) + XCTAssertEqual(edgeNode.length, 456) + XCTAssertEqual(edgeNode.child, Felt("0x789")) + } else { + XCTFail("Expected an edgeNode, but got \(node)") + } + } + + func testInvalidEdgeNode() throws { + let json = """ + { + "path": "0x123", + "length": 456 + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + + XCTAssertThrowsError(try decoder.decode(MerkleNode.self, from: json)) + } + + func testInvalidNodeWithMixedKeys() throws { + let json = """ + { + "path": "0x123", + "length": "456", + "left": 10 + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + + XCTAssertThrowsError(try decoder.decode(MerkleNode.self, from: json)) + } +} diff --git a/Tests/StarknetTests/Data/TransactionReceiptTests.swift b/Tests/StarknetTests/Data/TransactionReceiptTests.swift index 751e83104..1836391a2 100644 --- a/Tests/StarknetTests/Data/TransactionReceiptTests.swift +++ b/Tests/StarknetTests/Data/TransactionReceiptTests.swift @@ -16,7 +16,7 @@ let invokeReceiptWithBlockInfo = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}} + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789} } """ let invokeReceipt = """ @@ -31,7 +31,7 @@ let invokeReceipt = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}} + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789} } """ let declareReceiptWithBlockInfo = """ @@ -48,7 +48,7 @@ let declareReceiptWithBlockInfo = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}} + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789} } """ let declareReceipt = """ @@ -63,7 +63,7 @@ let declareReceipt = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}} + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789} } """ let deployAccountReceiptWithBlockInfo = """ @@ -80,7 +80,7 @@ let deployAccountReceiptWithBlockInfo = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}}, + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789}, "contract_address": "0x789" } """ @@ -96,7 +96,7 @@ let deployAccountReceipt = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}}, + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789}, "contract_address": "0x789" } """ @@ -114,7 +114,7 @@ let deployReceiptWithBlockInfo = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}}, + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789}, "contract_address": "0x789" } """ @@ -130,7 +130,7 @@ let deployReceipt = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}}, + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789}, "contract_address": "0x789" } """ @@ -148,7 +148,7 @@ let l1HandlerReceiptWithBlockInfo = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}}, + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789}, "message_hash":"0x2137" } """ @@ -164,7 +164,7 @@ let l1HandlerReceipt = """ "events": [], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L2", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}}, + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789}, "message_hash":"0x2137" } """ @@ -255,7 +255,7 @@ final class TransactionReceiptTests: XCTestCase { ], "execution_status": "SUCCEEDED", "finality_status": "ACCEPTED_ON_L1", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}} + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789} } """.data(using: .utf8)! @@ -285,7 +285,7 @@ final class TransactionReceiptTests: XCTestCase { "revert_reason": "Error in the called contract (0x03b1b7a7ae9a136a327b01b89ddfee24a474c74bf76032876b5754e44cd7040b):\\nError at pc=0:32:\\nGot an exception while executing a hint: Custom Hint Error: Requested contract address ContractAddress(PatriciaKey(StarkFelt(\\"0x0000000000000000000000000000000000000000000000000000000000000042\\"))) is not deployed.\\nCairo traceback (most recent call last):\\nUnknown location (pc=0:557)\\nUnknown location (pc=0:519)\\nUnknown location (pc=0:625)\\n", "execution_status": "REVERTED", "finality_status": "ACCEPTED_ON_L1", - "execution_resources": {"steps": 999, "memory_holes" : 1, "range_check_builtin_applications": 21, "pedersen_builtin_applications": 37, "poseidon_builtin_applications": 451, "ec_op_builtin_applications": 123, "ecdsa_builtin_applications": 789, "bitwise_builtin_applications": 1, "keccak_builtin_applications": 1, "segment_arena_builtin": 2, "data_availability": {"l1_gas": 123, "l1_data_gas": 456}} + "execution_resources": {"l1_gas": 123, "l1_data_gas": 456, "l2_gas": 789} } """.data(using: .utf8)! diff --git a/Tests/StarknetTests/Data/TransactionStatusTests.swift b/Tests/StarknetTests/Data/TransactionStatusTests.swift index 074f2dfd0..58500e63e 100644 --- a/Tests/StarknetTests/Data/TransactionStatusTests.swift +++ b/Tests/StarknetTests/Data/TransactionStatusTests.swift @@ -10,6 +10,9 @@ final class TransactionStatusTests: XCTestCase { let json2 = """ {"finality_status":"ACCEPTED_ON_L2","execution_status":"SUCCEEDED"} """.data(using: .utf8)! + let json3 = """ + {"finality_status":"ACCEPTED_ON_L2","execution_status":"SUCCEEDED","failure_reason": "xyz"} + """.data(using: .utf8)! let invalidJson = """ {"finality_status":"INVALID_STATUS"} """.data(using: .utf8)! @@ -18,9 +21,15 @@ final class TransactionStatusTests: XCTestCase { let status = try decoder.decode(StarknetGetTransactionStatusResponse.self, from: json) XCTAssertNil(status.executionStatus) + XCTAssertNil(status.failureReason) let status2 = try decoder.decode(StarknetGetTransactionStatusResponse.self, from: json2) XCTAssertNotNil(status2.executionStatus) + XCTAssertNil(status2.failureReason) + + let status3 = try decoder.decode(StarknetGetTransactionStatusResponse.self, from: json3) + XCTAssertNotNil(status3.executionStatus) + XCTAssertNotNil(status3.failureReason) XCTAssertThrowsError(try decoder.decode(StarknetGetTransactionStatusResponse.self, from: invalidJson)) } diff --git a/Tests/StarknetTests/Providers/ProviderTests.swift b/Tests/StarknetTests/Providers/ProviderTests.swift index f56527b3a..458c58767 100644 --- a/Tests/StarknetTests/Providers/ProviderTests.swift +++ b/Tests/StarknetTests/Providers/ProviderTests.swift @@ -232,16 +232,16 @@ final class ProviderTests: XCTestCase { let call = StarknetCall(contractAddress: contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) let call2 = StarknetCall(contractAddress: contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [100_000_000_000]) - let params1 = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: .zero) + let params1 = StarknetInvokeParamsV3(nonce: nonce, resourceBounds: StarknetResourceBoundsMapping.zero) let tx1 = try account.signV3(calls: [call], params: params1, forFeeEstimation: true) - let params2 = StarknetInvokeParamsV3(nonce: Felt(nonce.value + 1)!, l1ResourceBounds: .zero) + let params2 = StarknetInvokeParamsV3(nonce: Felt(nonce.value + 1)!, resourceBounds: StarknetResourceBoundsMapping.zero) let tx2 = try account.signV3(calls: [call, call2], params: params2, forFeeEstimation: true) let _ = try await provider.send(request: RequestBuilder.estimateFee(for: [tx1, tx2], simulationFlags: [])) - let tx1WithoutSignature = StarknetInvokeTransactionV3(senderAddress: tx1.senderAddress, calldata: tx1.calldata, signature: [], l1ResourceBounds: tx1.resourceBounds.l1Gas, nonce: nonce, forFeeEstimation: true) - let tx2WithoutSignature = StarknetInvokeTransactionV3(senderAddress: tx2.senderAddress, calldata: tx2.calldata, signature: [], l1ResourceBounds: tx2.resourceBounds.l1Gas, nonce: Felt(nonce.value + 1)!, forFeeEstimation: true) + let tx1WithoutSignature = StarknetInvokeTransactionV3(senderAddress: tx1.senderAddress, calldata: tx1.calldata, signature: [], resourceBounds: tx1.resourceBounds, nonce: nonce, forFeeEstimation: true) + let tx2WithoutSignature = StarknetInvokeTransactionV3(senderAddress: tx2.senderAddress, calldata: tx2.calldata, signature: [], resourceBounds: tx2.resourceBounds, nonce: Felt(nonce.value + 1)!, forFeeEstimation: true) let _ = try await provider.send(request: RequestBuilder.estimateFee(for: [tx1WithoutSignature, tx2WithoutSignature], simulationFlags: [.skipValidate])) } @@ -277,13 +277,14 @@ final class ProviderTests: XCTestCase { let nonce = await (try? provider.send(request: newAccount.getNonce())) ?? .zero - let params = StarknetDeployAccountParamsV3(nonce: nonce, l1ResourceBounds: .zero) + let resourceBounds = StarknetResourceBoundsMapping(l1Gas: StarknetResourceBounds.zero, l2Gas: StarknetResourceBounds.zero) + let params = StarknetDeployAccountParamsV3(nonce: nonce, resourceBounds: resourceBounds) let tx = try newAccount.signDeployAccountV3(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, params: params, forFeeEstimation: true) let _ = try await provider.send(request: RequestBuilder.estimateFee(for: tx)) - let txWithoutSignature = StarknetDeployAccountTransactionV3(signature: [], l1ResourceBounds: tx.resourceBounds.l1Gas, nonce: tx.nonce, contractAddressSalt: tx.contractAddressSalt, constructorCalldata: tx.constructorCalldata, classHash: tx.classHash, forFeeEstimation: true) + let txWithoutSignature = StarknetDeployAccountTransactionV3(signature: [], resourceBounds: tx.resourceBounds, nonce: tx.nonce, contractAddressSalt: tx.contractAddressSalt, constructorCalldata: tx.constructorCalldata, classHash: tx.classHash, forFeeEstimation: true) let _ = try await provider.send(request: RequestBuilder.estimateFee(for: txWithoutSignature, simulationFlags: [.skipValidate])) } @@ -306,10 +307,12 @@ final class ProviderTests: XCTestCase { at: StarknetBlockId.tag(.pending) )) - XCTAssertNotEqual(Felt.zero, feeEstimate.gasPrice) - XCTAssertNotEqual(Felt.zero, feeEstimate.gasConsumed) + XCTAssertNotEqual(Felt.zero, feeEstimate.l1GasPrice) + XCTAssertNotEqual(Felt.zero, feeEstimate.l1GasConsumed) + XCTAssertNotEqual(Felt.zero, feeEstimate.l2GasPrice) + XCTAssertNotEqual(Felt.zero, feeEstimate.l2GasConsumed) XCTAssertNotEqual(Felt.zero, feeEstimate.overallFee) - XCTAssertEqual(feeEstimate.gasPrice.value * feeEstimate.gasConsumed.value + feeEstimate.dataGasPrice.value * feeEstimate.dataGasConsumed.value, feeEstimate.overallFee.value) + XCTAssertEqual(feeEstimate.l1GasPrice.value * feeEstimate.l1GasConsumed.value + feeEstimate.l2GasPrice.value * feeEstimate.l2GasConsumed.value + feeEstimate.l1DataGasPrice.value * feeEstimate.l1DataGasConsumed.value, feeEstimate.overallFee.value) } func testSimulateTransactionsV1() async throws { @@ -359,7 +362,8 @@ final class ProviderTests: XCTestCase { try await Self.devnetClient.prefundAccount(address: account.address, amount: 5_000_000_000_000_000_000, unit: .fri) let invokeL1Gas = StarknetResourceBounds(maxAmount: 500_000, maxPricePerUnit: 100_000_000_000) - let params = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: invokeL1Gas) + let invokResourceBounds = StarknetResourceBoundsMapping(l1Gas: invokeL1Gas, l2Gas: StarknetResourceBounds.zero) + let params = StarknetInvokeParamsV3(nonce: nonce, resourceBounds: invokResourceBounds) let invokeTx = try account.signV3(calls: [call], params: params, forFeeEstimation: false) @@ -372,7 +376,8 @@ final class ProviderTests: XCTestCase { try await Self.devnetClient.prefundAccount(address: newAccountAddress, amount: 5_000_000_000_000_000_000, unit: .fri) let deployAccountL1Gas = StarknetResourceBounds(maxAmount: 500_000, maxPricePerUnit: 100_000_000_000) - let newAccountParams = StarknetDeployAccountParamsV3(nonce: 0, l1ResourceBounds: deployAccountL1Gas) + let deployAccountResourceBounds = StarknetResourceBoundsMapping(l1Gas: deployAccountL1Gas, l2Gas: StarknetResourceBounds.zero) + let newAccountParams = StarknetDeployAccountParamsV3(nonce: 0, resourceBounds: deployAccountResourceBounds) let deployAccountTx = try newAccount.signDeployAccountV3(classHash: accountClassHash, calldata: [newPublicKey], salt: .zero, params: newAccountParams, forFeeEstimation: false) let simulations = try await provider.send(request: RequestBuilder.simulateTransactions([invokeTx, deployAccountTx], at: .tag(.pending), simulationFlags: [])) @@ -385,13 +390,13 @@ final class ProviderTests: XCTestCase { senderAddress: invokeTx.senderAddress, calldata: invokeTx.calldata, signature: [], - l1ResourceBounds: invokeTx.resourceBounds.l1Gas, + resourceBounds: invokeTx.resourceBounds, nonce: invokeTx.nonce ) let deployAccountWithoutSignature = StarknetDeployAccountTransactionV3( signature: [], - l1ResourceBounds: deployAccountTx.resourceBounds.l1Gas, nonce: deployAccountTx.nonce, + resourceBounds: deployAccountTx.resourceBounds, nonce: deployAccountTx.nonce, contractAddressSalt: deployAccountTx.contractAddressSalt, constructorCalldata: deployAccountTx.constructorCalldata, classHash: deployAccountTx.classHash