Skip to content

Commit

Permalink
100% Documentation coverage and every error now has localizedDescript…
Browse files Browse the repository at this point in the history
…ion!
  • Loading branch information
v57 committed Nov 26, 2018
1 parent 0ebc89e commit 563f201
Show file tree
Hide file tree
Showing 37 changed files with 520 additions and 104 deletions.
1 change: 1 addition & 0 deletions Sources/ABIv2/ABIv2Parsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extension ABIv2 {
case parameterTypeNotFound
/// Invalid ABI
case abiInvalid
/// Printable / user displayable description
public var localizedDescription: String {
switch self {
case .elementTypeInvalid:
Expand Down
15 changes: 15 additions & 0 deletions Sources/ABIv2/SolidityDataReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,24 @@ private extension Int {

/// SolidityDataReader errors
public enum SolidityDataReaderError: Error {
/// Element not found
case notFound
/// Cannot get excepted type
case wrongType
/// Not enough data
case overflows

/// Printable / user displayable description
public var localizedDescription: String {
switch self {
case .notFound:
return "Smart Contract response error: Element not found"
case .wrongType:
return "Smart Contract response error: Cannot get excepted type"
case .overflows:
return "Smart Contract response error: Not enough data"
}
}
}

/// Solidity data reader
Expand Down
133 changes: 92 additions & 41 deletions Sources/ABIv2/SolidityFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,6 @@ extension Array: SolidityDataRepresentable where Element == SolidityDataRepresen
}
return data
}

// func dynamicSolidityData() -> Data {
// var data = Data(capacity: 32 * (count+1))
// data.append(count.solidityData)
// for element in self {
// data.append(element.solidityData)
// }
// return data
// }
// func staticSolidityData(count: Int) -> Data {
// let capacity = 32 * count
// var data = Data(capacity: capacity)
// for element in self {
// data.append(element.solidityData)
// }
// if data.count < capacity {
// data.append(Data(count: capacity - data.count))
// }
// return data
// }
func data(function: String) -> Data {
var data = Data(capacity: count * 32 + 4)
data.append(function.keccak256()[0..<4])
Expand All @@ -90,6 +70,15 @@ extension Array: SolidityDataRepresentable where Element == SolidityDataRepresen
}

extension Address {
/// Prepares transaction to send. Estimates gas usage, nonce and gas price.
///
/// - Parameters:
/// - function: Smart contract function
/// - arguments: Function arguments
/// - web3: Node address. default: .default
/// - options: Transaction options. default: nil
/// - onBlock: Future transaction block. default: "pending"
/// - Returns: Promise for the assembled transaction
public func assemble(_ function: String, _ arguments: [Any], web3: Web3 = .default, options: Web3Options? = nil, onBlock: String = "pending") -> Promise<EthereumTransaction> {
let options = web3.options.merge(with: options)

Expand All @@ -106,36 +95,57 @@ extension Address {
optionsForGasEstimation.from = options.from
optionsForGasEstimation.to = options.to
optionsForGasEstimation.value = options.value
let getNoncePromise = web3.eth.getTransactionCountPromise(address: from, onBlock: onBlock)
let gasEstimatePromise = web3.eth.estimateGasPromise(assembledTransaction, options: optionsForGasEstimation, onBlock: onBlock)
let gasPricePromise = web3.eth.getGasPricePromise()
var promisesToFulfill: [Promise<BigUInt>] = [getNoncePromise, gasPricePromise, gasPricePromise]
when(resolved: getNoncePromise, gasEstimatePromise, gasPricePromise).map(on: queue, { (results: [Result<BigUInt>]) throws -> EthereumTransaction in

promisesToFulfill.removeAll()
guard case let .fulfilled(nonce) = results[0] else {
throw Web3Error.processingError("Failed to fetch nonce")
}
guard case let .fulfilled(gasEstimate) = results[1] else {
throw Web3Error.processingError("Failed to fetch gas estimate")
}
guard case let .fulfilled(gasPrice) = results[2] else {
throw Web3Error.processingError("Failed to fetch gas price")
}
let estimate = Web3Options.smartMergeGasLimit(originalOptions: options, extraOptions: options, gasEstimate: gasEstimate)
assembledTransaction.nonce = nonce
let nonce = web3.eth.getTransactionCountPromise(address: from, onBlock: onBlock)
let gasEstimate = web3.eth.estimateGasPromise(assembledTransaction, options: optionsForGasEstimation, onBlock: onBlock)
let gasPrice = web3.eth.getGasPricePromise()
nonce.catch { error in
seal.reject(Web3Error.processingError("Failed to fetch nonce"))
}
gasEstimate.catch { error in
seal.reject(Web3Error.processingError("Failed to fetch gas estimate"))
}
gasPrice.catch { error in
seal.reject(Web3Error.processingError("Failed to fetch gas price"))
}

_ = when(fulfilled: nonce,gasEstimate,gasPrice).done(on: queue) { _ in
let estimate = Web3Options.smartMergeGasLimit(originalOptions: options, extraOptions: options, gasEstimate: gasEstimate.value!)
assembledTransaction.nonce = nonce.value!
assembledTransaction.gasLimit = estimate
let finalGasPrice = Web3Options.smartMergeGasPrice(originalOptions: options, extraOptions: options, priceEstimate: gasPrice)
let finalGasPrice = Web3Options.smartMergeGasPrice(originalOptions: options, extraOptions: options, priceEstimate: gasPrice.value!)
assembledTransaction.gasPrice = finalGasPrice
return assembledTransaction
}).done(on: queue, seal.fulfill).catch(on: queue, seal.reject)
seal.fulfill(assembledTransaction)
}
}
return returnPromise
}

/// Sends transaction to call mutable smart contract function.
/// - Important: Set the sender in Web3Options.from
///
/// - Parameters:
/// - function: Smart contract function
/// - arguments: Function arguments
/// - password: Password do decrypt your private key
/// - web3: Node address. default: .default
/// - options: Web3Options. default: nil
/// - onBlock: Future transaction block. default: "pending"
/// - Returns: Promise for sent transaction and its hash
public func send(_ function: String, _ arguments: Any..., password: String = "BANKEXFOUNDATION", web3: Web3 = .default, options: Web3Options? = nil, onBlock: String = "pending") -> Promise<TransactionSendingResult> {
return send(function, arguments, password: password, web3: web3, options: options, onBlock: onBlock)
}

/// Sends transaction to call mutable smart contract function.
/// - Important: Set the sender in Web3Options.from
///
/// - Parameters:
/// - function: Smart contract function
/// - arguments: Function arguments
/// - password: Password do decrypt your private key
/// - web3: Node address. default: .default
/// - options: Web3Options. default: nil
/// - onBlock: Future transaction block. default: "pending"
/// - Returns: Promise for sent transaction and its hash
public func send(_ function: String, _ arguments: [Any], password: String = "BANKEXFOUNDATION", web3: Web3 = .default, options: Web3Options? = nil, onBlock: String = "pending") -> Promise<TransactionSendingResult> {
let options = web3.options.merge(with: options)
let queue = web3.requestDispatcher.queue
Expand All @@ -146,9 +156,30 @@ extension Address {
return web3.eth.sendTransactionPromise(transaction, options: cleanedOptions, password: password)
}
}


/// Call a smart contract function.
///
/// - Parameters:
/// - function: Function to call
/// - arguments: Function arguments
/// - web3: Node address. default: .default
/// - options: Web3Options. default: nil
/// - onBlock: Call block. default: "latest"
/// - Returns: Promise for function result
public func call(_ function: String, _ arguments: Any..., web3: Web3 = .default, options: Web3Options? = nil, onBlock: String = "latest") -> Promise<SolidityDataReader> {
return call(function, arguments, web3: web3, options: options, onBlock: onBlock)
}

/// Call a smart contract function.
///
/// - Parameters:
/// - function: Function to call
/// - arguments: Function arguments
/// - web3: Node address. default: .default
/// - options: Web3Options. default: nil
/// - onBlock: Call block. default: "latest"
/// - Returns: Promise for function result
public func call(_ function: String, _ arguments: [Any], web3: Web3 = .default, options: Web3Options? = nil, onBlock: String = "latest") -> Promise<SolidityDataReader> {
let options = web3.options.merge(with: options)
let function = try! SolidityFunction(function: function)
Expand All @@ -166,9 +197,29 @@ extension Address {
}
}


/// Estimates gas price for transaction
///
/// - Parameters:
/// - function: Smart contract function
/// - arguments: Function arguments
/// - web3: Node address. default: .default
/// - options: Web3Options. default: nil
/// - onBlock: Gas estimation block. default: "latest"
/// - Returns: Promise for estimated gas
public func estimateGas(_ function: String, _ arguments: Any..., web3: Web3 = .default, options: Web3Options? = nil, onBlock: String = "latest") -> Promise<BigUInt> {
return estimateGas(function, arguments, web3: web3, options: options, onBlock: onBlock)
}

/// Estimates gas price for transaction
///
/// - Parameters:
/// - function: Smart contract function
/// - arguments: Function arguments
/// - web3: Node address. default: .default
/// - options: Web3Options. default: nil
/// - onBlock: Gas estimation block. default: "latest"
/// - Returns: Promise for estimated gas
public func estimateGas(_ function: String, _ arguments: [Any], web3: Web3 = .default, options: Web3Options? = nil, onBlock: String = "latest") -> Promise<BigUInt> {
let options = web3.options.merge(with: options)
let function = try! SolidityFunction(function: function)
Expand Down
61 changes: 39 additions & 22 deletions Sources/ABIv2/SolidityTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@
import Foundation
import BigInt

/// Abi errors
public enum AbiError: Error {
/// Unsupported types: function, tuple
/// Solidity types (function, tuple) are currently not supported
case unsupportedType
/// Printable / user displayable description
public var localizedDescription: String {
switch self {
case .unsupportedType:
return "Solidity types (function, tuple) are currently not supported"
}
}
}

/**
Expand Down Expand Up @@ -70,7 +78,15 @@ public class SolidityType: Equatable, CustomStringConvertible {

/// Type conversion error
public enum Error: Swift.Error {
case corrupted
/// Unknown solidity type "\(string)"
case corrupted(String)
/// Printable / user displayable description
public var localizedDescription: String {
switch self {
case let .corrupted(string):
return "Unknown solidity type: \"\(string)\""
}
}
}

/// Represents solidity uintN type
Expand Down Expand Up @@ -203,30 +219,30 @@ extension SolidityType {
"int": SInt(bits: 256)
]
private static func scan(tuple string: String, from index: Int) throws -> SolidityType {
guard string.last! == ")" else { throw Error.corrupted }
guard string[..<index] == "tuple" else { throw Error.corrupted }
guard string.last! == ")" else { throw Error.corrupted(string) }
guard string[..<index] == "tuple" else { throw Error.corrupted(string) }
let string = string[index+1..<string.count-1]
let array = try string.split(separator: ",").map { try scan(type: String($0)) }
return SolidityTuple(types: array)
}
private static func scan(arraySize string: String, from index: Int) throws -> SolidityType {
guard string.last! == "]" else { throw Error.corrupted }
guard string.last! == "]" else { throw Error.corrupted(string) }
let prefix = string[..<index]
guard let type = knownTypes[String(prefix)] else { throw Error.corrupted }
guard let type = knownTypes[String(prefix)] else { throw Error.corrupted(string) }
// type.isValid == true
let string = string[index+1..<string.count-1]
if string.isEmpty {
return SDynamicArray(type: type)
} else {
guard let count = Int(string) else { throw Error.corrupted }
guard count > 0 else { throw Error.corrupted }
guard let count = Int(string) else { throw Error.corrupted(string) }
guard count > 0 else { throw Error.corrupted(string) }
return SStaticArray(count: count, type: type)
}
}
private static func scan(bytesArray string: String, from index: Int) throws -> SolidityType {
guard let count = Int(string[index...]) else { throw Error.corrupted }
guard let count = Int(string[index...]) else { throw Error.corrupted(string) }
let type = SBytes(count: count)
guard type.isValid else { throw Error.corrupted }
guard type.isValid else { throw Error.corrupted(string) }
return type
}
private static func scan(number string: String, from index: Int) throws -> SolidityType {
Expand All @@ -237,36 +253,36 @@ extension SolidityType {
isSigned = false
case "int":
isSigned = true
default: throw Error.corrupted
default: throw Error.corrupted(string)
}
let i = index+1
for (index2,character) in string[i...].enumerated() {
switch character {
case "[":
guard let number = Int(string[index...index+index2]) else { throw Error.corrupted }
guard let number = Int(string[index...index+index2]) else { throw Error.corrupted(string) }
let type = isSigned ? SInt(bits: number) : SUInt(bits: number)
guard type.isValid else { throw Error.corrupted }
guard string.last! == "]" else { throw Error.corrupted }
guard type.isValid else { throw Error.corrupted(string) }
guard string.last! == "]" else { throw Error.corrupted(string) }
// type.isValid == true
let string = string[index+index2+2..<string.count-1]
if string.isEmpty {
return SDynamicArray(type: type)
} else {
guard let count = Int(string) else { throw Error.corrupted }
guard count > 0 else { throw Error.corrupted }
guard let count = Int(string) else { throw Error.corrupted(string) }
guard count > 0 else { throw Error.corrupted(string) }
let array = SStaticArray(count: count, type: type)
guard array.isValid else { throw Error.corrupted }
guard array.isValid else { throw Error.corrupted(string) }
return array
}
case "0"..."9":
guard index2 < 3 else { throw Error.corrupted }
guard index2 < 3 else { throw Error.corrupted(string) }
continue
default: throw Error.corrupted
default: throw Error.corrupted(string)
}
}
guard let number = Int(string[index...]) else { throw Error.corrupted }
guard let number = Int(string[index...]) else { throw Error.corrupted(string) }
let type = isSigned ? SInt(bits: number) : SUInt(bits: number)
guard type.isValid else { throw Error.corrupted }
guard type.isValid else { throw Error.corrupted(string) }
return type
}
/**
Expand Down Expand Up @@ -302,7 +318,7 @@ extension SolidityType {
} else if let type = knownTypes[String(string)] {
return type
} else {
throw Error.corrupted
throw Error.corrupted(string)
}
}
}
Expand Down Expand Up @@ -357,6 +373,7 @@ public class SolidityFunction: CustomStringConvertible {
case invalidFormat(String)
/// Throws if function name is empty
case emptyFunctionName(String)
/// Printable / user displayable description
public var localizedDescription: String {
switch self {
case let .invalidFormat(function):
Expand Down
1 change: 1 addition & 0 deletions Sources/Contract/ContractABIv2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public struct ContractV2: ContractProtocol {
case notFound
/// Cannot encode data with given parameters
case cannotEncodeDataWithGivenParameters
/// Printable / user displayable description
public var localizedDescription: String {
switch self {
case .noAddress:
Expand Down
9 changes: 2 additions & 7 deletions Sources/Contracts/ERC721.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import PromiseKit


/**
Native implementation of ERC20 token
Native implementation of ERC721 token
- Important: NOT main thread friendly
*/
public class ERC721 {
Expand Down Expand Up @@ -42,7 +42,7 @@ public class ERC721 {
self.password = password
options.from = from
}

/// Returns current balance of user
public func balance(of user: Address) throws -> BigUInt {
return try address.call("balanceOf(address)",user).wait().uint256()
}
Expand Down Expand Up @@ -87,11 +87,6 @@ public class ERC721 {
var address: Address { return parent.address }
var options: Web3Options { return parent.options }

/**
Native implementation of ERC20 token
- Important: NOT main thread friendly
- Returns: full information for all pending and queued transactions
*/
init(_ parent: ERC721) {
self.parent = parent
}
Expand Down
Loading

0 comments on commit 563f201

Please sign in to comment.