diff --git a/build.gradle b/build.gradle index 1149d9d..097763d 100755 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java-library' id 'maven-publish' id 'signing' + id 'io.codearte.nexus-staging' version '0.30.0' } group = 'io.zksync' @@ -52,9 +53,9 @@ publishing { } developers { developer { - id = 'mfischuk' - name = 'Maxim Fischuk' - email = 'mfischuk@vareger.com' + id = 'matterLabs' + name = 'Matter Labs' + email = 'hello@matterlabs.io' } } scm { @@ -77,6 +78,11 @@ publishing { } } +nexusStaging { + packageGroup = "io.zksync.zksync2" //optional if packageGroup == project.getGroup() + stagingProfileId findProperty("OSSRH_STAGING_PROFILE_ID") +} + signing { def signingKeyId = findProperty("signingKeyId") diff --git a/src/main/java/io/zksync/ZkSyncWallet.java b/src/main/java/io/zksync/ZkSyncWallet.java index 64a8afe..88aab09 100644 --- a/src/main/java/io/zksync/ZkSyncWallet.java +++ b/src/main/java/io/zksync/ZkSyncWallet.java @@ -19,9 +19,11 @@ import org.web3j.abi.datatypes.Function; import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.crypto.Credentials; +import org.web3j.crypto.Hash; import org.web3j.protocol.core.DefaultBlockParameter; import org.web3j.protocol.core.RemoteCall; import org.web3j.protocol.core.methods.response.EthSendTransaction; +import org.web3j.protocol.core.methods.response.Log; import org.web3j.protocol.core.methods.response.TransactionReceipt; import org.web3j.protocol.exceptions.TransactionException; import org.web3j.tx.ReadonlyTransactionManager; @@ -183,28 +185,43 @@ public RemoteCall withdraw(String to, BigInteger amount, @Nu String l2Bridge; ArrayList parameters = new ArrayList(); parameters.add(new Address(to)); - + Transaction estimate; if (tokenToUse.isETH()) { + Function function = new Function( + IL2Bridge.FUNC_WITHDRAW, + Arrays.asList(new Address(to)), + Collections.emptyList()); + + String calldata = FunctionEncoder.encode(function); l2Bridge = L2_ETH_TOKEN_ADDRESS; + + estimate = Transaction.createFunctionCallTransaction( + signer.getAddress(), + l2Bridge, + BigInteger.ZERO, + BigInteger.ZERO, + amount, + calldata + ); } else { - parameters.add(new Address(tokenToUse.getL2Address())); - parameters.add(new Uint256(amount)); + Function function = new Function( + IL2Bridge.FUNC_WITHDRAW, + Arrays.asList(new Address(to), + new Address(tokenToUse.getL2Address()), + new Uint256(amount)), + Collections.emptyList()); + + String calldata = FunctionEncoder.encode(function); l2Bridge = zksync.zksGetBridgeContracts().send().getResult().getL2Erc20DefaultBridge(); - } - Function function = new Function( - IL2Bridge.FUNC_WITHDRAW, - parameters, - Collections.emptyList()); - String calldata = FunctionEncoder.encode(function); - - Transaction estimate = Transaction.createFunctionCallTransaction( - signer.getAddress(), - l2Bridge, - BigInteger.ZERO, - BigInteger.ZERO, - calldata - ); + estimate = Transaction.createFunctionCallTransaction( + signer.getAddress(), + l2Bridge, + BigInteger.ZERO, + BigInteger.ZERO, + calldata + ); + } EthSendTransaction sent = estimateAndSend(estimate, nonceToUse).join(); diff --git a/src/main/java/io/zksync/methods/response/L2toL1Log.java b/src/main/java/io/zksync/methods/response/L2toL1Log.java new file mode 100644 index 0000000..ca33a6f --- /dev/null +++ b/src/main/java/io/zksync/methods/response/L2toL1Log.java @@ -0,0 +1,107 @@ +package io.zksync.methods.response; + +import org.web3j.abi.datatypes.Address; + +import java.math.BigInteger; + +public class L2toL1Log { + public String BlockNumber; + public String BlockHash; + public String L1BatchNumber; + public String TransactionIndex; + public String ShardId; + public boolean IsService; + public Address Sender; + public String Key; + public String Value; + public String TxHash; + public int Index; + + public String getBlockNumber() { + return BlockNumber; + } + + public void setBlockNumber(String blockNumber) { + BlockNumber = blockNumber; + } + + public String getBlockHash() { + return BlockHash; + } + + public void setBlockHash(String blockHash) { + BlockHash = blockHash; + } + + public String getL1BatchNumber() { + return L1BatchNumber; + } + + public void setL1BatchNumber(String l1BatchNumber) { + L1BatchNumber = l1BatchNumber; + } + + public String getTransactionIndex() { + return TransactionIndex; + } + + public void setTransactionIndex(String transactionIndex) { + TransactionIndex = transactionIndex; + } + + public String getShardId() { + return ShardId; + } + + public void setShardId(String shardId) { + ShardId = shardId; + } + + public boolean isService() { + return IsService; + } + + public void setService(boolean service) { + IsService = service; + } + + public Address getSender() { + return Sender; + } + + public void setSender(Address sender) { + Sender = sender; + } + + public String getKey() { + return Key; + } + + public void setKey(String key) { + Key = key; + } + + public String getValue() { + return Value; + } + + public void setValue(String value) { + Value = value; + } + + public String getTxHash() { + return TxHash; + } + + public void setTxHash(String txHash) { + TxHash = txHash; + } + + public int getIndex() { + return Index; + } + + public void setIndex(int index) { + Index = index; + } +} diff --git a/src/main/java/io/zksync/methods/response/ZkTransactionReceipt.java b/src/main/java/io/zksync/methods/response/ZkTransactionReceipt.java index c28f69b..eb77efb 100644 --- a/src/main/java/io/zksync/methods/response/ZkTransactionReceipt.java +++ b/src/main/java/io/zksync/methods/response/ZkTransactionReceipt.java @@ -1,10 +1,13 @@ package io.zksync.methods.response; +import kotlin.OverloadResolutionByLambdaReturnType; +import kotlin.jvm.JvmOverloads; import org.web3j.protocol.core.methods.response.Log; import org.web3j.protocol.core.methods.response.TransactionReceipt; import org.web3j.utils.Numeric; import java.math.BigInteger; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -12,13 +15,15 @@ public class ZkTransactionReceipt extends TransactionReceipt { private String l1BatchNumber; private String l1BatchTxIndex; + private List l2ToL1Logs; public ZkTransactionReceipt() { } - public ZkTransactionReceipt(String transactionHash, String transactionIndex, String blockHash, String blockNumber, String cumulativeGasUsed, String gasUsed, String contractAddress, String root, String status, String from, String to, List logs, String logsBloom, String revertReason, String type, String effectiveGasPrice, String l1BatchNumber, String l1BatchTxIndex) { + public ZkTransactionReceipt(String transactionHash, String transactionIndex, String blockHash, String blockNumber, String cumulativeGasUsed, String gasUsed, String contractAddress, String root, String status, String from, String to, List logs, String logsBloom, String revertReason, String type, String effectiveGasPrice, String l1BatchNumber, String l1BatchTxIndex, List l2ToL1Logs) { super(transactionHash, transactionIndex, blockHash, blockNumber, cumulativeGasUsed, gasUsed, contractAddress, root, status, from, to, logs, logsBloom, revertReason, type, effectiveGasPrice); this.l1BatchNumber = l1BatchNumber; this.l1BatchTxIndex = l1BatchTxIndex; + this.l2ToL1Logs = l2ToL1Logs; } public String getL1BatchNumberRaw() { @@ -29,7 +34,6 @@ public BigInteger getL1BatchNumber() { return Numeric.decodeQuantity(l1BatchNumber); } - public void setL1BatchNumber(String l1BatchNumber) { this.l1BatchNumber = l1BatchNumber; } @@ -46,6 +50,14 @@ public void setL1BatchTxIndex(String l1BatchTxIndex) { this.l1BatchTxIndex = l1BatchTxIndex; } + public void setL2ToL1Logs(List l2ToL1Logs) { + this.l2ToL1Logs = l2ToL1Logs; + } + + public List getl2ToL1Logs() { + return l2ToL1Logs; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/io/zksync/protocol/JsonRpc2_0ZkSync.java b/src/main/java/io/zksync/protocol/JsonRpc2_0ZkSync.java index 983ccab..7a508af 100755 --- a/src/main/java/io/zksync/protocol/JsonRpc2_0ZkSync.java +++ b/src/main/java/io/zksync/protocol/JsonRpc2_0ZkSync.java @@ -76,6 +76,11 @@ public Request zksGetL2ToL1MsgProof(Integer block, String se return new Request<>("zks_getL2ToL1MsgProof", Arrays.asList(block, sender, message), web3jService, ZksMessageProof.class); } + @Override + public Request zksGetL2ToL1LogProof(String txHash, int index) { + return new Request<>("zks_getL2ToL1LogProof", Arrays.asList(txHash, index), web3jService, ZksMessageProof.class); + } + @Override public Request ethEstimateGas(Transaction transaction) { return new Request<>( diff --git a/src/main/java/io/zksync/protocol/ZkSync.java b/src/main/java/io/zksync/protocol/ZkSync.java index c289b14..0480d23 100755 --- a/src/main/java/io/zksync/protocol/ZkSync.java +++ b/src/main/java/io/zksync/protocol/ZkSync.java @@ -86,6 +86,7 @@ static ZkSync build(Web3jService web3jService) { */ Request zksGetL2ToL1MsgProof(Integer block, String sender, String message, @Nullable Long l2LogPosition); + Request zksGetL2ToL1LogProof(String txHash, int index); /** * Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. * diff --git a/src/main/java/io/zksync/protocol/provider/DefaultEthereumProvider.java b/src/main/java/io/zksync/protocol/provider/DefaultEthereumProvider.java index 6eece7e..a9d7795 100755 --- a/src/main/java/io/zksync/protocol/provider/DefaultEthereumProvider.java +++ b/src/main/java/io/zksync/protocol/provider/DefaultEthereumProvider.java @@ -1,24 +1,39 @@ package io.zksync.protocol.provider; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -import io.zksync.wrappers.*; +import io.zksync.methods.response.L2toL1Log; +import io.zksync.methods.response.ZkTransactionReceipt; +import io.zksync.protocol.ZkSync; +import io.zksync.protocol.core.L2ToL1MessageProof; +import io.zksync.protocol.core.Token; +import io.zksync.wrappers.ERC20; +import io.zksync.wrappers.IL1Bridge; +import io.zksync.wrappers.IL2Bridge; +import io.zksync.wrappers.ZkSyncContract; +import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.Nullable; +import org.web3j.abi.EventValues; import org.web3j.abi.datatypes.DynamicBytes; +import org.web3j.crypto.Hash; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.methods.response.EthGasPrice; +import org.web3j.protocol.core.methods.response.Log; import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; import org.web3j.tx.TransactionManager; import org.web3j.tx.Transfer; import org.web3j.tx.gas.ContractGasProvider; import org.web3j.tx.gas.StaticGasProvider; import org.web3j.utils.Convert.Unit; +import org.web3j.utils.Numeric; -import io.zksync.protocol.core.Token; -import lombok.RequiredArgsConstructor; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static io.zksync.utils.ZkSyncAddresses.L2_ETH_TOKEN_ADDRESS; +import static io.zksync.utils.ZkSyncAddresses.MESSENGER_ADDRESS; +import static io.zksync.wrappers.L1Messenger.L1MESSAGESENT_EVENT; @RequiredArgsConstructor public class DefaultEthereumProvider implements EthereumProvider { @@ -33,6 +48,7 @@ public class DefaultEthereumProvider implements EthereumProvider { private static final BigInteger L1_TO_L2_GAS_PER_PUBDATA = BigInteger.valueOf(800); private final Web3j web3j; + private final ZkSync zkSync; private final TransactionManager transactionManager; private final ContractGasProvider gasProvider; private final ZkSyncContract contract; @@ -106,6 +122,65 @@ public CompletableFuture withdraw(Token token, BigInteger am throw new UnsupportedOperationException(); } + @Override + public TransactionReceipt finalizeWithdraw(String txHash, int index) throws Exception { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + ZkTransactionReceipt receipt = zkSync.zksGetTransactionReceipt(txHash).sendAsync().join().getResult(); + + int logIndex = getWithdrawalLogIndex(receipt.getLogs(), index); + Log log = receipt.getLogs().get(logIndex); + + int l2ToL1LogIndex = getWithdrawalL2ToL1LogIndex(receipt.getl2ToL1Logs(), index); + L2ToL1MessageProof l2ToL1MessageProof = zkSync.zksGetL2ToL1LogProof(txHash, l2ToL1LogIndex).sendAsync().join().getResult(); + + EventValues eventValues = Contract.staticExtractEventParameters(L1MESSAGESENT_EVENT, log); + byte[] bytes_data = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + + BigInteger l1BatchNumber = receipt.getL1BatchNumber(); + + List merkle_proof = new ArrayList<>(); + for (int i = 0 ; i < l2ToL1MessageProof.getProof().size() ; i++){ + merkle_proof.add(Numeric.hexStringToByteArray(l2ToL1MessageProof.getProof().get(i))); + } + String sender = log.getTopics().get(1); + + if (sender.equals(L2_ETH_TOKEN_ADDRESS)){ + return contract.finalizeEthWithdrawal(l1BatchNumber, BigInteger.valueOf(l2ToL1MessageProof.getId()), receipt.getL1BatchTxIndex(), bytes_data, merkle_proof).sendAsync().join(); + } + IL2Bridge il2Bridge = IL2Bridge.load(sender, web3j, transactionManager, gasProvider); + IL1Bridge il1Bridge = IL1Bridge.load(il2Bridge.l1Bridge().send(), web3j, transactionManager, gasProvider); + + return il1Bridge.finalizeWithdrawal(l1BatchNumber, BigInteger.valueOf(l2ToL1MessageProof.getId()), receipt.getL1BatchTxIndex(), bytes_data, merkle_proof).sendAsync().join(); + } + + public int getWithdrawalLogIndex(List logs, int index){ + String topic = "L1MessageSent(address,bytes32,bytes)"; + List logIndex = new ArrayList<>(); + + for (int i = 0 ; i < logs.size() ; i++){ + if (logs.get(i).getAddress().equals(MESSENGER_ADDRESS) && Arrays.equals(logs.get(i).getTopics().get(0).getBytes(), Hash.sha3String(topic).getBytes())){ + logIndex.add(i); + } + } + + return logIndex.get(index); + } + + public int getWithdrawalL2ToL1LogIndex(List logs, int index){ + List logIndex = new ArrayList<>(); + + for (int i = 0 ; i < logs.size() ; i++){ + if (logs.get(i).getSender().getValue().equals(MESSENGER_ADDRESS)){ + logIndex.add(i); + } + } + + return logIndex.get(index); + } @Override public CompletableFuture isDepositApproved(Token token, String to, Optional threshold) { ERC20 tokenContract = ERC20.load(token.getL1Address(), web3j, transactionManager, diff --git a/src/main/java/io/zksync/protocol/provider/EthereumProvider.java b/src/main/java/io/zksync/protocol/provider/EthereumProvider.java index c5f85b4..1875cac 100755 --- a/src/main/java/io/zksync/protocol/provider/EthereumProvider.java +++ b/src/main/java/io/zksync/protocol/provider/EthereumProvider.java @@ -85,6 +85,7 @@ public interface EthereumProvider { * @return CompletableFuture for waiting for transaction mine */ CompletableFuture withdraw(Token token, BigInteger amount, String userAddress); + TransactionReceipt finalizeWithdraw(String txHash, int index) throws Exception; /** * Check if deposit is approved in enough amount @@ -109,7 +110,7 @@ static CompletableFuture load(ZkSync zksync, Web3j ethereum, T String mainContract = zksync.zksMainContract().sendAsync().join().getResult(); IL1Bridge erc20Bridge = IL1Bridge.load(bridgeAddresses.getL1Erc20DefaultBridge(), ethereum, transactionManager, gasProvider); ZkSyncContract zkSyncContract = ZkSyncContract.load(mainContract, ethereum, transactionManager, gasProvider); - return new DefaultEthereumProvider(ethereum, transactionManager, gasProvider, zkSyncContract, erc20Bridge); + return new DefaultEthereumProvider(ethereum,zksync, transactionManager, gasProvider, zkSyncContract, erc20Bridge); }); } diff --git a/src/main/java/io/zksync/wrappers/L1Messenger.java b/src/main/java/io/zksync/wrappers/L1Messenger.java new file mode 100644 index 0000000..0e40523 --- /dev/null +++ b/src/main/java/io/zksync/wrappers/L1Messenger.java @@ -0,0 +1,134 @@ +package io.zksync.wrappers; + +import io.reactivex.Flowable; +import io.reactivex.functions.Function; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.web3j.abi.EventEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.DynamicBytes; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.request.EthFilter; +import org.web3j.protocol.core.methods.response.BaseEventResponse; +import org.web3j.protocol.core.methods.response.Log; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + *

