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 dea652e commit c0858a7
Show file tree
Hide file tree
Showing 35 changed files with 6,642 additions and 1,644 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.zksync.methods.response;

import org.web3j.protocol.core.Response;

public class ZksGetBaseTokenContractAddress extends Response<String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.zksync.methods.response;

import org.web3j.protocol.core.Response;

public class ZksGetBridgehubContract extends Response<String> {
}
101 changes: 85 additions & 16 deletions src/main/java/io/zksync/protocol/JsonRpc2_0ZkSync.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@
import io.zksync.transaction.type.TransferTransaction;
import io.zksync.transaction.type.WithdrawTransaction;
import io.zksync.utils.TransactionStatus;
import io.zksync.utils.WalletUtils;
import io.zksync.utils.ZkSyncAddresses;
import io.zksync.wrappers.ERC20;
import io.zksync.wrappers.IEthToken;
import io.zksync.wrappers.IL2Bridge;
import org.jetbrains.annotations.Nullable;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.protocol.Web3jService;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.JsonRpc2_0Web3j;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.*;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.response.Log;
Expand All @@ -31,6 +34,8 @@
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.utils.Numeric;

import static io.zksync.wrappers.IL2Bridge.FUNC_WITHDRAW;

public class JsonRpc2_0ZkSync extends JsonRpc2_0Web3j implements ZkSync {

public static final int DEFAULT_BLOCK_COMMIT_TIME = 800;
Expand All @@ -50,6 +55,11 @@ public Request<?, ZksMainContract> zksMainContract() {
return new Request<>("zks_getMainContract", Collections.emptyList(), web3jService, ZksMainContract.class);
}

@Override
public Request<?, ZksGetBridgehubContract> zksGetBridgehubContract() {
return new Request<>("zks_getBridgehubContract", Collections.emptyList(), web3jService, ZksGetBridgehubContract.class);
}

@Override
public Request<?, ZksTokens> zksGetConfirmedTokens(Integer from, Short limit) {
return new Request<>(
Expand Down Expand Up @@ -89,6 +99,21 @@ public Request<?, ZksBridgeAddresses> zksGetBridgeContracts() {
return new Request<>("zks_getBridgeContracts", Collections.emptyList(), web3jService, ZksBridgeAddresses.class);
}

@Override
public Request<?, ZksGetBaseTokenContractAddress> zksGetBaseTokenContractAddress() {
return new Request<>("zks_getBaseTokenL1Address", Collections.emptyList(), web3jService, ZksGetBaseTokenContractAddress.class);
}
@Override
public boolean isEthBasedChain() {
String baseTokenAddress = zksGetBaseTokenContractAddress().sendAsync().join().getResult();
return ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS.equalsIgnoreCase(baseTokenAddress);
}
@Override
public boolean isBaseToken(String tokenAddress) {
String baseTokenAddress = zksGetBaseTokenContractAddress().sendAsync().join().getResult();
return tokenAddress.equalsIgnoreCase(baseTokenAddress) || tokenAddress.equalsIgnoreCase(ZkSyncAddresses.L2_BASE_TOKEN_ADDRESS);
}

@Override
public Request<?, ZksMessageProof> zksGetL2ToL1MsgProof(Integer block, String sender, String message, @Nullable Long l2LogPosition) {
return new Request<>("zks_getL2ToL1MsgProof", Arrays.asList(block, sender, message), web3jService, ZksMessageProof.class);
Expand Down Expand Up @@ -162,6 +187,23 @@ public Request<?, EthEstimateGas> estimateGasL1(Transaction transaction) {
EthEstimateGas.class);
}

public String l2TokenAddress(String tokenAddress){
if (tokenAddress.equalsIgnoreCase(ZkSyncAddresses.LEGACY_ETH_ADDRESS)){
tokenAddress = ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS;
}

String baseToken = zksGetBaseTokenContractAddress().sendAsync().join().getResult();
if (baseToken.equalsIgnoreCase(tokenAddress)){
return ZkSyncAddresses.L2_BASE_TOKEN_ADDRESS;
}

BridgeAddresses bridgeAddresses = zksGetBridgeContracts().sendAsync().join().getResult();
BigInteger gas = ethGasPrice().sendAsync().join().getGasPrice();
IL2Bridge shared = IL2Bridge.load(bridgeAddresses.getL2SharedDefaultBridge(), this, WalletUtils.createRandomCredentials(), gas, gas);

return shared.l2TokenAddress(tokenAddress).sendAsync().join();
}

public Request<?, EthEstimateGas> estimateL1ToL2Execute(String contractAddress, byte[] calldata, String caller, @Nullable BigInteger l2GasLimit, @Nullable BigInteger l2Value, @Nullable byte[][] factoryDeps, @Nullable BigInteger operatorTip, @Nullable BigInteger gasPerPubDataByte, @Nullable String refoundRecepient) {
if (gasPerPubDataByte == null){
gasPerPubDataByte = BigInteger.valueOf(800);
Expand All @@ -176,14 +218,30 @@ public Request<?, EthEstimateGas> estimateL1ToL2Execute(String contractAddress,
}

public Transaction getWithdrawTransaction(WithdrawTransaction tx, ContractGasProvider gasProvider, TransactionManager transactionManager) throws Exception {
boolean isEthBasedChain = isEthBasedChain();

if (tx.tokenAddress != null &&
!tx.tokenAddress.isEmpty() &&
tx.tokenAddress.equalsIgnoreCase(ZkSyncAddresses.LEGACY_ETH_ADDRESS) &&
!isEthBasedChain){
tx.tokenAddress = l2TokenAddress(ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS);
} else if (tx.tokenAddress == null ||
tx.tokenAddress.isEmpty() ||
isBaseToken(tx.tokenAddress)){
tx.tokenAddress = ZkSyncAddresses.L2_BASE_TOKEN_ADDRESS;
}

if (tx.tokenAddress.equalsIgnoreCase(ZkSyncAddresses.LEGACY_ETH_ADDRESS)){
tx.tokenAddress = ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS;
}
if (tx.from == null && tx.to == null){
throw new Error("Withdrawal target address is undefined!");
}

tx.to = tx.to == null ? tx.from : tx.to;
tx.options = tx.options == null ? new TransactionOptions() : tx.options;

if (tx.tokenAddress == ZkSyncAddresses.ETH_ADDRESS){
if (ZkSyncAddresses.isEth(tx.tokenAddress)){
if (tx.options.getValue() == null){
tx.options.setValue(tx.amount);
}
Expand All @@ -202,23 +260,34 @@ public Transaction getWithdrawTransaction(WithdrawTransaction tx, ContractGasPro
}
if (tx.bridgeAddress == null){
BridgeAddresses bridgeAddresses = zksGetBridgeContracts().sendAsync().join().getResult();
IL2Bridge l2WethBridge = IL2Bridge.load(bridgeAddresses.getL2wETHBridge(), this, transactionManager, gasProvider);

String l1WethToken = ZkSyncAddresses.ETH_ADDRESS;
try{
l1WethToken = l2WethBridge.l1TokenAddress(tx.tokenAddress).sendAsync().join();
}catch (Exception e){}

tx.bridgeAddress = l1WethToken != ZkSyncAddresses.ETH_ADDRESS ? bridgeAddresses.getL2wETHBridge() : bridgeAddresses.getL2Erc20DefaultBridge();
tx.bridgeAddress = bridgeAddresses.getL2SharedDefaultBridge();
}
IL2Bridge bridge = IL2Bridge.load(tx.bridgeAddress, this, transactionManager, gasProvider);
String data = bridge.encodeWithdraw(tx.to, tx.tokenAddress, tx.amount);
final Function function = new Function(
FUNC_WITHDRAW,
Arrays.<Type>asList(new Address(160, tx.to),
new Address(160, tx.tokenAddress),
new org.web3j.abi.datatypes.generated.Uint256(tx.amount)),
Collections.<TypeReference<?>>emptyList());
String data = FunctionEncoder.encode(function);
Eip712Meta meta = new Eip712Meta(BigInteger.valueOf(50000), null, null, tx.paymasterParams);

return new Transaction(tx.from, tx.bridgeAddress, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, data, meta);
}

public Transaction getTransferTransaction(TransferTransaction tx, TransactionManager transactionManager, ContractGasProvider gasProvider){
boolean isEthBasedChain = isEthBasedChain();

if (tx.tokenAddress != null &&
!tx.tokenAddress.isEmpty() &&
tx.tokenAddress.equalsIgnoreCase(ZkSyncAddresses.LEGACY_ETH_ADDRESS) &&
!isEthBasedChain){
tx.tokenAddress = l2TokenAddress(ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS);
} else if (tx.tokenAddress == null ||
tx.tokenAddress.isEmpty() ||
isBaseToken(tx.tokenAddress)){
tx.tokenAddress = ZkSyncAddresses.L2_BASE_TOKEN_ADDRESS;
}

tx.to = tx.to == null ? tx.from : tx.to;
tx.options = tx.options == null ? new TransactionOptions() : tx.options;
BigInteger gasPrice = tx.options.getGasPrice();
Expand All @@ -230,7 +299,7 @@ public Transaction getTransferTransaction(TransferTransaction tx, TransactionMan
}
Eip712Meta meta = new Eip712Meta(tx.gasPerPubData, null, null, tx.paymasterParams);

if (tx.tokenAddress == null || tx.tokenAddress == ZkSyncAddresses.ETH_ADDRESS){
if (tx.tokenAddress == null || tx.tokenAddress == ZkSyncAddresses.LEGACY_ETH_ADDRESS || isBaseToken(tx.tokenAddress)){
return new Transaction(tx.from, tx.to, BigInteger.ZERO, gasPrice, tx.amount, "0x", meta);
}
ERC20 token = ERC20.load(tx.tokenAddress, this, transactionManager, gasProvider);
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/io/zksync/protocol/ZkSync.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.web3j.protocol.Web3j;
import org.web3j.protocol.Web3jService;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.RemoteFunctionCall;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
Expand Down Expand Up @@ -38,6 +39,11 @@ static ZkSync build(Web3jService web3jService) {
*/
Request<?, ZksMainContract> zksMainContract();

/**
* Returns address of the Bridgehub smart contract.
*/
Request<?, ZksGetBridgehubContract> zksGetBridgehubContract();

/**
* Get list of the tokens supported by ZkSync.
* The tokens are returned in alphabetical order by their symbol, so basically, the token id is its position in an alphabetically sorted array of tokens.
Expand Down Expand Up @@ -82,6 +88,21 @@ static ZkSync build(Web3jService web3jService) {
*/
Request<?, ZksBridgeAddresses> zksGetBridgeContracts();

/**
* Returns the L1 base token address.
*/
Request<?, ZksGetBaseTokenContractAddress> zksGetBaseTokenContractAddress();

/**
* Returns whether the chain is ETH-based.
*/
boolean isEthBasedChain();

/**
* Returns whether the `token` is the base token.
*/
boolean isBaseToken(String tokenAddress);

/**
* Get the proof for the message sent via the IL1Messenger system contract.
*
Expand Down Expand Up @@ -166,6 +187,16 @@ Request<?, ZksBlock> zksGetBlockByHash(
Request<?, ZksBlock> zksGetBlockByNumber(
DefaultBlockParameter defaultBlockParameter, boolean returnFullTransactionObjects);

/**
* Returns the L2 token address equivalent for a L1 token address as they are not necessarily equal.
* The ETH address is set to the zero address.
*
* @remarks Only works for tokens bridged on default zkSync Era bridges.
*
* @param token The address of the token on L1.
*/
String l2TokenAddress(String tokenAddress);

Request<?, EthEstimateGas> estimateL1ToL2Execute(String contractAddress, byte[] calldata, String caller, @Nullable BigInteger l2GasLimit, @Nullable BigInteger l2Value, @Nullable byte[][] factoryDeps, @Nullable BigInteger operatorTip, @Nullable BigInteger gasPerPubDataByte, @Nullable String refoundRecepient);

Transaction getWithdrawTransaction(WithdrawTransaction tx, ContractGasProvider gasProvider, TransactionManager transactionManager) throws Exception;
Expand Down
15 changes: 9 additions & 6 deletions src/main/java/io/zksync/protocol/account/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@
@Getter
public class Wallet extends WalletL1{

private final TransactionReceiptProcessor transactionReceiptProcessor;
private final ZkSyncTransactionReceiptProcessor transactionReceiptProcessor;
private final ZkTransactionFeeProvider feeProviderL2;
protected final EthSigner signerL2;

public Wallet(Web3j providerL1, ZkSync providerL2, TransactionManager transactionManager, ContractGasProvider feeProviderL1, ZkTransactionFeeProvider feeProviderL2, TransactionReceiptProcessor transactionReceiptProcessor, Credentials credentials) {
public Wallet(Web3j providerL1, ZkSync providerL2, TransactionManager transactionManager, ContractGasProvider feeProviderL1, ZkTransactionFeeProvider feeProviderL2, ZkSyncTransactionReceiptProcessor transactionReceiptProcessor, Credentials credentials) {
super(providerL1, providerL2, transactionManager, feeProviderL1, credentials);
this.transactionReceiptProcessor = transactionReceiptProcessor;
this.feeProviderL2 = feeProviderL2;
Expand Down Expand Up @@ -97,7 +97,7 @@ public String getAddress(){
*/
public L2BridgeContracts getL2BridgeContracts(){
BridgeAddresses bridgeAddresses = providerL2.zksGetBridgeContracts().sendAsync().join().getResult();
return new L2BridgeContracts(bridgeAddresses.getL2Erc20DefaultBridge(), bridgeAddresses.getL2wETHBridge(), providerL2, transactionManager, feeProviderL2);
return new L2BridgeContracts(bridgeAddresses.getL2Erc20DefaultBridge(), bridgeAddresses.getL2WethBridge(), providerL2, transactionManager, feeProviderL2);
}

public CompletableFuture<BigInteger> getDeploymentNonce(){
Expand Down Expand Up @@ -135,7 +135,7 @@ public RemoteCall<TransactionReceipt> transfer(TransferTransaction tx) {
* @return Prepared remote call of transaction
*/
public RemoteCall<TransactionReceipt> withdraw(WithdrawTransaction tx) {
tx.tokenAddress = tx.tokenAddress == null ? ZkSyncAddresses.ETH_ADDRESS : tx.tokenAddress;
tx.tokenAddress = tx.tokenAddress == null ? ZkSyncAddresses.LEGACY_ETH_ADDRESS : tx.tokenAddress;
tx.from = tx.from == null ? getAddress() : tx.from;
tx.options = tx.options == null ? new TransactionOptions() : tx.options;
BigInteger maxPriorityFeePerGas = tx.options.getMaxPriorityFeePerGas() == null ? BigInteger.ZERO : tx.options.getMaxPriorityFeePerGas();
Expand Down Expand Up @@ -295,7 +295,7 @@ public RemoteCall<TransactionReceipt> execute(String contractAddress, Function f
* @return Prepared get balance call
*/
public RemoteCall<BigInteger> getBalance() {
return getBalance(getAddress(), ZkSyncAddresses.ETH_ADDRESS, ZkBlockParameterName.COMMITTED);
return getBalance(getAddress(), getBaseToken().sendAsync().join(), ZkBlockParameterName.COMMITTED);
}

/**
Expand Down Expand Up @@ -329,7 +329,10 @@ public RemoteCall<BigInteger> getBalance(String address, String token) {
* @return Prepared get balance call
*/
public RemoteCall<BigInteger> getBalance(String address, String token, DefaultBlockParameter at) {
if (token == ZkSyncAddresses.ETH_ADDRESS) {
if (token.equalsIgnoreCase(ZkSyncAddresses.LEGACY_ETH_ADDRESS)){
token = ZkSyncAddresses.ETH_ADDRESS_IN_CONTRACTS;
}
if (providerL2.isBaseToken(token)) {
return new RemoteCall<>(() ->
this.providerL2.ethGetBalance(address, at).sendAsync().join().getBalance());
} else {
Expand Down
Loading

0 comments on commit c0858a7

Please sign in to comment.