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

Add hotfix from v0.10.2 to main #211

Merged
merged 2 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
117 changes: 74 additions & 43 deletions Sources/Starknet/Data/TypedData/StarknetTypedData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public struct StarknetTypedData: Codable, Equatable, Hashable {
self.domain = domain
self.message = message

self.revision = try domain.resolveRevision()
self.revision = domain.revision

self.allTypes = self.types.merging(
Self.PresetType.cases(revision: self.revision).reduce(into: [:]) { $0[$1.rawValue] = $1.params },
Expand Down Expand Up @@ -397,34 +397,38 @@ public extension StarknetTypedData {
public let name: Element
public let version: Element
public let chainId: Element
public let revision: Element?

public func resolveRevision() throws -> Revision {
guard let revision else {
return .v0
}
switch revision {
case let .felt(felt):
guard let revision = Revision(rawValue: felt) else {
throw StarknetTypedDataError.invalidRevision(felt)
}
return revision
default:
throw StarknetTypedDataError.decodingError
}
}
public let revision: Revision

public var separatorName: String {
switch try! resolveRevision() {
switch revision {
case .v0: "StarkNetDomain"
case .v1: "StarknetDomain"
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(Element.self, forKey: .name)
self.version = try container.decode(Element.self, forKey: .version)
self.chainId = try container.decode(Element.self, forKey: .chainId)
self.revision = try container.decodeIfPresent(Revision.self, forKey: .revision) ?? .v0
}
}

enum Revision: Felt, Codable, Equatable {
case v0 = 0
case v1 = 1
enum Revision: String, Codable, Equatable {
case v0 = "0"
case v1 = "1"

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringValue = try? container.decode(String.self), let value = Revision(rawValue: stringValue) {
self = value
} else if let intValue = try? container.decode(Int.self), let value = Revision(rawValue: String(intValue)) {
self = value
} else {
throw StarknetTypedDataError.decodingError
}
}
}

enum Element: Codable, Hashable, Equatable {
Expand All @@ -434,6 +438,8 @@ public extension StarknetTypedData {
case felt(Felt)
case signedFelt(Felt)
case bool(Bool)
case decimal(UInt64)
case signedDecimal(Int64)

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
Expand All @@ -442,21 +448,21 @@ public extension StarknetTypedData {
self = .object(object)
} else if let array = try? container.decode([Element].self) {
self = .array(array)
} else if let uint = try? container.decode(UInt64.self) {
self = .decimal(UInt64(uint))
} else if let int = try? container.decode(Int64.self) {
self = .signedDecimal(Int64(int))
} else if let felt = try? container.decode(Felt.self) {
self = .felt(felt)
} else if let uint = try? container.decode(UInt64.self),
let felt = Felt(uint)
{
self = .felt(felt)
} else if let int = try? container.decode(Int64.self),
let felt = Felt(fromSigned: int)
{
self = .signedFelt(felt)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let string = try? container.decode(String.self) {
if let uint = BigUInt(string),
let felt = Felt(uint)
if let uint = UInt64(string) {
self = .decimal(uint)
} else if let int = Int64(string) {
self = .signedDecimal(int)
} else if let uint = BigUInt(string),
let felt = Felt(uint)
{
self = .felt(felt)
} else if let int = BigInt(string),
Expand All @@ -475,6 +481,10 @@ public extension StarknetTypedData {
switch self {
case let .string(string):
try string.encode(to: encoder)
case let .decimal(decimal):
try decimal.encode(to: encoder)
case let .signedDecimal(signedDecimal):
try signedDecimal.encode(to: encoder)
case let .felt(felt):
try felt.encode(to: encoder)
case let .signedFelt(felt):
Expand Down Expand Up @@ -514,6 +524,11 @@ extension StarknetTypedData {

func unwrapFelt(from element: Element) throws -> Felt {
switch element {
case let .decimal(decimal):
guard let felt = Felt(decimal) else {
throw StarknetTypedDataError.decodingError
}
return felt
case let .felt(felt):
return felt
case let .string(string):
Expand All @@ -527,23 +542,34 @@ extension StarknetTypedData {
}

func unwrapU128(from element: Element) throws -> Felt {
guard case let .felt(felt) = element else {
throw StarknetTypedDataError.invalidNumericValue(element)
}

guard felt.value < BigUInt(2).power(128) else {
switch element {
case let .decimal(decimal):
guard decimal < BigUInt(2).power(128) else {
throw StarknetTypedDataError.invalidNumericValue(element)
}
return Felt(integerLiteral: decimal)
case let .felt(felt):
guard felt.value < BigUInt(2).power(128) else {
throw StarknetTypedDataError.invalidNumericValue(element)
}
return felt
default:
throw StarknetTypedDataError.invalidNumericValue(element)
}

return felt
}

func unwrapI128(from element: Element) throws -> Felt {
let felt = switch element {
case let .felt(felt):
felt
case let .signedFelt(signedFelt):
signedFelt
let felt: Felt

switch element {
case let .decimal(decimalValue):
felt = Felt(decimalValue)!
case let .signedDecimal(signedDecimalValue):
felt = Felt(fromSigned: signedDecimalValue)!
case let .felt(feltValue):
felt = feltValue
case let .signedFelt(signedFeltValue):
felt = signedFeltValue
default:
throw StarknetTypedDataError.invalidNumericValue(element)
}
Expand Down Expand Up @@ -580,6 +606,11 @@ extension StarknetTypedData {

func unwrapBool(from element: Element) throws -> Felt {
switch element {
case let .decimal(decimal):
guard decimal == 0 || decimal == 1 else {
throw StarknetTypedDataError.invalidBool(element)
}
return decimal == 0 ? .zero : .one
case let .felt(felt):
guard felt == .zero || felt == .one else {
throw StarknetTypedDataError.invalidBool(element)
Expand Down
2 changes: 2 additions & 0 deletions Sources/Starknet/Data/TypedData/TypeDeclaration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public extension StarknetTypedData {
fileprivate enum CodingKeys: String, CodingKey {
case name
case contains
case type
}
}

Expand All @@ -43,6 +44,7 @@ public extension StarknetTypedData {
fileprivate enum CodingKeys: String, CodingKey {
case name
case contains
case type
}
}

Expand Down
18 changes: 18 additions & 0 deletions Tests/StarknetTests/Data/TypedDataTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,22 @@ final class TypedDataTests: XCTestCase {
XCTAssertEqual(hash, expectedResult)
}
}

func testEncodingTimestamp() throws {
let tdJsonStr = try loadTypedDataJsonStringFromFile(name: "typed_data_rev_1_timestamp_example")
guard let tdEncodedContents = tdJsonStr.data(using: .utf8) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to load typed data"))
}

do {
let td = try JSONDecoder().decode(StarknetTypedData.self, from: tdEncodedContents)
let tdEncoded = try JSONEncoder().encode(td)
let originalJson = try JSONSerialization.jsonObject(with: tdEncodedContents, options: []) as? [String: Any]
let encodedJson = try JSONSerialization.jsonObject(with: tdEncoded, options: []) as? [String: Any]

XCTAssertEqual(originalJson as NSDictionary?, encodedJson as NSDictionary?)
} catch let e {
throw e
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"types": {
"StarknetDomain": [
{
"name": "name",
"type": "shortstring"
},
{
"name": "version",
"type": "shortstring"
},
{
"name": "chainId",
"type": "shortstring"
},
{
"name": "revision",
"type": "shortstring"
}
],
"Allowed Method": [
{
"name": "Contract Address",
"type": "ContractAddress"
},
{
"name": "selector",
"type": "selector"
}
],
"Session": [
{
"name": "Expires At",
"type": "timestamp"
},
{
"name": "Allowed Methods",
"type": "merkletree",
"contains": "Allowed Method"
},
{
"name": "Metadata",
"type": "string"
},
{
"name": "Session Key",
"type": "felt"
}
]
},
"primaryType": "Session",
"domain": {
"name": "SessionAccount.session",
"version": "0x31",
"chainId": "0x534e5f5345504f4c4941",
"revision": "1"
},
"message": {
"Expires At": 1720704208,
"Allowed Methods": [
{
"Contract Address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"selector": "transfer"
}
],
"Metadata": "{\"projectID\":\"test-dapp\",\"txFees\":[{\"tokenAddress\":\"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7\",\"maxAmount\":\"100000000000000000\"}]}",
"Session Key": "0x3f94fb0d79cba8507a8775dc4cd4328f91ba33d1a32cb54c8c28eef0e258fa3"
}
}
9 changes: 9 additions & 0 deletions Tests/StarknetTests/Utils/StarknetTypedData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ func loadTypedDataFromFile(name: String) throws -> StarknetTypedData {

return try JSONDecoder().decode(StarknetTypedData.self, from: contentsData)
}

func loadTypedDataJsonStringFromFile(name: String) throws -> String {
guard let url = Bundle.module.url(forResource: name, withExtension: "json"),
let contents = try? String(contentsOf: url)
else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to load typed data from file \(name)"))
}
return contents
}
Loading