Skip to content

Commit

Permalink
Merge pull request #319 from argentlabs/feature/zksync-era-support
Browse files Browse the repository at this point in the history
ZKSync Era support
  • Loading branch information
DarthMike authored Mar 14, 2023
2 parents 9d65867 + 1f5dfc8 commit fe2be2c
Show file tree
Hide file tree
Showing 24 changed files with 683 additions and 84 deletions.
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

0 comments on commit fe2be2c

Please sign in to comment.