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