Skip to content

Commit 1c5145c

Browse files
committed
refactored automatic gas pricing algorithm to utilize the new Oracle, implement the new gas parameters, and take transaction type into account
fixes web3swift-team#546 by preserving the transaction type across the transaction rewrite
1 parent 79eda1f commit 1c5145c

File tree

1 file changed

+87
-21
lines changed

1 file changed

+87
-21
lines changed

Sources/web3swift/Web3/Web3+MutatingTransaction.swift

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ public class WriteTransaction: ReadTransaction {
7373
optionsForGasEstimation.value = mergedOptions.value
7474
optionsForGasEstimation.gasLimit = mergedOptions.gasLimit
7575
optionsForGasEstimation.callOnBlock = mergedOptions.callOnBlock
76+
optionsForGasEstimation.type = mergedOptions.type
77+
optionsForGasEstimation.accessList = mergedOptions.accessList
78+
7679

7780
// assemble promise for gasLimit
7881
var gasEstimatePromise: Promise<BigUInt>? = nil
@@ -102,20 +105,75 @@ public class WriteTransaction: ReadTransaction {
102105
getNoncePromise = Promise<BigUInt>.value(nonce)
103106
}
104107

105-
// assemble promise for gasPrice
106-
var gasPricePromise: Promise<BigUInt>? = nil
107-
guard let gasPricePolicy = mergedOptions.gasPrice else {
108-
seal.reject(Web3Error.inputError(desc: "No gasPrice policy provided"))
109-
return
110-
}
111-
switch gasPricePolicy {
112-
case .automatic, .withMargin:
113-
gasPricePromise = self.web3.eth.getGasPricePromise()
114-
case .manual(let gasPrice):
115-
gasPricePromise = Promise<BigUInt>.value(gasPrice)
108+
// determine gas costing, taking transaction type into account
109+
let oracle = Web3.Oracle(self.web3, percentiles: [75])
110+
let finalGasPrice: BigUInt? // legacy gas model
111+
let finalGasFee: BigUInt? // EIP-1559 gas model
112+
let finalTipFee: BigUInt? // EIP-1559 gas model
113+
114+
if mergedOptions.type == nil || mergedOptions.type != .eip1559 { // legacy Gas
115+
// set unused gas parameters to nil
116+
finalGasFee = nil
117+
finalTipFee = nil
118+
119+
// determine the (legacy) gas price
120+
guard let gasPricePolicy = mergedOptions.gasPrice else {
121+
seal.reject(Web3Error.inputError(desc: "No gasPrice policy provided"))
122+
return
123+
}
124+
switch gasPricePolicy {
125+
case .automatic, .withMargin:
126+
let percentiles = oracle.gasPriceLegacyPercentiles
127+
guard !percentiles.isEmpty else {
128+
throw Web3Error.processingError(desc: "Failed to fetch gas price")
129+
}
130+
finalGasPrice = percentiles[0]
131+
case .manual(let gasPrice):
132+
finalGasPrice = gasPrice
133+
}
134+
} else { // else new gas fees (EIP-1559)
135+
// set unused gas parametes to nil
136+
finalGasPrice = nil
137+
138+
// determine the tip
139+
guard let maxPriorityFeePerGasPolicy = mergedOptions.maxPriorityFeePerGas else {
140+
seal.reject(Web3Error.inputError(desc: "No maxPriorityFeePerGas policy provided"))
141+
return
142+
}
143+
switch maxPriorityFeePerGasPolicy {
144+
case .automatic:
145+
let percentiles = oracle.tipFeePercentiles
146+
guard !percentiles.isEmpty else {
147+
throw Web3Error.processingError(desc: "Failed to fetch maxPriorityFeePerGas data")
148+
}
149+
finalTipFee = percentiles[0]
150+
case .manual(let maxPriorityFeePerGas):
151+
finalTipFee = maxPriorityFeePerGas
152+
}
153+
154+
// determine the baseFee, and calculate the maxFeePerGas
155+
guard let maxFeePerGasPolicy = mergedOptions.maxFeePerGas else {
156+
seal.reject(Web3Error.inputError(desc: "No maxFeePerGas policy provided"))
157+
return
158+
}
159+
switch maxFeePerGasPolicy {
160+
case .automatic:
161+
let percentiles = oracle.baseFeePercentiles
162+
guard !percentiles.isEmpty else {
163+
throw Web3Error.processingError(desc: "Failed to fetch baseFee data")
164+
}
165+
guard let tipFee = finalTipFee else {
166+
throw Web3Error.processingError(desc: "Missing tip value")
167+
}
168+
finalGasFee = percentiles[0] + tipFee
169+
case .manual(let maxFeePerGas):
170+
finalGasFee = maxFeePerGas
171+
}
116172
}
117-
var promisesToFulfill: [Promise<BigUInt>] = [getNoncePromise!, gasPricePromise!, gasEstimatePromise!]
118-
when(resolved: getNoncePromise!, gasEstimatePromise!, gasPricePromise!).map(on: queue, { (results: [PromiseResult<BigUInt>]) throws -> EthereumTransaction in
173+
174+
// wait for promises to resolve
175+
var promisesToFulfill: [Promise<BigUInt>] = [getNoncePromise!, gasEstimatePromise!]
176+
when(resolved: getNoncePromise!, gasEstimatePromise!).map(on: queue, { (results: [PromiseResult<BigUInt>]) throws -> EthereumTransaction in
119177

120178
promisesToFulfill.removeAll()
121179
guard case .fulfilled(let nonce) = results[0] else {
@@ -124,17 +182,25 @@ public class WriteTransaction: ReadTransaction {
124182
guard case .fulfilled(let gasEstimate) = results[1] else {
125183
throw Web3Error.processingError(desc: "Failed to fetch gas estimate")
126184
}
127-
guard case .fulfilled(let gasPrice) = results[2] else {
128-
throw Web3Error.processingError(desc: "Failed to fetch gas price")
129-
}
130-
131-
let estimate = mergedOptions.resolveGasLimit(gasEstimate)
132-
let finalGasPrice = mergedOptions.resolveGasPrice(gasPrice)
133185

134186
var finalOptions = TransactionOptions()
187+
finalOptions.type = mergedOptions.type
135188
finalOptions.nonce = .manual(nonce)
136-
finalOptions.gasLimit = .manual(estimate)
137-
finalOptions.gasPrice = .manual(finalGasPrice)
189+
finalOptions.gasLimit = .manual(mergedOptions.resolveGasLimit(gasEstimate))
190+
finalOptions.accessList = mergedOptions.accessList
191+
192+
// set the finalized gas parameters
193+
if let gasPrice = finalGasPrice {
194+
finalOptions.gasPrice = .manual(mergedOptions.resolveGasPrice(gasPrice))
195+
}
196+
197+
if let tipFee = finalTipFee {
198+
finalOptions.maxPriorityFeePerGas = .manual(mergedOptions.resolveMaxPriorityFeePerGas(tipFee))
199+
}
200+
201+
if let gasFee = finalGasFee {
202+
finalOptions.maxFeePerGas = .manual(mergedOptions.resolveMaxFeePerGas(gasFee))
203+
}
138204

139205
assembledTransaction.applyOptions(finalOptions)
140206

0 commit comments

Comments
 (0)