Skip to content

Commit

Permalink
feat: provide support for Bridgehub
Browse files Browse the repository at this point in the history
  • Loading branch information
petarTxFusion committed Jun 6, 2024
1 parent 53f9b28 commit 21cb01f
Show file tree
Hide file tree
Showing 14 changed files with 1,581 additions and 313 deletions.
4 changes: 2 additions & 2 deletions Sources/ZkSync2/Accounts/Adapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public protocol AdapterL1 {
func getL1BridgeContracts() async throws -> BridgeAddresses
// BalanceL1 returns the balance of the specified token on L1 that can be
// either ETH or any ERC20 token.
func balanceL1() async -> BigUInt
func balanceL1(token: String, blockNumber: BlockNumber) async -> BigUInt
// BaseCost returns base cost for L2 transaction.
func baseCost(_ gasLimit: BigUInt, gasPerPubdataByte: BigUInt, gasPrice: BigUInt?) async throws -> [String: Any]
// Deposit transfers the specified token from the associated account on the L1 network
Expand All @@ -42,7 +42,7 @@ public protocol AdapterL1 {
// To check the amount of approved tokens for a specific bridge, use the AdapterL1.AllowanceL1 method.
func deposit(transaction: DepositTransaction) async throws -> TransactionSendingResult
func estimateGasDeposit(transaction: DepositTransaction) async throws -> BigUInt
func getDepositTransaction(transaction: DepositTransaction) async throws -> DepositTransaction
func getDepositTransaction(transaction: DepositTransaction) async throws -> Any
// ClaimFailedDeposit withdraws funds from the initiated deposit, which failed when finalizing on L2.
// If the deposit L2 transaction has failed, it sends an L1 transaction calling ClaimFailedDeposit method
// of the L1 bridge, which results in returning L1 tokens back to the depositor, otherwise throws the error.
Expand Down
1 change: 0 additions & 1 deletion Sources/ZkSync2/Accounts/EthSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ public class BaseSigner: ETHSigner {
useExtraEntropy: Bool = false) throws -> Data? {
var privateKey = try keystore.UNSAFE_getPrivateKeyData(password: password, account: account)
defer { Data.zero(&privateKey) }
print(privateKey.toHexString())
#if DEBUG
print("Message hash: \(message.sha3(.keccak256).toHexString().addHexPrefix())")
#endif
Expand Down
824 changes: 580 additions & 244 deletions Sources/ZkSync2/Accounts/WalletL1.swift

Large diffs are not rendered by default.

53 changes: 51 additions & 2 deletions Sources/ZkSync2/Clients/BaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class BaseClient: ZkSyncClient {

let transport: Transport
var mainContractAddress: String?
var bridgehubContract: String?
var baseToken: String?

public init(_ providerURL: URL) {
self.web3 = Web3(provider: Web3HttpProvider(url: providerURL, network: .Mainnet))
Expand Down Expand Up @@ -92,6 +94,30 @@ public class BaseClient: ZkSyncClient {
return (mainContractAddress)!
}

public func getBridgehubContractAddress() async throws -> String {
if bridgehubContract == nil {
bridgehubContract = try await transport.send(method: "zks_getBridgehubContract", parameters: [])
}
return (bridgehubContract)!
}

public func getBaseTokenContractAddress() async throws -> String {
if baseToken == nil {
baseToken = try await transport.send(method: "zks_getBaseTokenL1Address", parameters: [])
}
return (baseToken)!
}

public func isEthBasedChain() async throws -> Bool {
return ZkSyncAddresses.isAddressEq(a: try await self.getBaseTokenContractAddress(), b: ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS)
}

public func isBaseToken(tokenAddress: String) async throws -> Bool {
let baseToken = try await self.getBaseTokenContractAddress()
return ZkSyncAddresses.isAddressEq(a: baseToken, b: tokenAddress) ||
ZkSyncAddresses.isAddressEq(a: tokenAddress, b: ZkSyncAddresses.L2_BASE_TOKEN_ADDRESS)
}

public func tokenPrice(_ tokenAddress: String) async throws -> Decimal {
let parameters = [
JRPC.Parameter(type: .string, value: tokenAddress),
Expand Down Expand Up @@ -247,6 +273,17 @@ public class BaseClient: ZkSyncClient {
}

public func getWithdrawTx(_ amount: BigUInt, from: String, to: String?, token: String? = nil, options: TransactionOption? = nil, paymasterParams: PaymasterParams? = nil) async throws -> CodableTransaction{
let isEthChain = try! await isEthBasedChain()
var token = token != nil && ZkSyncAddresses.isAddressEq(a: token!, b: ZkSyncAddresses.LEGACY_ETH_ADDRESS) ? ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS : token
let isBaseToken = token != nil ? try! await isBaseToken(tokenAddress: token!) : true

if token != nil &&
ZkSyncAddresses.isAddressEq(a: token!, b: ZkSyncAddresses.LEGACY_ETH_ADDRESS) && !isEthChain {
token = try! await l2TokenAddress(address: ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS)
} else if token == nil || (isBaseToken){
token = ZkSyncAddresses.L2_BASE_TOKEN_ADDRESS
}

let to = to ?? from
var options = options
let nonce: BigUInt
Expand All @@ -256,7 +293,7 @@ public class BaseClient: ZkSyncClient {
nonce = try await web3.eth.getTransactionCount(for: EthereumAddress(from)!)
}
let prepared = CodableTransaction.createEtherTransaction(from: EthereumAddress(from)!, to: EthereumAddress(to)!, value: amount, nonce: nonce, paymasterParams: paymasterParams)
if token == ZkSyncAddresses.EthAddress || token == nil {
if isBaseToken {
if options?.value == nil {
options?.value = amount
}
Expand All @@ -281,10 +318,22 @@ public class BaseClient: ZkSyncClient {
}

public func getTransferTx(_ to: String, amount: BigUInt, from:String, token: String? = nil, options: TransactionOption? = nil, paymasterParams: PaymasterParams? = nil) async -> CodableTransaction {
let isEthChain = try! await isEthBasedChain()
var token = token
let isBaseToken = token != nil ? try! await isBaseToken(tokenAddress: token!) : true

if token != nil &&
ZkSyncAddresses.isAddressEq(a: token!, b: ZkSyncAddresses.LEGACY_ETH_ADDRESS) &&
!isEthChain {
token = try! await l2TokenAddress(address: token!)
} else if token == nil || (isBaseToken){
token = ZkSyncAddresses.L2_BASE_TOKEN_ADDRESS
}

let nonce = try! await web3.eth.getTransactionCount(for: EthereumAddress(from)!)
let prepared = CodableTransaction.createEtherTransaction(from: EthereumAddress(from)!, to: EthereumAddress(to)!, value: amount, nonce: nonce, paymasterParams: paymasterParams)

if token == nil || token == ZkSyncAddresses.EthAddress{
if isBaseToken{
return prepared
}
let tokenContract = web3.contract(Web3Utils.IERC20, at: EthereumAddress(token!)!, transaction: prepared)
Expand Down
8 changes: 8 additions & 0 deletions Sources/ZkSync2/Clients/ZkSyncClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ public protocol ZkSyncClient {

// MainContractAddress returns the address of the zkSync Era contract.
func mainContract() async throws -> String
// Returns the Bridgehub smart contract address.
func getBridgehubContractAddress() async throws -> String
// Returns the L1 base token address.
func getBaseTokenContractAddress() async throws -> String
// Returns whether the chain is ETH-based.
func isEthBasedChain() async throws -> Bool
// Returns whether the `token` is the base token.
func isBaseToken(tokenAddress: String) async throws -> Bool
// TestnetPaymaster returns the testnet paymaster address if available, or nil.
func getTestnetPaymaster() async throws -> String
// BridgeContracts returns the addresses of the default zkSync Era bridge
Expand Down
32 changes: 24 additions & 8 deletions Sources/ZkSync2/Extensions/Web3Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,15 @@ public extension Web3.Utils {
static let L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"

static func applyL1ToL2Alias(address: String) -> String{
return ((BigUInt(address.stripHexPrefix(), radix: 16)! + BigUInt(L1_TO_L2_ALIAS_OFFSET.stripHexPrefix(), radix: 16)!) % ADDRESS_MODULO).toHexString().addHexPrefix()
return padTo42Characters(((BigUInt(address.stripHexPrefix(), radix: 16)! + BigUInt(L1_TO_L2_ALIAS_OFFSET.stripHexPrefix(), radix: 16)!) % ADDRESS_MODULO).toHexString().addHexPrefix())
}

static func padTo42Characters(_ address: String) -> String {
var paddedAddress = address.stripHexPrefix()
while paddedAddress.count < 40 {
paddedAddress = "0" + paddedAddress
}
return "0x" + paddedAddress
}

static let L1_FEE_ESTIMATION_COEF_NUMERATOR = BigUInt(12)
Expand All @@ -112,19 +120,26 @@ public extension Web3.Utils {
}

static func getERC20DefaultBridgeData(l1TokenAddress: String, provider: Web3) async -> Data?{
var l1TokenAddress = l1TokenAddress
if ZkSyncAddresses.isAddressEq(a: l1TokenAddress, b: ZkSyncAddresses.LEGACY_ETH_ADDRESS){
l1TokenAddress = ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS
}
let token = provider.contract(
Web3.Utils.IERC20,
at: EthereumAddress(l1TokenAddress)
)!

let name = try! await token.createWriteOperation("name")?.callContractMethod()["0"] as? String
let symbol = try! await token.createWriteOperation("symbol")?.callContractMethod()["0"] as? String
let decimals = try! await token.createWriteOperation("decimals")?.callContractMethod()["0"] as? BigUInt
let name = ZkSyncAddresses.isAddressEq(a: l1TokenAddress, b: ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS) ?
"Ether" : try! await token.createWriteOperation("name")?.callContractMethod()["0"] as? String
let symbol = ZkSyncAddresses.isAddressEq(a: l1TokenAddress, b: ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS) ?
"ETH" : try! await token.createWriteOperation("symbol")?.callContractMethod()["0"] as? String
let decimals = ZkSyncAddresses.isAddressEq(a: l1TokenAddress, b: ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS) ?
BigUInt(18) : try! await token.createWriteOperation("decimals")?.callContractMethod()["0"] as? BigUInt

let encodedName = ABIEncoder.encode(types: [ABI.Element.ParameterType.string], values: [name!])
let encodedSymbol = ABIEncoder.encode(types: [ABI.Element.ParameterType.string], values: [symbol!])
let encodedDecimals = ABIEncoder.encode(types: [ABI.Element.ParameterType.uint(bits: 256)], values: [decimals!])

return ABIEncoder.encode(types: [ABI.Element.ParameterType.dynamicBytes, ABI.Element.ParameterType.dynamicBytes, ABI.Element.ParameterType.dynamicBytes], values: [encodedName!, encodedSymbol!, encodedDecimals!])
}

Expand Down Expand Up @@ -160,7 +175,8 @@ public extension Web3.Utils {
gasPerPubdataByte: BigUInt? = BigUInt(800),
l2Value: BigUInt? = nil) async throws -> BigUInt{
let calldata = try Web3Utils.getERC20BridgeCalldata(provider: provider, l1TokenAddress: token, l1Sender: from, l2Receiver: to, amount: amount, bridgeData: bridgeData)
return try await provider.estimateL1ToL2Execute(l2BridgeAddress.address, from: Web3Utils.applyL1ToL2Alias(address: l1BridgeAddress.address), calldata: calldata, amount: BigUInt.zero, gasPerPubData: gasPerPubdataByte!)

return try await provider.estimateL1ToL2Execute(l2BridgeAddress.address, from: Web3Utils.applyL1ToL2Alias(address: l1BridgeAddress.address), calldata: calldata, amount: l2Value ?? BigUInt.zero, gasPerPubData: gasPerPubdataByte!)
}

static func getERC20BridgeCalldata(provider: ZkSyncClient ,l1TokenAddress: EthereumAddress, l1Sender: EthereumAddress, l2Receiver: EthereumAddress, amount: BigUInt, bridgeData: Data) throws -> Data {
Expand All @@ -169,12 +185,12 @@ public extension Web3.Utils {
}

static func estimateDefaultBridgeDepositL2Gas(providerL1: Web3, providerL2: ZkSyncClient, token: String, amount: BigUInt, to: String, from: String, gasPerPubDataByte: BigUInt = BigUInt(800)) async throws -> BigUInt{
if token == ZkSyncAddresses.EthAddress {
if try await providerL2.isBaseToken(tokenAddress: token) {
return try await providerL2.estimateL1ToL2Execute(to, from: from, calldata: Data(hex: "0x"), amount: amount, gasPerPubData: gasPerPubDataByte)
}
let bridgeAddresses = try await providerL2.bridgeContracts()
let bridgeData = await Web3.Utils.getERC20DefaultBridgeData(l1TokenAddress: token, provider: providerL1)

return try await Web3.Utils.estimateCustomBridgeDepositL2Gas(provider: providerL2, l1BridgeAddress: EthereumAddress(bridgeAddresses.l1Erc20DefaultBridge)!, l2BridgeAddress: EthereumAddress(bridgeAddresses.l2Erc20DefaultBridge)!, token: EthereumAddress(token)!, amount: amount, to: EthereumAddress(to)!, bridgeData: bridgeData!, from: EthereumAddress(from)!)
return try await Web3.Utils.estimateCustomBridgeDepositL2Gas(provider: providerL2, l1BridgeAddress: EthereumAddress(bridgeAddresses.l1SharedDefaultBridge)!, l2BridgeAddress: EthereumAddress(bridgeAddresses.l2SharedDefaultBridge)!, token: EthereumAddress(token)!, amount: amount, to: EthereumAddress(to)!, bridgeData: bridgeData!, from: EthereumAddress(from)!)
}
}
14 changes: 14 additions & 0 deletions Sources/ZkSync2/Types/AllowanceParams.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by Petar Kopestinskij on 25.5.24..
//

import Foundation
import BigInt

public struct AllowanceParams{
let token: String
let allowance: BigUInt
}
15 changes: 15 additions & 0 deletions Sources/ZkSync2/Types/GetDepositTransaction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// File.swift
//
//
//
import Web3Core
import BigInt
import Foundation

public struct GetDepositTransaction {

public var tx: CodableTransaction

public var mintValue: BigUInt
}
3 changes: 3 additions & 0 deletions Sources/ZkSync2/Types/TransactionTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public struct RequestExecuteTransaction {
var calldata: Data
var from: Address?
var l2Value: BigUInt?
var mintValue: BigUInt?
var l2GasLimit: BigUInt?
var operatorTip: BigUInt?
var gasPerPubdataByte: BigUInt?
Expand All @@ -25,8 +26,10 @@ public struct RequestExecuteTransaction {
public struct DepositTransaction {
var token: Address
var amount: BigUInt
var mintValue: BigUInt?
var to: Address?
var approveERC20: Bool?
var approveBaseERC20: Bool?
var operatorTip: BigUInt?
var bridgeAddress: Address?
var l2GasLimit: BigUInt?
Expand Down
14 changes: 14 additions & 0 deletions Sources/ZkSync2/Utils/Response/BlockDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//

import Foundation
import BigInt
import Web3Core

public struct BlockDetails: Decodable {

Expand Down Expand Up @@ -33,3 +35,15 @@ public struct BlockDetails: Decodable {

let timestamp: UInt
}

struct L2TransactionRequestDirect {
let chainId: BigUInt
let mintValue: BigUInt
let l2Contract: EthereumAddress
let l2Value: BigUInt
let l2Calldata: Data
let l2GasLimit: BigUInt
let l2GasPerPubdataByteLimit: BigUInt
let factoryDeps: [Data]
let refundRecipient: EthereumAddress
}
4 changes: 4 additions & 0 deletions Sources/ZkSync2/Utils/Response/BridgeAddresses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ public struct BridgeAddresses: Decodable {
let l1Erc20DefaultBridge: String

let l2Erc20DefaultBridge: String

let l1SharedDefaultBridge: String

let l2SharedDefaultBridge: String
}
Loading

0 comments on commit 21cb01f

Please sign in to comment.