Skip to content
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,10 @@
package org.arkecosystem.crypto.enums;

public enum ContractAbiType {
CONSENSUS,
MULTIPAYMENT,
USERNAMES,
ERC20BATCH_TRANSFER,
TOKEN,
CUSTOM
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import org.arkecosystem.crypto.configuration.Network;
import org.arkecosystem.crypto.encoding.Hex;
import org.arkecosystem.crypto.enums.AbiFunction;
import org.arkecosystem.crypto.transactions.types.*;
import org.arkecosystem.crypto.utils.AbiDecoder;
import org.arkecosystem.crypto.utils.RlpDecoder;
import org.arkecosystem.crypto.utils.TransactionTypeIdentifier;

public class Deserializer {

Expand Down Expand Up @@ -90,40 +88,24 @@ private AbstractTransaction guessTransactionFromTransactionData(
return new Transfer();
}

Map<String, Object> payloadData = decodePayload(transactionData);
if (payloadData == null) {
String payload = transactionData.data != null ? transactionData.data : "";
if (payload.isEmpty()) {
return new EvmCall();
}

String functionName = (String) payloadData.get("functionName");

if (functionName.equals(AbiFunction.VOTE.toString())) {
if (TransactionTypeIdentifier.isVote(payload)) {
return new Vote(transactionData.toHashMap());
} else if (functionName.equals(AbiFunction.UNVOTE.toString())) {
} else if (TransactionTypeIdentifier.isUnvote(payload)) {
return new Unvote(transactionData.toHashMap());
} else if (functionName.equals(AbiFunction.VALIDATOR_REGISTRATION.toString())) {
} else if (TransactionTypeIdentifier.isValidatorRegistration(payload)) {
return new ValidatorRegistration(transactionData.toHashMap());
} else if (functionName.equals(AbiFunction.VALIDATOR_RESIGNATION.toString())) {
} else if (TransactionTypeIdentifier.isValidatorResignation(payload)) {
return new ValidatorResignation(transactionData.toHashMap());
}

return new EvmCall();
}

private Map<String, Object> decodePayload(AbstractTransaction transaction) {
String payload = transaction.data != null ? transaction.data : "";
if (payload.isEmpty()) {
return null;
}

try {
AbiDecoder abiDecoder = new AbiDecoder();
return abiDecoder.decodeFunctionData(payload);
} catch (Exception e) {
return null;
}
}

private static long bytesToLong(byte[] bytes) {
if (bytes.length == 0) return 0;
return new BigInteger(1, bytes).longValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.List;
import org.arkecosystem.crypto.enums.AbiFunction;
import org.arkecosystem.crypto.enums.ContractAbiType;
import org.arkecosystem.crypto.transactions.types.AbstractTransaction;
import org.arkecosystem.crypto.transactions.types.EvmCall;
import org.arkecosystem.crypto.utils.AbiEncoder;
Expand All @@ -21,7 +22,7 @@ public TokenApproveBuilder spender(String address, BigInteger amount) {

try {
String payload =
new AbiEncoder("Abi.Token.json")
new AbiEncoder(ContractAbiType.TOKEN)
.encodeFunctionCall(AbiFunction.APPROVE.toString(), args);

this.transaction.data = payload.replaceFirst("^0x", "");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.List;
import org.arkecosystem.crypto.enums.AbiFunction;
import org.arkecosystem.crypto.enums.ContractAbiType;
import org.arkecosystem.crypto.transactions.types.AbstractTransaction;
import org.arkecosystem.crypto.transactions.types.EvmCall;
import org.arkecosystem.crypto.utils.AbiEncoder;
Expand All @@ -21,7 +22,7 @@ public TokenTransferBuilder recipient(String address, BigInteger amount) {

try {
String payload =
new AbiEncoder("Abi.Token.json")
new AbiEncoder(ContractAbiType.TOKEN)
.encodeFunctionCall(AbiFunction.TRANSFER.toString(), args);

this.transaction.data = payload.replaceFirst("^0x", "");
Expand Down
79 changes: 73 additions & 6 deletions src/main/java/org/arkecosystem/crypto/utils/AbiBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.arkecosystem.crypto.enums.ContractAbiType;
import org.web3j.crypto.Hash;
import org.web3j.utils.Numeric;

Expand All @@ -14,15 +17,35 @@ public abstract class AbiBase {
protected List<Map<String, Object>> abi;

public AbiBase() throws IOException {
this("Abi.Consensus.json");
this(ContractAbiType.CONSENSUS, null);
}

public AbiBase(String abiFilePath) throws IOException {
InputStream abiInputStream = getClass().getClassLoader().getResourceAsStream(abiFilePath);
public AbiBase(ContractAbiType type) throws IOException {
this(type, null);
}

ObjectMapper mapper = new ObjectMapper();
Map<String, Object> abiJson = mapper.readValue(abiInputStream, Map.class);
this.abi = (List<Map<String, Object>>) abiJson.get("abi");
public AbiBase(ContractAbiType type, String path) throws IOException {
String abiFilePath = contractAbiPath(type, path);
Map<String, Object> decodedAbi = loadAbiJson(abiFilePath);
this.abi = (List<Map<String, Object>>) decodedAbi.get("abi");
}

public static Map<String, String> methodIdentifiers(ContractAbiType type) throws IOException {
return methodIdentifiers(type, null);
}

public static Map<String, String> methodIdentifiers(ContractAbiType type, String path)
throws IOException {
String abiFilePath = contractAbiPath(type, path);
Map<String, Object> decodedAbi = loadAbiJson(abiFilePath);

Object methodIdentifiers = decodedAbi.get("methodIdentifiers");
if (!(methodIdentifiers instanceof Map)) {
throw new RuntimeException(
"ABI JSON does not contain methodIdentifiers: " + abiFilePath);
}

return (Map<String, String>) methodIdentifiers;
}

protected static String[] getArrayComponents(String type) {
Expand Down Expand Up @@ -77,4 +100,48 @@ protected String concatHex(List<String> hexes) {
}
return result.toString();
}

protected static String contractAbiPath(ContractAbiType type, String path) {
switch (type) {
case CONSENSUS:
return "Abi.Consensus.json";
case MULTIPAYMENT:
return "Abi.Multipayment.json";
case USERNAMES:
return "Abi.Usernames.json";
case ERC20BATCH_TRANSFER:
return "Abi.ERC20BatchTransfer.json";
case TOKEN:
return "Abi.Token.json";
case CUSTOM:
if (path == null || path.isEmpty()) {
throw new IllegalArgumentException(
"A non-empty path must be provided when using ContractAbiType.CUSTOM.");
}
return path;
default:
throw new IllegalArgumentException("Unhandled ContractAbiType: " + type.name());
}
}

private static Map<String, Object> loadAbiJson(String path) throws IOException {
InputStream stream = AbiBase.class.getClassLoader().getResourceAsStream(path);
if (stream == null) {
if (Files.exists(Paths.get(path))) {
stream = Files.newInputStream(Paths.get(path));
} else {
throw new RuntimeException("Unable to load ABI JSON: " + path);
}
}

ObjectMapper mapper = new ObjectMapper();
Map<String, Object> decoded = mapper.readValue(stream, Map.class);

Object abi = decoded.get("abi");
if (!(abi instanceof List)) {
throw new RuntimeException("ABI JSON does not contain a valid abi array: " + path);
}

return decoded;
}
}
9 changes: 9 additions & 0 deletions src/main/java/org/arkecosystem/crypto/utils/AbiDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
import org.arkecosystem.crypto.enums.ContractAbiType;
import org.web3j.utils.Numeric;

public class AbiDecoder extends AbiBase {
Expand All @@ -11,6 +12,14 @@ public AbiDecoder() throws IOException {
super();
}

public AbiDecoder(ContractAbiType type) throws IOException {
super(type);
}

public AbiDecoder(ContractAbiType type, String path) throws IOException {
super(type, path);
}

public Map<String, Object> decodeFunctionData(String data) throws Exception {
data = stripHexPrefix(data);

Expand Down
9 changes: 7 additions & 2 deletions src/main/java/org/arkecosystem/crypto/utils/AbiEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
import org.arkecosystem.crypto.enums.ContractAbiType;
import org.web3j.utils.Numeric;

public class AbiEncoder extends AbiBase {
Expand All @@ -11,8 +12,12 @@ public AbiEncoder() throws IOException {
super();
}

public AbiEncoder(String abiFilePath) throws IOException {
super(abiFilePath);
public AbiEncoder(ContractAbiType type) throws IOException {
super(type);
}

public AbiEncoder(ContractAbiType type, String path) throws IOException {
super(type, path);
}

public String encodeFunctionCall(String functionName) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.arkecosystem.crypto.utils;

import java.util.HashMap;
import java.util.Map;
import org.arkecosystem.crypto.enums.ContractAbiType;
import org.web3j.utils.Numeric;

public class TransactionTypeIdentifier {

private static final String TRANSFER_SIGNATURE = "";

private static Map<String, String> signatures;

public static boolean isTransfer(String data) {
return TRANSFER_SIGNATURE.equals(data);
}

public static boolean isVote(String data) {
return startsWithSignature(data, signatures().get("vote"));
}

public static boolean isUnvote(String data) {
return startsWithSignature(data, signatures().get("unvote"));
}

public static boolean isMultiPayment(String data) {
return startsWithSignature(data, signatures().get("multiPayment"));
}

public static boolean isUsernameRegistration(String data) {
return startsWithSignature(data, signatures().get("registerUsername"));
}

public static boolean isUsernameResignation(String data) {
return startsWithSignature(data, signatures().get("resignUsername"));
}

public static boolean isValidatorRegistration(String data) {
return startsWithSignature(data, signatures().get("registerValidator"));
}

public static boolean isValidatorResignation(String data) {
return startsWithSignature(data, signatures().get("resignValidator"));
}

public static boolean isUpdateValidator(String data) {
return startsWithSignature(data, signatures().get("updateValidator"));
}

public static boolean isTokenTransfer(String data) {
Map<String, Object> decodedData = decodeTokenFunction(data);
return decodedData != null && "transfer".equals(decodedData.get("functionName"));
}

private static boolean startsWithSignature(String data, String signature) {
if (data == null || signature == null) {
return false;
}
return Numeric.cleanHexPrefix(data).toLowerCase().startsWith(signature.toLowerCase());
}

private static synchronized Map<String, String> signatures() {
if (signatures != null) {
return signatures;
}

try {
Map<String, String> consensusMethods =
AbiBase.methodIdentifiers(ContractAbiType.CONSENSUS);
Map<String, String> multipaymentMethods =
AbiBase.methodIdentifiers(ContractAbiType.MULTIPAYMENT);
Map<String, String> usernamesMethods =
AbiBase.methodIdentifiers(ContractAbiType.USERNAMES);

Map<String, String> map = new HashMap<>();
map.put("multiPayment", multipaymentMethods.get("pay(address[],uint256[])"));
map.put("registerUsername", usernamesMethods.get("registerUsername(string)"));
map.put("resignUsername", usernamesMethods.get("resignUsername()"));
map.put("registerValidator", consensusMethods.get("registerValidator(bytes)"));
map.put("resignValidator", consensusMethods.get("resignValidator()"));
map.put("vote", consensusMethods.get("vote(address)"));
map.put("unvote", consensusMethods.get("unvote()"));
map.put("updateValidator", consensusMethods.get("updateValidator(bytes)"));
map.put("transfer", "transfer");

signatures = map;
return signatures;
} catch (Exception e) {
throw new RuntimeException("Unable to load method identifiers", e);
}
}

private static Map<String, Object> decodeTokenFunction(String data) {
try {
return new AbiDecoder(ContractAbiType.TOKEN).decodeFunctionData(data);
} catch (Exception e) {
return null;
}
}
}
Loading
Loading