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 }