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

feat: provide support for Bridgehub #40

Merged
merged 1 commit into from
Jun 6, 2024
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
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
Loading