From 339701fea3d070e07cb89b8b9a594a63aef18966 Mon Sep 17 00:00:00 2001 From: Luis Reisewitz Date: Wed, 8 Nov 2017 15:44:46 +0100 Subject: [PATCH] Small fixes. (#11) - Eliminates warnings in the generated code by using special handling for methods that would result in a `Void` return. - Fix crash when address does respond to a certain function (e.g. if address is not a HumanStandardToken decoding the`0x` return data for `Name` function would crash). - Revert back to official CryptoSwift. - Improve testing guide. - Add `unwrap()` to some types to allow for getting the wrapped value out of the solidity wrapper. --- .gitignore | 5 +++ .swiftlint.yml | 2 +- Package.resolved | 10 +++--- Package.swift | 4 +-- README.md | 8 +++-- Resources/BivrostError.swift | 2 ++ Resources/Coding/BaseDecoder.swift | 11 ++++--- Resources/Extensions/StringExtension.swift | 10 ++++++ Resources/Types/Address.swift | 4 +-- Resources/Types/ArrayX.swift | 2 +- Resources/Types/Bool.swift | 6 ++++ Resources/Types/Bytes.swift | 4 +++ Resources/Types/BytesX.swift | 4 +++ Resources/Types/Function.swift | 2 +- Resources/Types/IntX.swift | 4 +++ Resources/Types/String.swift | 7 +++++ Resources/Types/UIntX.swift | 4 +++ Resources/Types/VariableArray.swift | 2 +- .../Generating/ContractTemplateModel.swift | 8 ++++- .../Templates/ContractTemplate.swift | 31 ++++++++++++------- .../BivrostKit/Parsing/ContractParser.swift | 2 +- Sources/BivrostKit/Parsing/ParsingError.swift | 2 +- 22 files changed, 99 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 318d37f..f66a4ca 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,8 @@ fastlane/test_output # Xcodeproj generated with `swift package generate-xcodeproj`. # Can be safely ignored and recreated, as it's only used for development. Bivrost.xcodeproj + +# Local test data should not be committed. +testcontracts +testoutput + diff --git a/.swiftlint.yml b/.swiftlint.yml index 23d6185..41a476c 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -39,4 +39,4 @@ opt_in_rules: # some rules are only opt-in # Make certain rules give out an error force_cast: error force_try: error -force_unwrapping: error \ No newline at end of file +force_unwrapping: warning diff --git a/Package.resolved b/Package.resolved index 521a329..0cfa4dd 100644 --- a/Package.resolved +++ b/Package.resolved @@ -21,11 +21,11 @@ }, { "package": "CryptoSwift", - "repositoryURL": "https://github.com/zweigraf/CryptoSwift.git", + "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift", "state": { "branch": null, - "revision": "fdd115318528c9c25a06c0a89553f71bde7233da", - "version": null + "revision": "d0084e4a6fe9490b3baab3d3c2aad58d3a852daf", + "version": "0.8.0" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/Quick/Quick.git", "state": { "branch": null, - "revision": "c498edf4aabb694a5b8a861ec3d69f0c5ab57d9e", - "version": null + "revision": "0ff81f2c665b4381f526bd656f8708dd52a9ea2f", + "version": "1.2.0" } }, { diff --git a/Package.swift b/Package.swift index 46e5221..787b4f4 100644 --- a/Package.swift +++ b/Package.swift @@ -11,8 +11,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/krzyzanowskim/CryptoSwift", .upToNextMinor(from: "0.8.0")), .package(url: "https://github.com/attaswift/BigInt.git", .upToNextMajor(from: "3.0.0")), - .package(url: "https://github.com/Quick/Nimble.git", .upToNextMajor(from: "7.0.2")), - .package(url: "https://github.com/Quick/Quick.git", .revision("c498edf4aabb694a5b8a861ec3d69f0c5ab57d9e")), + .package(url: "https://github.com/Quick/Nimble.git", .upToNextMinor(from: "7.0.2")), + .package(url: "https://github.com/Quick/Quick.git", .upToNextMinor(from: "1.2.0")), .package(url: "https://github.com/kylef/Commander.git", .revision("e0cbee1bd73778c1076c675eaf660e97d09f3b32")), // PathKit fork supporting SPM4 .package(url: "https://github.com/PoissonBallon/PathKit.git", .branch("master")), diff --git a/README.md b/README.md index ae93b9c..7f723d1 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ 🔥 🌈 Bridge between Solidity Contracts and Swift -[![CI Status](http://img.shields.io/travis/gnosis/bivrost-swift.svg?style=flat)](https://travis-ci.org/gnosis/bivrost-swift) - Bivrost is in very early development. Do not use this for anything important. ## Description @@ -116,4 +114,8 @@ Bivrost is available under the MIT license. See the LICENSE file for more info. ## Issues -Tests currently do not work when generating an Xcode project via `swift package generate-xcodeproj`. This issue is described at the bottom of . Drop to the CMD and use `swift test` for testing. \ No newline at end of file +Tests currently do not work when generating an Xcode project via `swift package generate-xcodeproj`. This issue is described in . + +Workarounds: +- Drop to the CMD and use `swift test` for testing. +- Go to the `QuickSpecBase` target build settings in the generated Xcode project. Set `Enables Module` to `YES` (`CLANG_ENABLE_MODULES=YES`). Now testing should work. \ No newline at end of file diff --git a/Resources/BivrostError.swift b/Resources/BivrostError.swift index 2bee261..0c52cef 100644 --- a/Resources/BivrostError.swift +++ b/Resources/BivrostError.swift @@ -12,6 +12,8 @@ import Foundation enum BivrostError: Error { enum Decoder: Error { + case endOfSourceData + // Decoding case invalidBool(hex: String) case invalidUInt(hex: String) diff --git a/Resources/Coding/BaseDecoder.swift b/Resources/Coding/BaseDecoder.swift index 27ef845..0e40bc8 100644 --- a/Resources/Coding/BaseDecoder.swift +++ b/Resources/Coding/BaseDecoder.swift @@ -59,14 +59,14 @@ struct BaseDecoder { } static func decodeBytes(source: PartitionData) throws -> Data { - let sizePart = source.consume() + let sizePart = try source.consume() guard let size = Int(sizePart, radix: 16) else { throw BivrostError.Decoder.invalidBytesLength(hex: sizePart) } var byteHolder = Data() while byteHolder.count < size { - if let data = Data(fromHexEncodedString: source.consume()) { + if let data = Data(fromHexEncodedString: try source.consume()) { byteHolder.append(data) } } @@ -107,7 +107,7 @@ struct BaseDecoder { } // We have dynamic types, we need to check the dynamic array // Consume all locations to jump cursor ahead to dynamic section - (0.. String { + func consume() throws -> String { + guard index < lines.count else { + throw BivrostError.Decoder.endOfSourceData + } let returnValue = lines[index] index += 1 return returnValue diff --git a/Resources/Extensions/StringExtension.swift b/Resources/Extensions/StringExtension.swift index 9d92f8f..32f810a 100644 --- a/Resources/Extensions/StringExtension.swift +++ b/Resources/Extensions/StringExtension.swift @@ -84,3 +84,13 @@ extension String { return forBytes * 2 } } + +// MARK: - Hex Helper +extension String { + /// Returns a new string, removing a '0x' prefix if present. + var withoutHexPrefix: String { + return hasPrefix("0x") + ? String(dropFirst(2)) + : self + } +} diff --git a/Resources/Types/Address.swift b/Resources/Types/Address.swift index 57a0d27..f332d9d 100644 --- a/Resources/Types/Address.swift +++ b/Resources/Types/Address.swift @@ -15,9 +15,7 @@ public extension Solidity { private let bigInt: BigUInt init(_ address: Swift.String) throws { - let hex = address.hasPrefix("0x") - ? Swift.String(address[address.index(address.startIndex, offsetBy: 2)...]) - : address + let hex = address.withoutHexPrefix guard let bigInt = BigUInt(hex, radix: 16) else { throw BivrostError.Address.invalidAddress(hex) } diff --git a/Resources/Types/ArrayX.swift b/Resources/Types/ArrayX.swift index 82d18ed..f1bfc95 100644 --- a/Resources/Types/ArrayX.swift +++ b/Resources/Types/ArrayX.swift @@ -10,7 +10,7 @@ extension _DoNotUse { // swiftlint:disable:next type_name class _ArrayX { - private let items: [T] + let items: [T] class var length: UInt { fatalError("Not meant to be called directly.") } diff --git a/Resources/Types/Bool.swift b/Resources/Types/Bool.swift index 5983039..4762d84 100644 --- a/Resources/Types/Bool.swift +++ b/Resources/Types/Bool.swift @@ -10,14 +10,20 @@ import BigInt public extension Solidity { struct Bool { + private let value: Swift.Bool private let wrapper: UInt8 init(_ value: Swift.Bool) { + self.value = value guard let wrapper = try? UInt8(BigUInt(value ? 1 : 0)) else { fatalError("Solidity.Bool could not be created with value of \(value). This should not happen.") } self.wrapper = wrapper } + + func unwrap() -> Swift.Bool { + return value + } } } diff --git a/Resources/Types/Bytes.swift b/Resources/Types/Bytes.swift index 7642128..81fcc44 100644 --- a/Resources/Types/Bytes.swift +++ b/Resources/Types/Bytes.swift @@ -21,6 +21,10 @@ public extension Solidity { self.value = value self.length = length } + + func unwrap() -> Data { + return value + } } } diff --git a/Resources/Types/BytesX.swift b/Resources/Types/BytesX.swift index eb2658a..248790b 100644 --- a/Resources/Types/BytesX.swift +++ b/Resources/Types/BytesX.swift @@ -24,6 +24,10 @@ public extension _DoNotUse { } self.value = value } + + func unwrap() -> Data { + return value + } } } diff --git a/Resources/Types/Function.swift b/Resources/Types/Function.swift index 86045ad..52c96d8 100644 --- a/Resources/Types/Function.swift +++ b/Resources/Types/Function.swift @@ -41,7 +41,7 @@ extension Solidity.Function: StaticType { } static func decode(source: BaseDecoder.PartitionData) throws -> Solidity.Function { - let line = source.consume() + let line = try source.consume() // 20 bytes / 40 chars for Address as UInt160 let addressHex = String(line[line.startIndex ..< line.index(startDistance: 40)]) let uint = try BaseDecoder.decodeUInt(data: addressHex) diff --git a/Resources/Types/IntX.swift b/Resources/Types/IntX.swift index 26f7a3c..4cfb033 100644 --- a/Resources/Types/IntX.swift +++ b/Resources/Types/IntX.swift @@ -26,6 +26,10 @@ public extension _DoNotUse { } value = int } + + func unwrap() -> BigInt { + return value + } } } diff --git a/Resources/Types/String.swift b/Resources/Types/String.swift index 38eb89c..2cc0b89 100644 --- a/Resources/Types/String.swift +++ b/Resources/Types/String.swift @@ -9,14 +9,21 @@ public extension Solidity { struct String { let wrapper: Solidity.Bytes + let value: Swift.String init?(_ value: Swift.String) { + self.value = value + guard let data = value.data(using: .utf8), let bytes = Solidity.Bytes(data) else { return nil } self.wrapper = bytes } + + func unwrap() -> Swift.String { + return value + } } } diff --git a/Resources/Types/UIntX.swift b/Resources/Types/UIntX.swift index 24e7266..cb15280 100644 --- a/Resources/Types/UIntX.swift +++ b/Resources/Types/UIntX.swift @@ -26,6 +26,10 @@ public extension _DoNotUse { } value = uint } + + func unwrap() -> BigUInt { + return value + } } } diff --git a/Resources/Types/VariableArray.swift b/Resources/Types/VariableArray.swift index 412ed99..6bca880 100644 --- a/Resources/Types/VariableArray.swift +++ b/Resources/Types/VariableArray.swift @@ -26,7 +26,7 @@ extension Solidity { // MARK: - DynamicType extension Solidity.VariableArray: DynamicType { static func decode(source: BaseDecoder.PartitionData) throws -> Solidity.VariableArray { - let sizePart = source.consume() + let sizePart = try source.consume() guard let size = UInt(sizePart, radix: 16) else { throw BivrostError.Decoder.invalidArrayLength(hex: sizePart) } diff --git a/Sources/BivrostKit/Generating/ContractTemplateModel.swift b/Sources/BivrostKit/Generating/ContractTemplateModel.swift index ad75676..cff9336 100644 --- a/Sources/BivrostKit/Generating/ContractTemplateModel.swift +++ b/Sources/BivrostKit/Generating/ContractTemplateModel.swift @@ -20,9 +20,11 @@ struct ContractTemplateModel { let decodeReturnReturnValue: String // Name to be returned at the end of decode(returnData:) let decodeReturnTypes: [DecodeType] + let hasEmptyDecodeReturnFunction: Bool let decodeArgumentsReturnValue: String // Name to be returned at the end of decode(argumentsData:) let decodeArgumentsTypes: [DecodeType] + let hasEmptyDecodeArgumentsFunction: Bool struct DecodeType { let name: String @@ -61,12 +63,16 @@ struct ContractTemplateModel { let outputString = tupleString(for: object.outputs) let encodeArgumentsString = encodeArguments(for: object.inputs) + let decodeReturnTypesArray = decodeTypes(for: object.outputs) + let hasEmptyDecodeReturnFunction = decodeReturnTypesArray.isEmpty let decodeReturnReturnValueString = decodeReturnReturnValue(for: decodeReturnTypesArray) + let decodeArgumentsTypesArray = decodeTypes(for: object.inputs) + let hasEmptyDecodeArgumentsFunction = decodeArgumentsTypesArray.isEmpty let decodeArgumentsReturnValueString = decodeArgumentsReturnValue(for: decodeArgumentsTypesArray) - return ContractTemplateModel.Function(name: preparedFunctionName, methodId: functionMethodId, input: inputString, output: outputString, encodeArguments: encodeArgumentsString, decodeReturnReturnValue: decodeReturnReturnValueString, decodeReturnTypes: decodeReturnTypesArray, decodeArgumentsReturnValue: decodeArgumentsReturnValueString, decodeArgumentsTypes: decodeArgumentsTypesArray) + return ContractTemplateModel.Function(name: preparedFunctionName, methodId: functionMethodId, input: inputString, output: outputString, encodeArguments: encodeArgumentsString, decodeReturnReturnValue: decodeReturnReturnValueString, decodeReturnTypes: decodeReturnTypesArray, hasEmptyDecodeReturnFunction: hasEmptyDecodeReturnFunction, decodeArgumentsReturnValue: decodeArgumentsReturnValueString, decodeArgumentsTypes: decodeArgumentsTypesArray, hasEmptyDecodeArgumentsFunction: hasEmptyDecodeArgumentsFunction) } } } diff --git a/Sources/BivrostKit/Generating/Templates/ContractTemplate.swift b/Sources/BivrostKit/Generating/Templates/ContractTemplate.swift index 216fded..a52e998 100644 --- a/Sources/BivrostKit/Generating/Templates/ContractTemplate.swift +++ b/Sources/BivrostKit/Generating/Templates/ContractTemplate.swift @@ -17,27 +17,31 @@ extension Templates { struct {{ contract.name }} { {% for function in contract.functions %} + struct {{ function.name }}: SolidityFunction { static let methodId = "{{ function.methodId }}" typealias Return = {{ function.output }} typealias Arguments = {{ function.input }} - + static func encodeCall(arguments: Arguments) -> String { return "0x\\(methodId)\\(BaseEncoder.encode(arguments: {{ function.encodeArguments }}))" } - + + {% if function.hasEmptyDecodeReturnFunction %} + static func decode(returnData: String) throws -> Return {} + {% else %} static func decode(returnData: String) throws -> Return { let source = BaseDecoder.partition(returnData) - // Static Types & Location + // Decode Static Types & Locations for Dynamic Types {% for decodedType in function.decodeReturnTypes %} {% if decodedType.isDynamic %} - // Ignore location for dynamic type - _ = source.consume() + // Ignore location for {{ decodedType.name }} (dynamic type) + _ = try source.consume() {% else %} let {{ decodedType.name }} = try {{ decodedType.type }}.decode(source: source) {% endif %} {% endfor %} - // Dynamic Types + // Dynamic Types (if any) {% for decodedType in function.decodeReturnTypes %} {% if decodedType.isDynamic %} let {{ decodedType.name }} = try {{ decodedType.type }}.decode(source: source) @@ -45,19 +49,23 @@ extension Templates { {% endfor %} return {{ function.decodeReturnReturnValue }} } - + {% endif %} + + {% if function.hasEmptyDecodeArgumentsFunction %} + static func decode(argumentsData: String) throws -> Arguments {} + {% else %} static func decode(argumentsData: String) throws -> Arguments { let source = BaseDecoder.partition(argumentsData) - // Static Types & Location + // Decode Static Types & Locations for Dynamic Types {% for decodedType in function.decodeArgumentsTypes %} {% if decodedType.isDynamic %} - // Ignore location for dynamic type - _ = source.consume() + // Ignore location for {{ decodedType.name }} (dynamic type) + _ = try source.consume() {% else %} let {{ decodedType.name }} = try {{ decodedType.type }}.decode(source: source) {% endif %} {% endfor %} - // Dynamic Types + // Dynamic Types (if any) {% for decodedType in function.decodeArgumentsTypes %} {% if decodedType.isDynamic %} let {{ decodedType.name }} = try {{ decodedType.type }}.decode(source: source) @@ -65,6 +73,7 @@ extension Templates { {% endfor %} return {{ function.decodeArgumentsReturnValue }} } + {% endif %} } {% endfor %} } diff --git a/Sources/BivrostKit/Parsing/ContractParser.swift b/Sources/BivrostKit/Parsing/ContractParser.swift index fa39dd8..4fcbdc7 100644 --- a/Sources/BivrostKit/Parsing/ContractParser.swift +++ b/Sources/BivrostKit/Parsing/ContractParser.swift @@ -21,7 +21,7 @@ struct ContractParser { /// - Throws: Throws if the json was malformed, e.g. a required field was missing. static func parseContract(from json: [String: Any]) throws -> Contract { guard let name = json[.contractName] as? String else { - throw ParsingError.contractNameInvalid + throw ParsingError.contractNameInvalid(json: json) } guard let elementsJson = json[.abi] as? [[String: Any]] else { throw ParsingError.contractAbiInvalid diff --git a/Sources/BivrostKit/Parsing/ParsingError.swift b/Sources/BivrostKit/Parsing/ParsingError.swift index a8c5aee..75200b7 100644 --- a/Sources/BivrostKit/Parsing/ParsingError.swift +++ b/Sources/BivrostKit/Parsing/ParsingError.swift @@ -19,6 +19,6 @@ enum ParsingError: Error { case eventInputInvalid case parameterTypeInvalid case parameterTypeNotFound - case contractNameInvalid + case contractNameInvalid(json: [String: Any]) case contractAbiInvalid }