Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Fix decoding crashes in decoding for get account and get actions. #213

Merged
merged 3 commits into from
Aug 15, 2019
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
4 changes: 4 additions & 0 deletions EosioSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
B4178067226E472E00084A6E /* DynamicKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4178066226E472E00084A6E /* DynamicKey.swift */; };
B42C0EFB2268F86200E7A28F /* ResolverExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B42C0EFA2268F86200E7A28F /* ResolverExtensions.swift */; };
B44461632267CE37002C77C9 /* EosioTransactionPromises.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44461622267CE37002C77C9 /* EosioTransactionPromises.swift */; };
B46535962305B33A001B0DBD /* EosioInt64.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46535952305B33A001B0DBD /* EosioInt64.swift */; };
B466F0962229DA870082DC4E /* EosioSerializationProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B466F0952229DA870082DC4E /* EosioSerializationProviderProtocol.swift */; };
B47513D6227A43B8006CF07A /* EosioUInt64.swift in Sources */ = {isa = PBXBuildFile; fileRef = B47513D5227A43B8006CF07A /* EosioUInt64.swift */; };
B4892C6D221E0CAA0050FA72 /* RequestModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4892C6C221E0CAA0050FA72 /* RequestModels.swift */; };
Expand Down Expand Up @@ -125,6 +126,7 @@
B4178066226E472E00084A6E /* DynamicKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicKey.swift; sourceTree = "<group>"; };
B42C0EFA2268F86200E7A28F /* ResolverExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverExtensions.swift; sourceTree = "<group>"; };
B44461622267CE37002C77C9 /* EosioTransactionPromises.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EosioTransactionPromises.swift; sourceTree = "<group>"; };
B46535952305B33A001B0DBD /* EosioInt64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EosioInt64.swift; sourceTree = "<group>"; };
B466F0952229DA870082DC4E /* EosioSerializationProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EosioSerializationProviderProtocol.swift; sourceTree = "<group>"; };
B47513D5227A43B8006CF07A /* EosioUInt64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EosioUInt64.swift; sourceTree = "<group>"; };
B4892C6C221E0CAA0050FA72 /* RequestModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestModels.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -290,6 +292,7 @@
B4A80E28221F15F200C5134D /* RpcProviderResponseModels.swift */,
8F49E8CE225E6E6A00082631 /* RpcProviderRequestModels.swift */,
B47513D5227A43B8006CF07A /* EosioUInt64.swift */,
B46535952305B33A001B0DBD /* EosioInt64.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -580,6 +583,7 @@
8F49E8CF225E6E6A00082631 /* RpcProviderRequestModels.swift in Sources */,
2856028F22182EFA00642377 /* EosioTransactionAbis.swift in Sources */,
B4E8F6BC2232020100FA7E63 /* RIPEMD160.swift in Sources */,
B46535962305B33A001B0DBD /* EosioInt64.swift in Sources */,
8F6B04B822287A5300215CD0 /* EosioName.swift in Sources */,
B466F0962229DA870082DC4E /* EosioSerializationProviderProtocol.swift in Sources */,
8F6B04B922287A5300215CD0 /* ZAssert.swift in Sources */,
Expand Down
58 changes: 58 additions & 0 deletions EosioSwift/EosioRpcProvider/Models/EosioInt64.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// EosioInt64.swift
// EosioSwift

// Created by Steve McCoole on 8/15/19
// Copyright (c) 2017-2019 block.one and its contributors. All rights reserved.
//

import Foundation

/// Meta type that can hold a 64 bit signed value that can come from the server as a Int64 or a String.
public enum EosioInt64: Codable {
/// Value as a `Int64`.
case int64(Int64)
/// Value as a `String`.
case string(String)

public var value: Int64 {
switch self {
case .int64(let value):
return value
case .string(let str):
let val: Int64? = Int64(str)
return val ?? 0
}
}

/// Initialize from a decoder, attempting to decode as a `Int64` first. If that is unsuccessful, attempt to decode as `String`.
///
/// - Parameter decoder: Decoder to read from.
/// - Throws: DecodingError if the value cannot be decoded as `Int64` or `String`.
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int64(container.decode(Int64.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(EosioInt64.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}

/// Encode to an encoder, attempting to encode as a `Int64` first. If that is unsuccessful, attempt to encode as `String`.
///
/// - Parameter encoder: Encoder to encode to.
/// - Throws: EncodingError if the value cannot be encoded as `Int64` or `String`.
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int64(let int64):
try container.encode(int64)
case .string(let string):
try container.encode(string)
}
}
}
4 changes: 2 additions & 2 deletions EosioSwift/EosioRpcProvider/Models/EosioUInt64.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum EosioUInt64: Codable {
}
}