Auto generated code. + *

Do not modify! + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. + * + *

Generated with web3j version 1.4.2. + */ +@SuppressWarnings("rawtypes") +public class L1Messenger extends Contract { + public static final String BINARY = "Bin file was not provided"; + + public static final String FUNC_SENDTOL1 = "sendToL1"; + + public static final Event L1MESSAGESENT_EVENT = new Event("L1MessageSent", + Arrays.>asList(new TypeReference

(true) {}, new TypeReference(true) {}, new TypeReference() {})); + ; + + @Deprecated + protected L1Messenger(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected L1Messenger(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected L1Messenger(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected L1Messenger(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static List getL1MessageSentEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(L1MESSAGESENT_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (Contract.EventValuesWithLog eventValues : valueList) { + L1MessageSentEventResponse typedResponse = new L1MessageSentEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse._sender = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse._hash = (byte[]) eventValues.getIndexedValues().get(1).getValue(); + typedResponse._message = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public Flowable l1MessageSentEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(new Function() { + @Override + public L1MessageSentEventResponse apply(Log log) { + Contract.EventValuesWithLog eventValues = extractEventParametersWithLog(L1MESSAGESENT_EVENT, log); + L1MessageSentEventResponse typedResponse = new L1MessageSentEventResponse(); + typedResponse.log = log; + typedResponse._sender = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse._hash = (byte[]) eventValues.getIndexedValues().get(1).getValue(); + typedResponse._message = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + }); + } + + public Flowable l1MessageSentEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(L1MESSAGESENT_EVENT)); + return l1MessageSentEventFlowable(filter); + } + + public RemoteFunctionCall sendToL1(byte[] _message) { + final org.web3j.abi.datatypes.Function function = new org.web3j.abi.datatypes.Function( + FUNC_SENDTOL1, + Arrays.asList(new org.web3j.abi.datatypes.DynamicBytes(_message)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + @Deprecated + public static L1Messenger load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new L1Messenger(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static L1Messenger load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new L1Messenger(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static L1Messenger load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return new L1Messenger(contractAddress, web3j, credentials, contractGasProvider); + } + + public static L1Messenger load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new L1Messenger(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static class L1MessageSentEventResponse extends BaseEventResponse { + public String _sender; + + public byte[] _hash; + + public byte[] _message; + } +} diff --git a/src/test/java/io/zksync/crypto/signer/SelfTransferTest.java b/src/test/java/io/zksync/crypto/signer/SelfTransferTest.java index b2a943d..6fa1892 100644 --- a/src/test/java/io/zksync/crypto/signer/SelfTransferTest.java +++ b/src/test/java/io/zksync/crypto/signer/SelfTransferTest.java @@ -155,4 +155,4 @@ public void testDeposit() throws IOException { } -} +} \ No newline at end of file