Merge pull request #6037 from oa-s/#6036

contract abi improvement #6036
pull/6044/head
Crypto Pank 2 years ago committed by GitHub
commit 7adaa679cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 33
      AlphaWalletTests/Transfer/Types/UnconfirmedTransactionTests.swift
  2. 2
      modules/AlphaWalletFoundation/AlphaWalletFoundation/CoinTicker/TickerIdsFetcher/AlphaWalletRemoteTickerIdsFetcher.swift
  3. 2
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/ABI/DecodedFunctionCall.swift
  4. 7
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Ethereum/Methods/AnyContractMethod.swift
  5. 28
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/JSON/TokenGroupIdentifier.swift
  6. 3
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Transfer/Types/TransactionType.swift
  7. 2
      modules/AlphaWalletWeb3/AlphaWalletWeb3/ABI/ABITypes.swift
  8. 8
      modules/AlphaWalletWeb3/AlphaWalletWeb3/ABIv2/ABIv2Decoding.swift
  9. 89
      modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv1.swift
  10. 220
      modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractABIv2.swift
  11. 17
      modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/ContractProtocol.swift
  12. 64
      modules/AlphaWalletWeb3/AlphaWalletWeb3/Contract/EventFiltering.swift
  13. 17
      modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+Contract.swift
  14. 20
      modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+EventParser.swift
  15. 9
      modules/AlphaWalletWeb3/AlphaWalletWeb3/Web3/Web3+TransactionIntermediate.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)
}
}

@ -40,7 +40,7 @@ public class AlphaWalletRemoteTickerIdsFetcher: TickerIdsFetcher {
private func resolveTickerId(in tokenEntries: [TokenEntry], for token: TokenMappedToTicker) -> AnyPublisher<TickerIdString?, Never> {
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<TickerIdString?, Never> in
guard let strongSelf = self else { return .empty() }

@ -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)

@ -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)
}
}

@ -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 {

@ -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()
}

@ -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: ",")))"

@ -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)
}

@ -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
}
}

@ -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
}
}

@ -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 {

@ -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

@ -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? {

@ -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

@ -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")
}

Loading…
Cancel
Save