diff --git a/AlphaWalletTests/Transfer/Types/UnconfirmedTransactionTests.swift b/AlphaWalletTests/Transfer/Types/UnconfirmedTransactionTests.swift index 085ccc7cf..2aadaf877 100644 --- a/AlphaWalletTests/Transfer/Types/UnconfirmedTransactionTests.swift +++ b/AlphaWalletTests/Transfer/Types/UnconfirmedTransactionTests.swift @@ -30,6 +30,13 @@ class UnconfirmedTransactionTests: XCTestCase { XCTAssertEqual(_recipient, recipient) XCTAssertEqual(_amount, amount) + + let _contract = try Contract(abi: String(data: AlphaWallet.Ethereum.ABI.erc20, encoding: .utf8)!) + guard let result = _contract.decodeInputData(data) else { fatalError() } + + XCTAssertEqual(result.name, "transfer") + XCTAssertEqual((result.params?["tokens"] as? BigUInt), amount) + XCTAssertEqual((result.params?["to"] as? AlphaWalletWeb3.EthereumAddress)?.description, recipient.eip55String) } func testNativeCryptoTransfer() throws { @@ -43,17 +50,24 @@ class UnconfirmedTransactionTests: XCTestCase { XCTAssertEqual(transaction.contract, nil) XCTAssertEqual(transaction.recipient, recipient) XCTAssertEqual(transaction.data, Data()) - } func testErc721Transfer() throws { let contract = AlphaWallet.Address(uncheckedAgainstNullAddress: "0x0000000000000000000000000000000000000009")! let recipient = AlphaWallet.Address(uncheckedAgainstNullAddress: "0x0000000000000000000000000000000000000001")! let token = Token(contract: contract, name: "Erc721", decimals: 0, type: .erc721) + let tokenId: BigUInt = "1" let tokenHolders = [TokenHolder(tokens: [ - TokenScript.Token(tokenIdOrEvent: .tokenId(tokenId: "1"), tokenType: .erc721, index: 0, name: "Name", symbol: "Symbol", status: .available, values: [:]) - ], contractAddress: contract, hasAssetDefinition: false).select(with: .allFor(tokenId: "1"))] + TokenScript.Token( + tokenIdOrEvent: .tokenId(tokenId: tokenId), + tokenType: .erc721, + index: 0, + name: "Name", + symbol: "Symbol", + status: .available, + values: [:]) + ], contractAddress: contract, hasAssetDefinition: false).select(with: .allFor(tokenId: tokenId))] let transaction = try TransactionType(nonFungibleToken: token, tokenHolders: tokenHolders).buildSendErc721Token(recipient: recipient, account: Constants.nullAddress) @@ -61,12 +75,15 @@ class UnconfirmedTransactionTests: XCTestCase { XCTAssertEqual(transaction.recipient, recipient) guard let data = transaction.data else { fatalError() } - let web3 = try Web3.instance(for: .main, timeout: 0) - let _contract = try Web3.Contract(web3: web3, abiString: AlphaWallet.Ethereum.ABI.erc721String) + + let _contract = try Contract(abi: AlphaWallet.Ethereum.ABI.erc721String) guard let result = _contract.decodeInputData(data) else { fatalError() } - XCTAssertEqual((result["tokenId"] as? BigUInt), "1") - XCTAssertEqual((result["from"] as? AlphaWalletWeb3.EthereumAddress)?.description, Constants.nullAddress.eip55String) - XCTAssertEqual((result["to"] as? AlphaWalletWeb3.EthereumAddress)?.description, recipient.eip55String) + XCTAssertEqual(result.signature, "42842e0e") + XCTAssertEqual(result.name, "safeTransferFrom") + + XCTAssertEqual((result.params?["tokenId"] as? BigUInt), "1") + XCTAssertEqual((result.params?["from"] as? AlphaWalletWeb3.EthereumAddress)?.description, Constants.nullAddress.eip55String) + XCTAssertEqual((result.params?["to"] as? AlphaWalletWeb3.EthereumAddress)?.description, recipient.eip55String) } } diff --git a/modules/AlphaWalletFoundation/AlphaWalletFoundation/CoinTicker/TickerIdsFetcher/AlphaWalletRemoteTickerIdsFetcher.swift b/modules/AlphaWalletFoundation/AlphaWalletFoundation/CoinTicker/TickerIdsFetcher/AlphaWalletRemoteTickerIdsFetcher.swift index 3188e9b9d..b80e12d30 100644 --- a/modules/AlphaWalletFoundation/AlphaWalletFoundation/CoinTicker/TickerIdsFetcher/AlphaWalletRemoteTickerIdsFetcher.swift +++ b/modules/AlphaWalletFoundation/AlphaWalletFoundation/CoinTicker/TickerIdsFetcher/AlphaWalletRemoteTickerIdsFetcher.swift @@ -40,7 +40,7 @@ public class AlphaWalletRemoteTickerIdsFetcher: TickerIdsFetcher { private func resolveTickerId(in tokenEntries: [TokenEntry], for token: TokenMappedToTicker) -> AnyPublisher { return Just(token) .map { token -> TokenEntry? in - let targetContract: Contract = .init(address: token.contractAddress.eip55String, chainId: token.server.chainID) + let targetContract: TokenEntry.Contract = .init(address: token.contractAddress.eip55String, chainId: token.server.chainID) return tokenEntries.first(where: { entry in entry.contracts.contains(targetContract) }) }.flatMap { [weak self] entry -> AnyPublisher in guard let strongSelf = self else { return .empty() } diff --git a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/ABI/DecodedFunctionCall.swift b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/ABI/DecodedFunctionCall.swift index b841d16b0..cceb5e0f1 100644 --- a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/ABI/DecodedFunctionCall.swift +++ b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/ABI/DecodedFunctionCall.swift @@ -51,7 +51,7 @@ public struct DecodedFunctionCall { case erc20Transfer(recipient: AlphaWallet.Address, value: BigUInt) case erc20Approve(spender: AlphaWallet.Address, value: BigUInt) case erc721ApproveAll(spender: AlphaWallet.Address, value: Bool) - //NOTE: native crypty + //NOTE: native crypto case nativeCryptoTransfer(value: BigUInt) //NOTE: erc1155 case erc1155SafeTransfer(spender: AlphaWallet.Address) diff --git a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/Methods/AnyContractMethod.swift b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/Methods/AnyContractMethod.swift index 93f6f8b81..705157c25 100644 --- a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/Methods/AnyContractMethod.swift +++ b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/Methods/AnyContractMethod.swift @@ -20,10 +20,7 @@ public struct AnyContractMethod: ContractMethod { } public func encodedABI() throws -> Data { - let web3 = try Web3.instance(for: .main, timeout: 60) - let contract = try Web3.Contract(web3: web3, abiString: abi) - let promiseCreator = try contract.method(method, parameters: params) - - return promiseCreator.transaction.data + let contract = try Contract(abi: abi) + return try contract.methodData(method, parameters: params) } } diff --git a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/JSON/TokenGroupIdentifier.swift b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/JSON/TokenGroupIdentifier.swift index e3f8a8ea7..1cb5132c8 100644 --- a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/JSON/TokenGroupIdentifier.swift +++ b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/JSON/TokenGroupIdentifier.swift @@ -7,27 +7,29 @@ import Foundation -public struct Contract: Codable, Hashable { - let address: String - let chainId: Int +public struct TokenEntry: Decodable { + let contracts: [Contract] + let group: String? +} + +extension TokenEntry { + public struct Contract: Codable, Hashable { + let address: String + let chainId: Int - var key: String { - let returnKey = address + ":" + String(chainId) - return returnKey.trimmed.lowercased() + var key: String { + let returnKey = address + ":" + String(chainId) + return returnKey.trimmed.lowercased() + } } } -extension Contract: Equatable { - public static func == (lhs: Contract, rhs: Contract) -> Bool { +extension TokenEntry.Contract: Equatable { + public static func == (lhs: TokenEntry.Contract, rhs: TokenEntry.Contract) -> Bool { return lhs.key == rhs.key } } -public struct TokenEntry: Decodable { - let contracts: [Contract] - let group: String? -} - public class TokenJsonReader { enum error: Error { diff --git a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Transfer/Types/TransactionType.swift b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Transfer/Types/TransactionType.swift index 70101e236..b79484759 100644 --- a/modules/AlphaWalletFoundation/AlphaWalletFoundation/Transfer/Types/TransactionType.swift +++ b/modules/AlphaWalletFoundation/AlphaWalletFoundation/Transfer/Types/TransactionType.swift @@ -65,7 +65,6 @@ public enum TransactionType { } case nativeCryptocurrency(Token, destination: AddressOrEnsName?, amount: FungibleAmount) - //TODO: replace string with BigInt case erc20Token(Token, destination: AddressOrEnsName?, amount: FungibleAmount) case erc875Token(Token, tokenHolders: [TokenHolder]) case erc721Token(Token, tokenHolders: [TokenHolder]) @@ -272,7 +271,7 @@ extension TransactionType { let data: Data if tokenHolder.contractAddress.isLegacy721Contract { - data = (try? Erc721TransferFrom(recipient: recipient, tokenId: token.id).encodedABI()) ?? Data() + data = (try? Erc721TransferFrom(recipient: recipient, tokenId: token.id).encodedABI()) ?? Data() } else { data = (try? Erc721SafeTransferFrom(recipient: recipient, account: account, tokenId: token.id).encodedABI()) ?? Data() } diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABI/ABITypes.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABI/ABITypes.swift index fd9e1804f..19ab3d86b 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABI/ABITypes.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABI/ABITypes.swift @@ -347,7 +347,7 @@ extension ABIElement.ParameterType.DynamicType: AbiValidating { } } -// MARK: - Method ID for ContractV1 +// MARK: - Method ID for ContractAbiV1 extension ABIElement.Function { public var signature: String { return "\(name ?? "")(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABIv2/ABIv2Decoding.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABIv2/ABIv2Decoding.swift index 411d003b8..5d3325919 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABIv2/ABIv2Decoding.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/ABIv2/ABIv2Decoding.swift @@ -9,15 +9,11 @@ import Foundation import BigInt -public struct ABIv2Decoder { - -} +public struct ABIv2Decoder { } extension ABIv2Decoder { public static func decode(types: [ABIv2.Element.InOut], data: Data) -> [AnyObject]? { - let params = types.compactMap { (el) -> ABIv2.Element.ParameterType in - return el.type - } + let params = types.compactMap { $0.type } return decode(types: params, data: data) } diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv1.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv1.swift index 2c599e87f..b7db03b30 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv1.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv1.swift @@ -1,5 +1,5 @@ // -// ContractV1.swift +// ContractAbiV1.swift // web3swift // // Created by Alexander Vlasov on 10.12.2017. @@ -10,17 +10,17 @@ import Foundation import BigInt @available(*, deprecated) -public struct ContractV1: ContractProtocol { - public var allEvents: [String] { +struct ContractAbiV1: ContractRepresentable { + var allEvents: [String] { return events.keys.flatMap { $0 } } - public var allMethods: [String] { + var allMethods: [String] { return methods.keys.flatMap { $0 } } - public var address: EthereumAddress? + var address: EthereumAddress? var abi: [ABIElement] - public var methods: [String: ABIElement] { + var methods: [String: ABIElement] { var toReturn: [String: ABIElement] = [:] for m in self.abi { switch m { @@ -34,7 +34,7 @@ public struct ContractV1: ContractProtocol { return toReturn } - public var constructor: ABIElement? { + var constructor: ABIElement? { var toReturn: ABIElement? for elem in self.abi { if toReturn != nil { @@ -51,7 +51,7 @@ public struct ContractV1: ContractProtocol { return toReturn ?? ABIElement.constructor(ABIElement.Constructor.init(inputs: [], constant: false, payable: false)) } - public var events: [String: ABIElement] { + var events: [String: ABIElement] { var toReturn: [String: ABIElement] = [:] for elem in self.abi { switch elem { @@ -65,9 +65,7 @@ public struct ContractV1: ContractProtocol { return toReturn } - public var options: Web3Options? = Web3Options.defaultOptions() - - public init(abi abiString: String, address: EthereumAddress? = nil) throws { + init(abi abiString: String, address: EthereumAddress? = nil) throws { do { guard let jsonData = abiString.data(using: .utf8) else { throw Web3.ContractError.abiError(.abiInvalid) } @@ -78,35 +76,34 @@ public struct ContractV1: ContractProtocol { } } - public init(abi: [ABIElement]) { + init(abi: [ABIElement]) { self.abi = abi } - public init(abi: [ABIElement], at: EthereumAddress) { + init(abi: [ABIElement], at: EthereumAddress) { self.abi = abi self.address = at } - public func deploy(bytecode: Data, parameters: [AnyObject] = [AnyObject](), extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { + func deploy(bytecode: Data, parameters: [AnyObject] = [AnyObject](), extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { let to: EthereumAddress = EthereumAddress.contractDeploymentAddress() - let mergedOptions = Web3Options.merge(self.options, with: options) var gasLimit: BigUInt - if let gasInOptions = mergedOptions?.gasLimit { + if let gasInOptions = options?.gasLimit { gasLimit = gasInOptions } else { throw Web3.ContractError.gasLimitNotFound } var gasPrice: BigUInt - if let gasPriceInOptions = mergedOptions?.gasPrice { + if let gasPriceInOptions = options?.gasPrice { gasPrice = gasPriceInOptions } else { throw Web3.ContractError.gasPriceNotFound } var value: BigUInt - if let valueInOptions = mergedOptions?.value { + if let valueInOptions = options?.value { value = valueInOptions } else { value = BigUInt(0) @@ -123,73 +120,83 @@ public struct ContractV1: ContractProtocol { return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: fullData) } - public func method(_ method: String = "fallback", parameters: [AnyObject] = [AnyObject](), extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { + func method(_ method: String = "fallback", parameters: [AnyObject] = [], extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { var to: EthereumAddress - let mergedOptions = Web3Options.merge(self.options, with: options) - if self.address != nil { - to = self.address! - } else if let toFound = mergedOptions?.to, toFound.isValid { - to = toFound + if let address = address { + to = address + } else if let address = options?.to, address.isValid { + to = address } else { throw Web3.ContractError.toNotFound } var gasLimit: BigUInt - if let gasInOptions = mergedOptions?.gasLimit { + if let gasInOptions = options?.gasLimit { gasLimit = gasInOptions } else { throw Web3.ContractError.gasLimitNotFound } var gasPrice: BigUInt - if let gasPriceInOptions = mergedOptions?.gasPrice { + if let gasPriceInOptions = options?.gasPrice { gasPrice = gasPriceInOptions } else { throw Web3.ContractError.gasPriceNotFound } var value: BigUInt - if let valueInOptions = mergedOptions?.value { + if let valueInOptions = options?.value { value = valueInOptions } else { value = BigUInt(0) } + + let data = try methodData(method, parameters: parameters, fallbackData: extraData) - if method == "fallback" { - return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: extraData) - } else { - guard let abiElement = methods[method] else { throw Web3.ContractError.abiError(.methodNotFound(method)) } - guard let data = abiElement.encodeParameters(parameters) else { throw Web3.ContractError.abiError(.encodeParamFailure(parameters)) } + return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: extraData) + } - return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: data) - } + func methodData(_ method: String = "fallback", parameters: [AnyObject] = [], fallbackData: Data) throws -> Data { + guard method != "fallback" else { return fallbackData } + + guard let abiElement = methods[method] else { throw Web3.ContractError.abiError(.methodNotFound(method)) } + guard let data = abiElement.encodeParameters(parameters) else { throw Web3.ContractError.abiError(.encodeParamFailure(parameters)) } + + return data } - public func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) { + func parseEvent(_ eventLog: EventLog) -> (eventName: String, eventData: [String: Any])? { for (eName, ev) in self.events { guard let parsed = ev.decodeReturnedLogs(eventLog) else { continue } return (eName, parsed) } - return (nil, nil) + return nil } - public func decodeReturnData(_ method: String, data: Data) -> [String: Any]? { - guard method != "fallback" else { return [:] } + func decodeReturnData(_ method: String, data: Data) -> [String: Any]? { + guard method != "fallback" else { + let resultHex = data.toHexString().addHexPrefix() + return ["result": resultHex as Any] + } guard let function = methods[method] else { return nil } guard case .function = function else { return nil } return function.decodeReturnData(data) } - public func testBloomForEventPrecence(eventName: String, bloom: EthereumBloomFilter) -> Bool? { + func testBloomForEventPrecence(eventName: String, bloom: EthereumBloomFilter) -> Bool? { return false } - public func decodeInputData(_ method: String, data: Data) -> [String: Any]? { + func decodeInputData(_ method: String, data: Data) -> [String: Any]? { return nil } - public func decodeInputData(_ data: Data) -> [String: Any]? { + func decodeInputData(_ data: Data) -> FunctionalCall? { + return nil + } + + func encodeTopicToGetLogs(eventName: String, filter: EventFilter) -> EventFilterParameters? { return nil } } diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv2.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv2.swift index d494ab6f4..0e6cc9464 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv2.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv2.swift @@ -9,25 +9,76 @@ import Foundation import BigInt -public struct ContractV2: ContractProtocol { +public struct Contract: ContractRepresentable { + private var contract: ContractRepresentable + + public var address: EthereumAddress? { + get { return contract.address } + set { contract.address = newValue } + } + + public var allMethods: [String] { contract.allEvents } + public var allEvents: [String] { contract.allEvents } + + public init(abi: String, address: EthereumAddress? = nil) throws { + do { + contract = try ContractAbiV2(abi: abi, address: address) + } catch { + contract = try ContractAbiV1(abi: abi, address: address) + } + } + + public func deploy(bytecode: Data, parameters: [AnyObject], extraData: Data, options: Web3Options?) throws -> EthereumTransaction { + try contract.deploy(bytecode: bytecode, parameters: parameters, extraData: extraData, options: options) + } + + public func method(_ method: String, parameters: [AnyObject], extraData: Data, options: Web3Options?) throws -> EthereumTransaction { + try contract.method(method, parameters: parameters, extraData: extraData, options: options) + } + + public func methodData(_ method: String = "fallback", parameters: [AnyObject] = [], fallbackData: Data = Data()) throws -> Data { + try contract.methodData(method, parameters: parameters, fallbackData: fallbackData) + } + + public func decodeReturnData(_ method: String, data: Data) -> [String: Any]? { + return contract.decodeReturnData(method, data: data) + } + + public func decodeInputData(_ method: String, data: Data) -> [String: Any]? { + contract.decodeInputData(method, data: data) + } + + public func decodeInputData(_ data: Data) -> FunctionalCall? { + contract.decodeInputData(data) + } + + public func parseEvent(_ eventLog: EventLog) -> (eventName: String, eventData: [String: Any])? { + contract.parseEvent(eventLog) + } + + public func testBloomForEventPrecence(eventName: String, bloom: EthereumBloomFilter) -> Bool? { + contract.testBloomForEventPrecence(eventName: eventName, bloom: bloom) + } + + public func encodeTopicToGetLogs(eventName: String, filter: EventFilter) -> EventFilterParameters? { + contract.encodeTopicToGetLogs(eventName: eventName, filter: filter) + } +} + +struct ContractAbiV2: ContractRepresentable { - public var allEvents: [String] { + var allEvents: [String] { return events.keys.compactMap { $0 } } - public var allMethods: [String] { + var allMethods: [String] { return methods.keys.compactMap { $0 } } - public struct EventFilter { - public var parameterName: String - public var parameterValues: [AnyObject] - } - - public var address: EthereumAddress? + var address: EthereumAddress? var abi: [ABIv2.Element] - public var methods: [String: ABIv2.Element] { + var methods: [String: ABIv2.Element] { var toReturn: [String: ABIv2.Element] = [:] for m in self.abi { switch m { @@ -41,7 +92,7 @@ public struct ContractV2: ContractProtocol { return toReturn } - public var constructor: ABIv2.Element? { + var constructor: ABIv2.Element? { var toReturn: ABIv2.Element? for element in self.abi { if toReturn != nil { @@ -60,7 +111,7 @@ public struct ContractV2: ContractProtocol { return toReturn } - public var events: [String: ABIv2.Element.Event] { + var events: [String: ABIv2.Element.Event] { var toReturn: [String: ABIv2.Element.Event] = [:] for element in self.abi { switch element { @@ -74,9 +125,7 @@ public struct ContractV2: ContractProtocol { return toReturn } - public var options: Web3Options? = Web3Options.defaultOptions() - - public init(abi: String, address: EthereumAddress? = nil) throws { + init(abi: String, address: EthereumAddress? = nil) throws { do { guard let json = abi.data(using: .utf8) else { throw Web3.ContractError.abiError(.abiInvalid) } @@ -87,34 +136,33 @@ public struct ContractV2: ContractProtocol { } } - public init(abi: [ABIv2.Element]) { + init(abi: [ABIv2.Element]) { self.abi = abi } - public init(abi: [ABIv2.Element], at: EthereumAddress) { + init(abi: [ABIv2.Element], at: EthereumAddress) { self.abi = abi self.address = at } - public func deploy(bytecode: Data, parameters: [AnyObject] = [], extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { + func deploy(bytecode: Data, parameters: [AnyObject] = [], extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { let to: EthereumAddress = EthereumAddress.contractDeploymentAddress() - let mergedOptions = Web3Options.merge(self.options, with: options) var gasLimit: BigUInt - if let gasInOptions = mergedOptions?.gasLimit { + if let gasInOptions = options?.gasLimit { gasLimit = gasInOptions } else { throw Web3.ContractError.gasLimitNotFound } var gasPrice: BigUInt - if let gasPriceInOptions = mergedOptions?.gasPrice { + if let gasPriceInOptions = options?.gasPrice { gasPrice = gasPriceInOptions } else { throw Web3.ContractError.gasPriceNotFound } var value: BigUInt - if let valueInOptions = mergedOptions?.value { + if let valueInOptions = options?.value { value = valueInOptions } else { value = BigUInt(0) @@ -131,49 +179,52 @@ public struct ContractV2: ContractProtocol { return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: data) } - public func method(_ method: String = "fallback", parameters: [AnyObject] = [], extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { + func method(_ method: String = "fallback", parameters: [AnyObject] = [], extraData: Data = Data(), options: Web3Options?) throws -> EthereumTransaction { var to: EthereumAddress - let mergedOptions = Web3Options.merge(self.options, with: options) if let address = address { to = address - } else if let toAddress = mergedOptions?.to, toAddress.isValid { - to = toAddress + } else if let address = options?.to, address.isValid { + to = address } else { throw Web3.ContractError.toNotFound } var gasLimit: BigUInt - if let gasInOptions = mergedOptions?.gasLimit { + if let gasInOptions = options?.gasLimit { gasLimit = gasInOptions } else { throw Web3.ContractError.gasLimitNotFound } var gasPrice: BigUInt - if let gasPriceInOptions = mergedOptions?.gasPrice { + if let gasPriceInOptions = options?.gasPrice { gasPrice = gasPriceInOptions } else { throw Web3.ContractError.gasPriceNotFound } var value: BigUInt - if let valueInOptions = mergedOptions?.value { + if let valueInOptions = options?.value { value = valueInOptions } else { value = BigUInt(0) } - - if method == "fallback" { - return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: extraData) - } else { - guard let abiElement = methods[method] else { throw Web3.ContractError.abiError(.methodNotFound(method)) } - guard let data = abiElement.encodeParameters(parameters) else { throw Web3.ContractError.abiError(.encodeParamFailure(parameters)) } - return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: data) - } + let data = try methodData(method, parameters: parameters, fallbackData: extraData) + + return EthereumTransaction(gasPrice: gasPrice, gasLimit: gasLimit, to: to, value: value, data: data) + } + + func methodData(_ method: String = "fallback", parameters: [AnyObject] = [], fallbackData: Data = Data()) throws -> Data { + guard method != "fallback" else { return fallbackData } + + guard let abiElement = methods[method] else { throw Web3.ContractError.abiError(.methodNotFound(method)) } + guard let data = abiElement.encodeParameters(parameters) else { throw Web3.ContractError.abiError(.encodeParamFailure(parameters)) } + + return data } - public func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) { + func parseEvent(_ eventLog: EventLog) -> (eventName: String, eventData: [String: Any])? { for (eName, ev) in self.events { if !ev.anonymous { if eventLog.topics[0] != ev.topic { @@ -189,10 +240,10 @@ public struct ContractV2: ContractProtocol { } } } - return (nil, nil) + return nil } - public func testBloomForEventPrecence(eventName: String, bloom: EthereumBloomFilter) -> Bool? { + func testBloomForEventPrecence(eventName: String, bloom: EthereumBloomFilter) -> Bool? { guard let event = events[eventName] else { return nil } if event.anonymous { return true @@ -200,41 +251,102 @@ public struct ContractV2: ContractProtocol { return bloom.test(topic: event.topic) } - public func decodeReturnData(_ method: String, data: Data) -> [String: Any]? { - guard method != "fallback" else { return [:] } + func decodeReturnData(_ method: String, data: Data) -> [String: Any]? { + guard method != "fallback" else { + let resultHex = data.toHexString().addHexPrefix() + return ["result": resultHex as Any] + } guard let function = methods[method] else { return nil } guard case .function = function else { return nil } + return function.decodeReturnData(data) } - public func decodeInputData(_ method: String, data: Data) -> [String: Any]? { + func decodeInputData(_ method: String, data: Data) -> [String: Any]? { guard method != "fallback" else { return nil } guard let function = methods[method] else { return nil } switch function { - case .function: - return function.decodeInputData(data) - case .constructor: + case .function, .constructor: return function.decodeInputData(data) default: return nil } } - public func decodeInputData(_ data: Data) -> [String: Any]? { + func decodeInputData(_ data: Data) -> FunctionalCall? { guard data.count % 32 == 4 else { return nil } - let methodSignature = data[0..<4] - let foundFunction = self.abi.filter { (m) -> Bool in - switch m { + let signature = data[0..<4] + let functions = self.abi.filter { element -> Bool in + switch element { case .function(let function): - return function.methodEncoding == methodSignature + return function.methodEncoding == signature default: return false } } - guard foundFunction.count == 1 else { return nil } - - return foundFunction[0].decodeInputData(Data(data[4 ..< data.count])) + + guard functions.count == 1 else { return nil } + let function = functions[0] + + switch function { + case .function(let f): + let decoded = function.decodeInputData(Data(data[4 ..< data.count])) + return FunctionalCall(name: f.name, signature: f.methodString, params: decoded) + case .constructor, .fallback, .event: + return nil + } + } + + func encodeTopicToGetLogs(eventName: String, filter: EventFilter) -> EventFilterParameters? { + guard let event = events[eventName] else { return nil } + + var topics: [[String?]?] = [[event.topic.toHexString().addHexPrefix()]] + + if let parameterFilters = filter.parameterFilters { + var lastNonemptyFilter = -1 + for i in 0 ..< parameterFilters.count { + let filterValue = parameterFilters[i] + if filterValue != nil { + lastNonemptyFilter = i + } + } + if lastNonemptyFilter != -1 { + guard lastNonemptyFilter <= event.inputs.count else { return nil } + for i in 0 ... lastNonemptyFilter { + let filterValues = parameterFilters[i] + if filterValues != nil { + var isFound = false + var targetIndexedPosition = i + for j in 0 ..< event.inputs.count where event.inputs[j].indexed { + if targetIndexedPosition == 0 { + isFound = true + break + } + targetIndexedPosition -= 1 + } + + if !isFound { return nil } + } + + if filterValues == nil { + topics.append(nil as [String?]?) + continue + } + var encodings = [String]() + for val in filterValues! { + guard let enc = val.eventFilterEncoded() else { return nil } + encodings.append(enc) + } + topics.append(encodings) + } + } + } + + var preEncoding = filter.rpcPreEncode() + preEncoding.topics = topics + + return preEncoding } } diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractProtocol.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractProtocol.swift index 173c95229..f21897e26 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractProtocol.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractProtocol.swift @@ -1,5 +1,5 @@ // -// ContractProtocol.swift +// ContractRepresentable.swift // web3swift // // Created by Alexander Vlasov on 04.04.2018. @@ -9,9 +9,8 @@ import Foundation import BigInt -public protocol ContractProtocol { +public protocol ContractRepresentable { var address: EthereumAddress? { get set } - var options: Web3Options? { get set } var allMethods: [String] { get } var allEvents: [String] { get } @@ -19,11 +18,19 @@ public protocol ContractProtocol { func deploy(bytecode: Data, parameters: [AnyObject], extraData: Data, options: Web3Options?) throws -> EthereumTransaction func method(_ method: String, parameters: [AnyObject], extraData: Data, options: Web3Options?) throws -> EthereumTransaction + func methodData(_ method: String, parameters: [AnyObject], fallbackData: Data) throws -> Data func decodeReturnData(_ method: String, data: Data) -> [String: Any]? func decodeInputData(_ method: String, data: Data) -> [String: Any]? - func decodeInputData(_ data: Data) -> [String: Any]? - func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) + func decodeInputData(_ data: Data) -> FunctionalCall? + func parseEvent(_ eventLog: EventLog) -> (eventName: String, eventData: [String: Any])? func testBloomForEventPrecence(eventName: String, bloom: EthereumBloomFilter) -> Bool? + func encodeTopicToGetLogs(eventName: String, filter: EventFilter) -> EventFilterParameters? +} + +public struct FunctionalCall { + public let name: String? + public let signature: String + public let params: [String: Any]? } public protocol EventFilterComparable { diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/EventFiltering.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/EventFiltering.swift index 025d3bc0f..8325d14c2 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/EventFiltering.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/EventFiltering.swift @@ -56,66 +56,7 @@ internal func filterLogs(decodedLogs: [EventParserResultProtocol], eventFilter: return filteredLogs } -internal func encodeTopicToGetLogs(contract: ContractV2, eventName: String?, filter: EventFilter) -> EventFilterParameters? { - var eventTopic: Data? - var event: ABIv2.Element.Event? - if eventName != nil { - guard let ev = contract.events[eventName!] else { return nil } - event = ev - eventTopic = ev.topic - } - var topics = [[String?]?]() - if eventTopic != nil { - topics.append([eventTopic!.toHexString().addHexPrefix()]) - } else { - topics.append(nil as [String?]?) - } - if filter.parameterFilters != nil { - if event == nil { return nil } - var lastNonemptyFilter = -1 - for i in 0 ..< filter.parameterFilters!.count { - let filterValue = filter.parameterFilters![i] - if filterValue != nil { - lastNonemptyFilter = i - } - } - if lastNonemptyFilter != -1 { - guard lastNonemptyFilter <= event!.inputs.count else { return nil } - for i in 0 ... lastNonemptyFilter { - let filterValues = filter.parameterFilters![i] - if filterValues != nil { - var isFound = false - var targetIndexedPosition = i - for j in 0 ..< event!.inputs.count where event!.inputs[j].indexed { - if targetIndexedPosition == 0 { - isFound = true - break - } - targetIndexedPosition -= 1 - } - - if !isFound { return nil } - } - - if filterValues == nil { - topics.append(nil as [String?]?) - continue - } - var encodings = [String]() - for val in filterValues! { - guard let enc = val.eventFilterEncoded() else { return nil } - encodings.append(enc) - } - topics.append(encodings) - } - } - } - var preEncoding = filter.rpcPreEncode() - preEncoding.topics = topics - return preEncoding -} - -internal func parseReceiptForLogs(receipt: TransactionReceipt, contract: ContractProtocol, eventName: String, filter: EventFilter?) -> [EventParserResultProtocol]? { +internal func parseReceiptForLogs(receipt: TransactionReceipt, contract: ContractRepresentable, eventName: String, filter: EventFilter?) -> [EventParserResultProtocol]? { guard let bloom = receipt.logsBloom else { return nil } if contract.address != nil { let addressPresent = bloom.test(topic: contract.address!.addressData) @@ -147,8 +88,7 @@ internal func parseReceiptForLogs(receipt: TransactionReceipt, contract: Contrac }) } let decodedLogs = allLogs.compactMap({ (log) -> EventParserResultProtocol? in - let (n, d) = contract.parseEvent(log) - guard let evName = n, let evData = d else { return nil } + guard let (evName, evData) = contract.parseEvent(log) else { return nil } var result = EventParserResult(eventName: evName, transactionReceipt: receipt, contractAddress: log.address, decodedResult: evData) result.eventLog = log return result diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+Contract.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+Contract.swift index 7b97a9b6d..2e751888f 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+Contract.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+Contract.swift @@ -1,4 +1,4 @@ -// Web3+ContractV1.swift +// Web3+ContractAbiV1.swift // web3swift // // Created by Alexander Vlasov on 19.12.2017. @@ -8,6 +8,8 @@ import Foundation import BigInt +fileprivate typealias GlobalContract = Contract + extension Web3 { enum ABIError: Error { case abiInvalid @@ -25,7 +27,7 @@ extension Web3 { } public class Contract { - let contract: ContractProtocol + let contract: ContractRepresentable let web3: Web3 public var options: Web3Options? @@ -34,12 +36,7 @@ extension Web3 { self.web3 = web3 self.options = web3.options - var contract: ContractProtocol - do { - contract = try ContractV2(abi: abiString, address: at) - } catch { - contract = try ContractV1(abi: abiString, address: at) - } + var contract = try GlobalContract(abi: abiString, address: at) var mergedOptions = Web3Options.merge(self.options, with: options) if at != nil { @@ -69,7 +66,7 @@ extension Web3 { return TransactionIntermediate(transaction: transaction, web3: web3, contract: contract, method: method, options: mergedOptions) } - public func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) { + public func parseEvent(_ eventLog: EventLog) -> (eventName: String, eventData: [String: Any])? { return contract.parseEvent(eventLog) } @@ -86,7 +83,7 @@ extension Web3 { } public func decodeInputData(_ data: Data) -> [String: Any]? { - contract.decodeInputData(data) + contract.decodeInputData(data)?.params } public func testBloomForEventPrecence(eventName: String, bloom: EthereumBloomFilter) -> Bool? { diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+EventParser.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+EventParser.swift index 65c2c5e28..33fe8e360 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+EventParser.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+EventParser.swift @@ -13,12 +13,12 @@ import PromiseKit extension Web3.Contract { public struct EventParser: EventParserProtocol { - public let contract: ContractProtocol + public let contract: ContractRepresentable public let eventName: String public let filter: EventFilter? private let web3: Web3 - public init? (web3 web3Instance: Web3, eventName: String, contract: ContractProtocol, filter: EventFilter? = nil) { + public init? (web3 web3Instance: Web3, eventName: String, contract: ContractRepresentable, filter: EventFilter? = nil) { guard let _ = contract.allEvents.index(of: eventName) else { return nil } self.eventName = eventName self.web3 = web3Instance @@ -193,22 +193,13 @@ extension Web3.Contract.EventParser { } extension Web3.Contract { - public func getIndexedEventsPromise(eventName: String?, filter: EventFilter, joinWithReceipts: Bool = false) -> Promise<[EventParserResultProtocol]> { + public func getIndexedEventsPromise(eventName: String, filter: EventFilter, joinWithReceipts: Bool = false) -> Promise<[EventParserResultProtocol]> { let queue = self.web3.queue do { - guard let rawContract = self.contract as? ContractV2 else { - throw Web3Error.nodeError("ABIv1 is not supported for this method") - } - guard let preEncoding = encodeTopicToGetLogs(contract: rawContract, eventName: eventName, filter: filter) else { + guard let preEncoding = contract.encodeTopicToGetLogs(eventName: eventName, filter: filter) else { throw Web3Error.inputError("Failed to encode topic for request") } - if let eventName = eventName { - guard rawContract.events[eventName] != nil else { - throw Web3Error.inputError("No such event in a contract") - } - } - let request = JSONRPCrequest(method: .getLogs, params: JSONRPCparams(params: [preEncoding])) let fetchLogsPromise = self.web3.dispatch(request).map(on: queue) { response throws -> [EventParserResult] in guard let value: [EventLog] = response.getValue() else { @@ -219,8 +210,7 @@ extension Web3.Contract { } let allLogs = value let decodedLogs = allLogs.compactMap({ (log) -> EventParserResult? in - let (n, d) = self.contract.parseEvent(log) - guard let evName = n, let evData = d else { return nil } + guard let (evName, evData) = self.contract.parseEvent(log) else { return nil } var res = EventParserResult(eventName: evName, transactionReceipt: nil, contractAddress: log.address, decodedResult: evData) res.eventLog = log return res diff --git a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+TransactionIntermediate.swift b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+TransactionIntermediate.swift index 4709ecb0c..90c6052ff 100644 --- a/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+TransactionIntermediate.swift +++ b/modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+TransactionIntermediate.swift @@ -14,16 +14,15 @@ extension Web3.Contract { public class TransactionIntermediate { public var transaction: EthereumTransaction - public var contract: ContractProtocol + var contract: ContractRepresentable public var method: String public var options: Web3Options? = Web3Options.defaultOptions() let web3: Web3 - public init(transaction: EthereumTransaction, web3: Web3, contract: ContractProtocol, method: String, options: Web3Options?) { + public init(transaction: EthereumTransaction, web3: Web3, contract: ContractRepresentable, method: String, options: Web3Options?) { self.transaction = transaction self.web3 = web3 self.contract = contract - self.contract.options = options self.method = method self.options = Web3Options.merge(web3.options, with: options) self.transaction.chainID = self.web3.chainID @@ -91,10 +90,6 @@ extension Web3.Contract.TransactionIntermediate { optionsForCall.excludeZeroGasPrice = mergedOptions.excludeZeroGasPrice return eth.callPromise(transaction, options: optionsForCall, onBlock: onBlock).map(on: web3.queue) { [method, contract] data in - if method == "fallback" { - let resultHex = data.toHexString().addHexPrefix() - return ["result": resultHex as Any] - } guard let decodedData = contract.decodeReturnData(method, data: data) else { throw Web3Error.inputError("Can not decode returned parameters") }