/// Initialize from a decoder, attempting to decode as a `UInt64` first. If that is unsuccessful, atttempt to decode as `String`.
/// Initialize from a decoder, attempting to decode as a `UInt64` first. If that is unsuccessful, attempt to decode as `String`.
///
/// - Parameter decoder: Decoder to read from.
/// - Throws: DecodingError if the value cannot be decoded as `UInt64` or `String`.
Expand All @@ -42,7 +42,7 @@ public enum EosioUInt64: Codable {
}
}

/// Encode to an encoder, attempting to encode as a `UInt64` first. If that is unsuccessful, atttempt to encode as `String`.
/// Encode to an encoder, attempting to encode as a `UInt64` first. If that is unsuccessful, attempt to encode as `String`.
///
/// - Parameter encoder: Encoder to encode to.
/// - Throws: EncodingError if the value cannot be encoded as `UInt64` or `String`.
Expand Down
20 changes: 10 additions & 10 deletions EosioSwift/EosioRpcProvider/Models/RpcProviderResponseModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,12 @@ public struct EosioRpcAccountResponse: Decodable, EosioRpcResponseProtocol {
public var lastCodeUpdate: String = ""
public var created: String = ""
public var coreLiquidBalance: String = ""
public var ramQuota: EosioUInt64 = EosioUInt64.uint64(0)
public var netWeight: EosioUInt64 = EosioUInt64.uint64(0)
public var cpuWeight: EosioUInt64 = EosioUInt64.uint64(0)
public var ramQuota: EosioInt64 = EosioInt64.int64(0)
public var netWeight: EosioInt64 = EosioInt64.int64(0)
public var cpuWeight: EosioInt64 = EosioInt64.int64(0)
public var netLimit: [String: Any]
public var cpuLimit: [String: Any]
public var ramUsage: EosioUInt64 = EosioUInt64.uint64(0)
public var ramUsage: EosioInt64 = EosioInt64.int64(0)
public var permissions: [Permission]
public var totalResources: [String: Any]?
public var selfDelegatedBandwidth: [String: Any]?
Expand Down Expand Up @@ -368,9 +368,9 @@ public struct EosioRpcAccountResponse: Decodable, EosioRpcResponseProtocol {
lastCodeUpdate = try container.decodeIfPresent(String.self, forKey: .lastCodeUpdate) ?? ""
created = try container.decodeIfPresent(String.self, forKey: .created) ?? ""
coreLiquidBalance = try container.decodeIfPresent(String.self, forKey: .coreLiquidBalance) ?? ""
ramQuota = try container.decodeIfPresent(EosioUInt64.self, forKey: .ramQuota) ?? EosioUInt64.uint64(0)
netWeight = try container.decodeIfPresent(EosioUInt64.self, forKey: .netWeight) ?? EosioUInt64.uint64(0)
cpuWeight = try container.decodeIfPresent(EosioUInt64.self, forKey: .cpuWeight) ?? EosioUInt64.uint64(0)
ramQuota = try container.decodeIfPresent(EosioInt64.self, forKey: .ramQuota) ?? EosioInt64.int64(0)
netWeight = try container.decodeIfPresent(EosioInt64.self, forKey: .netWeight) ?? EosioInt64.int64(0)
cpuWeight = try container.decodeIfPresent(EosioInt64.self, forKey: .cpuWeight) ?? EosioInt64.int64(0)

// netLimit = try container.decodeIfPresent(JSONValue.self, forKey: .netLimit)?.toDictionary() ?? [String: Any]()

Expand All @@ -379,7 +379,7 @@ public struct EosioRpcAccountResponse: Decodable, EosioRpcResponseProtocol {
let cpuLimitContainer = try? container.nestedContainer(keyedBy: DynamicKey.self, forKey: .cpuLimit)
cpuLimit = cpuLimitContainer?.decodeDynamicKeyValues() ?? [String: Any]()

ramUsage = try container.decodeIfPresent(EosioUInt64.self, forKey: .ramUsage) ?? EosioUInt64.uint64(0)
ramUsage = try container.decodeIfPresent(EosioInt64.self, forKey: .ramUsage) ?? EosioInt64.int64(0)
permissions = try container.decodeIfPresent([Permission].self, forKey: .permissions) ?? [Permission]()
let totalResourcesContainer = try? container.nestedContainer(keyedBy: DynamicKey.self, forKey: .totalResources)
totalResources = totalResourcesContainer?.decodeDynamicKeyValues()
Expand Down Expand Up @@ -930,7 +930,7 @@ public struct EosioRpcActionsResponseActionTrActDeltas: Decodable, EosioRpcRespo
public var _rawResponse: Any?

public var account: String
public var delta: EosioUInt64
public var delta: EosioInt64

enum CustomCodingKeys: String, CodingKey {
case account
Expand All @@ -941,7 +941,7 @@ public struct EosioRpcActionsResponseActionTrActDeltas: Decodable, EosioRpcRespo
let container = try decoder.container(keyedBy: CustomCodingKeys.self)

account = try container.decode(String.self, forKey: .account)
delta = try container.decode(EosioUInt64.self, forKey: .delta)
delta = try container.decode(EosioInt64.self, forKey: .delta)
}
}

Expand Down
114 changes: 114 additions & 0 deletions EosioSwiftTests/RpcProviderExtensionEndpointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class RpcProviderExtensionEndpointTests: XCTestCase {

/// Test testGetAccount() implementation.
// swiftlint:disable function_body_length
// swiftlint:disable cyclomatic_complexity
func testGetAccount() {
var callCount = 1
(stub(condition: isHost("localhost")) { request in
Expand Down Expand Up @@ -204,7 +205,73 @@ class RpcProviderExtensionEndpointTests: XCTestCase {
}
wait(for: [expect], timeout: 30)
}

func testGetAccountNegativeUsageValues() {
var callCount = 1
(stub(condition: isHost("localhost")) { request in
if let urlString = request.url?.absoluteString {
if callCount == 1 && urlString == "https://localhost/v1/chain/get_info" {
callCount += 1
return RpcTestConstants.getInfoOHHTTPStubsResponse()
} else if callCount == 2 && urlString == "https://localhost/v1/chain/get_account" {
return RpcTestConstants.getOHHTTPStubsResponseForJson(json: RpcTestConstants.accountNegativeUsageValuesJson)
} else {
return RpcTestConstants.getErrorOHHTTPStubsResponse(code: NSURLErrorUnknown, reason: "Unexpected call count in stub: \(callCount)")
}
} else {
return RpcTestConstants.getErrorOHHTTPStubsResponse(reason: "No valid url string in request in stub")
}
}).name = "Get Account Negative Usage Values stub"
let expect = expectation(description: "testGetAccountNegativeUsageValues")
let requestParameters = EosioRpcAccountRequest(accountName: "cryptkeeper")
rpcProvider?.getAccount(requestParameters: requestParameters) { response in
switch response {
case .success(let eosioRpcAccountResponse):
XCTAssertNotNil(eosioRpcAccountResponse)
XCTAssert(eosioRpcAccountResponse.accountName == "cryptkeeper")
XCTAssert(eosioRpcAccountResponse.ramQuota.value == -1)
XCTAssert(eosioRpcAccountResponse.cpuWeight.value == -1)
let permissions = eosioRpcAccountResponse.permissions
XCTAssertNotNil(permissions)
guard let activePermission = permissions.filter({$0.permName == "active"}).first else {
return XCTFail("Cannot find Active permission in permissions structure of the account")
}
XCTAssert(activePermission.parent == "owner")
guard let keysAndWeight = activePermission.requiredAuth.keys.first else {
return XCTFail("Cannot find key in keys structure of the account")
}
XCTAssert(keysAndWeight.key == "EOS5j67P1W2RyBXAL8sNzYcDLox3yLpxyrxgkYy1xsXzVCvzbYpba")
guard let firstPermission = activePermission.requiredAuth.accounts.first else {
return XCTFail("Can't find permission in keys structure of the account")
}
XCTAssert(firstPermission.permission.actor == "eosaccount1")
XCTAssert(firstPermission.permission.permission == "active")
XCTAssert(activePermission.requiredAuth.waits.first?.waitSec.value == 259200)
XCTAssertNotNil(eosioRpcAccountResponse.totalResources)
if let dict = eosioRpcAccountResponse.totalResources {
if let owner = dict["owner"] as? String {
XCTAssert(owner == "cryptkeeper")
} else {
XCTFail("Should be able to get total_resources owner as String and should equal cryptkeeper.")
}
if let rambytes = dict["ram_bytes"] as? UInt64 {
XCTAssert(rambytes == 13639863)
} else {
XCTFail("Should be able to get total_resources ram_bytes as UIn64 and should equal 13639863.")
}
} else {
XCTFail("Should be able to get total_resources as [String : Any].")
}
case .failure(let err):
print(err.description)
XCTFail("Failed get_account")
}
expect.fulfill()
}
wait(for: [expect], timeout: 30)
}
// swiftlint:enable function_body_length
// swiftlint:enable cyclomatic_complexity

/// Test testGetCurrencyBalance() implementation.
func testGetCurrencyBalance() {
Expand Down Expand Up @@ -524,6 +591,53 @@ class RpcProviderExtensionEndpointTests: XCTestCase {
wait(for: [expect], timeout: 30)
}

func testGetActionsNegativeDelta() {
var callCount = 1
(stub(condition: isHost("localhost")) { request in
if let urlString = request.url?.absoluteString {
if callCount == 1 && urlString == "https://localhost/v1/chain/get_info" {
callCount += 1
return RpcTestConstants.getInfoOHHTTPStubsResponse()
} else if callCount == 2 && urlString == "https://localhost/v1/history/get_actions" {
return RpcTestConstants.getOHHTTPStubsResponseForJson(json: RpcTestConstants.actionsNegativeDeltaJson)
} else {
return RpcTestConstants.getErrorOHHTTPStubsResponse(code: NSURLErrorUnknown, reason: "Unexpected call count in stub: \(callCount)")
}
} else {
return RpcTestConstants.getErrorOHHTTPStubsResponse(reason: "No valid url string in request in stub")
}
}).name = "Get Actions Negative Delta stub"
let expect = expectation(description: "testGetActionsNegativeDelta")
let requestParameters = EosioRpcHistoryActionsRequest(position: -1, offset: -20, accountName: "cryptkeeper")
rpcProvider?.getActions(requestParameters: requestParameters) { response in
switch response {
case .success(let eosioRpcActionsResponse):
XCTAssertNotNil(eosioRpcActionsResponse._rawResponse)
XCTAssert(eosioRpcActionsResponse.lastIrreversibleBlock.value == 55535908)
XCTAssert(eosioRpcActionsResponse.timeLimitExceededError == false)
XCTAssert(eosioRpcActionsResponse.actions.first?.globalActionSequence.value == 6483908013)
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.receipt.receiverSequence.value == 1236)
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.receipt.authorizationSequence.count == 1)
if let firstSequence = eosioRpcActionsResponse.actions.first?.actionTrace.receipt.authorizationSequence.first as? [Any] {
guard let accountName = firstSequence.first as? String, accountName == "powersurge22" else {
return XCTFail("Should be able to find account name")
}
}
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.action.name == "transfer")
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.action.authorization.first?.permission == "active")
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.action.data["memo"] as? String == "l2sbjsdrfd.m")
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.action.hexData == "10826257e3ab38ad000000004800a739f3eef20b00000000044d4545544f4e450c6c3273626a736472666a2e6f")
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.accountRamDeltas.first?.delta.value == -1)
XCTAssert(eosioRpcActionsResponse.actions.first?.actionTrace.inlineTrances.first?.receipt.actionDigest == "62021c2315d8245d0546180daf825d728a5564d2831e8b2d1f2d01309bf06b")
case .failure(let err):
print(err.description)
XCTFail("Failed get_actions")
}
expect.fulfill()
}
wait(for: [expect], timeout: 30)
}

/// Test getControlledAccounts implementation.
func testGetControlledAccounts() {
var callCount = 1
Expand Down
Loading