From fc8cc589a0bf92c819f86778cf3955fbbc588156 Mon Sep 17 00:00:00 2001 From: Zac Morris Date: Fri, 14 Sep 2018 12:47:22 -0400 Subject: [PATCH] Cleaning up ABI encoding to correctly encode function calls as tuples, also adding other Int/UInt ABIValueTypes --- EtherKit/ABI.swift | 108 +++++++++++++++++++-- EtherKit/Models/Function.swift | 34 +++---- Example/EtherKit.xcodeproj/project.pbxproj | 2 + Example/Podfile.lock | 12 +-- 4 files changed, 121 insertions(+), 35 deletions(-) diff --git a/EtherKit/ABI.swift b/EtherKit/ABI.swift index d458831..4df3c13 100644 --- a/EtherKit/ABI.swift +++ b/EtherKit/ABI.swift @@ -15,7 +15,8 @@ public indirect enum ABIType: CustomStringConvertible { case address(value: Address) case bytes(count: UnformattedDataMode, value: Data) case array(count: UnformattedDataMode, type: ABIType, value: [ABIType]) - case functionSelector(name: String, parameterTypes: [ABIType], contract: Address?) + case tuple(value: [ABIType]) + case function(name: String, parameters: [ABIType], contract: Address?) public var description: String { switch self { @@ -43,11 +44,13 @@ public indirect enum ABIType: CustomStringConvertible { case let .constrained(count): return String(describing: type) + "[\(count)]" } - case let .functionSelector(name, parameterTypes, _): - let parameterString = parameterTypes + case let .function(name, parameters, _): + let parameterString = parameters .compactMap { String(describing: $0) } .joined(separator: ",") return "\(name)(\(parameterString))" + case .tuple: + return "tuple[]" } } @@ -77,7 +80,14 @@ public indirect enum ABIType: CustomStringConvertible { case .constrained: return type.isDynamic } - case .functionSelector: + case .function: + return false + case let .tuple(value): + for subValue in value { + if subValue.isDynamic { + return true + } + } return false } } @@ -174,7 +184,7 @@ public indirect enum ABIType: CustomStringConvertible { data.append(elemData) } } - case let .functionSelector(_, _, contract): + case let .function(_, _, contract): let funcSig = String(describing: self) if let contract = contract { @@ -185,6 +195,42 @@ public indirect enum ABIType: CustomStringConvertible { } let fullHash = asciiBytes.sha3(.keccak256) data.append(fullHash[0 ..< 4]) + case let .tuple(value): + var headDatas: [Data] = [] + var valueDatas: [Data] = [] + var prefixLength = 0 + + for tupleElem in value { + let elemData = tupleElem.encode() + + if tupleElem.isDynamic { + // placeholder to be filled in later + let placeholder = BigUInt(1).abiType.encode() + headDatas.append(placeholder) + valueDatas.append(elemData) + prefixLength = prefixLength + placeholder.count + } else { + headDatas.append(elemData) + valueDatas.append(Data()) + prefixLength = prefixLength + elemData.count + } + } + + for i in 0 ... (value.count - 1) { + let tupleElem = value[i] + + if tupleElem.isDynamic { + headDatas[i] = BigUInt(prefixLength).abiType.encode() + prefixLength = prefixLength + valueDatas[i].count + } + } + + for headData in headDatas { + data.append(headData) + } + for valueData in valueDatas { + data.append(valueData) + } } return data @@ -223,12 +269,60 @@ extension Int: ABIValueType { } } +extension Int8: ABIValueType { + public var abiType: ABIType { + return .int(size: 8, value: BigInt(self)) + } +} + +extension Int16: ABIValueType { + public var abiType: ABIType { + return .int(size: 16, value: BigInt(self)) + } +} + +extension Int32: ABIValueType { + public var abiType: ABIType { + return .int(size: 32, value: BigInt(self)) + } +} + +extension Int64: ABIValueType { + public var abiType: ABIType { + return .int(size: 64, value: BigInt(self)) + } +} + extension UInt: ABIValueType { public var abiType: ABIType { return .uint(size: 64, value: BigUInt(self)) } } +extension UInt8: ABIValueType { + public var abiType: ABIType { + return .uint(size: 8, value: BigUInt(self)) + } +} + +extension UInt16: ABIValueType { + public var abiType: ABIType { + return .uint(size: 16, value: BigUInt(self)) + } +} + +extension UInt32: ABIValueType { + public var abiType: ABIType { + return .uint(size: 32, value: BigUInt(self)) + } +} + +extension UInt64: ABIValueType { + public var abiType: ABIType { + return .uint(size: 64, value: BigUInt(self)) + } +} + extension Address: ABIValueType { public var abiType: ABIType { return .address(value: self) @@ -264,12 +358,12 @@ extension Array: ABIValueType where Element: ABIValueType { } } -extension FunctionSelector: ABIValueType { +extension Function: ABIValueType { public var isDynamic: Bool { return false } public var abiType: ABIType { - return .functionSelector(name: name, parameterTypes: parameterTypes, contract: contract) + return .function(name: name, parameters: parameters, contract: contract) } } diff --git a/EtherKit/Models/Function.swift b/EtherKit/Models/Function.swift index 31a7783..25853ef 100644 --- a/EtherKit/Models/Function.swift +++ b/EtherKit/Models/Function.swift @@ -8,39 +8,29 @@ import BigInt import Foundation -public struct FunctionSelector { +public struct Function { public var name: String - public var parameterTypes: [ABIType] + public var parameters: [ABIType] public var contract: Address? - public init(name: String, parameterTypes: [ABIType], contract: Address? = nil) { + public init(name: String, parameters: [ABIType], contract: Address? = nil) { self.name = name - self.parameterTypes = parameterTypes - self.contract = contract - } -} - -public struct Function { - public var functionSelector: FunctionSelector - public var parameters: [ABIValueType] - - public init(functionSelector: FunctionSelector, parameters: [ABIValueType]) { - self.functionSelector = functionSelector self.parameters = parameters + self.contract = contract } - public init(name: String, parameters: [ABIValueType]) { - let parameterTypes: [ABIType] = parameters.compactMap { $0.abiType } - functionSelector = FunctionSelector(name: name, parameterTypes: parameterTypes) - self.parameters = parameters + public init(name: String, parameters: [ABIValueType], contract: Address? = nil) { + self.name = name + self.parameters = parameters.compactMap { $0.abiType } + self.contract = contract } public func encodeToCall() -> Data { var data = Data() - data.append(functionSelector.abiType.encode()) - for parameter in parameters { - data.append(parameter.abiType.encode()) - } + data.append(abiType.encode()) + + var paramTuple = ABIType.tuple(value: parameters) + data.append(paramTuple.encode()) return data } } diff --git a/Example/EtherKit.xcodeproj/project.pbxproj b/Example/EtherKit.xcodeproj/project.pbxproj index 6168d81..64f0410 100644 --- a/Example/EtherKit.xcodeproj/project.pbxproj +++ b/Example/EtherKit.xcodeproj/project.pbxproj @@ -100,7 +100,9 @@ A16A63C6B34BCC2C66991033 /* Pods */, 374062D57F115FAEB32E6109 /* Frameworks */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; 607FACD11AFB9204008FA782 /* Products */ = { isa = PBXGroup; diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 497b22b..29bb3be 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -2,17 +2,17 @@ PODS: - BigInt (3.1.0): - SipHash (~> 1.2) - CryptoSwift (0.10.0) - - EtherKit (0.1.5): - - EtherKit/Core (= 0.1.5) - - EtherKit/PromiseKit (= 0.1.5) - - EtherKit/Core (0.1.5): + - EtherKit (0.2.0-beta1): + - EtherKit/Core (= 0.2.0-beta1) + - EtherKit/PromiseKit (= 0.2.0-beta1) + - EtherKit/Core (0.2.0-beta1): - BigInt - CryptoSwift - Marshal - Result (~> 4.0.0) - secp256k1.swift - Starscream - - EtherKit/PromiseKit (0.1.5): + - EtherKit/PromiseKit (0.2.0-beta1): - EtherKit/Core - PromiseKit/CorePromise - Marshal (1.2.4) @@ -46,7 +46,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: BigInt: 76b5dfdfa3e2e478d4ffdf161aeede5502e2742f CryptoSwift: 6c778d69282bed3b4e975ff97a79d074f20bb011 - EtherKit: f0ea0a27a5996b898261892c421a8284ebf15c29 + EtherKit: 2e07c1d3f30f12cb31e721d92c747437ee2ea1aa Marshal: 8e04e6624e506921db7143b0bfd83caee03f32d6 PromiseKit: cf84bbb1235a61473b326c5cf0b41f6828f87ba5 Result: 7645bb3f50c2ce726dd0ff2fa7b6f42bbe6c3713