Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZKSync Era support #319

Merged
merged 1 commit into from
Mar 14, 2023
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
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"repositoryURL": "https://github.com/GigaBitcoin/secp256k1.swift.git",
"state": {
"branch": null,
"revision": "39dd39248e769ea88253a0ce300399b402a64529",
"version": "0.9.2"
"revision": "48fb20fce4ca3aad89180448a127d5bc16f0e44c",
"version": "0.10.0"
}
},
{
Expand Down
16 changes: 13 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ let package = Package(
.watchOS(.v7)
],
products: [
.library(name: "web3.swift", targets: ["web3"])
.library(name: "web3.swift", targets: ["web3"]),
.library(name: "web3-zksync.swift", targets: ["web3-zksync"])
],
dependencies: [
.package(name: "BigInt", url: "https://github.com/attaswift/BigInt", from: "5.0.0"),
Expand All @@ -32,7 +33,16 @@ let package = Package(
.product(name: "WebSocketKit", package: "websocket-kit"),
.product(name: "Logging", package: "swift-log")
],
path: "web3swift/src"
path: "web3swift/src",
exclude: ["ZKSync"]
),
.target(
name: "web3-zksync",
dependencies:
[
.target(name: "web3")
],
path: "web3swift/src/ZKSync"
),
.target(
name: "keccaktiny",
Expand All @@ -53,7 +63,7 @@ let package = Package(
),
.testTarget(
name: "web3swiftTests",
dependencies: ["web3"],
dependencies: ["web3", "web3-zksync"],
path: "web3sTests",
resources: [
.copy("Resources/rlptests.json"),
Expand Down
17 changes: 5 additions & 12 deletions web3sTests/Account/EthereumAccount+SignTransactionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,11 @@ class EthereumAccount_SignTransactionTests: XCTestCase {
let gasLimit = BigUInt(hex: "0x5208")!
let to = EthereumAddress("0x3535353535353535353535353535353535353535")
let value = BigUInt(hex: "0x0")!
let v = Int(hex: "0x25")!
let r = Data(hex: "0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d")!
let s = Data(hex: "0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d")!

var chainId = v
if chainId >= 37 {
chainId = (chainId - 35) / 2
}

let tx = EthereumTransaction(from: nil, to: to, value: value, data: nil, nonce: nonce, gasPrice: gasPrice, gasLimit: gasLimit, chainId: chainId)
let signed = SignedTransaction(transaction: tx, v: v, r: r, s: s)

let signature = "0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d25".web3.hexData!

let tx = EthereumTransaction(from: nil, to: to, value: value, data: nil, nonce: nonce, gasPrice: gasPrice, gasLimit: gasLimit, chainId: 37)
let signed = SignedTransaction(transaction: tx, signature: signature)

let raw = signed.raw!.web3.hexString
let hash = signed.hash!.web3.hexString

Expand Down
12 changes: 12 additions & 0 deletions web3sTests/TestConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,16 @@ struct TestConfig {
static let erc165Contract = "0xA2618a1c426a1684E00cA85b5C736164AC391d35"

static let webSocketConfig = WebSocketConfiguration(maxFrameSize: 1_000_000)

enum ZKSync {
static let chainId = 280
static let clientURL = URL(string: "https://zksync2-testnet.zksync.dev")!
}
}


@discardableResult public func with<Root>(_ root: Root, _ block: (inout Root) throws -> Void) rethrows -> Root {
var copy = root
try block(&copy)
return copy
}
60 changes: 60 additions & 0 deletions web3sTests/ZKSync/EthereumClient+ZKSyncTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// web3.swift
// Copyright © 2022 Argent Labs Limited. All rights reserved.
//

import Foundation
@testable import web3_zksync
@testable import web3
import XCTest
import BigInt


final class EthereumClientZKSyncTests: XCTestCase {
let eoaAccount = try! EthereumAccount(keyStorage: TestEthereumKeyStorage(privateKey: TestConfig.privateKey))
let client = ZKSyncClient(url: TestConfig.ZKSync.clientURL)
var eoaEthTransfer = ZKSyncTransaction(
from: .init(TestConfig.publicKey),
to: .init("0x64d0eA4FC60f27E74f1a70Aa6f39D403bBe56793"),
value: 100,
data: Data(),
gasLimit: 300000
)

func test_GivenEOAAccount_WhenSendETH_ThenSendsCorrectly() async {
do {
let gasPrice = try await client.eth_gasPrice()
eoaEthTransfer.gasPrice = gasPrice
let txHash = try await client.eth_sendRawZKSyncTransaction(eoaEthTransfer, withAccount: eoaAccount)
XCTAssertNotNil(txHash, "No tx hash, ensure key is valid in TestConfig.swift")
} catch {
XCTFail("Expected tx but failed \(error).")
}
}

// TODO: Integrate paymaster
// func test_GivenEOAAccount_WhenSendETH_AndFeeIsInUSDC_ThenSendsCorrectly() async {
// do {
// let txHash = try await client.eth_sendRawZKSyncTransaction(with(eoaEthTransfer) {
// $0.feeToken = EthereumAddress("0x54a14D7559BAF2C8e8Fa504E019d32479739018c")
// }, withAccount: eoaAccount)
// XCTAssertNotNil(txHash, "No tx hash, ensure key is valid in TestConfig.swift")
// } catch {
// XCTFail("Expected tx but failed \(error).")
// }
// }

func test_GivenEOATransaction_gasEstimationCorrect() async {
do {
let estimate = try await client.estimateGas(
with(eoaEthTransfer) {
$0.gasPrice = nil
$0.gasLimit = nil
}
)
XCTAssertGreaterThan(estimate, 1000)
} catch {
XCTFail("Expected value but failed \(error).")
}
}
}
40 changes: 40 additions & 0 deletions web3sTests/ZKSync/ZKSyncTransactionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// web3.swift
// Copyright © 2022 Argent Labs Limited. All rights reserved.
//

import XCTest
@testable import web3_zksync
@testable import web3
import BigInt

final class ZKSyncTransactionTests: XCTestCase {

let eoaAccount = try! EthereumAccount(keyStorage: TestEthereumKeyStorage(privateKey: TestConfig.privateKey))

let eoaTransfer = ZKSyncTransaction(
from: .init(TestConfig.publicKey),
to: .init("0x64d0eA4FC60f27E74f1a70Aa6f39D403bBe56793"),
value: BigUInt(hex: "0x5af3107a4000")!,
data: Data(),
chainId: TestConfig.ZKSync.chainId,
nonce: 4,
gasPrice: BigUInt(hex: "0x05f5e100")!,
gasLimit: BigUInt(hex: "0x080a22")!
)

func test_GivenEOATransfer_EncodesCorrectly() {
let signature = "0x55943b2228183717fd3be583bde0f6ec168247ea8d304eb13b3e7e76ebf6bf2c3c77734e163711c5963ac25a15f95d9ac63b82c2c427fd4eb011c5e3a22f89221b".web3.hexData!
let signed = ZKSyncSignedTransaction(
transaction: eoaTransfer, signature: .init(raw: signature)
)
XCTAssertEqual(signed.raw?.web3.hexString, "0x71f891048405f5e1008405f5e10083080a229464d0ea4fc60f27e74f1a70aa6f39d403bbe56793865af3107a400080820118808082011894e78e5ecb061fe3dd1672ddda7b5116213b23b99a82c350c0b84155943b2228183717fd3be583bde0f6ec168247ea8d304eb13b3e7e76ebf6bf2c3c77734e163711c5963ac25a15f95d9ac63b82c2c427fd4eb011c5e3a22f89221bc0")
}

func test_GivenEOATransfer_WhenSigningWithEOAAccount_ThenSignsAndEncodesCorrectly() {
let signed = try? eoaAccount.sign(zkTransaction: eoaTransfer)

XCTAssertEqual(signed?.raw?.web3.hexString,
"0x71f891048405f5e1008405f5e10083080a229464d0ea4fc60f27e74f1a70aa6f39d403bbe56793865af3107a400080820118808082011894e78e5ecb061fe3dd1672ddda7b5116213b23b99a82c350c0b841c956ba7bfdf54a6d3f3b21c51465ad37df22b6258835b6e162259d6d3eec02ae11f9d17c3aafd47df49bd77e33befed87bbaff44e4c497228bfa8bcc9fa64bc31bc0")
}
}
12 changes: 2 additions & 10 deletions web3swift/src/Account/EthereumAccount+SignTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ enum EthereumSignerError: Error {
case unknownError
}

public extension EthereumAccount {
public extension EthereumAccountProtocol {
func signRaw(_ transaction: EthereumTransaction) throws -> Data {
let signed: SignedTransaction = try sign(transaction: transaction)
guard let raw = signed.raw else {
Expand All @@ -28,14 +28,6 @@ public extension EthereumAccount {
throw EthereumSignerError.unknownError
}

let r = signature.subdata(in: 0 ..< 32)
let s = signature.subdata(in: 32 ..< 64)

var v = Int(signature[64])
if v < 37 {
v += (transaction.chainId ?? -1) * 2 + 35
}

return SignedTransaction(transaction: transaction, v: v, r: r, s: s)
return SignedTransaction(transaction: transaction, signature: signature)
}
}
2 changes: 2 additions & 0 deletions web3swift/src/Account/EthereumAccount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public protocol EthereumAccountProtocol {
func sign(hex: String) throws -> Data
func sign(message: Data) throws -> Data
func sign(message: String) throws -> Data
func signMessage(message: Data) throws -> String
func signMessage(message: TypedData) throws -> String
func sign(transaction: EthereumTransaction) throws -> SignedTransaction
}

Expand Down
58 changes: 58 additions & 0 deletions web3swift/src/Account/Signature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// web3.swift
// Copyright © 2022 Argent Labs Limited. All rights reserved.
//

import Foundation

public struct Signature: Equatable {
public let r: Data
public let s: Data
public let v: Int
public let recoveryParam: Int
let raw: Data

public var flattened: Data {
raw
}

public init(
r: Data,
s: Data,
v: Int,
recoveryParam: Int
) {
self.r = r
self.s = s
self.v = v
self.recoveryParam = recoveryParam
self.raw = r + s + Data([UInt8(v)])
}

public init(
raw: Data
) {
self.raw = raw
(self.r, self.s, self.v) = raw.extractRSV()
self.recoveryParam = 1 - (self.v % 2)
}

public static let zero: Signature = .init(raw: Data(repeating: 0, count: 65))
}

extension Data {
func extractRSV() -> (Data, Data, Int) {
guard count >= 65 else {
fatalError("Invalid usage: Need a correctly sized signature")
}

let r = subdata(in: 0 ..< 32)
let s = subdata(in: 32 ..< 64)
var v = Int(self[64])
if v < 27 { // recid == v
v += 27
}

return (r.web3.strippingZeroesFromBytes, s.web3.strippingZeroesFromBytes, v)
}
}
19 changes: 3 additions & 16 deletions web3swift/src/Client/BaseEthereumClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import Foundation
import FoundationNetworking
#endif

public class BaseEthereumClient: EthereumClientProtocol {
open class BaseEthereumClient: EthereumClientProtocol {
public let url: URL

let networkProvider: NetworkProviderProtocol
public let networkProvider: NetworkProviderProtocol

private let logger: Logger

public var network: EthereumNetwork?

init(
public init(
networkProvider: NetworkProviderProtocol,
url: URL,
logger: Logger? = nil,
Expand Down Expand Up @@ -205,19 +205,6 @@ public class BaseEthereumClient: EthereumClientProtocol {
}
}

public func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock) async throws -> Int {
do {
let data = try await networkProvider.send(method: "eth_getTransactionCount", params: [address.asString(), block.stringValue], receive: String.self)
if let resString = data as? String, let count = Int(hex: resString) {
return count
} else {
throw EthereumClientError.unexpectedReturnValue
}
} catch {
throw failureHandler(error)
}
}

public func eth_getTransaction(byHash txHash: String) async throws -> EthereumTransaction {
do {
let data = try await networkProvider.send(method: "eth_getTransactionByHash", params: [txHash], receive: EthereumTransaction.self)
Expand Down
6 changes: 3 additions & 3 deletions web3swift/src/Client/Models/EthereumNetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public enum EthereumNetwork: Equatable, Decodable {
case goerli
case sepolia
case custom(String)
static func fromString(_ networkId: String) -> EthereumNetwork {
public static func fromString(_ networkId: String) -> EthereumNetwork {
switch networkId {
case "1":
return .mainnet
Expand All @@ -26,7 +26,7 @@ public enum EthereumNetwork: Equatable, Decodable {
}
}

var stringValue: String {
public var stringValue: String {
switch self {
case .mainnet:
return "1"
Expand All @@ -41,7 +41,7 @@ public enum EthereumNetwork: Equatable, Decodable {
}
}

var intValue: Int {
public var intValue: Int {
switch self {
case .mainnet:
return 1
Expand Down
28 changes: 21 additions & 7 deletions web3swift/src/Client/Models/EthereumTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,29 @@ public struct EthereumTransaction: EthereumTransactionProtocol, Equatable, Codab

public struct SignedTransaction {
public let transaction: EthereumTransaction
let v: Int
let r: Data
let s: Data
public let signature: Signature

public init(transaction: EthereumTransaction, v: Int, r: Data, s: Data) {
public init(
transaction: EthereumTransaction,
signature raw: Data
) {
self.transaction = transaction
self.v = v
self.r = r.web3.strippingZeroesFromBytes
self.s = s.web3.strippingZeroesFromBytes
self.signature = .init(raw: raw)
}

var r: Data {
signature.r
}

var s: Data {
signature.s
}

var v: Int {
guard signature.v < 37 else {
return signature.v
}
return signature.v + (transaction.chainId ?? -1) * 2 + 8
}

public var raw: Data? {
Expand Down
Loading