/*
* 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.JsonValue;
import org.ethereum.util.ByteUtil;
import org.ethereum.vm.DataWord;
import org.spongycastle.util.encoders.Hex;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class SolidityType {
protected String name;
public SolidityType(String name) {
this.name = name;
}
/**
* The type name as it was specified in the interface description
*/
public String getName() {
return name;
}
/**
* The canonical type name (used for the method signature creation)
* E.g. 'int' - canonical 'int256'
*/
@JsonValue
public String getCanonicalName() {
return getName();
}
@JsonCreator
public static SolidityType getType(String typeName) {
if (typeName.contains("[")) return ArrayType.getType(typeName);
if ("bool".equals(typeName)) return new BoolType();
if (typeName.startsWith("int") || typeName.startsWith("uint")) return new IntType(typeName);
if ("address".equals(typeName)) return new AddressType();
if ("string".equals(typeName)) return new StringType();
if ("bytes".equals(typeName)) return new BytesType();
if ("function".equals(typeName)) return new FunctionType();
if (typeName.startsWith("bytes")) return new Bytes32Type(typeName);
throw new RuntimeException("Unknown type: " + typeName);
}
/**
* Encodes the value according to specific type rules
*
* @param value
*/
public abstract byte[] encode(Object value);
public abstract Object decode(byte[] encoded, int offset);
public Object decode(byte[] encoded) {
return decode(encoded, 0);
}
/**
* @return fixed size in bytes. For the dynamic types returns IntType.getFixedSize()
* which is effectively the int offset to dynamic data
*/
public int getFixedSize() {
return 32;
}
public boolean isDynamicType() {
return false;
}
@Override
public String toString() {
return getName();
}
public static abstract class ArrayType extends SolidityType {
public static ArrayType getType(String typeName) {
int idx1 = typeName.indexOf("[");
int idx2 = typeName.indexOf("]", idx1);
if (idx1 + 1 == idx2) {
return new DynamicArrayType(typeName);
} else {
return new StaticArrayType(typeName);
}
}
SolidityType elementType;
public ArrayType(String name) {
super(name);
int idx = name.indexOf("[");
String st = name.substring(0, idx);
int idx2 = name.indexOf("]", idx);
String subDim = idx2 + 1 == name.length() ? "" : name.substring(idx2 + 1);
elementType = SolidityType.getType(st + subDim);
}
@Override
public byte[] encode(Object value) {
if (value.getClass().isArray()) {
List<Object> elems = new ArrayList<>();
for (int i = 0; i < Array.getLength(value); i++) {
elems.add(Array.get(value, i));
}
return encodeList(elems);
} else if (value instanceof List) {
return encodeList((List) value);
} else {
throw new RuntimeException("List value expected for type " + getName());
}
}
public SolidityType getElementType() {
return elementType;
}
public abstract byte[] encodeList(List l);
}
public static class StaticArrayType extends ArrayType {
int size;
public StaticArrayType(String name) {
super(name);
int idx1 = name.indexOf("[");
int idx2 = name.indexOf("]", idx1);
String dim = name.substring(idx1 + 1, idx2);
size = Integer.parseInt(dim);
}
@Override
public String getCanonicalName() {
return elementType.getCanonicalName() + "[" + size + "]";
}
@Override
public byte[] encodeList(List l) {
if (l.size() != size) throw new RuntimeException("List size (" + l.size() + ") != " + size + " for type " + getName());
byte[][] elems = new byte[size][];
for (int i = 0; i < l.size(); i++) {
elems[i] = elementType.encode(l.get(i));
}
return ByteUtil.merge(elems);
}
@Override
public Object[] decode(byte[] encoded, int offset) {
Object[] result = new Object[size];
for (int i = 0; i < size; i++) {
result[i] = elementType.decode(encoded, offset + i * elementType.getFixedSize());
}
return result;
}
@Override
public int getFixedSize() {
// return negative if elementType is dynamic
return elementType.getFixedSize() * size;
}
}
public static class DynamicArrayType extends ArrayType {
public DynamicArrayType(String name) {
super(name);
}
@Override
public String getCanonicalName() {
return elementType.getCanonicalName() + "[]";
}
@Override
public byte[] encodeList(List l) {
byte[][] elems;
if (elementType.isDynamicType()) {
elems = new byte[l.size() * 2 + 1][];
elems[0] = IntType.encodeInt(l.size());
int offset = l.size() * 32;
for (int i = 0; i < l.size(); i++) {
elems[i + 1] = IntType.encodeInt(offset);
byte[] encoded = elementType.encode(l.get(i));
elems[l.size() + i + 1] = encoded;
offset += 32 * ((encoded.length - 1) / 32 + 1);
}
} else {
elems = new byte[l.size() + 1][];
elems[0] = IntType.encodeInt(l.size());
for (int i = 0; i < l.size(); i++) {
elems[i + 1] = elementType.encode(l.get(i));
}
}
return ByteUtil.merge(elems);
}
@Override
public Object decode(byte[] encoded, int origOffset) {
int len = IntType.decodeInt(encoded, origOffset).intValue();
origOffset += 32;
int offset = origOffset;
Object[] ret = new Object[len];
for (int i = 0; i < len; i++) {
if (elementType.isDynamicType()) {
ret[i] = elementType.decode(encoded, origOffset + IntType.decodeInt(encoded, offset).intValue());
} else {
ret[i] = elementType.decode(encoded, offset);
}
offset += elementType.getFixedSize();
}
return ret;
}
@Override
public boolean isDynamicType() {
return true;
}
}
public static class BytesType extends SolidityType {
protected BytesType(String name) {
super(name);
}
public BytesType() {
super("bytes");
}
@Override
public byte[] encode(Object value) {
if (!(value instanceof byte[])) throw new RuntimeException("byte[] value expected for type 'bytes'");
byte[] bb = (byte[]) value;
byte[] ret = new byte[((bb.length - 1) / 32 + 1) * 32]; // padding 32 bytes
System.arraycopy(bb, 0, ret, 0, bb.length);
return ByteUtil.merge(IntType.encodeInt(bb.length), ret);
}
@Override
public Object decode(byte[] encoded, int offset) {
int len = IntType.decodeInt(encoded, offset).intValue();
if (len == 0) return new byte[0];
offset += 32;
return Arrays.copyOfRange(encoded, offset, offset + len);
}
@Override
public boolean isDynamicType() {
return true;
}
}
public static class StringType extends BytesType {
public StringType() {
super("string");
}
@Override
public byte[] encode(Object value) {
if (!(value instanceof String)) throw new RuntimeException("String value expected for type 'string'");
return super.encode(((String)value).getBytes(StandardCharsets.UTF_8));
}
@Override
public Object decode(byte[] encoded, int offset) {
return new String((byte[]) super.decode(encoded, offset), StandardCharsets.UTF_8);
}
}
public static class Bytes32Type extends SolidityType {
public Bytes32Type(String s) {
super(s);
}
@Override
public byte[] encode(Object value) {
if (value instanceof Number) {
BigInteger bigInt = new BigInteger(value.toString());
return IntType.encodeInt(bigInt);
} else if (value instanceof String) {
byte[] ret = new byte[32];
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
System.arraycopy(bytes, 0, ret, 0, bytes.length);
return ret;
} else if (value instanceof byte[]) {
byte[] bytes = (byte[]) value;
byte[] ret = new byte[32];
System.arraycopy(bytes, 0, ret, 32 - bytes.length, bytes.length);
return ret;
}
throw new RuntimeException("Can't encode java type " + value.getClass() + " to bytes32");
}
@Override
public Object decode(byte[] encoded, int offset) {
return Arrays.copyOfRange(encoded, offset, offset + getFixedSize());
}
}
public static class AddressType extends IntType {
public AddressType() {
super("address");
}
@Override
public byte[] encode(Object value) {
if (value instanceof String && !((String)value).startsWith("0x")) {
// address is supposed to be always in hex
value = "0x" + value;
}
byte[] addr = super.encode(value);
for (int i = 0; i < 12; i++) {
if (addr[i] != 0) {
throw new RuntimeException("Invalid address (should be 20 bytes length): " + Hex.toHexString(addr));
}
}
return addr;
}
@Override
public Object decode(byte[] encoded, int offset) {
BigInteger bi = (BigInteger) super.decode(encoded, offset);
return ByteUtil.bigIntegerToBytes(bi, 20);
}
}
public static class IntType extends SolidityType {
public IntType(String name) {
super(name);
}
@Override
public String getCanonicalName() {
if (getName().equals("int")) return "int256";
if (getName().equals("uint")) return "uint256";
return super.getCanonicalName();
}
@Override
public byte[] encode(Object value) {
BigInteger bigInt;
if (value instanceof String) {
String s = ((String)value).toLowerCase().trim();
int radix = 10;
if (s.startsWith("0x")) {
s = s.substring(2);
radix = 16;
} else if (s.contains("a") || s.contains("b") || s.contains("c") ||
s.contains("d") || s.contains("e") || s.contains("f")) {
radix = 16;
}
bigInt = new BigInteger(s, radix);
} else if (value instanceof BigInteger) {
bigInt = (BigInteger) value;
} else if (value instanceof Number) {
bigInt = new BigInteger(value.toString());
} else if (value instanceof byte[]) {
bigInt = ByteUtil.bytesToBigInteger((byte[]) value);
} else {
throw new RuntimeException("Invalid value for type '" + this + "': " + value + " (" + value.getClass() + ")");
}
return encodeInt(bigInt);
}
@Override
public Object decode(byte[] encoded, int offset) {
return decodeInt(encoded, offset);
}
public static BigInteger decodeInt(byte[] encoded, int offset) {
return new BigInteger(Arrays.copyOfRange(encoded, offset, offset + 32));
}
public static byte[] encodeInt(int i) {
return encodeInt(new BigInteger("" + i));
}
public static byte[] encodeInt(BigInteger bigInt) {
return ByteUtil.bigIntegerToBytesSigned(bigInt, 32);
}
}
public static class BoolType extends IntType {
public BoolType() {
super("bool");
}
@Override
public byte[] encode(Object value) {
if (!(value instanceof Boolean)) throw new RuntimeException("Wrong value for bool type: " + value);
return super.encode(value == Boolean.TRUE ? 1 : 0);
}
@Override
public Object decode(byte[] encoded, int offset) {
return Boolean.valueOf(((Number) super.decode(encoded, offset)).intValue() != 0);
}
}
public static class FunctionType extends Bytes32Type {
public FunctionType() {
super("function");
}
@Override
public byte[] encode(Object value) {
if (!(value instanceof byte[])) throw new RuntimeException("Expected byte[] value for FunctionType");
if (((byte[]) value).length != 24) throw new RuntimeException("Expected byte[24] for FunctionType");
return super.encode(ByteUtil.merge((byte[]) value, new byte[8]));
}
}
}