diff --git a/org.aion.avm.core/src/org/aion/avm/core/dappreading/JarBuilder.java b/org.aion.avm.core/src/org/aion/avm/core/dappreading/JarBuilder.java index f90e1efff..738c462cc 100644 --- a/org.aion.avm.core/src/org/aion/avm/core/dappreading/JarBuilder.java +++ b/org.aion.avm.core/src/org/aion/avm/core/dappreading/JarBuilder.java @@ -14,10 +14,7 @@ import org.aion.avm.core.util.Helpers; import i.RuntimeAssertionError; -import org.aion.avm.userlib.AionBuffer; -import org.aion.avm.userlib.AionList; -import org.aion.avm.userlib.AionMap; -import org.aion.avm.userlib.AionSet; +import org.aion.avm.userlib.*; import org.aion.avm.userlib.abi.ABIDecoder; import org.aion.avm.userlib.abi.ABIEncoder; import org.aion.avm.userlib.abi.ABIException; @@ -37,7 +34,7 @@ public class JarBuilder { private static long FIXED_TIMESTAMP = 1_000_000_000_000L; private static Class[] userlibClasses = new Class[] {ABIDecoder.class, ABIEncoder.class, - ABIStreamingEncoder.class, ABIException.class, ABIToken.class, AionBuffer.class, AionList.class, AionMap.class, AionSet.class}; + ABIStreamingEncoder.class, ABIException.class, ABIToken.class, AionBuffer.class, AionList.class, AionMap.class, AionSet.class, AionUtilities.class}; /** * Creates the in-memory representation of a JAR with the given main class, other classes, and all classes in the Userlib. diff --git a/org.aion.avm.embed/test/org/aion/avm/embed/ContractLoggingTest.java b/org.aion.avm.embed/test/org/aion/avm/embed/ContractLoggingTest.java index e89ea1e55..66fbd1c2a 100644 --- a/org.aion.avm.embed/test/org/aion/avm/embed/ContractLoggingTest.java +++ b/org.aion.avm.embed/test/org/aion/avm/embed/ContractLoggingTest.java @@ -7,6 +7,7 @@ import java.math.BigInteger; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.aion.avm.core.*; import org.aion.kernel.TestingState; @@ -100,6 +101,21 @@ public void testLogsFiredOffInEachInternalTransactionUptoFive() { verifyLogs(result.logs, 5); } + @Test + public void padTruncateLogs() { + Transaction transaction = generateTxForMethodCall("padTruncateLogs"); + TransactionResult result = runTransaction(transaction); + assertTrue(result.transactionStatus.isSuccess()); + + assertEquals(1, result.logs.size()); + Log log = result.logs.get(0); + assertEquals(32, log.copyOfTopics().get(0).length); + assertEquals(BigInteger.valueOf(256), new BigInteger(log.copyOfTopics().get(0))); + byte[] topic2 = new byte[32]; + Arrays.fill(topic2, Byte.MAX_VALUE); + assertArrayEquals(topic2, log.copyOfTopics().get(1)); + } + /** * Checks that each of the logs is in its expected state and that it has been generated the * appropriate number of times. diff --git a/org.aion.avm.embed/test/org/aion/avm/embed/LoggingTarget.java b/org.aion.avm.embed/test/org/aion/avm/embed/LoggingTarget.java index 76d756bd8..d2f925948 100644 --- a/org.aion.avm.embed/test/org/aion/avm/embed/LoggingTarget.java +++ b/org.aion.avm.embed/test/org/aion/avm/embed/LoggingTarget.java @@ -2,6 +2,7 @@ import java.math.BigInteger; +import org.aion.avm.userlib.AionUtilities; import org.aion.avm.userlib.abi.ABIDecoder; import avm.Blockchain; import org.aion.avm.userlib.abi.ABIEncoder; @@ -38,6 +39,9 @@ public static byte[] main() { } else if (methodName.equals("hitLogs")) { hitLogs(); return new byte[0]; + } else if (methodName.equals("padTruncateLogs")) { + padTruncateLogs(); + return new byte[0]; } else { return new byte[0]; } @@ -92,6 +96,16 @@ public static void hitLogs() { hitLog3(); hitLog4(); hitLog5(); + + } + + public static void padTruncateLogs() { + byte[] topic1 = new BigInteger("256").toByteArray(); + byte[] topic2 = new byte[33]; + for (int i = 0; i < topic2.length; i++) { + topic2[i] = Byte.MAX_VALUE; + } + Blockchain.log(AionUtilities.padLeft(topic1), topic2, new byte[0]); } private static void hitLog1() { diff --git a/org.aion.avm.tooling/src/org/aion/avm/tooling/abi/ABICompiler.java b/org.aion.avm.tooling/src/org/aion/avm/tooling/abi/ABICompiler.java index 365304fea..eea738a83 100644 --- a/org.aion.avm.tooling/src/org/aion/avm/tooling/abi/ABICompiler.java +++ b/org.aion.avm.tooling/src/org/aion/avm/tooling/abi/ABICompiler.java @@ -13,10 +13,7 @@ import org.aion.avm.tooling.util.JarBuilder; import org.aion.avm.tooling.util.Utilities; -import org.aion.avm.userlib.AionBuffer; -import org.aion.avm.userlib.AionList; -import org.aion.avm.userlib.AionMap; -import org.aion.avm.userlib.AionSet; +import org.aion.avm.userlib.*; import org.aion.avm.userlib.abi.*; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; @@ -33,7 +30,7 @@ public class ABICompiler { private static final int DEFAULT_VERSION_NUMBER = 0; private static Class[] requiredUserlibClasses = new Class[] {ABIDecoder.class, ABIEncoder.class, - ABIStreamingEncoder.class, ABIException.class, ABIToken.class, AionBuffer.class, AionList.class, AionMap.class, AionSet.class}; + ABIStreamingEncoder.class, ABIException.class, ABIToken.class, AionBuffer.class, AionList.class, AionMap.class, AionSet.class, AionUtilities.class}; private String mainClassName; private byte[] mainClassBytes; diff --git a/org.aion.avm.tooling/test/org/aion/avm/tooling/abi/ABICompilerTest.java b/org.aion.avm.tooling/test/org/aion/avm/tooling/abi/ABICompilerTest.java index e7c931b83..58e3eee9c 100644 --- a/org.aion.avm.tooling/test/org/aion/avm/tooling/abi/ABICompilerTest.java +++ b/org.aion.avm.tooling/test/org/aion/avm/tooling/abi/ABICompilerTest.java @@ -17,10 +17,7 @@ import org.aion.avm.tooling.deploy.eliminator.TestUtil; import org.aion.avm.tooling.util.JarBuilder; import org.aion.avm.tooling.util.Utilities; -import org.aion.avm.userlib.AionBuffer; -import org.aion.avm.userlib.AionList; -import org.aion.avm.userlib.AionMap; -import org.aion.avm.userlib.AionSet; +import org.aion.avm.userlib.*; import org.aion.avm.userlib.abi.*; import org.junit.After; import org.junit.Assert; @@ -139,7 +136,7 @@ public void testGetMissingUserlibClasses() { byte[] jar = TestUtil.serializeClassesAsJar(ChattyCalculatorTarget.class, SilentCalculatorTarget.class, AionList.class, AionBuffer.class); Class[] expectedMissingClasses = new Class[] {ABIDecoder.class, ABIEncoder.class, - ABIStreamingEncoder.class, ABIException.class, ABIToken.class, AionMap.class, AionSet.class}; + ABIStreamingEncoder.class, ABIException.class, ABIToken.class, AionMap.class, AionSet.class, AionUtilities.class}; ABICompiler compiler = ABICompiler.compileJarBytes(jar); Class[] actualMissingClasses = ABICompiler.getMissingUserlibClasses(compiler.getClassMap()); diff --git a/org.aion.avm.userlib/src/org/aion/avm/userlib/AionUtilities.java b/org.aion.avm.userlib/src/org/aion/avm/userlib/AionUtilities.java new file mode 100644 index 000000000..cde17cbbc --- /dev/null +++ b/org.aion.avm.userlib/src/org/aion/avm/userlib/AionUtilities.java @@ -0,0 +1,31 @@ +package org.aion.avm.userlib; + +/** + * A collection of methods to facilitate contract development. + */ +public class AionUtilities { + + /** + * Returns a new byte array of length 32 that right-aligns the input bytes by padding them on the left with 0. + * Note that the input is not truncated if it is larger than 32 bytes. + * This method can be used to pad log topics. + * + * @param topic bytes to pad + * @return Zero padded topic + * @throws NullPointerException if topic is null + */ + public static byte[] padLeft(byte[] topic) { + int topicSize = 32; + byte[] result; + if (null == topic) { + throw new NullPointerException(); + } else if (topic.length < topicSize) { + result = new byte[topicSize]; + System.arraycopy(topic, 0, result, topicSize - topic.length, topic.length); + } else { + // if topic is larger than 32 bytes or the right size + result = topic; + } + return result; + } +} diff --git a/org.aion.avm.userlib/test/org/aion/avm/userlib/AionUtilitiesTest.java b/org.aion.avm.userlib/test/org/aion/avm/userlib/AionUtilitiesTest.java new file mode 100644 index 000000000..fd8faa8b3 --- /dev/null +++ b/org.aion.avm.userlib/test/org/aion/avm/userlib/AionUtilitiesTest.java @@ -0,0 +1,48 @@ +package org.aion.avm.userlib; + +import org.junit.Assert; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Arrays; + +public class AionUtilitiesTest { + + @Test + public void testBigIntegerPadding() { + BigInteger value = new BigInteger(new byte[]{10, 100, 120}); + byte[] topic = AionUtilities.padLeft(value.toByteArray()); + Assert.assertEquals(32, topic.length); + BigInteger topicValue = new BigInteger(topic); + Assert.assertEquals(value, topicValue); + + + byte[] arr = new byte[32]; + Arrays.fill(arr, Byte.MAX_VALUE); + value = new BigInteger(arr); + topic = AionUtilities.padLeft(value.toByteArray()); + Assert.assertEquals(32, topic.length); + topicValue = new BigInteger(topic); + Assert.assertEquals(value, topicValue); + + arr = new byte[33]; + Arrays.fill(arr, Byte.MAX_VALUE); + value = new BigInteger(arr); + topic = AionUtilities.padLeft(value.toByteArray()); + Assert.assertEquals(33, topic.length); + topicValue = new BigInteger(topic); + Assert.assertEquals(value, topicValue); + } + + @Test + public void nullInputTest() { + boolean exceptionThrown = false; + byte[] value = null; + try { + AionUtilities.padLeft(value); + } catch (NullPointerException e) { + exceptionThrown = true; + } + Assert.assertTrue(exceptionThrown); + } +}