Skip to content

Commit 40dbebf

Browse files
committed
Add policy resolver
1 parent 912f32f commit 40dbebf

File tree

3 files changed

+164
-0
lines changed

3 files changed

+164
-0
lines changed

Sources/Core/Transaction/CodableTransaction.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ public struct CodableTransaction {
187187
return self.envelope.encode(for: type)
188188
}
189189

190+
@available(*, deprecated, message: "Please use PolicyResolver instead")
190191
public mutating func resolve(provider: Web3Provider) async {
191192
// FIXME: Delete force try
192193
self.gasLimit = try! await self.gasLimitPolicy.resolve(provider: provider, transaction: self)
@@ -299,6 +300,7 @@ extension CodableTransaction {
299300
case limited(BigUInt)
300301
case withMargin(Double)
301302

303+
@available(*, deprecated, message: "Please use PolicyResolver instead")
302304
func resolve(provider: Web3Provider, transaction: CodableTransaction?) async throws -> BigUInt {
303305
guard let transaction = transaction else { throw Web3Error.valueError }
304306
let request: APIRequest = .estimateGas(transaction, transaction.callOnBlock ?? .latest)
@@ -323,6 +325,7 @@ extension CodableTransaction {
323325
case manual(BigUInt)
324326
case withMargin(Double)
325327

328+
@available(*, deprecated, message: "Please use PolicyResolver instead")
326329
func resolve(provider: Web3Provider, transaction: CodableTransaction? = nil) async throws -> BigUInt {
327330
let oracle = Oracle(provider)
328331
switch self {
@@ -338,6 +341,7 @@ extension CodableTransaction {
338341
case automatic
339342
case manual(BigUInt)
340343

344+
@available(*, deprecated, message: "Please use PolicyResolver instead")
341345
public func resolve(provider: Web3Provider, transaction: CodableTransaction? = nil) async throws -> BigUInt {
342346
let oracle = Oracle(provider)
343347
switch self {
@@ -353,6 +357,7 @@ extension CodableTransaction {
353357
case automatic
354358
case manual(BigUInt)
355359

360+
@available(*, deprecated, message: "Please use PolicyResolver instead")
356361
public func resolve(provider: Web3Provider, transaction: CodableTransaction? = nil) async throws -> BigUInt {
357362
let oracle = Oracle(provider)
358363
switch self {
@@ -364,6 +369,7 @@ extension CodableTransaction {
364369
}
365370
}
366371

372+
@available(*, deprecated, message: "Please use PolicyResolver instead")
367373
func resolveNonce(provider: Web3Provider) async throws -> BigUInt {
368374
switch noncePolicy {
369375
case .pending, .latest, .earliest:
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// Web3+Resolver.swift
3+
//
4+
//
5+
// Created by Jann Driessen on 01.11.22.
6+
//
7+
8+
import Foundation
9+
import BigInt
10+
import Core
11+
12+
public class PolicyResolver {
13+
private let provider: Web3Provider
14+
15+
public init(provider: Web3Provider) {
16+
self.provider = provider
17+
}
18+
19+
public func resolveAll(for tx: inout CodableTransaction) async throws {
20+
if tx.from != nil || tx.sender != nil {
21+
// Nonce should be resolved first - as this might be needed for some
22+
// tx's gas estimation
23+
tx.nonce = try await resolveNonce(for: tx)
24+
}
25+
26+
tx.gasLimit = try await resolveGasEstimate(for: tx)
27+
28+
if case .eip1559 = tx.type {
29+
tx.maxFeePerGas = await resolveGasBaseFee(for: tx.maxFeePerGasPolicy)
30+
tx.maxPriorityFeePerGas = await resolveGasPriorityFee(for: tx.maxPriorityFeePerGasPolicy)
31+
} else {
32+
tx.gasPrice = await resolveGasPrice(for: tx.gasPricePolicy)
33+
}
34+
}
35+
36+
public func resolveGasBaseFee(for policy: CodableTransaction.FeePerGasPolicy) async -> BigUInt {
37+
let oracle = Oracle(provider)
38+
switch policy {
39+
case .automatic:
40+
return await oracle.baseFeePercentiles().max() ?? 0
41+
case .manual(let value):
42+
return value
43+
}
44+
}
45+
46+
public func resolveGasEstimate(for transaction: CodableTransaction) async throws -> BigUInt {
47+
switch transaction.gasLimitPolicy {
48+
case .automatic, .withMargin:
49+
return try await estimateGas(for: transaction)
50+
case .manual(let value):
51+
return value
52+
case .limited(let limit):
53+
let result = try await estimateGas(for: transaction)
54+
if limit <= result {
55+
return result
56+
} else {
57+
return limit
58+
}
59+
}
60+
}
61+
62+
public func resolveGasPrice(for policy: CodableTransaction.GasPricePolicy) async -> BigUInt {
63+
let oracle = Oracle(provider)
64+
switch policy {
65+
case .automatic, .withMargin:
66+
return await oracle.gasPriceLegacyPercentiles().max() ?? 0
67+
case .manual(let value):
68+
return value
69+
}
70+
}
71+
72+
public func resolveGasPriorityFee(for policy: CodableTransaction.PriorityFeePerGasPolicy) async -> BigUInt {
73+
let oracle = Oracle(provider)
74+
switch policy {
75+
case .automatic:
76+
return await oracle.tipFeePercentiles().max() ?? 0
77+
case .manual(let value):
78+
return value
79+
}
80+
}
81+
82+
public func resolveNonce(for tx: CodableTransaction) async throws -> BigUInt {
83+
switch tx.noncePolicy {
84+
case .pending, .latest, .earliest:
85+
guard let address = tx.from ?? tx.sender else { throw Web3Error.valueError }
86+
let request: APIRequest = .getTransactionCount(address.address, tx.callOnBlock ?? .latest)
87+
let response: APIResponse<BigUInt> = try await APIRequest.sendRequest(with: provider, for: request)
88+
return response.result
89+
case .exact(let value):
90+
return value
91+
}
92+
}
93+
}
94+
95+
// MARK: - Private
96+
97+
extension PolicyResolver {
98+
private func estimateGas(for transaction: CodableTransaction) async throws -> BigUInt {
99+
let request: APIRequest = .estimateGas(transaction, transaction.callOnBlock ?? .latest)
100+
let response: APIResponse<BigUInt> = try await APIRequest.sendRequest(with: provider, for: request)
101+
return response.result
102+
}
103+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// PolicyResolverTests.swift
3+
//
4+
//
5+
// Created by Jann Driessen on 01.11.22.
6+
//
7+
8+
import XCTest
9+
import Core
10+
11+
@testable import web3swift
12+
13+
final class PolicyResolverTests: XCTestCase {
14+
15+
func testResolveAllForEIP1159Transaction() async throws {
16+
let web3 = await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken)
17+
let resolver = PolicyResolver(provider: web3.provider)
18+
var tx = CodableTransaction(
19+
type: .eip1559,
20+
to: EthereumAddress("0xb47292B7bBedA4447564B8336E4eD1f93735e7C7")!,
21+
chainID: web3.provider.network!.chainID,
22+
value: Utilities.parseToBigUInt("0.1", units: .eth)!,
23+
gasLimit: 21_000
24+
)
25+
// Vitalik's address
26+
tx.from = EthereumAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B")!
27+
try await resolver.resolveAll(for: &tx)
28+
print(tx.gasLimit)
29+
print(tx.nonce)
30+
XCTAssertGreaterThan(tx.gasLimit, 0)
31+
XCTAssertGreaterThan(tx.maxFeePerGas ?? 0, 0)
32+
XCTAssertGreaterThan(tx.maxPriorityFeePerGas ?? 0, 0)
33+
XCTAssertGreaterThan(tx.nonce, 0)
34+
}
35+
36+
func testResolveAllForLegacyTransaction() async throws {
37+
let web3 = await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken)
38+
let resolver = PolicyResolver(provider: web3.provider)
39+
var tx = CodableTransaction(
40+
type: .legacy,
41+
to: EthereumAddress("0xb47292B7bBedA4447564B8336E4eD1f93735e7C7")!,
42+
chainID: web3.provider.network!.chainID,
43+
value: Utilities.parseToBigUInt("0.1", units: .eth)!,
44+
gasLimit: 21_000
45+
)
46+
// Vitalik's address
47+
tx.from = EthereumAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B")!
48+
try await resolver.resolveAll(for: &tx)
49+
print(tx.gasLimit)
50+
print(tx.nonce)
51+
XCTAssertGreaterThan(tx.gasLimit, 0)
52+
XCTAssertGreaterThan(tx.gasPrice ?? 0, 0)
53+
XCTAssertGreaterThan(tx.nonce, 0)
54+
}
55+
}

0 commit comments

Comments
 (0)