/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.solidity; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.Predicate; import org.ethereum.util.ByteUtil; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static com.fasterxml.jackson.annotation.JsonInclude.Include; import static java.lang.String.format; import static org.apache.commons.collections4.ListUtils.select; import static org.apache.commons.lang3.ArrayUtils.subarray; import static org.apache.commons.lang3.StringUtils.join; import static org.apache.commons.lang3.StringUtils.stripEnd; import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.solidity.SolidityType.IntType.decodeInt; import static org.ethereum.solidity.SolidityType.IntType.encodeInt; public class Abi extends ArrayList<Abi.Entry> { private final static ObjectMapper DEFAULT_MAPPER = new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL); public static Abi fromJson(String json) { try { return DEFAULT_MAPPER.readValue(json, Abi.class); } catch (IOException e) { throw new RuntimeException(e); } } public String toJson() { try { return new ObjectMapper().writeValueAsString(this); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } private <T extends Abi.Entry> T find(Class<T> resultClass, final Abi.Entry.Type type, final Predicate<T> searchPredicate) { return (T) CollectionUtils.find(this, new Predicate<Abi.Entry>() { @Override public boolean evaluate(Abi.Entry entry) { return entry.type == type && searchPredicate.evaluate((T) entry); } }); } public Function findFunction(Predicate<Function> searchPredicate) { return find(Function.class, Abi.Entry.Type.function, searchPredicate); } public Event findEvent(Predicate<Event> searchPredicate) { return find(Event.class, Abi.Entry.Type.event, searchPredicate); } public Abi.Constructor findConstructor() { return find(Constructor.class, Entry.Type.constructor, new Predicate<Constructor>() { @Override public boolean evaluate(Constructor object) { return true; } }); } @Override public String toString() { return toJson(); } @JsonInclude(Include.NON_NULL) public static abstract class Entry { public enum Type { constructor, function, event, fallback } @JsonInclude(Include.NON_NULL) public static class Param { public Boolean indexed; public String name; public SolidityType type; public static List<?> decodeList(List<Param> params, byte[] encoded) { List<Object> result = new ArrayList<>(params.size()); int offset = 0; for (Param param : params) { Object decoded = param.type.isDynamicType() ? param.type.decode(encoded, decodeInt(encoded, offset).intValue()) : param.type.decode(encoded, offset); result.add(decoded); offset += param.type.getFixedSize(); } return result; } @Override public String toString() { return format("%s%s%s", type.getCanonicalName(), (indexed != null && indexed) ? " indexed " : " ", name); } } public final Boolean anonymous; public final Boolean constant; public final String name; public final List<Param> inputs; public final List<Param> outputs; public final Type type; public final Boolean payable; public Entry(Boolean anonymous, Boolean constant, String name, List<Param> inputs, List<Param> outputs, Type type, Boolean payable) { this.anonymous = anonymous; this.constant = constant; this.name = name; this.inputs = inputs; this.outputs = outputs; this.type = type; this.payable = payable; } public String formatSignature() { StringBuilder paramsTypes = new StringBuilder(); for (Entry.Param param : inputs) { paramsTypes.append(param.type.getCanonicalName()).append(","); } return format("%s(%s)", name, stripEnd(paramsTypes.toString(), ",")); } public byte[] fingerprintSignature() { return sha3(formatSignature().getBytes()); } public byte[] encodeSignature() { return fingerprintSignature(); } @JsonCreator public static Entry create(@JsonProperty("anonymous") boolean anonymous, @JsonProperty("constant") boolean constant, @JsonProperty("name") String name, @JsonProperty("inputs") List<Param> inputs, @JsonProperty("outputs") List<Param> outputs, @JsonProperty("type") Type type, @JsonProperty(value = "payable", required = false, defaultValue = "false") Boolean payable) { Entry result = null; switch (type) { case constructor: result = new Constructor(inputs, outputs); break; case function: result = new Function(constant, name, inputs, outputs, payable); break; case event: result = new Event(anonymous, name, inputs, outputs); break; } return result; } } public static class Constructor extends Entry { public Constructor(List<Param> inputs, List<Param> outputs) { super(null, null, "", inputs, outputs, Type.constructor, false); } public List<?> decode(byte[] encoded) { return Param.decodeList(inputs, encoded); } public String formatSignature(String contractName) { return format("function %s(%s)", contractName, join(inputs, ", ")); } } public static class Function extends Entry { private static final int ENCODED_SIGN_LENGTH = 4; public Function(boolean constant, String name, List<Param> inputs, List<Param> outputs, Boolean payable) { super(null, constant, name, inputs, outputs, Type.function, payable); } public byte[] encode(Object... args) { return ByteUtil.merge(encodeSignature(), encodeArguments(args)); } private byte[] encodeArguments(Object... args) { if (args.length > inputs.size()) throw new RuntimeException("Too many arguments: " + args.length + " > " + inputs.size()); int staticSize = 0; int dynamicCnt = 0; // calculating static size and number of dynamic params for (int i = 0; i < args.length; i++) { SolidityType type = inputs.get(i).type; if (type.isDynamicType()) { dynamicCnt++; } staticSize += type.getFixedSize(); } byte[][] bb = new byte[args.length + dynamicCnt][]; for (int curDynamicPtr = staticSize, curDynamicCnt = 0, i = 0; i < args.length; i++) { SolidityType type = inputs.get(i).type; if (type.isDynamicType()) { byte[] dynBB = type.encode(args[i]); bb[i] = encodeInt(curDynamicPtr); bb[args.length + curDynamicCnt] = dynBB; curDynamicCnt++; curDynamicPtr += dynBB.length; } else { bb[i] = type.encode(args[i]); } } return ByteUtil.merge(bb); } public List<?> decode(byte[] encoded) { return Param.decodeList(inputs, subarray(encoded, ENCODED_SIGN_LENGTH, encoded.length)); } public List<?> decodeResult(byte[] encoded) { return Param.decodeList(outputs, encoded); } @Override public byte[] encodeSignature() { return extractSignature(super.encodeSignature()); } public static byte[] extractSignature(byte[] data) { return subarray(data, 0, ENCODED_SIGN_LENGTH); } @Override public String toString() { String returnTail = ""; if (constant) { returnTail += " constant"; } if (!outputs.isEmpty()) { List<String> types = new ArrayList<>(); for (Param output : outputs) { types.add(output.type.getCanonicalName()); } returnTail += format(" returns(%s)", join(types, ", ")); } return format("function %s(%s)%s;", name, join(inputs, ", "), returnTail); } } public static class Event extends Entry { public Event(boolean anonymous, String name, List<Param> inputs, List<Param> outputs) { super(anonymous, null, name, inputs, outputs, Type.event, false); } public List<?> decode(byte[] data, byte[][] topics) { List<Object> result = new ArrayList<>(inputs.size()); byte[][] argTopics = anonymous ? topics : subarray(topics, 1, topics.length); List<?> indexed = Param.decodeList(filteredInputs(true), ByteUtil.merge(argTopics)); List<?> notIndexed = Param.decodeList(filteredInputs(false), data); for (Param input : inputs) { result.add(input.indexed ? indexed.remove(0) : notIndexed.remove(0)); } return result; } private List<Param> filteredInputs(final boolean indexed) { return select(inputs, new Predicate<Param>() { @Override public boolean evaluate(Param param) { return param.indexed == indexed; } }); } @Override public String toString() { return format("event %s(%s);", name, join(inputs, ", ")); } } }