Skip to content

Commit 4329f90

Browse files
Rewrite GasOracle
- Now it works with feeHistory JRON RPC Ethereum call - Now all method returns percentiles, which user set on object init.
1 parent 77a1c7e commit 4329f90

File tree

3 files changed

+63
-62
lines changed

3 files changed

+63
-62
lines changed

Sources/web3swift/Promises/Promise+Web3+Eth+FeeHistory.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,19 @@ extension web3.Eth {
1414
// public func feeHistory(blockCount: Int = 10, onBlock: String = "latest", percentiles:[Double] = [25, 50, 75]) -> Promise<FeeHistory> {
1515
// return feeHistory(address: addr, onBlock: onBlock)
1616
// }
17-
public func feeHistory(blockCount: Int, block: String, percentiles:[Double]) throws -> Promise<Oracle.FeeHistory> {
17+
18+
public func feeHistory(blockCount: BigUInt, block: String, percentiles:[Double]) throws -> Oracle.FeeHistory {
1819
let request = JSONRPCRequestFabric.prepareRequest(.feeHistory, parameters: [blockCount.description.addHexPrefix(), block, percentiles])
1920
let rp = web3.dispatch(request)
2021
let queue = web3.requestDispatcher.queue
21-
return rp.map(on: queue) { response in
22+
return try rp.map(on: queue) { response in
2223
guard let value: Oracle.FeeHistory = response.getValue() else {
2324
if response.error != nil {
2425
throw Web3Error.nodeError(desc: response.error!.message)
2526
}
2627
throw Web3Error.nodeError(desc: "Invalid value from Ethereum node")
2728
}
2829
return value
29-
}
30+
}.wait()
3031
}
3132
}

Sources/web3swift/Web3/Web3+Eth.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,4 @@ extension web3.Eth {
351351
guard let value = Web3.Utils.parseToBigUInt(amount, decimals: intDecimals) else {return nil}
352352
return sendERC20tokensWithKnownDecimals(tokenAddress: tokenAddress, from: from, to: to, amount: value, transactionOptions: mergedOptions)
353353
}
354-
355-
public func feeHistory(blockCount: Int = 10, block: String = "latest", percentiles:[Double] = [25, 50, 75]) throws -> Oracle.FeeHistory {
356-
let result = try self.feeHistory(blockCount: blockCount, block: block, percentiles: percentiles).wait()
357-
return result
358-
}
359-
360354
}

