Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Documentation/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,8 @@ let result = try! transaction.call()
##### Other Transaction Types

By default a `legacy` transaction will be created which is compatible across all chains, regardless of which fork.
To create one of the new transaction types introduced with the `london` fork you will need to set some additonal parameters
in the `TransactionOptions` object. Note you should only try to send one of tehse new types of transactions if you are on a chain
To create one of the new transaction types introduced with the `london` fork you will need to set some additional parameters
in the `TransactionOptions` object. Note you should only try to send one of these new types of transactions if you are on a chain
that supports them.

To send an EIP-2930 style transacton with an access list you need to set the transaction type, and the access list,
Expand All @@ -385,10 +385,10 @@ To send an EIP-1559 style transaction you set the transaction type, and the new
(you may also send an AccessList with an EIP-1559 transaction) When sending an EIP-1559 transaction, the older `gasPrice` parameter is ignored.
```swift
options.type = .eip1559
options.maxFeePerGas = .manual(...) // the maximum price per unit of gas, inclusive of baseFee and tip
options.maxPriorityFeePerGas = .manual(...) // the tip to be paid to the miner, per unit of gas
options.maxFeePerGas = .automatic // the maximum price per unit of gas, inclusive of baseFee and tip
options.maxPriorityFeePerGas = .automatic // the 'tip' to be paid to the miner, per unit of gas
```
Note there is a new `Oracle` object available that can be used to assist with estimating the new gas fees
Note: There is a new `Oracle` object available that can be used to assist with estimating the new gas fees if you wish to set them manually.

### Chain state

