Coverage Summary for Class: SolidityType (org.ethereum.solidity)

Class Method, % Line, %
SolidityType 50% (4/8) 45.8% (11/24)
SolidityType$AddressType 0% (0/3) 0% (0/11)
SolidityType$ArrayType 0% (0/3) 0% (0/20)
SolidityType$BoolType 0% (0/3) 0% (0/6)
SolidityType$Bytes32Type 0% (0/3) 0% (0/19)
SolidityType$BytesType 20% (1/5) 14.3% (2/14)
SolidityType$DynamicArrayType 0% (0/5) 0% (0/33)
SolidityType$IntType 0% (0/7) 0% (0/33)
SolidityType$StaticArrayType 0% (0/5) 0% (0/19)
SolidityType$StringType 33.3% (1/3) 33.3% (2/6)
Total 13.3% (6/45) 8.1% (15/185)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2017 RSK Labs Ltd. 4  * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>) 5  * 6  * This program is free software: you can redistribute it and/or modify 7  * it under the terms of the GNU Lesser General Public License as published by 8  * the Free Software Foundation, either version 3 of the License, or 9  * (at your option) any later version. 10  * 11  * This program is distributed in the hope that it will be useful, 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14  * GNU Lesser General Public License for more details. 15  * 16  * You should have received a copy of the GNU Lesser General Public License 17  * along with this program. If not, see <http://www.gnu.org/licenses/>. 18  */ 19  20 package org.ethereum.solidity; 21  22 import com.fasterxml.jackson.annotation.JsonCreator; 23 import com.fasterxml.jackson.annotation.JsonValue; 24 import org.ethereum.util.ByteUtil; 25 import org.ethereum.util.Utils; 26 import org.ethereum.vm.DataWord; 27  28 import java.lang.reflect.Array; 29 import java.math.BigInteger; 30 import java.nio.charset.StandardCharsets; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34  35 public abstract class SolidityType { 36  37  public static final String BYTES = "bytes"; 38  public static final String BYTES32 = "bytes32"; 39  public static final String STRING = "string"; 40  public static final String ADDRESS = "address"; 41  public static final String BOOL = "bool"; 42  public static final String INT = "int"; 43  public static final String INT256 = "int256"; 44  public static final String UINT = "uint"; 45  46  protected String name; 47  private final static int INT_32_SIZE = 32; 48  49  public SolidityType(String name) { 50  this.name = name; 51  } 52  53  /** 54  * The type name as it was specified in the interface description 55  */ 56  public String getName() { 57  return name; 58  } 59  60  /** 61  * The canonical type name (used for the method signature creation) 62  * E.g. 'int' - canonical 'int256' 63  */ 64  @JsonValue 65  public String getCanonicalName() { 66  return getName(); 67  } 68  69  @JsonCreator 70  public static SolidityType getType(String typeName) { 71  if (typeName.contains("[")) { 72  return ArrayType.getType(typeName); 73  } 74  if (BOOL.equals(typeName)) { 75  return new BoolType(); 76  } 77  if (typeName.startsWith(INT) || typeName.startsWith(UINT)) { 78  return new IntType(typeName); 79  } 80  if (ADDRESS.equals(typeName)) { 81  return new AddressType(); 82  } 83  if (STRING.equals(typeName)) { 84  return new StringType(); 85  } 86  if (BYTES.equals(typeName)) { 87  return new BytesType(); 88  } 89  if (typeName.startsWith(BYTES)) { 90  return new Bytes32Type(typeName); 91  } 92  throw new RuntimeException("Unknown type: " + typeName); 93  } 94  95  /** 96  * Encodes the value according to specific type rules 97  * 98  * @param value 99  */ 100  public abstract byte[] encode(Object value); 101  102  public abstract Object decode(byte[] encoded, int offset); 103  104  public Object decode(byte[] encoded) { 105  return decode(encoded, 0); 106  } 107  108  /** 109  * @return fixed size in bytes. For the dynamic types returns IntType.getFixedSize() 110  * which is effectively the int offset to dynamic data 111  */ 112  public int getFixedSize() { 113  return 32; 114  } 115  116  public boolean isDynamicType() { 117  return false; 118  } 119  120  @Override 121  public String toString() { 122  return getName(); 123  } 124  125  126  public abstract static class ArrayType extends SolidityType { 127  public static ArrayType getType(String typeName) { 128  int idx1 = typeName.indexOf("["); 129  int idx2 = typeName.indexOf("]", idx1); 130  if (idx1 + 1 == idx2) { 131  return new DynamicArrayType(typeName); 132  } else { 133  return new StaticArrayType(typeName); 134  } 135  } 136  137  SolidityType elementType; 138  139  public ArrayType(String name) { 140  super(name); 141  int idx = name.indexOf("["); 142  String st = name.substring(0, idx); 143  int idx2 = name.indexOf("]", idx); 144  String subDim = idx2 + 1 == name.length() ? "" : name.substring(idx2 + 1); 145  elementType = SolidityType.getType(st + subDim); 146  } 147  148  @Override 149  public byte[] encode(Object value) { 150  if (value.getClass().isArray()) { 151  List<Object> elems = new ArrayList<>(); 152  for (int i = 0; i < Array.getLength(value); i++) { 153  elems.add(Array.get(value, i)); 154  } 155  return encodeList(elems); 156  } else if (value instanceof List) { 157  return encodeList((List) value); 158  } else { 159  throw new RuntimeException("List value expected for type " + getName()); 160  } 161  } 162  163  public abstract byte[] encodeList(List l); 164  } 165  166  public static class StaticArrayType extends ArrayType { 167  int size; 168  private final int fixedSize; 169  170  public StaticArrayType(String name) { 171  super(name); 172  int idx1 = name.indexOf("["); 173  int idx2 = name.indexOf("]", idx1); 174  String dim = name.substring(idx1 + 1, idx2); 175  size = Integer.parseInt(dim); 176  fixedSize = Math.multiplyExact(elementType.getFixedSize(), size); 177  } 178  179  @Override 180  public String getCanonicalName() { 181  return elementType.getCanonicalName() + "[" + size + "]"; 182  } 183  184  @Override 185  public byte[] encodeList(List l) { 186  if (l.size() != size) { 187  throw new RuntimeException("List size (" + l.size() + ") != " + size + " for type " + getName()); 188  } 189  byte[][] elems = new byte[size][]; 190  for (int i = 0; i < l.size(); i++) { 191  elems[i] = elementType.encode(l.get(i)); 192  } 193  return ByteUtil.merge(elems); 194  } 195  196  @Override 197  public Object[] decode(byte[] encoded, int offset) { 198  Utils.validateArrayAllegedSize(encoded, offset, getFixedSize()); 199  Object[] result = new Object[size]; 200  for (int i = 0; i < size; i++) { 201  result[i] = elementType.decode(encoded, offset + i * elementType.getFixedSize()); 202  } 203  204  return result; 205  } 206  207  @Override 208  public int getFixedSize() { 209  return fixedSize; 210  } 211  } 212  213  public static class DynamicArrayType extends ArrayType { 214  public DynamicArrayType(String name) { 215  super(name); 216  } 217  218  @Override 219  public String getCanonicalName() { 220  return elementType.getCanonicalName() + "[]"; 221  } 222  223  @Override 224  public byte[] encodeList(List l) { 225  byte[][] elems; 226  if (elementType.isDynamicType()) { 227  elems = new byte[l.size() * 2 + 1][]; 228  elems[0] = IntType.encodeInt(l.size()); 229  int offset = l.size() * 32; 230  for (int i = 0; i < l.size(); i++) { 231  elems[i + 1] = IntType.encodeInt(offset); 232  byte[] encoded = elementType.encode(l.get(i)); 233  elems[l.size() + i + 1] = encoded; 234  offset += 32 * ((encoded.length - 1) / 32 + 1); 235  } 236  } else { 237  elems = new byte[l.size() + 1][]; 238  elems[0] = IntType.encodeInt(l.size()); 239  240  for (int i = 0; i < l.size(); i++) { 241  elems[i + 1] = elementType.encode(l.get(i)); 242  } 243  } 244  return ByteUtil.merge(elems); 245  } 246  247  @Override 248  public Object decode(byte[] encoded, int origOffset) { 249  if (encoded.length == 0) { 250  return new Object[0]; 251  } 252  int len = IntType.decodeInt(encoded, origOffset).intValue(); 253  int offset = origOffset + IntType.INT_SIZE; 254  // This is a lower bound check as we don't know the exact length of each element 255  // Sub-elements will perform stricter checks 256  Utils.validateArrayAllegedSize(encoded, offset, len); 257  Object[] ret = new Object[len]; 258  259  int elementOffset = offset; 260  for (int i = 0; i < len; i++) { 261  if (elementType.isDynamicType()) { 262  int dynamicElementOffset = IntType.decodeInt(encoded, elementOffset).intValue(); 263  ret[i] = elementType.decode(encoded, Math.addExact(offset, dynamicElementOffset)); 264  } else { 265  ret[i] = elementType.decode(encoded, elementOffset); 266  } 267  elementOffset = Math.addExact(elementOffset, elementType.getFixedSize()); 268  } 269  return ret; 270  } 271  272  @Override 273  public boolean isDynamicType() { 274  return true; 275  } 276  } 277  278  public static class BytesType extends SolidityType { 279  protected BytesType(String name) { 280  super(name); 281  } 282  283  public BytesType() { 284  super(BYTES); 285  } 286  287  @Override 288  public byte[] encode(Object value) { 289  if (!(value instanceof byte[])) { 290  throw new RuntimeException("byte[] value expected for type 'bytes'"); 291  } 292  byte[] bb = (byte[]) value; 293  byte[] ret = new byte[((bb.length - 1) / 32 + 1) * 32]; // padding 32 bytes 294  System.arraycopy(bb, 0, ret, 0, bb.length); 295  296  return ByteUtil.merge(IntType.encodeInt(bb.length), ret); 297  } 298  299  @Override 300  public Object decode(byte[] encoded, int offset) { 301  int len = IntType.decodeInt(encoded, offset).intValue(); 302  offset += IntType.INT_SIZE; 303  return Utils.safeCopyOfRange(encoded, offset, len); 304  } 305  306  @Override 307  public boolean isDynamicType() { 308  return true; 309  } 310  } 311  312  public static class StringType extends BytesType { 313  public StringType() { 314  super(STRING); 315  } 316  317  @Override 318  public byte[] encode(Object value) { 319  if (!(value instanceof String)) { 320  throw new RuntimeException("String value expected for type 'string'"); 321  } 322  return super.encode(((String) value).getBytes(StandardCharsets.UTF_8)); 323  } 324  325  @Override 326  public Object decode(byte[] encoded, int offset) { 327  return new String((byte[]) super.decode(encoded, offset), StandardCharsets.UTF_8); 328  } 329  } 330  331  public static class Bytes32Type extends SolidityType { 332  public Bytes32Type(String s) { 333  super(s); 334  } 335  336  @Override 337  public byte[] encode(Object value) { 338  if (value instanceof Number) { 339  BigInteger bigInt = new BigInteger(value.toString()); 340  return IntType.encodeInt(bigInt); 341  } else if (value instanceof String) { 342  byte[] ret = new byte[INT_32_SIZE]; 343  byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8); 344  System.arraycopy(bytes, 0, ret, 0, bytes.length); 345  return ret; 346  } else if (value instanceof byte[]) { 347  byte[] bytes = (byte[]) value; 348  byte[] ret = new byte[INT_32_SIZE]; 349  System.arraycopy(bytes, 0, ret, INT_32_SIZE - bytes.length, bytes.length); 350  return ret; 351  }else if (value == null) { 352  throw new RuntimeException("Can't encode null to bytes32"); 353  } 354  throw new RuntimeException("Can't encode java type " + value.getClass() + " to bytes32"); 355  } 356  357  @Override 358  public Object decode(byte[] encoded, int offset) { 359  return Utils.safeCopyOfRange(encoded, offset, getFixedSize()); 360  } 361  } 362  363  public static class AddressType extends IntType { 364  public AddressType() { 365  super(SolidityType.ADDRESS); 366  } 367  368  @Override 369  public byte[] encode(Object value) { 370  if (value instanceof String && !((String) value).startsWith("0x")) { 371  // address is supposed to be always in hex 372  value = "0x" + value; 373  } 374  byte[] addr = super.encode(value); 375  for (int i = 0; i < 12; i++) { 376  if (addr[i] != 0) { 377  throw new RuntimeException("Invalid address (should be 20 bytes length): " + ByteUtil.toHexString(addr)); 378  } 379  } 380  return addr; 381  } 382  383  @Override 384  public Object decode(byte[] encoded, int offset) { 385  BigInteger asBigInteger = (BigInteger) super.decode(encoded, offset); 386  return DataWord.valueOf(asBigInteger.toByteArray()); 387  } 388  } 389  390  public static class IntType extends SolidityType { 391  392  public static final int INT_SIZE = 32; 393  394  public IntType(String name) { 395  super(name); 396  } 397  398  @Override 399  public String getCanonicalName() { 400  if (getName().equals(INT)) { 401  return "int256"; 402  } 403  if (getName().equals(UINT)) { 404  return "uint256"; 405  } 406  return super.getCanonicalName(); 407  } 408  409  @Override 410  public byte[] encode(Object value) { 411  BigInteger bigInt; 412  413  if (value instanceof String) { 414  String s = ((String) value).toLowerCase().trim(); 415  int radix = 10; 416  if (s.startsWith("0x")) { 417  s = s.substring(2); 418  radix = 16; 419  } else if (s.contains("a") || s.contains("b") || s.contains("c") || 420  s.contains("d") || s.contains("e") || s.contains("f")) { 421  radix = 16; 422  } 423  bigInt = new BigInteger(s, radix); 424  } else if (value instanceof BigInteger) { 425  bigInt = (BigInteger) value; 426  } else if (value instanceof Number) { 427  bigInt = new BigInteger(value.toString()); 428  } else { 429  throw new RuntimeException("Invalid value for type '" + this + "': " + value + " (" + value.getClass() + ")"); 430  } 431  return encodeInt(bigInt); 432  } 433  434  @Override 435  public Object decode(byte[] encoded, int offset) { 436  return decodeInt(encoded, offset); 437  } 438  439  public static BigInteger decodeInt(byte[] encoded, int offset) { 440  // This is here because getGasForData might send an empty payload which will produce an exception 441  // But currently the bridge would return the cost of RELEASE_BTC in this situation 442  if (encoded.length == 0) { 443  return BigInteger.ZERO; 444  } 445  return new BigInteger(Utils.safeCopyOfRange(encoded, offset, INT_SIZE)); 446  } 447  448  public static byte[] encodeInt(int i) { 449  return encodeInt(new BigInteger("" + i)); 450  } 451  452  public static byte[] encodeInt(BigInteger bigInt) { 453  byte[] ret = new byte[INT_SIZE]; 454  Arrays.fill(ret, bigInt.signum() < 0 ? (byte) 0xFF : 0); 455  byte[] bytes = bigInt.toByteArray(); 456  System.arraycopy(bytes, 0, ret, INT_SIZE - bytes.length, bytes.length); 457  return ret; 458  } 459  } 460  461  public static class BoolType extends IntType { 462  public BoolType() { 463  super(BOOL); 464  } 465  466  @Override 467  public byte[] encode(Object value) { 468  if (!(value instanceof Boolean)) { 469  throw new RuntimeException("Wrong value for bool type: " + value); 470  } 471  return super.encode(value == Boolean.TRUE ? 1 : 0); 472  } 473  474  @Override 475  public Object decode(byte[] encoded, int offset) { 476  return Boolean.valueOf(((Number) super.decode(encoded, offset)).intValue() != 0); 477  } 478  } 479  480 }