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 }