Expand Down
107 changes: 86 additions & 21 deletions Sources/web3swift/Web3/Web3+MutatingTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public class WriteTransaction: ReadTransaction {
optionsForGasEstimation.value = mergedOptions.value
optionsForGasEstimation.gasLimit = mergedOptions.gasLimit
optionsForGasEstimation.callOnBlock = mergedOptions.callOnBlock
optionsForGasEstimation.type = mergedOptions.type
optionsForGasEstimation.accessList = mergedOptions.accessList

// assemble promise for gasLimit
var gasEstimatePromise: Promise<BigUInt>? = nil
Expand Down Expand Up @@ -102,20 +104,75 @@ public class WriteTransaction: ReadTransaction {
getNoncePromise = Promise<BigUInt>.value(nonce)
}

// assemble promise for gasPrice
var gasPricePromise: Promise<BigUInt>? = nil
guard let gasPricePolicy = mergedOptions.gasPrice else {
seal.reject(Web3Error.inputError(desc: "No gasPrice policy provided"))
return
}
switch gasPricePolicy {
case .automatic, .withMargin:
gasPricePromise = self.web3.eth.getGasPricePromise()
case .manual(let gasPrice):
gasPricePromise = Promise<BigUInt>.value(gasPrice)
// determine gas costing, taking transaction type into account
let oracle = Web3.Oracle(self.web3, percentiles: [75])
let finalGasPrice: BigUInt? // legacy gas model
let finalGasFee: BigUInt? // EIP-1559 gas model
let finalTipFee: BigUInt? // EIP-1559 gas model

if mergedOptions.type == nil || mergedOptions.type != .eip1559 { // legacy Gas
// set unused gas parameters to nil
finalGasFee = nil
finalTipFee = nil

// determine the (legacy) gas price
guard let gasPricePolicy = mergedOptions.gasPrice else {
seal.reject(Web3Error.inputError(desc: "No gasPrice policy provided"))
return
}
switch gasPricePolicy {
case .automatic, .withMargin:
let percentiles = oracle.gasPriceLegacyPercentiles
guard !percentiles.isEmpty else {
throw Web3Error.processingError(desc: "Failed to fetch gas price")
}
finalGasPrice = percentiles[0]
case .manual(let gasPrice):
finalGasPrice = gasPrice
}
} else { // else new gas fees (EIP-1559)
// set unused gas parametes to nil
finalGasPrice = nil

// determine the tip
guard let maxPriorityFeePerGasPolicy = mergedOptions.maxPriorityFeePerGas else {
seal.reject(Web3Error.inputError(desc: "No maxPriorityFeePerGas policy provided"))
return
}
switch maxPriorityFeePerGasPolicy {
case .automatic:
let percentiles = oracle.tipFeePercentiles
guard !percentiles.isEmpty else {
throw Web3Error.processingError(desc: "Failed to fetch maxPriorityFeePerGas data")
}
finalTipFee = percentiles[0]
case .manual(let maxPriorityFeePerGas):
finalTipFee = maxPriorityFeePerGas
}

// determine the baseFee, and calculate the maxFeePerGas
guard let maxFeePerGasPolicy = mergedOptions.maxFeePerGas else {
seal.reject(Web3Error.inputError(desc: "No maxFeePerGas policy provided"))
return
}
switch maxFeePerGasPolicy {
case .automatic:
let percentiles = oracle.baseFeePercentiles
guard !percentiles.isEmpty else {
throw Web3Error.processingError(desc: "Failed to fetch baseFee data")
}
guard let tipFee = finalTipFee else {
throw Web3Error.processingError(desc: "Missing tip value")
}
finalGasFee = percentiles[0] + tipFee
case .manual(let maxFeePerGas):
finalGasFee = maxFeePerGas
}
}
var promisesToFulfill: [Promise<BigUInt>] = [getNoncePromise!, gasPricePromise!, gasEstimatePromise!]
when(resolved: getNoncePromise!, gasEstimatePromise!, gasPricePromise!).map(on: queue, { (results: [PromiseResult<BigUInt>]) throws -> EthereumTransaction in

// wait for promises to resolve
var promisesToFulfill: [Promise<BigUInt>] = [getNoncePromise!, gasEstimatePromise!]
when(resolved: getNoncePromise!, gasEstimatePromise!).map(on: queue, { (results: [PromiseResult<BigUInt>]) throws -> EthereumTransaction in

promisesToFulfill.removeAll()
guard case .fulfilled(let nonce) = results[0] else {
Expand All @@ -124,17 +181,25 @@ public class WriteTransaction: ReadTransaction {
guard case .fulfilled(let gasEstimate) = results[1] else {
throw Web3Error.processingError(desc: "Failed to fetch gas estimate")
}
guard case .fulfilled(let gasPrice) = results[2] else {
throw Web3Error.processingError(desc: "Failed to fetch gas price")
}

let estimate = mergedOptions.resolveGasLimit(gasEstimate)
let finalGasPrice = mergedOptions.resolveGasPrice(gasPrice)

var finalOptions = TransactionOptions()
finalOptions.type = mergedOptions.type
finalOptions.nonce = .manual(nonce)
finalOptions.gasLimit = .manual(estimate)
finalOptions.gasPrice = .manual(finalGasPrice)
finalOptions.gasLimit = .manual(mergedOptions.resolveGasLimit(gasEstimate))
finalOptions.accessList = mergedOptions.accessList

// set the finalized gas parameters
if let gasPrice = finalGasPrice {
finalOptions.gasPrice = .manual(mergedOptions.resolveGasPrice(gasPrice))
}

if let tipFee = finalTipFee {
finalOptions.maxPriorityFeePerGas = .manual(mergedOptions.resolveMaxPriorityFeePerGas(tipFee))
}

if let gasFee = finalGasFee {
finalOptions.maxFeePerGas = .manual(mergedOptions.resolveMaxFeePerGas(gasFee))
}

assembledTransaction.applyOptions(finalOptions)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
import XCTest
@testable import web3swift

class ABIEncoderSoliditySha3Test: XCTestCase {
class ABIEncoderSoliditySha3Test: LocalTestCase {

func test_soliditySha3() throws {
var hex = try ABIEncoder.soliditySha3(true).toHexString().addHexPrefix()
Expand Down
2 changes: 1 addition & 1 deletion Tests/web3swiftTests/localTests/DataConversionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import XCTest

// Some base58 test vectors pulled from: https://tools.ietf.org/id/draft-msporny-base58-01.html
// note that one of the return values is incorrect in the reference above, it is corrected here
class DataConversionTests: XCTestCase {
class DataConversionTests: LocalTestCase {
// test an empty input for the base58 decoder & decoder
func testBase58() throws {
let vector = ""
Expand Down
2 changes: 1 addition & 1 deletion Tests/web3swiftTests/localTests/EIP1559BlockTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import BigInt
@testable
import web3swift

class EIP1559BlockTests: XCTestCase {
class EIP1559BlockTests: LocalTestCase {
let uselessBlockPart = (
number: BigUInt(12_965_000),
hash: Data(fromHex: "0xef95f2f1ed3ca60b048b4bf67cde2195961e0bba6f70bcbea9a2c4e133e34b46")!, // "hash":
Expand Down
63 changes: 31 additions & 32 deletions Tests/web3swiftTests/localTests/EIP712Tests.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import XCTest
@testable import web3swift

class EIP712Tests: XCTestCase {
class EIP712Tests: LocalTestCase {
func testWithoutChainId() throws {

let to = EthereumAddress("0x3F06bAAdA68bB997daB03d91DBD0B73e196c5A4d")!

let value = EIP712.UInt256(0)

let amountLinen = EIP712.UInt256("0001000000000000000")//

let function = ABI.Element.Function(
name: "approveAndMint",
inputs: [
Expand All @@ -18,22 +18,22 @@ class EIP712Tests: XCTestCase {
outputs: [.init(name: "", type: .bool)],
constant: false,
payable: false)

let object = ABI.Element.function(function)

let safeTxData = object.encodeParameters([
EthereumAddress("0x41B5844f4680a8C38fBb695b7F9CFd1F64474a72")! as AnyObject,
amountLinen as AnyObject
])!

let operation: EIP712.UInt8 = 1

let safeTxGas = EIP712.UInt256(250000)

let baseGas = EIP712.UInt256(60000)

let gasPrice = EIP712.UInt256("20000000000")

let gasToken = EthereumAddress("0x0000000000000000000000000000000000000000")!
print(1)
let refundReceiver = EthereumAddress("0x7c07D32e18D6495eFDC487A32F8D20daFBa53A5e")!
Expand Down Expand Up @@ -76,13 +76,13 @@ class EIP712Tests: XCTestCase {
}

func testWithChainId() throws {

let to = EthereumAddress("0x3F06bAAdA68bB997daB03d91DBD0B73e196c5A4d")!

let value = EIP712.UInt256(0)

let amount = EIP712.UInt256("0001000000000000000")

let function = ABI.Element.Function(
name: "approveAndMint",
inputs: [
Expand All @@ -91,28 +91,28 @@ class EIP712Tests: XCTestCase {
outputs: [.init(name: "", type: .bool)],
constant: false,
payable: false)

let object = ABI.Element.function(function)

let safeTxData = object.encodeParameters([
EthereumAddress("0x41B5844f4680a8C38fBb695b7F9CFd1F64474a72")! as AnyObject,
amount as AnyObject
])!

let operation: EIP712.UInt8 = 1

let safeTxGas = EIP712.UInt256(250000)

let baseGas = EIP712.UInt256(60000)

let gasPrice = EIP712.UInt256("20000000000")

let gasToken = EthereumAddress("0x0000000000000000000000000000000000000000")!

let refundReceiver = EthereumAddress("0x7c07D32e18D6495eFDC487A32F8D20daFBa53A5e")!

let nonce: EIP712.UInt256 = .init(0)

let safeTX = SafeTx(
to: to,
value: value,
Expand All @@ -124,25 +124,24 @@ class EIP712Tests: XCTestCase {
gasToken: gasToken,
refundReceiver: refundReceiver,
nonce: nonce)

let mnemonic = "normal dune pole key case cradle unfold require tornado mercy hospital buyer"
let keystore = try! BIP32Keystore(mnemonics: mnemonic, password: "", mnemonicsPassword: "")!

let verifyingContract = EthereumAddress("0x76106814dc6150b0fe510fbda4d2d877ac221270")!

let account = keystore.addresses?[0]
let password = ""
let chainId: EIP712.UInt256? = EIP712.UInt256(42)

let signature = try Web3Signer.signEIP712(
safeTx: safeTX,
keystore: keystore,
verifyingContract: verifyingContract,
account: account!,
password: password,
chainId: chainId)

XCTAssertEqual(signature.toHexString(), "f1f423cb23efad5035d4fb95c19cfcd46d4091f2bd924680b88c4f9edfa1fb3a4ce5fc5d169f354e3b464f45a425ed3f6203af06afbacdc5c8224a300ce9e6b21b")
}
}

44 changes: 44 additions & 0 deletions Tests/web3swiftTests/localTests/LocalTestCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Foundation
import XCTest
import BigInt

import web3swift

// SuperClass that all local tests should be derived from
// while this class does show up in the navigator, it has no associated tests
class LocalTestCase: XCTestCase {
static let url = URL.init(string: "http://127.0.0.1:8545")!
let ganache = try! Web3.new(LocalTestCase.url)
static var isSetUp = false

override class func setUp() {
super.setUp()

// check to see if we need to run the one-time setup
if isSetUp { return }
isSetUp = true

let web3 = try! Web3.new(LocalTestCase.url)

let block = try! web3.eth.getBlockNumber()
if block >= 25 { return }

print("\n ****** Preloading Ganache (\(25 - block) blocks) *****\n")

let allAddresses = try! web3.eth.getAccounts()
let sendToAddress = allAddresses[0]
let contract = web3.contract(Web3.Utils.coldWalletABI, at: sendToAddress, abiVersion: 2)
let value = Web3.Utils.parseToBigUInt("1.0", units: .eth)

let from = allAddresses[0]
let writeTX = contract!.write("fallback")!
writeTX.transactionOptions.from = from
writeTX.transactionOptions.value = value
writeTX.transactionOptions.gasLimit = .manual(78423)
writeTX.transactionOptions.gasPrice = .manual(20000000000)

for _ in block..<25 {
let _ = try! writeTX.sendPromise(password: "").wait()
}
}
}
3 changes: 2 additions & 1 deletion Tests/web3swiftTests/localTests/LocalTests.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"RemoteParsingTests",
"RemoteTests",
"ST20AndSecurityTokenTests",
"WebsocketTests"
"WebsocketTests",
"LocalTestCase"
],
"target" : {
"containerPath" : "container:web3swift.xcodeproj",
Expand Down
Loading