Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support RPC 0.8.0 #220

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a778a82
Support storage proof
franciszekjob Oct 24, 2024
d73d3e0
Move `ContractStorageKey`
franciszekjob Oct 24, 2024
fe40311
Support `starknet_getMessagesStatus`
franciszekjob Oct 24, 2024
4b56a9f
Adapt execution resources
franciszekjob Oct 25, 2024
d24a76f
Support failure reason in transaction status
franciszekjob Oct 25, 2024
34dcb78
Remove unnecessary coding key change in `StarknetFeeEstimate`
franciszekjob Oct 25, 2024
34ab541
Add `StarknetResourceBoundsMapping.zero`
franciszekjob Oct 28, 2024
b41e8fc
Fix `testEstimateInvokeV3Fee`
franciszekjob Oct 28, 2024
fe432f1
Restore `==` in `BinaryNode`
franciszekjob Oct 28, 2024
032e7fa
Update Sources/Starknet/Crypto/FeeCalculation.swift
franciszekjob Oct 28, 2024
3fd0189
Make `contractAddresses` and `contractStorageKeys` optional in `getSt…
franciszekjob Oct 28, 2024
cf88930
Add merkle node tests
franciszekjob Oct 28, 2024
009f27f
Refactor `init` in `MerkleNode`
franciszekjob Oct 28, 2024
3d0393b
Add `==` in `MerkleNode`
franciszekjob Oct 28, 2024
39283b7
Format
franciszekjob Oct 28, 2024
1fa6003
Update values in `MerkleNodeTests`
franciszekjob Oct 28, 2024
9e02d86
Add todo
franciszekjob Oct 28, 2024
f66140b
Fix typo in `contracts_storage_keys`
franciszekjob Oct 28, 2024
e1185e6
Rename `ContractStorageKey` to `ContractStorageKey`
franciszekjob Oct 28, 2024
f758824
Make `blockId` optional
franciszekjob Oct 28, 2024
18a907a
Make `blockId` optional in `getStorageProof`
franciszekjob Oct 28, 2024
fac3657
Change `path` type to `NumAsHex`
franciszekjob Oct 28, 2024
a27bff3
Revert optional block id changes
franciszekjob Oct 28, 2024
3933cb5
Update `StarknetFeeEstimate` description
franciszekjob Oct 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Sources/Starknet/Accounts/StarknetAccount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ 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 {
StarknetDeployAccountTransactionV1(signature: signature, maxFee: params.maxFee, nonce: params.nonce, contractAddressSalt: salt, constructorCalldata: calldata, classHash: classHash, forFeeEstimation: forFeeEstimation)
}

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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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] : [])
Expand All @@ -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] : [])
Expand Down
6 changes: 3 additions & 3 deletions Sources/Starknet/Accounts/StarknetAccountProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions Sources/Starknet/Crypto/FeeCalculation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,28 @@ 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.
/// - unitPriceMultiplier: multiplier for max price per unit, defaults to 1.5.
///
/// - 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.
Expand Down
19 changes: 8 additions & 11 deletions Sources/Starknet/Data/Execution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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 = []
Expand All @@ -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)
}
}

Expand Down
11 changes: 11 additions & 0 deletions Sources/Starknet/Data/MessageStatus.swift
Original file line number Diff line number Diff line change
@@ -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"
}
}
62 changes: 43 additions & 19 deletions Sources/Starknet/Data/Responses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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"
}
}
9 changes: 9 additions & 0 deletions Sources/Starknet/Data/StorageProof/ContractStorageKey.swift
Original file line number Diff line number Diff line change
@@ -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"
}
}
29 changes: 29 additions & 0 deletions Sources/Starknet/Data/StorageProof/ContractsProof.swift
Original file line number Diff line number Diff line change
@@ -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)
}

Comment on lines +10 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
}

Comment on lines +16 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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"
}
}
}
11 changes: 11 additions & 0 deletions Sources/Starknet/Data/StorageProof/GlobalRoots.swift
Original file line number Diff line number Diff line change
@@ -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"
}
}
74 changes: 74 additions & 0 deletions Sources/Starknet/Data/StorageProof/MerkleNode.swift
Original file line number Diff line number Diff line change
@@ -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)
}
Comment on lines +8 to +28
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me this is slightly overcomplicated:

Suggested change
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)
}
if let binaryNode = try? BinaryNode(from: decoder) {
self = .binaryNode(binaryNode)
} else if let edgeNode = try? EdgeNode(from: decoder) {
self = .edgeNode(edgeNode)
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Failed to decode MerkleNode."))
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And while the spec is not merged yet, do you think maybe it's worth it to reach out to starknet-specs maintainers and ask "type" field to be added? "Type" fields are used heavily in the spec overall, so this might be a reasonable ask.

}

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
}
}
Comment on lines +42 to +51
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be necessary? 🤔

The advantage of the enum approach is that Equatable conformance should be synthesized automatically.

Suggested change
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
}
}
Loading
Loading