Coverage Summary for Class: CallTransaction (org.ethereum.core)

Class Method, % Line, %
CallTransaction 0% (0/4) 0% (0/15)
CallTransaction$BoolType 0% (0/3) 0% (0/5)
CallTransaction$CallTransactionException 0% (0/1) 0% (0/2)
CallTransaction$Contract 40% (2/5) 22.6% (7/31)
CallTransaction$Function 39.1% (9/23) 26.7% (36/135)
CallTransaction$FunctionType 100% (2/2) 100% (5/5)
CallTransaction$IntType 0% (0/7) 0% (0/30)
CallTransaction$Invocation 0% (0/2) 0% (0/7)
CallTransaction$Param 33.3% (1/3) 25% (2/8)
CallTransaction$Type 0% (0/8) 0% (0/13)
Total 24.1% (14/58) 19.9% (50/251)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2017 RSK Labs Ltd. 4  * (derived from ethereumJ libr`ary, 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.core; 21  22 import co.rsk.core.RskAddress; 23 import com.fasterxml.jackson.annotation.JsonCreator; 24 import com.fasterxml.jackson.annotation.JsonGetter; 25 import com.fasterxml.jackson.annotation.JsonInclude; 26 import com.fasterxml.jackson.databind.DeserializationFeature; 27 import com.fasterxml.jackson.databind.ObjectMapper; 28 import org.ethereum.crypto.HashUtil; 29 import org.ethereum.solidity.SolidityType; 30 import org.ethereum.util.ByteUtil; 31 import org.ethereum.util.FastByteComparisons; 32  33 import java.io.IOException; 34 import java.math.BigInteger; 35 import java.nio.charset.StandardCharsets; 36 import java.util.Arrays; 37  38 import static java.lang.String.format; 39 import static org.apache.commons.lang3.ArrayUtils.subarray; 40 import static org.apache.commons.lang3.StringUtils.stripEnd; 41 import static org.ethereum.util.ByteUtil.longToBytesNoLeadZeroes; 42  43 /** 44  * Creates a contract function call transaction. 45  * Serializes arguments according to the function ABI . 46  * <p> 47  * Created by Anton Nashatyrev on 25.08.2015. 48  */ 49 public class CallTransaction { 50  51  private static final ObjectMapper DEFAULT_MAPPER = new ObjectMapper() 52  .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) 53  .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL); 54  55  public static Transaction createRawTransaction(long nonce, long gasPrice, long gasLimit, RskAddress toAddress, 56  long value, byte[] data, byte chainId) { 57  return Transaction 58  .builder() 59  .nonce(longToBytesNoLeadZeroes(nonce)) 60  .gasPrice(longToBytesNoLeadZeroes(gasPrice)) 61  .gasLimit(longToBytesNoLeadZeroes(gasLimit)) 62  .destination(toAddress.equals(RskAddress.nullAddress()) ? null : toAddress.getBytes()) 63  .value(longToBytesNoLeadZeroes(value)) 64  .data(data) 65  .chainId(chainId) 66  .build(); 67  } 68  69  70  public static Transaction createCallTransaction(long nonce, long gasPrice, long gasLimit, RskAddress toAddress, 71  long value, Function callFunc, byte chainId, Object... funcArgs) { 72  73  byte[] callData = callFunc.encode(funcArgs); 74  return createRawTransaction(nonce, gasPrice, gasLimit, toAddress, value, callData, chainId); 75  } 76  77  /** 78  * Generic ABI type 79  */ 80  public abstract static class Type { 81  protected String name; 82  83  public Type(String name) { 84  this.name = name; 85  } 86  87  /** 88  * The type name as it was specified in the interface description 89  */ 90  public String getName() { 91  return name; 92  } 93  94  /** 95  * The canonical type name (used for the method signature creation) 96  * E.g. 'int' - canonical 'int256' 97  */ 98  public String getCanonicalName() { 99  return getName(); 100  } 101  102  @JsonCreator 103  public static Type getType(String typeName) { 104  if ("bool".equals(typeName)) { 105  return new BoolType(); 106  } 107  108  if (typeName.startsWith("int") || typeName.startsWith("uint")) { 109  return new IntType(typeName); 110  } 111  112  throw new RuntimeException("Unknown type: " + typeName); 113  } 114  115  /** 116  * Encodes the value according to specific type rules 117  * 118  * @param value 119  */ 120  public abstract byte[] encode(Object value); 121  122  public abstract Object decode(byte[] encoded, int offset); 123  124  public Object decode(byte[] encoded) { 125  return decode(encoded, 0); 126  } 127  128  /** 129  * @return fixed size in bytes. For the dynamic types returns IntType.getFixedSize() 130  * which is effectively the int offset to dynamic data 131  */ 132  public int getFixedSize() { 133  return 32; 134  } 135  136  public boolean isDynamicType() { 137  return false; 138  } 139  140  @Override 141  public String toString() { 142  return getName(); 143  } 144  } 145  146  public static class IntType extends Type { 147  public IntType(String name) { 148  super(name); 149  } 150  151  @Override 152  public String getCanonicalName() { 153  if ("int".equals(getName())) { 154  return "int256"; 155  } 156  157  if ("uint".equals(getName())) { 158  return "uint256"; 159  } 160  161  return super.getCanonicalName(); 162  } 163  164  @Override 165  public byte[] encode(Object value) { 166  BigInteger bigInt; 167  168  if (value instanceof String) { 169  String s = ((String) value).toLowerCase().trim(); 170  int radix = 10; 171  if (s.startsWith("0x")) { 172  s = s.substring(2); 173  radix = 16; 174  } else if (s.contains("a") || s.contains("b") || s.contains("c") || 175  s.contains("d") || s.contains("e") || s.contains("f")) { 176  radix = 16; 177  } 178  bigInt = new BigInteger(s, radix); 179  } else if (value instanceof BigInteger) { 180  bigInt = (BigInteger) value; 181  } else if (value instanceof Number) { 182  bigInt = new BigInteger(value.toString()); 183  } else { 184  throw new RuntimeException("Invalid value for type '" + this + "': " + value + " (" + value.getClass() + ")"); 185  } 186  return encodeInt(bigInt); 187  } 188  189  @Override 190  public Object decode(byte[] encoded, int offset) { 191  return decodeInt(encoded, offset); 192  } 193  194  public static BigInteger decodeInt(byte[] encoded, int offset) { 195  return new BigInteger(Arrays.copyOfRange(encoded, offset, offset + 32)); 196  } 197  198  public static byte[] encodeInt(int i) { 199  return encodeInt(new BigInteger("" + i)); 200  } 201  202  public static byte[] encodeInt(BigInteger bigInt) { 203  byte[] ret = new byte[32]; 204  Arrays.fill(ret, bigInt.signum() < 0 ? (byte) 0xFF : 0); 205  byte[] bytes = bigInt.toByteArray(); 206  System.arraycopy(bytes, 0, ret, 32 - bytes.length, bytes.length); 207  return ret; 208  } 209  } 210  211  public static class BoolType extends IntType { 212  public BoolType() { 213  super("bool"); 214  } 215  216  @Override 217  public byte[] encode(Object value) { 218  if (!(value instanceof Boolean)) { 219  throw new RuntimeException("Wrong value for bool type: " + value); 220  } 221  return super.encode(value == Boolean.TRUE ? 1 : 0); 222  } 223  224  @Override 225  public Object decode(byte[] encoded, int offset) { 226  return Boolean.valueOf(((Number) super.decode(encoded, offset)).intValue() != 0); 227  } 228  } 229  230  @JsonInclude(JsonInclude.Include.NON_NULL) 231  public static class Param { 232  public Boolean indexed; 233  public String name; 234  public SolidityType type; 235  236  public Param() { 237  } 238  239  public Param(Boolean indexed, String name, SolidityType type) { 240  this.indexed = indexed; 241  this.name = name; 242  this.type = type; 243  } 244  245  @JsonGetter("type") 246  public String getType() { 247  return type.getName(); 248  } 249  } 250  251  public enum FunctionType { 252  constructor, 253  function, 254  event, 255  fallback 256  } 257  258  public static class Function { 259  public boolean anonymous; 260  public boolean constant; 261  public boolean payable; 262  public String name = ""; 263  public Param[] inputs = new Param[0]; 264  public Param[] outputs = new Param[0]; 265  public FunctionType type; 266  267  private Function() { 268  } 269  270  public byte[] encode(Object... args) { 271  return ByteUtil.merge(encodeSignature(), encodeArguments(args)); 272  } 273  274  public byte[] encodeArguments(Param[] params, Object... args) { 275  if (args.length > params.length) { 276  throw new RuntimeException("Too many arguments: " + args.length + " > " + params.length); 277  } 278  279  int staticSize = 0; 280  int dynamicCnt = 0; 281  // calculating static size and number of dynamic params 282  for (int i = 0; i < args.length; i++) { 283  Param param = params[i]; 284  if (param.type.isDynamicType()) { 285  dynamicCnt++; 286  } 287  staticSize += param.type.getFixedSize(); 288  } 289  290  byte[][] bb = new byte[args.length + dynamicCnt][]; 291  292  int curDynamicPtr = staticSize; 293  int curDynamicCnt = 0; 294  for (int i = 0; i < args.length; i++) { 295  Param param = params[i]; 296  if (param.type.isDynamicType()) { 297  byte[] dynBB = param.type.encode(args[i]); 298  bb[i] = IntType.encodeInt(curDynamicPtr); 299  bb[args.length + curDynamicCnt] = dynBB; 300  curDynamicCnt++; 301  curDynamicPtr += dynBB.length; 302  } else { 303  bb[i] = param.type.encode(args[i]); 304  } 305  } 306  return ByteUtil.merge(bb); 307  } 308  309  public byte[] encodeArguments(Object... args) { 310  return encodeData(inputs, args); 311  } 312  313  //args order should be the same as function params order 314  public byte[][] encodeEventTopics(Object... args) { 315  checkFunctionType(FunctionType.event); 316  317  Param[] topicInputs = Arrays.stream(inputs).filter(i -> i.indexed).toArray(Param[]::new); 318  int topicsCount = topicInputs.length; 319  checkArgumentsCount(topicsCount, args.length); 320  321  topicsCount++; //Plus one for the event signature 322  byte[][] topics = new byte[topicsCount][]; 323  int topicIndex = 1; 324  topics[0] = this.encodeSignatureLong(); 325  for (int i = 0; i < args.length; i++) { 326  Param param = topicInputs[i]; 327  if (param.type.isDynamicType()) { 328  //Dynamic data types nor array (dynamic or static) are currently not supported as topics 329  topics[topicIndex] = HashUtil.keccak256(param.type.encode(args[i])); 330  } else { 331  topics[topicIndex] = param.type.encode(args[i]); 332  } 333  topicIndex++; 334  } 335  336  return topics; 337  } 338  339  //args order should be the same as function params order 340  public byte[] encodeEventData(Object... args) { 341  checkFunctionType(FunctionType.event); 342  343  Param[] dataInputs = Arrays.stream(inputs).filter(i -> !i.indexed).toArray(Param[]::new); 344  checkArgumentsCount(dataInputs.length, args.length); 345  346  return encodeData(dataInputs, args); 347  } 348  349  public Object[] decodeEventData(byte[] encodedData) { 350  checkFunctionType(FunctionType.event); 351  Param[] dataInputs = Arrays.stream(inputs).filter(i -> !i.indexed).toArray(Param[]::new); 352  353  return decode(encodedData, dataInputs); 354  } 355  356  private void checkFunctionType(FunctionType expected) { 357  if (this.type != expected) { 358  throw new RuntimeException(String.format("Wrong function type. Expected %s, Received: %s", expected, this.type)); 359  } 360  } 361  362  private void checkArgumentsCount(int expected, int received) { 363  if (expected != received) { 364  throw new RuntimeException(String.format("Wrong amount of arguments. Expected %d, Received %d", expected, received)); 365  } 366  } 367  368  //args of type byte32 should be passed as Hex string for encoding to work 369  private byte[] encodeData(Param[] params, Object... args) { 370  if (args.length > params.length) { 371  throw new CallTransactionException("Too many arguments: " + args.length + " > " + params.length); 372  } 373  374  int staticSize = 0; 375  int dynamicCnt = 0; 376  // calculating static size and number of dynamic params 377  for (int i = 0; i < args.length; i++) { 378  Param param = params[i]; 379  if (param.type.isDynamicType()) { 380  dynamicCnt++; 381  } 382  staticSize += param.type.getFixedSize(); 383  } 384  385  byte[][] bb = new byte[args.length + dynamicCnt][]; 386  387  int curDynamicPtr = staticSize; 388  int curDynamicCnt = 0; 389  for (int i = 0; i < args.length; i++) { 390  Param param = params[i]; 391  if (param.type.isDynamicType()) { 392  byte[] dynBB = param.type.encode(args[i]); 393  bb[i] = SolidityType.IntType.encodeInt(curDynamicPtr); 394  bb[args.length + curDynamicCnt] = dynBB; 395  curDynamicCnt++; 396  curDynamicPtr += dynBB.length; 397  } else { 398  bb[i] = param.type.encode(args[i]); 399  } 400  } 401  return ByteUtil.merge(bb); 402  } 403  404  public byte[] encodeOutputs(Object... args) { 405  return encodeArguments(outputs, args); 406  } 407  408  private Object[] decode(byte[] encoded, Param[] params) { 409  Object[] ret = new Object[params.length]; 410  411  int off = 0; 412  for (int i = 0; i < params.length; i++) { 413  if (params[i].type.isDynamicType()) { 414  ret[i] = params[i].type.decode(encoded, IntType.decodeInt(encoded, off).intValue()); 415  } else { 416  ret[i] = params[i].type.decode(encoded, off); 417  } 418  off += params[i].type.getFixedSize(); 419  } 420  return ret; 421  } 422  423  public Object[] decode(byte[] encoded) { 424  return decode(subarray(encoded, 4, encoded.length), inputs); 425  } 426  427  public Object[] decodeResult(byte[] encodedRet) { 428  return decode(encodedRet, outputs); 429  } 430  431  public String formatSignature() { 432  StringBuilder paramsTypes = new StringBuilder(); 433  for (Param param : inputs) { 434  paramsTypes.append(param.type.getCanonicalName()).append(","); 435  } 436  437  return format("%s(%s)", name, stripEnd(paramsTypes.toString(), ",")); 438  } 439  440  public byte[] encodeSignatureLong() { 441  String signature = formatSignature(); 442  return HashUtil.keccak256(signature.getBytes(StandardCharsets.UTF_8)); 443  } 444  445  public byte[] encodeSignature() { 446  return Arrays.copyOfRange(encodeSignatureLong(), 0, 4); 447  } 448  449  @Override 450  public String toString() { 451  return formatSignature(); 452  } 453  454  public static Function fromJsonInterface(String json) { 455  try { 456  return DEFAULT_MAPPER.readValue(json, Function.class); 457  } catch (IOException e) { 458  throw new RuntimeException(e); 459  } 460  } 461  462  public static Function fromSignature(String funcName, String... paramTypes) { 463  return fromSignature(funcName, paramTypes, new String[0]); 464  } 465  466  public static Function fromSignature(String funcName, String[] paramTypes, String[] resultTypes) { 467  Function ret = new Function(); 468  ret.name = funcName; 469  ret.constant = false; 470  ret.type = FunctionType.function; 471  ret.inputs = new Param[paramTypes.length]; 472  for (int i = 0; i < paramTypes.length; i++) { 473  ret.inputs[i] = new Param(); 474  ret.inputs[i].name = "param" + i; 475  ret.inputs[i].type = SolidityType.getType(paramTypes[i]); 476  } 477  ret.outputs = new Param[resultTypes.length]; 478  for (int i = 0; i < resultTypes.length; i++) { 479  ret.outputs[i] = new Param(); 480  ret.outputs[i].name = "res" + i; 481  ret.outputs[i].type = SolidityType.getType(resultTypes[i]); 482  } 483  return ret; 484  } 485  486  public static Function fromEventSignature(String eventName, Param[] params) { 487  validateEventParams(params); 488  489  Function event = new Function(); 490  event.name = eventName; 491  event.constant = false; 492  event.type = FunctionType.event; 493  event.inputs = params; 494  return event; 495  } 496  497  private static void validateEventParams(Param[] params) { 498  int topicsCount = 0; 499  for (Param param : params) { 500  if (Boolean.TRUE.equals(param.indexed)) { 501  topicsCount++; 502  if (param.type.isDynamicType() || param.type.getName().contains("[")) { 503  throw new RuntimeException("Dynamic or array type topics are not supported"); 504  } 505  } 506  } 507  508  if (topicsCount > 3) { 509  throw new RuntimeException("Too many indexed params: " + topicsCount + ". Max 3 indexed params allowed."); 510  } 511  } 512  } 513  514  public static class Contract { 515  516  public Contract(String jsonInterface) { 517  try { 518  functions = new ObjectMapper().readValue(jsonInterface, Function[].class); 519  } catch (IOException e) { 520  throw new RuntimeException(e); 521  } 522  } 523  524  public Function getByName(String name) { 525  for (Function function : functions) { 526  if (name.equals(function.name)) { 527  return function; 528  } 529  } 530  return null; 531  } 532  533  public Function getConstructor() { 534  for (Function function : functions) { 535  if (function.type == FunctionType.constructor) { 536  return function; 537  } 538  } 539  return null; 540  } 541  542  private Function getBySignatureHash(byte[] hash) { 543  if (hash.length == 4) { 544  for (Function function : functions) { 545  if (FastByteComparisons.equalBytes(function.encodeSignature(), hash)) { 546  return function; 547  } 548  } 549  } else if (hash.length == 32) { 550  for (Function function : functions) { 551  if (FastByteComparisons.equalBytes(function.encodeSignatureLong(), hash)) { 552  return function; 553  } 554  } 555  } else { 556  throw new CallTransactionException("Function signature hash should be 4 or 32 bytes length"); 557  } 558  return null; 559  } 560  561  /** 562  * Parses function and its arguments from transaction invocation binary data 563  */ 564  public Invocation parseInvocation(byte[] data) { 565  if (data.length < 4) { 566  throw new CallTransactionException("Invalid data length: " + data.length); 567  } 568  Function function = getBySignatureHash(Arrays.copyOfRange(data, 0, 4)); 569  if (function == null) { 570  throw new CallTransactionException("Can't find function/event by it signature"); 571  } 572  Object[] args = function.decode(data); 573  return new Invocation(this, function, args); 574  } 575  576  public Function[] functions; 577  } 578  579  580  /** 581  * Represents either function invocation with its arguments 582  * or Event instance with its data members 583  */ 584  public static class Invocation { 585  public final Contract contract; 586  public final Function function; 587  public final Object[] args; 588  589  public Invocation(Contract contract, Function function, Object[] args) { 590  this.contract = contract; 591  this.function = function; 592  this.args = args; 593  } 594  595  @Override 596  public String toString() { 597  return "[" + "contract=" + contract + 598  (function.type == FunctionType.event ? ", event=" : ", function=") 599  + function + ", args=" + Arrays.toString(args) + ']'; 600  } 601  } 602  603  public static class CallTransactionException extends RuntimeException { 604  public CallTransactionException(String msg) { 605  super(msg); 606  } 607  } 608 }