Sources/web3swift/Web3/Web3+GasOracle.swift

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ extension web3.Eth {
1616
/// Web3 provider by which accessing to the blockchain
1717
private let web3Provider: web3
1818

19+
private var feeHistory: FeeHistory?
20+
1921
/// Ethereum scope shortcut
2022
private var eth: web3.Eth { web3Provider.eth }
2123

@@ -34,7 +36,7 @@ extension web3.Eth {
3436
/// - block: Number of block from which counts starts
3537
/// - blockCount: Count of block to calculate statistics
3638
/// - percentiles: Percentiles of fees which will split in fees history
37-
public init(_ provider: web3, block: String = "latest", blockCount: BigUInt = 20, percentiles: [Double] = [25, 50, 75]) {
39+
public init(_ provider: web3, block: String = "latest", blockCount: BigUInt = 20, percentiles: [Double] = [10, 50, 90]) {
3840
self.web3Provider = provider
3941
self.block = block
4042
self.blockCount = blockCount
@@ -57,7 +59,7 @@ extension web3.Eth {
5759
// swiftlint:disable force_unwrapping
5860
case .minimum: return unwrappedArray.min()!
5961
case .mean: return unwrappedArray.mean()!
60-
case .median: return unwrappedArray.median()!
62+
case .median: return unwrappedArray.mean()!
6163
case .maximum:
6264
// Checking that suggestedBaseFee is not lower than it will be in the next block
6365
// because in the maximum statistic we should guarantee that transaction would be included in it.
@@ -67,47 +69,50 @@ extension web3.Eth {
6769
// swiftlint:enable force_unwrapping
6870
}
6971

70-
// private func suggestGasValues( statistic: Statistic) throws -> FeeHistory {
71-
//
72-
// }
73-
74-
private func suggestTipValue(_ statistic: Statistic) throws -> BigUInt {
75-
76-
let latestBlockNumber = try eth.getBlockNumber()
72+
private func soft(twoDimentsion array: [[BigUInt]]) -> [BigUInt] {
73+
/// We've got `[[min],[middle],[max]]` 2 dimensional array
74+
/// we're getting `[min, middle, max].count == self.percentiles.count`,
75+
/// where each value are mean from the input percentile array
76+
array.compactMap { percentileArray -> [BigUInt]? in
77+
guard !percentileArray.isEmpty else { return nil }
78+
// swiftlint:disable force_unwrapping
79+
return [percentileArray.mean()!]
80+
// swiftlint:enable force_unwrapping
81+
}
82+
.flatMap { $0 }
83+
}
7784

78-
var block: Block
85+
private func calculatePercentiles(for data: [BigUInt]) -> [BigUInt] {
86+
percentiles.compactMap { percentile in
87+
data.percentile(of: percentile)
88+
}
89+
}
7990

80-
// TODO: Make me work with cache
81-
repeat {
82-
block = try eth.getBlockByNumber(latestBlockNumber, fullTransactions: true)
83-
} while block.transactions.count < 20
91+
private func suggestGasValues() throws -> FeeHistory {
92+
// This is some kind of cache.
93+
// It stores about 9 seconds, than it rewrites it with newer data.
94+
// TODO: Disabled until 3.0 version, coz `distance` available from iOS 13.
95+
// guard feeHistory != nil, feeHistory!.timestamp.distance(to: Date()) < cacheTimeout else { return feeHistory! }
8496

85-
// Storing last block to calculate baseFee of the next block
86-
// latestBlock = block
97+
return try eth.feeHistory(blockCount: blockCount, block: block, percentiles: percentiles)
98+
}
8799

88-
let transactionsTips = block.transactions
89-
.compactMap { t -> EthereumTransaction? in
90-
guard case let .transaction(transaction) = t else { return nil }
91-
return transaction
100+
/// Suggesting tip values
101+
/// - Returns: `[percentile 1, percentile 2, percentile 3].count == self.percentile.count`
102+
/// by default there's 3 percentile.
103+
private func suggestTipValue() throws -> [BigUInt] {
104+
/// reaarange `[[min, middle, max]]` to `[[min], [middle], [max]]`
105+
let rearrengedArray = try suggestGasValues().reward
106+
.map { internalArray -> [BigUInt] in
107+
var newArray = [BigUInt]()
108+
internalArray.enumerated().forEach { newArray[$0] = $1 }
109+
return newArray
92110
}
93-
// TODO: Add filter for transaction types
94-
.map { $0.maxPriorityFeePerGas }
95-
96-
return try calculateStatistic(for: statistic, data: transactionsTips)
111+
return soft(twoDimentsion: rearrengedArray)
97112
}
98113

99-
private func suggestBaseFee(_ statistic: Statistic) throws -> BigUInt {
100-
let latestBlockNumber = try eth.getBlockNumber()
101-
102-
// Assigning last block to object var to predict baseFee of the next block
103-
// latestBlock = try eth.getBlockByNumber(latestBlockNumber)
104-
// TODO: Make me work with cache
105-
let lastNthBlocksBaseFees = try (latestBlockNumber - blockCount ... latestBlockNumber)
106-
.map { try eth.getBlockByNumber($0) }
107-
.filter { !$0.transactions.isEmpty }
108-
.compactMap { $0.baseFeePerGas }
109-
110-
return try calculateStatistic(for: statistic, data: lastNthBlocksBaseFees)
114+
private func suggestBaseFee() throws -> [BigUInt] {
115+
calculatePercentiles(for: try suggestGasValues().baseFeePerGas)
111116
}
112117

113118
private func suggestGasFeeLegacy(_ statistic: Statistic) throws -> BigUInt {
@@ -141,8 +146,8 @@ public extension web3.Eth.Oracle {
141146
///
142147
/// - Parameter statistic: Statistic to apply for base fee calculation
143148
/// - Returns: Suggested base fee amount according to statistic, nil if failed to perdict
144-
func predictBaseFee(_ statistic: Statistic) -> BigUInt? {
145-
guard let value = try? suggestBaseFee(statistic) else { return nil }
149+
func predictBaseFee() -> [BigUInt] {
150+
guard let value = try? suggestBaseFee() else { return [] }
146151
return value
147152
}
148153

@@ -155,8 +160,8 @@ public extension web3.Eth.Oracle {
155160
///
156161
/// - Parameter statistic: Statistic to apply for tip calculation
157162
/// - Returns: Suggested tip amount according to statistic, nil if failed to perdict
158-
func predictTip(_ statistic: Statistic) -> BigUInt? {
159-
guard let value = try? suggestTipValue(statistic) else { return nil }
163+
func predictTip() -> [BigUInt] {
164+
guard let value = try? suggestTipValue() else { return [] }
160165
return value
161166
}
162167

@@ -166,9 +171,9 @@ public extension web3.Eth.Oracle {
166171
/// - baseFee: Statistic to apply for baseFee
167172
/// - tip: Statistic to apply for tip
168173
/// - Returns: Tuple where `baseFee` — base fee, `tip` — tip, nil if failed to predict
169-
func predictBothFees(baseFee: Statistic, tip: Statistic) -> (baseFee: BigUInt, tip: BigUInt)? {
170-
guard let baseFee = try? suggestBaseFee(baseFee) else { return nil }
171-
guard let tip = try? suggestTipValue(tip) else { return nil }
174+
func predictBothFees() -> (baseFee: [BigUInt], tip: [BigUInt])? {
175+
guard let baseFee = try? suggestBaseFee() else { return nil }
176+
guard let tip = try? suggestTipValue() else { return nil }
172177

173178
return (baseFee: baseFee, tip: tip)
174179
}
@@ -177,10 +182,10 @@ public extension web3.Eth.Oracle {
177182
/// Method to get legacy gas price
178183
/// - Parameter statistic: Statistic to apply for gas price
179184
/// - Returns: Suggested gas price amount according to statistic, nil if failed to predict
180-
func predictGasPriceLegacy(_ statistic: Statistic) -> BigUInt? {
181-
guard let value = try? suggestGasFeeLegacy(statistic) else { return nil}
182-
return value
183-
}
185+
// func predictGasPriceLegacy() -> BigUInt? {
186+
// guard let value = try? suggestGasFeeLegacy() else { return nil}
187+
// return value
188+
// }
184189
}
185190

186191
public extension web3.Eth.Oracle {
@@ -199,6 +204,7 @@ public extension web3.Eth.Oracle {
199204

200205
public extension web3.Eth.Oracle {
201206
struct FeeHistory {
207+
let timestamp = Date()
202208
let baseFeePerGas: [BigUInt]
203209
let gasUsedRatio: [Double]
204210
let oldestBlock: BigUInt
@@ -244,14 +250,14 @@ extension Array where Element: BinaryInteger {
244250
return BigUInt(self.reduce(0, +)) / BigUInt(self.count)
245251
}
246252

247-
func median() -> BigUInt? {
253+
func percentile(of value: Double) -> BigUInt? {
248254
guard !self.isEmpty else { return nil }
249255

250256
let sorted_data = self.sorted()
251-
if self.count % 2 == 1 {
252-
return BigUInt(sorted_data[Int(floor(Double(self.count) / 2))])
253-
} else {
254-
return BigUInt(sorted_data[self.count / 2] + sorted_data[(self.count / 2) - 1] / 2)
255-
}
257+
// if self.count % 2 == 1 {
258+
return BigUInt(sorted_data[Int(floor(Double(self.count) / value / 10))])
259+
// } else {
260+
// return BigUInt(sorted_data[self.count / Int(value) / 10] + sorted_data[(self.count / Int(value) / 10) - 1] /( value) / 10)
261+
// }
256262
}
257263
}

0 commit comments

Comments
 (0)