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 17 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
6 changes: 3 additions & 3 deletions Sources/Starknet/Crypto/FeeCalculation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import Foundation
public extension StarknetFeeEstimate {
/// Convert estimated fee to resource bounds with applied multipliers
///
/// Calculates `maxAmountL1 = overallFee / l1GasPrice`, unless `l1GasPrice` is 0, then `maxAmount` is 0.
/// Calculates `maxAmountL2 = overallFee / l2GasPrice`, unless `l2GasPrice` is 0, then `maxAmount` is 0.
/// 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 `maxPricePerUnit` by **round((unitPriceMultiplier) \* 100)** and performs integer division by 100 on both.
/// 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:
Expand Down
6 changes: 3 additions & 3 deletions Sources/Starknet/Data/StorageProof/ContractStorageKey.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
public struct ContractStorageKey: Encodable {
public struct ContractStorageKeys: Encodable {
let contractAddress: Felt
let key: Felt
let storageKeys: [Felt]

enum CodingKeys: String, CodingKey {
case contractAddress = "contract_address"
case key
case storageKeys = "storage_keys"
}
}
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
}
}
13 changes: 0 additions & 13 deletions Sources/Starknet/Data/StorageProof/MerkleNode/BinaryNode.swift

This file was deleted.

18 changes: 0 additions & 18 deletions Sources/Starknet/Data/StorageProof/MerkleNode/EdgeNode.swift

This file was deleted.

14 changes: 0 additions & 14 deletions Sources/Starknet/Data/StorageProof/MerkleNode/MerkleNode.swift

This file was deleted.

23 changes: 4 additions & 19 deletions Sources/Starknet/Data/StorageProof/NodeHashToNodeMappingItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ public typealias NodeHashToNodeMapping = [NodeHashToNodeMappingItem]

public struct NodeHashToNodeMappingItem: Decodable, Equatable {
public let nodeHash: Felt
public let node: any MerkleNode
public let node: MerkleNode

enum CodingKeys: String, CodingKey {
case nodeHash = "node_hash"
Expand All @@ -11,27 +11,12 @@ public struct NodeHashToNodeMappingItem: Decodable, Equatable {

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
nodeHash = try container.decode(Felt.self, forKey: .nodeHash)

let nodeContainer = try container.decode(NodeTypeContainer.self, forKey: .node)
switch nodeContainer.type {
case .binaryNode:
node = try container.decode(BinaryNode.self, forKey: .node)
case .edgeNode:
node = try container.decode(EdgeNode.self, forKey: .node)
}
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 && (try? lhs.node.isEqual(to: rhs.node)) ?? false
}

private struct NodeTypeContainer: Decodable {
let type: NodeType

enum NodeType: String, Decodable {
case binaryNode = "BinaryNode"
case edgeNode = "EdgeNode"
}
lhs.nodeHash == rhs.nodeHash && lhs.node == rhs.node
}
}
4 changes: 2 additions & 2 deletions Sources/Starknet/Network/StarknetRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ public enum RequestBuilder {
return StarknetRequest(method: .getEvents, params: .getEvents(params))
}

public static func getStorageProof(blockId: StarknetBlockId, classHashes: [Felt]?, contractAddresses: [Felt], contractStorageKeys: [ContractStorageKey]) -> StarknetRequest<StarknetGetStorageProofResponse> {
let params = GetStorageProofParams(blockId: blockId, classHashes: classHashes, contractAddresses: contractAddresses, contractStorageKeys: contractStorageKeys)
public static func getStorageProof(blockId: StarknetBlockId, classHashes: [Felt]?, contractAddresses: [Felt]?, contractsStorageKeys: [ContractStorageKeys]?) -> StarknetRequest<StarknetGetStorageProofResponse> {
let params = GetStorageProofParams(blockId: blockId, classHashes: classHashes, contractAddresses: contractAddresses, contractsStorageKeys: contractsStorageKeys)

return StarknetRequest(method: .getStorageProof, params: .getStorageProof(params))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ struct GetStorageProofParams: Encodable {
let blockId: StarknetBlockId
let classHashes: [Felt]?
let contractAddresses: [Felt]?
let contractStorageKeys: [ContractStorageKey]?
let contractsStorageKeys: [ContractStorageKeys]?

enum CodingKeys: String, CodingKey {
case blockId = "block_id"
case classHashes = "class_hashes"
case contractAddresses = "contract_addresses"
case contractStorageKeys = "contract_storage_keys"
case contractsStorageKeys = "contracts_storage_keys"
}
}

Expand Down
85 changes: 85 additions & 0 deletions Tests/StarknetTests/Data/MerkleNodeTests.swift
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

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

0x123 != 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))
}
}
2 changes: 1 addition & 1 deletion Tests/StarknetTests/Providers/ProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ final class ProviderTests: XCTestCase {
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)!, resourceBounds: resourceBounds)
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: []))
Expand Down
Loading