Skip to content

Commit

Permalink
disallow additional instances of UnitType (except BigDecimalTypes);
Browse files Browse the repository at this point in the history
write message to System.err before throwing IllegalStateException;
  • Loading branch information
esaulpaugh committed Sep 26, 2024
1 parent 27fbe5b commit 7e76ecb
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 89 deletions.
7 changes: 6 additions & 1 deletion src/main/java/com/esaulpaugh/headlong/abi/ABIType.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ public abstract class ABIType<J> {
)
|| (c == ByteType.class && /* enforce singleton */ ByteType.INSTANCE == null) ;
if (!permitted) {
throw new IllegalStateException ("lol no");
System.err.println("unexpected instance creation rejected: " + c.getName());
throw illegalState("class not permitted");
}
}

Expand Down Expand Up @@ -358,4 +359,8 @@ static String padLabel(int leftPadding, String unpadded) {
}
return padded.toString();
}

static IllegalStateException illegalState(String msg) {
return new IllegalStateException(msg);
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/esaulpaugh/headlong/abi/Address.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
public final class Address {

private static final int PREFIX_LEN = 2;
private static final int ADDRESS_DATA_BYTES = TypeFactory.ADDRESS_BIT_LEN / Byte.SIZE;
private static final int ADDRESS_DATA_BYTES = 160 / Byte.SIZE;
private static final int ADDRESS_HEX_CHARS = ADDRESS_DATA_BYTES * FastHex.CHARS_PER_BYTE;
private static final int ADDRESS_LEN_CHARS = PREFIX_LEN + ADDRESS_HEX_CHARS;
private static final int HEX_RADIX = 16;
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/com/esaulpaugh/headlong/abi/AddressType.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@
/** The {@link ABIType} for {@link Address}. Corresponds to the "address" type. */
public final class AddressType extends UnitType<Address> {

private static final int ADDRESS_BIT_LEN = 160;

static final AddressType INSTANCE = new AddressType();
static {
UnitType.ensureInitialized();
}

private static final BigIntegerType ADDRESS_INNER = new BigIntegerType("ADDRESS_INNER", TypeFactory.ADDRESS_BIT_LEN, true);
private static final BigIntegerType ADDRESS_INNER = new BigIntegerType("ADDRESS_INNER", ADDRESS_BIT_LEN, true);

private AddressType() {
super("address", Address.class, TypeFactory.ADDRESS_BIT_LEN, true);
super("address", Address.class, ADDRESS_BIT_LEN, true);
}

@Override
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/esaulpaugh/headlong/abi/BigDecimalType.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
/** Represents a decimal type such as fixed or ufixed. */
public final class BigDecimalType extends UnitType<BigDecimal> {

static final Object __ = "";

static {
UnitType.ensureInitialized();
}

final int scale;

BigDecimalType(String canonicalTypeString, int bitLength, int scale, boolean unsigned) {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/esaulpaugh/headlong/abi/BigIntegerType.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
/** Represents an integer type such as uint64 or int256. */
public final class BigIntegerType extends UnitType<BigInteger> {

static final Object __ = "";

static {
UnitType.ensureInitialized();
}

BigIntegerType(String canonicalType, int bitLength, boolean unsigned) {
super(canonicalType, BigInteger.class, bitLength, unsigned);
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/esaulpaugh/headlong/abi/BooleanType.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
public final class BooleanType extends UnitType<Boolean> {

static final BooleanType INSTANCE = new BooleanType();
static {
UnitType.ensureInitialized();
}

private static final byte[] BOOLEAN_FALSE = new byte[UNIT_LENGTH_BYTES];
private static final byte[] BOOLEAN_TRUE = new byte[UNIT_LENGTH_BYTES];
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/esaulpaugh/headlong/abi/ByteType.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
public final class ByteType extends ABIType<Byte> {

static final ByteType INSTANCE = new ByteType();
static {
UnitType.ensureInitialized();
}

private ByteType() {
super("BYTE", Byte.class, false);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/esaulpaugh/headlong/abi/IntType.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public final class IntType extends UnitType<Integer> {

static final IntType UINT21 = new IntType("uint21", 21, true); // small bit length for Denial-of-Service protection
static final IntType UINT31 = new IntType("uint31", 31, true);
static {
UnitType.ensureInitialized();
}

IntType(String canonicalType, int bitLength, boolean unsigned) {
super(canonicalType, Integer.class, bitLength, unsigned);
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/esaulpaugh/headlong/abi/LongType.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
/** Represents a long integer type such as int40, int64, uint32, or uint56. */
public final class LongType extends UnitType<Long> {

static final Object __ = "";

static {
UnitType.ensureInitialized();
}

LongType(String canonicalType, int bitLength, boolean unsigned) {
super(canonicalType, Long.class, bitLength, unsigned);
}
Expand Down
84 changes: 5 additions & 79 deletions src/main/java/com/esaulpaugh/headlong/abi/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,95 +18,21 @@
import com.esaulpaugh.headlong.util.Integers;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static com.esaulpaugh.headlong.abi.ArrayType.DYNAMIC_LENGTH;
import static com.esaulpaugh.headlong.abi.ArrayType.STRING_ARRAY_CLASS;
import static com.esaulpaugh.headlong.abi.ArrayType.STRING_CLASS;

/** Creates the appropriate {@link ABIType} object for a given type string. */
public final class TypeFactory {

private TypeFactory() {}

static final int ADDRESS_BIT_LEN = 160;

private static final int FIXED_BIT_LEN = 128;
private static final int FIXED_SCALE = 18;

private static final int FUNCTION_BYTE_LEN = 24;

private static final int MAX_LENGTH_CHARS = 2_000;

private static final Map<String, ABIType<?>> BASE_TYPE_MAP;
private static final Map<String, ABIType<?>> LEGACY_BASE_TYPE_MAP;

static {
final Map<String, ABIType<?>> local = new HashMap<>(256);

// optimized insertion order
local.put("string", new ArrayType<ByteType, Byte, String>("string", STRING_CLASS, ByteType.INSTANCE, DYNAMIC_LENGTH, STRING_ARRAY_CLASS, ABIType.FLAGS_NONE));
local.put("bool", BooleanType.INSTANCE);

for (int n = 1; n <= 32; n++) {
mapByteArray(local, "bytes" + n, n);
}
mapByteArray(local, "function", FUNCTION_BYTE_LEN);
mapByteArray(local, "bytes", DYNAMIC_LENGTH);

for (int n = 8; n <= 24; n += 8) mapInt(local, "uint" + n, n, true);
for (int n = 32; n <= 56; n += 8) mapLong(local, "uint" + n, n, true);
for (int n = 64; n <= 256; n += 8) mapBigInteger(local, "uint" + n, n, true);

local.put("uint", local.get("uint256"));

mapBigInteger(local, "int256", 256, false);
local.put("int", local.get("int256"));

for (int n = 8; n <= 32; n += 8) mapInt(local, "int" + n, n, false);
for (int n = 40; n <= 64; n += 8) mapLong(local, "int" + n, n, false);
for (int n = 72; n < 256; n += 8) mapBigInteger(local, "int" + n, n, false);

local.put("address", AddressType.INSTANCE);

local.put("fixed128x18", new BigDecimalType("fixed128x18", FIXED_BIT_LEN, FIXED_SCALE, false));
local.put("ufixed128x18", new BigDecimalType("ufixed128x18", FIXED_BIT_LEN, FIXED_SCALE, true));

local.put("decimal", local.get("int168"));
local.put("fixed", local.get("fixed128x18"));
local.put("ufixed", local.get("ufixed128x18"));

final Map<String, ABIType<?>> localLegacy = new HashMap<>(256);
for (Map.Entry<String, ABIType<?>> e : local.entrySet()) {
ABIType<?> value = e.getValue();
if (value instanceof ArrayType) {
final ArrayType<?, ?, ?> at = value.asArrayType();
value = new ArrayType<>(at.canonicalType, at.clazz, ByteType.INSTANCE, at.getLength(), at.arrayClass(), ABIType.FLAG_LEGACY_DECODE);
}
localLegacy.put(e.getKey(), value);
}

BASE_TYPE_MAP = Collections.unmodifiableMap(local);
LEGACY_BASE_TYPE_MAP = Collections.unmodifiableMap(localLegacy);
UnitType.ensureInitialized();
}

private static void mapInt(Map<String, ABIType<?>> map, String type, int bitLen, boolean unsigned) {
map.put(type, new IntType(type, bitLen, unsigned));
}

private static void mapLong(Map<String, ABIType<?>> map, String type, int bitLen, boolean unsigned) {
map.put(type, new LongType(type, bitLen, unsigned));
}
private static final int MAX_LENGTH_CHARS = 2_000;

private static void mapBigInteger(Map<String, ABIType<?>> map, String type, int bitLen, boolean unsigned) {
map.put(type, new BigIntegerType(type, bitLen, unsigned));
}
static final int ADDRESS_BIT_LEN = 160;

private static void mapByteArray(Map<String, ABIType<?>> map, String type, int arrayLen) {
map.put(type, new ArrayType<ByteType, Byte, byte[]>(type, byte[].class, ByteType.INSTANCE, arrayLen, byte[][].class, ABIType.FLAGS_NONE));
}
private TypeFactory() {}

/**
* Creates an {@link ABIType}. If the compiler can't infer the return type, use a type witness.
Expand Down Expand Up @@ -162,7 +88,7 @@ private static ABIType<?> buildUnchecked(final String rawType, final String[] el
if (rawType.charAt(0) == '(') {
return baseType != null ? baseType : parseTupleType(rawType, elementNames, flags);
} else {
ABIType<?> t = ((flags & ABIType.FLAG_LEGACY_DECODE) != 0 ? LEGACY_BASE_TYPE_MAP : BASE_TYPE_MAP).get(rawType);
ABIType<?> t = (flags & ABIType.FLAG_LEGACY_DECODE) != 0 ? UnitType.getLegacy(rawType) : UnitType.get(rawType);
return t != null ? t : tryParseFixed(rawType);
}
} catch (StringIndexOutOfBoundsException ignored) { // e.g. type equals "" or "82]" or "[]" or "[1]"
Expand Down
137 changes: 137 additions & 0 deletions src/main/java/com/esaulpaugh/headlong/abi/UnitType.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

import static com.esaulpaugh.headlong.abi.ArrayType.DYNAMIC_LENGTH;
import static com.esaulpaugh.headlong.abi.ArrayType.STRING_ARRAY_CLASS;
import static com.esaulpaugh.headlong.abi.ArrayType.STRING_CLASS;

/** Superclass for any 256-bit ("unit") Contract ABI type. Usually numbers or boolean. Not for arrays or tuples. */
public abstract class UnitType<J> extends ABIType<J> { // J generally extends Number or is Boolean

private static final AtomicLong INSTANCE_COUNT = new AtomicLong(0L);

public static final int UNIT_LENGTH_BYTES = 256 / Byte.SIZE;

private final long minLong;
Expand All @@ -34,6 +44,22 @@ public abstract class UnitType<J> extends ABIType<J> { // J generally extends Nu

UnitType(String canonicalType, Class<J> clazz, int bitLength, boolean unsigned) {
super(canonicalType, clazz, false);
if (!(this instanceof BigDecimalType)) {
// 69 non-BigDecimalType entries in BASE_TYPE_MAP
// - 3 which are only aliases to instances already counted (int, uint, decimal)
// + 0 unique instances in LEGACY_BASE_TYPE_MAP
// + 3 instances not in the maps (uint21, uint31, and ADDRESS_INNER)
// =
final int instanceLimit = 69;
if (INSTANCE_COUNT.incrementAndGet() > instanceLimit) {
INSTANCE_COUNT.decrementAndGet();
System.err.println("unexpected instance creation rejected by " + UnitType.class.getName());
throw illegalState("instance not permitted");
}
} else if (Integers.mod(bitLength, Byte.SIZE) != 0) {
System.err.println("unexpected bit length rejected");
throw illegalState("bit length not permitted");
}
this.bitLength = bitLength;
this.unsigned = unsigned;
this.min = minValue();
Expand Down Expand Up @@ -172,4 +198,115 @@ final BigInteger decodeValid(ByteBuffer bb, byte[] unitBuffer) {
validateBigInt(bi);
return bi;
}
//======================================================================================================================
private static final int FIXED_BIT_LEN = 128;
private static final int FIXED_SCALE = 18;

private static final int FUNCTION_BYTE_LEN = 24;

private static final Map<String, ABIType<?>> BASE_TYPE_MAP = new HashMap<>(256);
private static final Map<String, ABIType<?>> LEGACY_BASE_TYPE_MAP = new HashMap<>(256);

/* called from TypeFactory */
static ABIType<?> get(String rawType) {
return BASE_TYPE_MAP.get(rawType);
}

/* called from TypeFactory */
static ABIType<?> getLegacy(String rawType) {
return LEGACY_BASE_TYPE_MAP.get(rawType);
}

static void ensureInitialized() {
// trigger initialization of all subclasses
final Integer h = AddressType.INSTANCE.hashCode()
+ BooleanType.INSTANCE.hashCode()
+ ByteType.INSTANCE.hashCode()
+ IntType.UINT21.hashCode()
+ IntType.UINT31.hashCode()
+ LongType.__.hashCode()
+ BigDecimalType.__.hashCode()
+ BigIntegerType.__.hashCode();
initMaps(h); // synchronized
}

private static synchronized void initMaps(Object h) {
Objects.requireNonNull(h);

final int empty = 0;
final int full = 108;
final int size = BASE_TYPE_MAP.size();
final int legacySize = LEGACY_BASE_TYPE_MAP.size();
if (size != empty || legacySize != empty) {
if (size == full && legacySize == full) {
return;
}
IllegalStateException ise = illegalState("bad map size");
ise.printStackTrace();
throw ise;
}

final Map<String, ABIType<?>> map = BASE_TYPE_MAP;

// optimized insertion order
map.put("string", new ArrayType<ByteType, Byte, String>("string", STRING_CLASS, ByteType.INSTANCE, DYNAMIC_LENGTH, STRING_ARRAY_CLASS, ABIType.FLAGS_NONE));
map.put("bool", BooleanType.INSTANCE);

for (int n = 1; n <= 32; n++) {
mapByteArray(map, "bytes" + n, n);
}
mapByteArray(map, "function", FUNCTION_BYTE_LEN);
mapByteArray(map, "bytes", DYNAMIC_LENGTH);

for (int n = 8; n <= 24; n += 8) mapInt(map, "uint" + n, n, true);
for (int n = 32; n <= 56; n += 8) mapLong(map, "uint" + n, n, true);
for (int n = 64; n <= 256; n += 8) mapBigInteger(map, "uint" + n, n, true);

map.put("uint", map.get("uint256"));

mapBigInteger(map, "int256", 256, false);
map.put("int", map.get("int256"));

for (int n = 8; n <= 32; n += 8) mapInt(map, "int" + n, n, false);
for (int n = 40; n <= 64; n += 8) mapLong(map, "int" + n, n, false);
for (int n = 72; n < 256; n += 8) mapBigInteger(map, "int" + n, n, false);

map.put("address", AddressType.INSTANCE);

map.put("fixed128x18", new BigDecimalType("fixed128x18", FIXED_BIT_LEN, FIXED_SCALE, false));
map.put("ufixed128x18", new BigDecimalType("ufixed128x18", FIXED_BIT_LEN, FIXED_SCALE, true));

map.put("decimal", map.get("int168"));
map.put("fixed", map.get("fixed128x18"));
map.put("ufixed", map.get("ufixed128x18"));

for (Map.Entry<String, ABIType<?>> e : map.entrySet()) {
ABIType<?> value = e.getValue();
if (value instanceof ArrayType) {
final ArrayType<?, ?, ?> at = value.asArrayType();
value = new ArrayType<>(at.canonicalType, at.clazz, ByteType.INSTANCE, at.getLength(), at.arrayClass(), ABIType.FLAG_LEGACY_DECODE);
}
LEGACY_BASE_TYPE_MAP.put(e.getKey(), value);
}

if (BASE_TYPE_MAP.size() != full || LEGACY_BASE_TYPE_MAP.size() != full) {
throw new AssertionError();
}
}

private static void mapInt(Map<String, ABIType<?>> map, String type, int bitLen, boolean unsigned) {
map.put(type, new IntType(type, bitLen, unsigned));
}

private static void mapLong(Map<String, ABIType<?>> map, String type, int bitLen, boolean unsigned) {
map.put(type, new LongType(type, bitLen, unsigned));
}

private static void mapBigInteger(Map<String, ABIType<?>> map, String type, int bitLen, boolean unsigned) {
map.put(type, new BigIntegerType(type, bitLen, unsigned));
}

private static void mapByteArray(Map<String, ABIType<?>> map, String type, int arrayLen) {
map.put(type, new ArrayType<ByteType, Byte, byte[]>(type, byte[].class, ByteType.INSTANCE, arrayLen, byte[][].class, ABIType.FLAGS_NONE));
}
}
Loading

0 comments on commit 7e76ecb

Please sign in to comment.