diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2aaea06
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/Java/bin/
+/Java/target/
+/Java/.settings/
diff --git a/Java/.classpath b/Java/.classpath
new file mode 100644
index 0000000..f3d3455
--- /dev/null
+++ b/Java/.classpath
@@ -0,0 +1,27 @@
+
+
+ * For more information, see the specification document at:
+ * Automated Transactions API Specification
+ *
+ * Note that "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes two parts. The first part is a block height (32 + * bits) with the second part being the number of the transaction if applicable (also 32 bits and zero if not applicable). + * + */ +public interface API { + + /** Returns current blockchain's height */ + public int getCurrentBlockHeight(); + + /** Returns block height where AT was created */ + public int getATCreationBlockHeight(MachineState state); + + /** Returns previous block's height */ + default public int getPreviousBlockHeight() { + return getCurrentBlockHeight() - 1; + } + + /** Put previous block's signature hash in A */ + public void putPreviousBlockHashInA(MachineState state); + + /** Put next transaction to AT after timestamp in A */ + public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state); + + /** Return type from transaction in A, or 0xffffffffffffffff if A not valid transaction */ + public long getTypeFromTransactionInA(MachineState state); + + /** Return amount from transaction in A, after transaction fees have been deducted, or 0xffffffffffffffff if A not valid transaction */ + public long getAmountFromTransactionInA(MachineState state); + + /** Return timestamp from transaction in A, or 0xffffffffffffffff if A not valid transaction */ + public long getTimestampFromTransactionInA(MachineState state); + + /** + * Generate pseudo-random number using transaction in A. + *
+ * AT should sleep so it can use next block as source of entropy. + *
+ * Set state.isSleeping = true before exit on first call.
+ * state.steps will be zero on second call after wake-up.
+ *
+ * Returns 0xffffffffffffffff if A not valid transaction. + */ + public long generateRandomUsingTransactionInA(MachineState state); + + /** Put 'message' from transaction in A into B */ + public void putMessageFromTransactionInAIntoB(MachineState state); + + /** Put sender/creator address from transaction in A into B */ + public void putAddressFromTransactionInAIntoB(MachineState state); + + /** Put AT's creator's address into B */ + public void putCreatorAddressIntoB(MachineState state); + + /** Return AT's current balance */ + public long getCurrentBalance(MachineState state); + + /** Return AT's previous balance at end of last execution round. Does not include any amounts sent to AT since */ + public long getPreviousBalance(MachineState state); + + /** Pay passed amount, or current balance if necessary, (fee inclusive) to address in B */ + public void payAmountToB(long value1, MachineState state); + + /** Pay AT's current balance to address in B */ + public void payCurrentBalanceToB(MachineState state); + + /** Pay AT's previous balance to address in B */ + public void payPreviousBalanceToB(MachineState state); + + /** Send 'message' in A to address in B */ + public void messageAToB(MachineState state); + + /** + * Returns minutes of blocks added to 'timestamp' + *
+ * minutes is converted to rough number of blocks and added to 'timestamp' to create return value. + */ + public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state); + + /** AT has encountered fatal error. Return remaining funds to creator */ + public void onFatalError(MachineState state, ExecutionException e); + + /** Pre-execute checking of param requirements for platform-specific functions */ + public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException; + + /** + * Platform-specific function execution + * + * @throws ExecutionException + */ + public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException; + +} diff --git a/Java/src/org/ciyam/at/CodeSegmentException.java b/Java/src/org/ciyam/at/CodeSegmentException.java new file mode 100644 index 0000000..481fdd2 --- /dev/null +++ b/Java/src/org/ciyam/at/CodeSegmentException.java @@ -0,0 +1,21 @@ +package org.ciyam.at; + +@SuppressWarnings("serial") +public class CodeSegmentException extends ExecutionException { + + public CodeSegmentException() { + } + + public CodeSegmentException(String message) { + super(message); + } + + public CodeSegmentException(Throwable cause) { + super(cause); + } + + public CodeSegmentException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Java/src/org/ciyam/at/ExecutionException.java b/Java/src/org/ciyam/at/ExecutionException.java new file mode 100644 index 0000000..43e1daf --- /dev/null +++ b/Java/src/org/ciyam/at/ExecutionException.java @@ -0,0 +1,21 @@ +package org.ciyam.at; + +@SuppressWarnings("serial") +public class ExecutionException extends Exception { + + public ExecutionException() { + } + + public ExecutionException(String message) { + super(message); + } + + public ExecutionException(Throwable cause) { + super(cause); + } + + public ExecutionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Java/src/org/ciyam/at/FunctionCode.java b/Java/src/org/ciyam/at/FunctionCode.java new file mode 100644 index 0000000..1aa3aeb --- /dev/null +++ b/Java/src/org/ciyam/at/FunctionCode.java @@ -0,0 +1,956 @@ +package org.ciyam.at; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * This enum contains function codes for the CIYAM AT machine. + *
+ * Function codes are represented by a short. Functions can take 0 to 2 additional long values and optionally return a value too. + *
+ * FunctionCode instances can be obtained via the default FunctionCode.valueOf(String) or the additional FunctionCode.valueOf(int). + *
+ * Use the FunctionCode.execute method to perform the operation. + *
+ * For more details, view the API Specification.
+ *
+ * @see FunctionCode#valueOf(int)
+ * @see FunctionCode#execute(FunctionData, MachineState)
+ */
+public enum FunctionCode {
+ /**
+ * ECHO value to logger
+ * Can modify various fields of state, including programCounter.
+ *
+ * Throws a subclass of ExecutionException on error, e.g. InvalidAddressException.
+ *
+ * @param functionData
+ * @param state
+ * @throws ExecutionException
+ */
+ public void execute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ // Check passed functionData against requirements of this function
+ preExecuteCheck(functionData.paramCount, functionData.returnValueExpected, state, rawFunctionCode);
+
+ if (functionData.paramCount >= 1 && functionData.value1 == null)
+ throw new IllegalFunctionCodeException("Passed value1 is null but function has paramCount of (" + this.paramCount + ")");
+
+ if (functionData.paramCount == 2 && functionData.value2 == null)
+ throw new IllegalFunctionCodeException("Passed value2 is null but function has paramCount of (" + this.paramCount + ")");
+
+ state.logger.debug("Function \"" + this.name() + "\"");
+
+ postCheckExecute(functionData, state, rawFunctionCode);
+ }
+
+ /** Actually execute function */
+ abstract protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException;
+
+ // TODO: public abstract String disassemble();
+
+}
diff --git a/Java/src/org/ciyam/at/FunctionData.java b/Java/src/org/ciyam/at/FunctionData.java
new file mode 100644
index 0000000..baf47df
--- /dev/null
+++ b/Java/src/org/ciyam/at/FunctionData.java
@@ -0,0 +1,29 @@
+package org.ciyam.at;
+
+public class FunctionData {
+ public final int paramCount;
+ public final Long value1;
+ public final Long value2;
+ public final boolean returnValueExpected;
+ public Long returnValue;
+
+ private FunctionData(int paramCount, Long value1, Long value2, boolean returnValueExpected) {
+ this.paramCount = paramCount;
+ this.value1 = value1;
+ this.value2 = value2;
+ this.returnValueExpected = returnValueExpected;
+ this.returnValue = null;
+ }
+
+ public FunctionData(boolean returnValueExpected) {
+ this(0, null, null, returnValueExpected);
+ }
+
+ public FunctionData(Long value, boolean returnValueExpected) {
+ this(1, value, null, returnValueExpected);
+ }
+
+ public FunctionData(Long value1, Long value2, boolean returnValueExpected) {
+ this(2, value1, value2, returnValueExpected);
+ }
+}
diff --git a/Java/src/org/ciyam/at/IllegalFunctionCodeException.java b/Java/src/org/ciyam/at/IllegalFunctionCodeException.java
new file mode 100644
index 0000000..c7956c3
--- /dev/null
+++ b/Java/src/org/ciyam/at/IllegalFunctionCodeException.java
@@ -0,0 +1,21 @@
+package org.ciyam.at;
+
+@SuppressWarnings("serial")
+public class IllegalFunctionCodeException extends ExecutionException {
+
+ public IllegalFunctionCodeException() {
+ }
+
+ public IllegalFunctionCodeException(String message) {
+ super(message);
+ }
+
+ public IllegalFunctionCodeException(Throwable cause) {
+ super(cause);
+ }
+
+ public IllegalFunctionCodeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/Java/src/org/ciyam/at/IllegalOperationException.java b/Java/src/org/ciyam/at/IllegalOperationException.java
new file mode 100644
index 0000000..ae43602
--- /dev/null
+++ b/Java/src/org/ciyam/at/IllegalOperationException.java
@@ -0,0 +1,21 @@
+package org.ciyam.at;
+
+@SuppressWarnings("serial")
+public class IllegalOperationException extends ExecutionException {
+
+ public IllegalOperationException() {
+ }
+
+ public IllegalOperationException(String message) {
+ super(message);
+ }
+
+ public IllegalOperationException(Throwable cause) {
+ super(cause);
+ }
+
+ public IllegalOperationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/Java/src/org/ciyam/at/InvalidAddressException.java b/Java/src/org/ciyam/at/InvalidAddressException.java
new file mode 100644
index 0000000..672dbfa
--- /dev/null
+++ b/Java/src/org/ciyam/at/InvalidAddressException.java
@@ -0,0 +1,21 @@
+package org.ciyam.at;
+
+@SuppressWarnings("serial")
+public class InvalidAddressException extends ExecutionException {
+
+ public InvalidAddressException() {
+ }
+
+ public InvalidAddressException(String message) {
+ super(message);
+ }
+
+ public InvalidAddressException(Throwable cause) {
+ super(cause);
+ }
+
+ public InvalidAddressException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/Java/src/org/ciyam/at/LoggerInterface.java b/Java/src/org/ciyam/at/LoggerInterface.java
new file mode 100644
index 0000000..501493f
--- /dev/null
+++ b/Java/src/org/ciyam/at/LoggerInterface.java
@@ -0,0 +1,11 @@
+package org.ciyam.at;
+
+public interface LoggerInterface {
+
+ public void error(String message);
+
+ public void debug(String message);
+
+ public void echo(String message);
+
+}
diff --git a/Java/src/org/ciyam/at/MachineState.java b/Java/src/org/ciyam/at/MachineState.java
new file mode 100644
index 0000000..1175d42
--- /dev/null
+++ b/Java/src/org/ciyam/at/MachineState.java
@@ -0,0 +1,445 @@
+package org.ciyam.at;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class MachineState {
+
+ /** Header bytes length */
+ public static final int HEADER_LENGTH = 2 + 2 + 2 + 2 + 2 + 2; // version reserved code data call-stack user-stack
+
+ /** Size of value stored in data segment - typically 8 bytes (long) */
+ public static final int VALUE_SIZE = 8;
+
+ /** Size of code-address - typically 4 bytes (int) */
+ public static final int ADDRESS_SIZE = 4;
+
+ /** Maximum value for an address in the code segment */
+ public static final int MAX_CODE_ADDRESS = 0x1fffffff;
+
+ /** Bytes per code page */
+ public static final int CODE_PAGE_SIZE = 1;
+
+ /** Bytes per data page */
+ public static final int DATA_PAGE_SIZE = VALUE_SIZE;
+
+ /** Bytes per call stack page */
+ public static final int CALL_STACK_PAGE_SIZE = ADDRESS_SIZE;
+
+ /** Bytes per user stack page */
+ public static final int USER_STACK_PAGE_SIZE = VALUE_SIZE;
+
+ /** Program Counter: offset into code to point of current execution */
+ public int programCounter;
+
+ /** Initial program counter value to use on next block after current block's execution has stopped. 0 by default */
+ public int onStopAddress;
+
+ /** Program counter value to use if an error occurs during execution. If null upon error, refund all funds to creator and finish */
+ public Integer onErrorAddress;
+
+ /** Execution for current block has stopped. Continue at current program counter on next/specific block */
+ public boolean isSleeping;
+
+ /** Block height required to wake from sleeping, or null if not in use */
+ public Integer sleepUntilHeight;
+
+ /** Execution for current block has stopped. Restart at onStopAddress on next block */
+ public boolean isStopped;
+
+ /** Execution stopped due to lack of funds for processing. Restart at onStopAddress if frozenBalance increases */
+ public boolean isFrozen;
+
+ /** Balance at which there were not enough funds, or null if not in use */
+ public Long frozenBalance;
+
+ /** Execution permanently stopped */
+ public boolean isFinished;
+
+ /** Execution permanently stopped due to fatal error */
+ public boolean hadFatalError;
+
+ // 256-bit pseudo-registers
+ public long a1;
+ public long a2;
+ public long a3;
+ public long a4;
+
+ public long b1;
+ public long b2;
+ public long b3;
+ public long b4;
+
+ public int currentBlockHeight;
+
+ /** Number of opcodes processed this execution */
+ public int steps;
+
+ public API api;
+ LoggerInterface logger;
+
+ public short version;
+ public short reserved;
+ public short numCodePages;
+ public short numDataPages;
+ public short numCallStackPages;
+ public short numUserStackPages;
+
+ public byte[] headerBytes;
+
+ public ByteBuffer codeByteBuffer;
+ public ByteBuffer dataByteBuffer;
+ public ByteBuffer callStackByteBuffer;
+ public ByteBuffer userStackByteBuffer;
+
+ private class Flags {
+ private int flags;
+
+ public Flags() {
+ flags = 0;
+ }
+
+ public Flags(int value) {
+ this.flags = value;
+ }
+
+ public void push(boolean flag) {
+ flags <<= 1;
+ flags |= flag ? 1 : 0;
+ }
+
+ public boolean pop() {
+ boolean result = (flags & 1) != 0;
+ flags >>>= 1;
+ return result;
+ }
+
+ public int intValue() {
+ return flags;
+ }
+ }
+
+ /** For internal use when recreating a machine state */
+ private MachineState(API api, LoggerInterface logger, byte[] headerBytes) {
+ if (headerBytes.length != HEADER_LENGTH)
+ throw new IllegalArgumentException("headerBytes length " + headerBytes.length + " incorrect, expected " + HEADER_LENGTH);
+
+ this.headerBytes = headerBytes;
+ parseHeader();
+
+ this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * CODE_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+
+ this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+
+ this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * CALL_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end
+
+ this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * USER_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end
+
+ this.api = api;
+ this.currentBlockHeight = api.getCurrentBlockHeight();
+ this.steps = 0;
+ this.logger = logger;
+ }
+
+ /** For creating a new machine state */
+ public MachineState(API api, LoggerInterface logger, byte[] headerBytes, byte[] codeBytes, byte[] dataBytes) {
+ this(api, logger, headerBytes);
+
+ // XXX: Why don't we simply ByteBuffer.wrap(codeBytes) as they're read-only?
+ // This would do away with the need to specify numCodePages, save space and provide automatic end-of-code detection during execution thanks to
+ // ByteBuffer's BufferUnderflowException
+
+ if (codeBytes.length > this.numCodePages * CODE_PAGE_SIZE)
+ throw new IllegalArgumentException("Number of code pages too small to hold code bytes");
+
+ if (dataBytes.length > this.numDataPages * DATA_PAGE_SIZE)
+ throw new IllegalArgumentException("Number of data pages too small to hold data bytes");
+
+ System.arraycopy(codeBytes, 0, this.codeByteBuffer.array(), 0, codeBytes.length);
+
+ System.arraycopy(dataBytes, 0, this.dataByteBuffer.array(), 0, dataBytes.length);
+
+ this.programCounter = 0;
+ this.onStopAddress = 0;
+ this.onErrorAddress = null;
+ this.isSleeping = false;
+ this.sleepUntilHeight = null;
+ this.isStopped = false;
+ this.isFinished = false;
+ this.hadFatalError = false;
+ this.isFrozen = false;
+ this.frozenBalance = null;
+ }
+
+ /** For serializing a machine state */
+ public byte[] toBytes() {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+ try {
+ // Header first
+ bytes.write(this.headerBytes);
+
+ // Code
+ bytes.write(this.codeByteBuffer.array());
+
+ // Data
+ bytes.write(this.dataByteBuffer.array());
+
+ // Call stack length (32bit unsigned int)
+ int callStackLength = this.callStackByteBuffer.limit() - this.callStackByteBuffer.position();
+ bytes.write(toByteArray(callStackLength));
+ // Call stack
+ bytes.write(this.callStackByteBuffer.array(), this.callStackByteBuffer.position(), callStackLength);
+
+ // User stack length (32bit unsigned int)
+ int userStackLength = this.userStackByteBuffer.limit() - this.userStackByteBuffer.position();
+ bytes.write(toByteArray(userStackLength));
+ // User stack
+ bytes.write(this.userStackByteBuffer.array(), this.userStackByteBuffer.position(), userStackLength);
+
+ // Actual state
+ bytes.write(toByteArray(this.programCounter));
+ bytes.write(toByteArray(this.onStopAddress));
+
+ // Various flags
+ Flags flags = new Flags();
+ flags.push(this.isSleeping);
+ flags.push(this.isStopped);
+ flags.push(this.isFinished);
+ flags.push(this.hadFatalError);
+ flags.push(this.isFrozen);
+
+ flags.push(this.onErrorAddress != null); // has onErrorAddress?
+ flags.push(this.sleepUntilHeight != null); // has sleepUntilHeight?
+ flags.push(this.frozenBalance != null); // has frozenBalance?
+
+ boolean hasNonZeroA = this.a1 != 0 || this.a2 != 0 || this.a3 != 0 || this.a4 != 0;
+ flags.push(hasNonZeroA);
+
+ boolean hasNonZeroB = this.b1 != 0 || this.b2 != 0 || this.b3 != 0 || this.b4 != 0;
+ flags.push(hasNonZeroB);
+
+ bytes.write(toByteArray(flags.intValue()));
+
+ // Optional flag-indicated extra info in same order as above
+ if (this.onErrorAddress != null)
+ bytes.write(toByteArray(this.onErrorAddress));
+
+ if (this.sleepUntilHeight != null)
+ bytes.write(toByteArray(this.sleepUntilHeight));
+
+ if (this.frozenBalance != null)
+ bytes.write(toByteArray(this.frozenBalance));
+
+ if (hasNonZeroA) {
+ bytes.write(toByteArray(this.a1));
+ bytes.write(toByteArray(this.a2));
+ bytes.write(toByteArray(this.a3));
+ bytes.write(toByteArray(this.a4));
+ }
+
+ if (hasNonZeroB) {
+ bytes.write(toByteArray(this.b1));
+ bytes.write(toByteArray(this.b2));
+ bytes.write(toByteArray(this.b3));
+ bytes.write(toByteArray(this.b4));
+ }
+ } catch (IOException e) {
+ return null;
+ }
+
+ return bytes.toByteArray();
+ }
+
+ /** For restoring a previously serialized machine state */
+ public static MachineState fromBytes(API api, LoggerInterface logger, byte[] bytes) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+ byte[] headerBytes = new byte[HEADER_LENGTH];
+ byteBuffer.get(headerBytes);
+
+ MachineState state = new MachineState(api, logger, headerBytes);
+
+ byte[] codeBytes = new byte[state.codeByteBuffer.capacity()];
+ byteBuffer.get(codeBytes);
+ System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length);
+
+ byte[] dataBytes = new byte[state.dataByteBuffer.capacity()];
+ byteBuffer.get(dataBytes);
+ System.arraycopy(dataBytes, 0, state.dataByteBuffer.array(), 0, dataBytes.length);
+
+ int callStackLength = byteBuffer.getInt();
+ byte[] callStackBytes = new byte[callStackLength];
+ byteBuffer.get(callStackBytes);
+ // Restore call stack pointer, and useful for copy below
+ state.callStackByteBuffer.position(state.callStackByteBuffer.limit() - callStackLength);
+ // Call stack grows downwards so copy to end
+ System.arraycopy(callStackBytes, 0, state.callStackByteBuffer.array(), state.callStackByteBuffer.position(), callStackLength);
+
+ int userStackLength = byteBuffer.getInt();
+ byte[] userStackBytes = new byte[userStackLength];
+ byteBuffer.get(userStackBytes);
+ // Restore user stack pointer, and useful for copy below
+ state.userStackByteBuffer.position(state.userStackByteBuffer.limit() - userStackLength);
+ // User stack grows downwards so copy to end
+ System.arraycopy(userStackBytes, 0, state.userStackByteBuffer.array(), state.userStackByteBuffer.position(), userStackLength);
+
+ // Actual state
+ state.programCounter = byteBuffer.getInt();
+ state.onStopAddress = byteBuffer.getInt();
+
+ // Various flags (reverse order to toBytes)
+ Flags flags = state.new Flags(byteBuffer.getInt());
+ boolean hasNonZeroB = flags.pop();
+ boolean hasNonZeroA = flags.pop();
+ boolean hasFrozenBalance = flags.pop();
+ boolean hasSleepUntilHeight = flags.pop();
+ boolean hasOnErrorAddress = flags.pop();
+
+ state.isFrozen = flags.pop();
+ state.hadFatalError = flags.pop();
+ state.isFinished = flags.pop();
+ state.isStopped = flags.pop();
+ state.isSleeping = flags.pop();
+
+ // Optional extras (same order as toBytes)
+ if (hasOnErrorAddress)
+ state.onErrorAddress = byteBuffer.getInt();
+
+ if (hasSleepUntilHeight)
+ state.sleepUntilHeight = byteBuffer.getInt();
+
+ if (hasFrozenBalance)
+ state.frozenBalance = byteBuffer.getLong();
+
+ if (hasNonZeroA) {
+ state.a1 = byteBuffer.getLong();
+ state.a2 = byteBuffer.getLong();
+ state.a3 = byteBuffer.getLong();
+ state.a4 = byteBuffer.getLong();
+ }
+
+ if (hasNonZeroB) {
+ state.b1 = byteBuffer.getLong();
+ state.b2 = byteBuffer.getLong();
+ state.b3 = byteBuffer.getLong();
+ state.b4 = byteBuffer.getLong();
+ }
+
+ return state;
+ }
+
+ /** Convert int to little-endian byte array */
+ private byte[] toByteArray(int value) {
+ return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) };
+ }
+
+ /** Convert long to little-endian byte array */
+ private byte[] toByteArray(long value) {
+ return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24), (byte) (value >> 32), (byte) (value >> 40),
+ (byte) (value >> 48), (byte) (value >> 56) };
+ }
+
+ private void parseHeader() {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ this.version = byteBuffer.getShort();
+ if (this.version < 1)
+ throw new IllegalArgumentException("Version must be >= 0");
+
+ this.reserved = byteBuffer.getShort();
+
+ this.numCodePages = byteBuffer.getShort();
+ if (this.numCodePages < 1)
+ throw new IllegalArgumentException("Number of code pages must be > 0");
+
+ this.numDataPages = byteBuffer.getShort();
+ if (this.numDataPages < 1)
+ throw new IllegalArgumentException("Number of data pages must be > 0");
+
+ this.numCallStackPages = byteBuffer.getShort();
+ if (this.numCallStackPages < 0)
+ throw new IllegalArgumentException("Number of call stack pages must be >= 0");
+
+ this.numUserStackPages = byteBuffer.getShort();
+ if (this.numUserStackPages < 0)
+ throw new IllegalArgumentException("Number of user stack pages must be >= 0");
+ }
+
+ public void execute() {
+ // Set byte buffer position using program counter
+ codeByteBuffer.position(this.programCounter);
+
+ // Reset for this round of execution
+ this.isSleeping = false;
+ this.sleepUntilHeight = null;
+ this.isStopped = false;
+ this.isFrozen = false;
+ this.frozenBalance = null;
+ this.steps = 0;
+
+ while (!this.isSleeping && !this.isStopped && !this.isFinished && !this.isFrozen) {
+ byte rawOpCode = codeByteBuffer.get();
+ OpCode nextOpCode = OpCode.valueOf(rawOpCode);
+
+ try {
+ if (nextOpCode == null)
+ throw new IllegalOperationException("OpCode 0x" + String.format("%02x", rawOpCode) + " not recognised");
+
+ this.logger.debug("[PC: " + String.format("%04x", this.programCounter) + "] " + nextOpCode.name());
+
+ nextOpCode.execute(codeByteBuffer, dataByteBuffer, userStackByteBuffer, callStackByteBuffer, this);
+ this.programCounter = codeByteBuffer.position();
+ } catch (ExecutionException e) {
+ this.logger.debug("Error at PC " + String.format("%04x", this.programCounter) + ": " + e.getMessage());
+
+ if (this.onErrorAddress == null) {
+ this.isFinished = true;
+ this.hadFatalError = true;
+ this.api.onFatalError(this, e);
+ break;
+ }
+
+ this.programCounter = this.onErrorAddress;
+ codeByteBuffer.position(this.programCounter);
+ }
+
+ ++this.steps;
+ }
+
+ if (this.isStopped) {
+ this.logger.debug("Setting program counter to stop address: " + String.format("%04x", this.onStopAddress));
+ this.programCounter = this.onStopAddress;
+ }
+ }
+
+ // public String disassemble(List
+ * Op codes are represented by a single byte and maybe be followed by additional arguments like data addresses, offset, immediate values, etc.
+ *
+ * OpCode instances can be obtained via the default OpCode.valueOf(String) or the additional OpCode.valueOf(int).
+ *
+ * Use the OpCode.execute method to perform the operation.
+ *
+ * In the documentation for each OpCode:
+ *
+ * @addr means "store at addr"
+ *
+ * $addr means "fetch from addr"
+ *
+ * @($addr) means "store at address fetched from addr", i.e. indirect
+ *
+ * $($addr1 + $addr2) means "fetch from address fetched from addr1 plus offset fetched from addr2", i.e. indirect indexed
+ *
+ * @see OpCode#valueOf(int)
+ * @see OpCode#execute(ByteBuffer, ByteBuffer, ByteBuffer, ByteBuffer, MachineState)
+ */
+public enum OpCode {
+
+ /**
+ * No OPeration
+ * Assumes codeByteBuffer.position() is already placed immediately after opcode.
+ *
+ * Updates codeByteBuffer.position() as arguments are fetched, so caller should update state.programCounter using
+ * codeByteBuffer.position() on return.
+ *
+ * Can also modify userStackByteBuffer and various fields of state.
+ *
+ * Throws a subclass of ExecutionException on error, e.g. InvalidAddressException.
+ *
+ * @param codeByteBuffer
+ * @param dataByteBuffer
+ * @param userStackByteBuffer
+ * @param callStackByteBuffer
+ * @param state
+ * @throws ExecutionException
+ */
+ public abstract void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException;
+
+ /**
+ * Returns string representing disassembled OpCode and parameters
+ *
+ * @param codeByteBuffer
+ * @param dataByteBuffer
+ * @return String
+ * @throws ExecutionException
+ */
+ public String disassemble(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ String output = this.name();
+
+ int postOpcodeProgramCounter = codeByteBuffer.position();
+
+ for (OpCodeParam param : this.params) {
+ output += " " + param.disassemble(codeByteBuffer, dataByteBuffer, postOpcodeProgramCounter);
+ }
+
+ return output;
+ }
+
+ /**
+ * Common code for ADD_DAT/SUB_DAT/MUL_DAT/DIV_DAT/MOD_DAT/SHL_DAT/SHR_DAT
+ *
+ * @param codeByteBuffer
+ * @param dataByteBuffer
+ * @param operator
+ * - typically a lambda operating on two long params, e.g. (a, b) -> a + b
+ * @throws ExecutionException
+ */
+ private static void executeDataOperation(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, TwoValueOperator operator) throws ExecutionException {
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long value1 = dataByteBuffer.getLong(address1);
+ long value2 = dataByteBuffer.getLong(address2);
+
+ long newValue = operator.apply(value1, value2);
+
+ dataByteBuffer.putLong(address1, newValue);
+ }
+
+ /**
+ * Common code for BGT/BLT/BGE/BLE/BEQ/BNE
+ *
+ * @param codeByteBuffer
+ * @param dataByteBuffer
+ * @param state
+ * @param comparator
+ * - typically a lambda comparing two long params, e.g. (a, b) -> a == b
+ * @throws ExecutionException
+ */
+ private static void executeBranchConditional(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, MachineState state, TwoValueComparator comparator)
+ throws ExecutionException {
+ int opCodePosition = codeByteBuffer.position() - 1; // i.e. before this OpCode
+
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ byte offset = Utils.getCodeOffset(codeByteBuffer);
+
+ int branchTarget = opCodePosition + offset;
+
+ long value1 = dataByteBuffer.getLong(address1);
+ long value2 = dataByteBuffer.getLong(address2);
+
+ if (comparator.compare(value1, value2))
+ codeByteBuffer.position(branchTarget);
+ }
+
+ /**
+ * Common code for executing a function.
+ *
+ * Updates programCounter to codeByteBuffer's position before calling function.
+ * This is needed for functions that might use/alter the programCounter during their execution,
+ * With CIYAM-ATs, "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes two parts. The first part is a block height
+ * (32 bits) with the second part being the number of the transaction if applicable (also 32 bits and zero if not applicable). Timestamps can thus be
+ * represented as a 64 bit long.
+ *
+ *
+ * @see Timestamp#Timestamp(int, int)
+ * @see Timestamp#Timestamp(long)
+ * @see Timestamp#longValue()
+ * @see Timestamp#toLong(int, int)
+ *
+ */
+public class Timestamp {
+
+ public int blockHeight;
+ public int transactionSequence;
+
+ /**
+ * Constructs new CIYAM-AT "timestamp" using block height and transaction sequence.
+ *
+ * @param blockHeight
+ * @param transactionSequence
+ */
+ public Timestamp(int blockHeight, int transactionSequence) {
+ this.blockHeight = blockHeight;
+ this.transactionSequence = transactionSequence;
+ }
+
+ /**
+ * Constructs new CIYAM-AT "timestamp" using long packed with block height and transaction sequence.
+ *
+ * @param timestamp
+ */
+ public Timestamp(long timestamp) {
+ this.blockHeight = (int) (timestamp >> 32);
+ this.transactionSequence = (int) (timestamp & 0xffffff);
+ }
+
+ /**
+ * Returns CIYAM-AT "timestamp" long representing block height and transaction sequence.
+ *
+ * @return CIYAM-AT "timestamp" as long
+ */
+ public long longValue() {
+ return Timestamp.toLong(this.blockHeight, this.transactionSequence);
+ }
+
+ /**
+ * Returns CIYAM-AT "timestamp" long representing block height and transaction sequence.
+ *
+ * @param blockHeight
+ * @param transactionSequence
+ * @return CIYAM-AT "timestamp" as long
+ */
+ public static long toLong(int blockHeight, int transactionSequence) {
+ long longValue = blockHeight;
+ longValue <<= 32;
+ longValue |= transactionSequence;
+ return longValue;
+ }
+
+}
diff --git a/Java/src/org/ciyam/at/TwoValueComparator.java b/Java/src/org/ciyam/at/TwoValueComparator.java
new file mode 100644
index 0000000..00c6430
--- /dev/null
+++ b/Java/src/org/ciyam/at/TwoValueComparator.java
@@ -0,0 +1,7 @@
+package org.ciyam.at;
+
+public interface TwoValueComparator {
+
+ public boolean compare(long a, long b);
+
+}
diff --git a/Java/src/org/ciyam/at/TwoValueOperator.java b/Java/src/org/ciyam/at/TwoValueOperator.java
new file mode 100644
index 0000000..4cc6a55
--- /dev/null
+++ b/Java/src/org/ciyam/at/TwoValueOperator.java
@@ -0,0 +1,7 @@
+package org.ciyam.at;
+
+public interface TwoValueOperator {
+
+ public long apply(long a, long b);
+
+}
diff --git a/Java/src/org/ciyam/at/Utils.java b/Java/src/org/ciyam/at/Utils.java
new file mode 100644
index 0000000..42e3dba
--- /dev/null
+++ b/Java/src/org/ciyam/at/Utils.java
@@ -0,0 +1,126 @@
+package org.ciyam.at;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+public class Utils {
+
+ /**
+ * Returns immediate function code enum from code bytes at current position.
+ *
+ * Initial position is codeByteBuffer.position() but on return is incremented by 2.
+ *
+ * @param codeByteBuffer
+ * @return FunctionCode enum
+ * @throws CodeSegmentException
+ * @throws InvalidAddressException
+ */
+ public static FunctionCode getFunctionCode(ByteBuffer codeByteBuffer) throws CodeSegmentException, IllegalFunctionCodeException {
+ try {
+ int rawFunctionCode = codeByteBuffer.getShort();
+
+ FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
+
+ if (functionCode == null)
+ throw new IllegalFunctionCodeException("Unknown function code");
+
+ return functionCode;
+ } catch (BufferUnderflowException e) {
+ throw new CodeSegmentException("No code bytes left to get function code", e);
+ }
+ }
+
+ /**
+ * Returns code address from code bytes at current position.
+ *
+ * Initial position is codeByteBuffer.position() but on return is incremented by 4.
+ *
+ * Note: address is not scaled by Constants.VALUE_SIZE unlike other methods in this class.
+ *
+ * @param codeByteBuffer
+ * @return int address into code bytes
+ * @throws CodeSegmentException
+ * @throws InvalidAddressException
+ */
+ public static int getCodeAddress(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException {
+ try {
+ int address = codeByteBuffer.getInt();
+
+ if (address < 0 || address > MachineState.MAX_CODE_ADDRESS || address >= codeByteBuffer.limit())
+ throw new InvalidAddressException("Code address out of bounds");
+
+ return address;
+ } catch (BufferUnderflowException e) {
+ throw new CodeSegmentException("No code bytes left to get code address", e);
+ }
+ }
+
+ /**
+ * Returns data address from code bytes at current position.
+ *
+ * Initial position is codeByteBuffer.position() but on return is incremented by 4.
+ *
+ * Note: address is returned scaled by Constants.VALUE_SIZE.
+ *
+ * @param codeByteBuffer
+ * @return int address into data bytes
+ * @throws CodeSegmentException
+ * @throws InvalidAddressException
+ */
+ public static int getDataAddress(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws CodeSegmentException, InvalidAddressException {
+ try {
+ int address = codeByteBuffer.getInt() * MachineState.VALUE_SIZE;
+
+ if (address < 0 || address + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+ throw new InvalidAddressException("Data address out of bounds");
+
+ return address;
+ } catch (BufferUnderflowException e) {
+ throw new CodeSegmentException("No code bytes left to get data address", e);
+ }
+ }
+
+ /**
+ * Returns byte offset from code bytes at current position.
+ *
+ * Initial position is codeByteBuffer.position() but on return is incremented by 1.
+ *
+ * Note: offset is not scaled by Constants.VALUE_SIZE unlike other methods in this class.
+ *
+ * @param codeByteBuffer
+ * @return byte offset
+ * @throws CodeSegmentException
+ * @throws InvalidAddressException
+ */
+ public static byte getCodeOffset(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException {
+ try {
+ byte offset = codeByteBuffer.get();
+
+ if (codeByteBuffer.position() + offset < 0 || codeByteBuffer.position() + offset >= codeByteBuffer.limit())
+ throw new InvalidAddressException("Code offset out of bounds");
+
+ return offset;
+ } catch (BufferUnderflowException e) {
+ throw new CodeSegmentException("No code bytes left to get code offset", e);
+ }
+ }
+
+ /**
+ * Returns long immediate value from code bytes at current position.
+ *
+ * Initial position is codeByteBuffer.position() but on return is incremented by 8.
+ *
+ * @param codeByteBuffer
+ * @return long value
+ * @throws CodeSegmentException
+ * @throws InvalidAddressException
+ */
+ public static long getCodeValue(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException {
+ try {
+ return codeByteBuffer.getLong();
+ } catch (BufferUnderflowException e) {
+ throw new CodeSegmentException("No code bytes left to get immediate value", e);
+ }
+ }
+
+}
diff --git a/Java/tests/BranchingOpCodeTests.java b/Java/tests/BranchingOpCodeTests.java
new file mode 100644
index 0000000..1f03606
--- /dev/null
+++ b/Java/tests/BranchingOpCodeTests.java
@@ -0,0 +1,496 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class BranchingOpCodeTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ private void simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ do {
+ execute();
+
+ // Bump block height
+ state.currentBlockHeight++;
+ } while (!state.isFinished);
+
+ }
+
+ @Test
+ public void testBZR_DATtrue() throws ExecutionException {
+ int targetAddr = 0x21;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(0).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBZR_DATfalse() throws ExecutionException {
+ int targetAddr = 0x21;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(0).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBNZ_DATtrue() throws ExecutionException {
+ int targetAddr = 0x21;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(0).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBNZ_DATfalse() throws ExecutionException {
+ int targetAddr = 0x21;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(0).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBGT_DATtrue() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BGT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBGT_DATfalse() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BGT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBLT_DATtrue() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBLT_DATfalse() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBGE_DATtrue1() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBGE_DATtrue2() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBGE_DATfalse() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBLE_DATtrue1() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BLE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBLE_DATtrue2() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BLE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBLE_DATfalse() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BLE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBEQ_DATtrue() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BEQ_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBEQ_DATfalse() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BEQ_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBNE_DATtrue() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBNE_DATfalse() throws ExecutionException {
+ int targetAddr = 0x32;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2222L);
+ int tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(0).putInt(1).put((byte) (targetAddr - tempPC));
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+}
diff --git a/Java/tests/CallStackOpCodeTests.java b/Java/tests/CallStackOpCodeTests.java
new file mode 100644
index 0000000..d4afa8c
--- /dev/null
+++ b/Java/tests/CallStackOpCodeTests.java
@@ -0,0 +1,255 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class CallStackOpCodeTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ private void simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ do {
+ execute();
+
+ // Bump block height
+ state.currentBlockHeight++;
+ } while (!state.isFinished);
+
+ }
+
+ @Test
+ public void testJMP_SUB() throws ExecutionException {
+ int subAddr = 0x06;
+
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr);
+ int returnAddress = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // subAddr:
+ assertEquals(subAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedCallStackPosition = (state.numCallStackPages - 1) * MachineState.CALL_STACK_PAGE_SIZE;
+ assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+
+ assertEquals("Return address does not match", returnAddress, state.callStackByteBuffer.getInt(expectedCallStackPosition));
+
+ assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testJMP_SUB2() throws ExecutionException {
+ int subAddr1 = 0x06;
+ int subAddr2 = 0x19;
+
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr1);
+ int returnAddress1 = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // subAddr1:
+ assertEquals(subAddr1, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr2);
+ int returnAddress2 = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // subAddr2:
+ assertEquals(subAddr2, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(5555L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * MachineState.CALL_STACK_PAGE_SIZE;
+ assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+
+ assertEquals("Return address does not match", returnAddress2, state.callStackByteBuffer.getInt(expectedCallStackPosition));
+ assertEquals("Return address does not match", returnAddress1, state.callStackByteBuffer.getInt(expectedCallStackPosition + MachineState.ADDRESS_SIZE));
+
+ assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testJMP_SUBoverflow() throws ExecutionException {
+ // Call stack is 0x0010 entries in size, so exceed this to test overflow
+ for (int i = 0; i < 20; ++i) {
+ // sub address is next opcode!
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(i * (1 + 4));
+ }
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testRET_SUB() throws ExecutionException {
+ int subAddr = 0x13;
+
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(7777L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // subAddr:
+ assertEquals(subAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.RET_SUB.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE;
+ assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+
+ assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
+
+ assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ assertEquals("Data does not match", 7777L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testRET_SUB2() throws ExecutionException {
+ int subAddr1 = 0x13;
+ int subAddr2 = 0x34;
+
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr1);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(7777L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // subAddr1:
+ assertEquals(subAddr1, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr2);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.RET_SUB.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
+
+ // subAddr2:
+ assertEquals(subAddr2, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.RET_SUB.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE;
+ assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+
+ assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
+
+ assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ assertEquals("Data does not match", 7777L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testRET_SUBoverflow() throws ExecutionException {
+ codeByteBuffer.put(OpCode.RET_SUB.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testRET_SUBoverflow2() throws ExecutionException {
+ // sub address is next opcode!
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(1 + 4);
+ // this is return address too
+ codeByteBuffer.put(OpCode.RET_SUB.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+}
diff --git a/Java/tests/DataOpCodeTests.java b/Java/tests/DataOpCodeTests.java
new file mode 100644
index 0000000..6bdf312
--- /dev/null
+++ b/Java/tests/DataOpCodeTests.java
@@ -0,0 +1,816 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class DataOpCodeTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ private void simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ do {
+ execute();
+
+ // Bump block height
+ state.currentBlockHeight++;
+ } while (!state.isFinished);
+
+ }
+
+ @Test
+ public void testSET_VAL() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSET_VALunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testSET_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSET_DATunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_DAT.value).putInt(9999).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testSET_DATunbounded2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(9999);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testCLR_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ // Check data all zero
+ state.dataByteBuffer.position(0);
+ while (state.dataByteBuffer.hasRemaining())
+ assertEquals((byte) 0, state.dataByteBuffer.get());
+ }
+
+ @Test
+ public void testCLR_DATunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(9999);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testINC_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L + 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testINC_DATunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.INC_DAT.value).putInt(9999);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testINC_DAToverflow() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0xffffffffffffffffL);
+ codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testDEC_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L - 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testDEC_DATunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(9999);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testDEC_DATunderflow() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0L);
+ codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 0xffffffffffffffffL, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testADD_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L + 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testADD_DATunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(9999).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testADD_DATunbounded2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(9999);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testADD_DAToverflow() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0x7fffffffffffffffL);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(0x8000000000000099L);
+ codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 0x0000000000000098L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSUB_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SUB_DAT.value).putInt(3).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 3333L - 2222L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testMUL_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.MUL_DAT.value).putInt(3).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", (3333L * 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testDIV_DAT() throws ExecutionException {
+ // Note: fatal error because error handler not set
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", (3333L / 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testDIV_DATzeroWithOnError() throws ExecutionException {
+ // Note: non-fatal error because error handler IS set
+
+ int errorAddr = 0x29;
+
+ codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // errorAddr:
+ assertEquals(errorAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testBOR_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.BOR_DAT.value).putInt(3).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", (3333L | 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testAND_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.AND_DAT.value).putInt(3).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", (3333L & 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testXOR_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.XOR_DAT.value).putInt(3).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", (3333L ^ 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testNOT_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.NOT_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", ~2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSET_IND() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ // @(6) = $($0) aka $(3) aka 3333
+ codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(6 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSET_INDunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ // @(6) = $($9999) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(9999);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testSET_INDunbounded2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ // @(6) = $($0) aka $(9999) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testSET_IDX() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @(0) = $($6 + $7) aka $(1 + 3) aka $(4) aka 4444
+ codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSET_IDXunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @(0) = $($9999 + $7) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(9999).putInt(7);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testSET_IDXunbounded2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(9999L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @(0) = $($6 + $7) aka $(9999 + 1) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testSET_IDXunbounded3() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(9999L);
+ // @(0) = $($6 + $7) aka $(1 + 9999) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testSET_IDXunbounded4() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @(0) = $($6 + $9999) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(9999);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testIND_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ // @($0) aka @(3) = $(5) = 5555
+ codeByteBuffer.put(OpCode.IND_DAT.value).putInt(0).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testIND_DATDunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ // @($9999) = $(5) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IND.value).putInt(9999).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testIND_DATDunbounded2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ // @($0) aka @(9999) = $(5) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.SET_IND.value).putInt(0).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testIDX_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @($6 + $7) aka @(1 + 3) aka @(4) = $(5) aka 5555
+ codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(4 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testIDX_DATunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @($9999 + $7) = $(5) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(9999).putInt(7).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testIDX_DATunbounded2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(9999L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @($6 + $7) aka @(9999 + 3) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testIDX_DATunbounded3() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(9999L);
+ // @($6 + $7) aka @(1 + 9999) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testIDX_DATunbounded4() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(6).putLong(1L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(7).putLong(3L);
+ // @($6 + $9999) = $(5) but data address 9999 is out of bounds
+ codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(9999).putInt(5);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testMOD_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.MOD_DAT.value).putInt(2).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L % 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testMOD_DATzeroWithOnError() throws ExecutionException {
+ // Note: non-fatal error because error handler IS set
+
+ int errorAddr = 0x29;
+
+ codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.MOD_DAT.value).putInt(3).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // errorAddr:
+ assertEquals(errorAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSHL_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3L);
+ codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L << 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSHL_DATexcess() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSHR_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3L);
+ codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2222L >> 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSHR_DATexcess() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+}
diff --git a/Java/tests/DisassemblyTests.java b/Java/tests/DisassemblyTests.java
new file mode 100644
index 0000000..0812ec2
--- /dev/null
+++ b/Java/tests/DisassemblyTests.java
@@ -0,0 +1,116 @@
+import static common.TestUtils.hexToBytes;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.FunctionCode;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class DisassemblyTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ @Test
+ public void testMD160disassembly() throws ExecutionException {
+ // MD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_A_WITH_B.value).putInt(1);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ System.out.println(state.disassemble());
+ }
+
+ @Test
+ public void testACCTdisassembly() throws ExecutionException {
+ codeByteBuffer.put(hexToBytes("3501030900000006040000000900000029302009000000040000000f1ab4000000330403090000003525010a000000260a00"));
+ codeByteBuffer.put(hexToBytes("0000320903350703090000003526010a0000001b0a000000cd32280133160100000000331701010000003318010200000033"));
+ codeByteBuffer.put(hexToBytes("1901030000003505020a0000001b0a000000a1320b033205041e050000001833000509000000320a033203041ab400000033"));
+ codeByteBuffer.put(hexToBytes("160105000000331701060000003318010700000033190108000000320304320b033203041ab7000000000000000000000000"));
+ codeByteBuffer.put(hexToBytes("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"));
+ codeByteBuffer.put(hexToBytes("000000000000"));
+
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ System.out.println(state.disassemble());
+ }
+
+}
diff --git a/Java/tests/FunctionCodeTests.java b/Java/tests/FunctionCodeTests.java
new file mode 100644
index 0000000..e9fd8df
--- /dev/null
+++ b/Java/tests/FunctionCodeTests.java
@@ -0,0 +1,311 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.FunctionCode;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.ciyam.at.Timestamp;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class FunctionCodeTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ private void simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ do {
+ execute();
+
+ // Bump block height
+ state.currentBlockHeight++;
+ } while (!state.isFinished);
+
+ }
+
+ @Test
+ public void testMD5() throws ExecutionException {
+ // MD5 of ffffffffffffffffffffffffffffffff is 8d79cbc9a4ecdde112fc91ba625b13c2
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ // A3 unused
+ // A4 unused
+
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MD5_A_TO_B.value);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8d79cbc9a4ecdde1"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12fc91ba625b13c2"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1);
+
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testCHECK_MD5() throws ExecutionException {
+ // MD5 of ffffffffffffffffffffffffffffffff is 8d79cbc9a4ecdde112fc91ba625b13c2
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ // A3 unused
+ // A4 unused
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8d79cbc9a4ecdde1"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12fc91ba625b13c2"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_MD5_A_WITH_B.value).putInt(1);
+
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testHASH160() throws ExecutionException {
+ // RIPEMD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ // A4 unused
+
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.HASH160_A_TO_B.value);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000000000000"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1);
+
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testCHECK_HASH160() throws ExecutionException {
+ // RIPEMD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ // A4 unused
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("90e735014ea23aa8"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("9190121b229c06d5"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("8fc71e8300000000"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_A_WITH_B.value).putInt(1);
+
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testSHA256() throws ExecutionException {
+ // SHA256 of ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff is af9613760f72635fbdb44a5a0a63c39f12af30f950a6ee5c971be188e89c4051
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SHA256_A_TO_B.value);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("af9613760f72635f"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("bdb44a5a0a63c39f"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12af30f950a6ee5c"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("971be188e89c4051"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1);
+
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testCHECK_SHA256() throws ExecutionException {
+ // SHA256 of ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff is af9613760f72635fbdb44a5a0a63c39f12af30f950a6ee5c971be188e89c4051
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("ffffffffffffffff"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A1.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A2.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A3.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A4.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("af9613760f72635f"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("bdb44a5a0a63c39f"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("12af30f950a6ee5c"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("971be188e89c4051"));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(0);
+
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(1);
+
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testRandom() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(0);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GENERATE_RANDOM_USING_TX_IN_A.value).putInt(1);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertNotEquals("Random wasn't generated", 0L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testInvalidFunctionCode() throws ExecutionException {
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort((short) 0xaaaa);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testPlatformSpecific0501() {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0));
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort((short) 0x0501).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testPlatformSpecific0501Error() {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0));
+ codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort((short) 0x0501).putInt(0).putInt(0); // Wrong OPCODE for function
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+}
diff --git a/Java/tests/MiscTests.java b/Java/tests/MiscTests.java
new file mode 100644
index 0000000..c0a0d87
--- /dev/null
+++ b/Java/tests/MiscTests.java
@@ -0,0 +1,111 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.FunctionCode;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class MiscTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ private void simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ do {
+ execute();
+
+ // Bump block height
+ state.currentBlockHeight++;
+ } while (!state.isFinished);
+
+ }
+
+ @Test
+ public void testSimpleCode() throws ExecutionException {
+ long testValue = 8888L;
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(testValue);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", testValue, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testInvalidOpCode() throws ExecutionException {
+ codeByteBuffer.put((byte) 0xdd);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+}
diff --git a/Java/tests/OpCodeTests.java b/Java/tests/OpCodeTests.java
new file mode 100644
index 0000000..7ee530d
--- /dev/null
+++ b/Java/tests/OpCodeTests.java
@@ -0,0 +1,292 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class OpCodeTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ private void simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ do {
+ execute();
+
+ // Bump block height
+ state.currentBlockHeight++;
+ } while (!state.isFinished && !state.isFrozen && !state.isSleeping && !state.isStopped);
+
+ }
+
+ @Test
+ public void testNOP() throws ExecutionException {
+ codeByteBuffer.put(OpCode.NOP.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ // Check data unchanged
+ state.dataByteBuffer.position(0);
+ while (state.dataByteBuffer.hasRemaining())
+ assertEquals((byte) 0, state.dataByteBuffer.get());
+ }
+
+ @Test
+ public void testJMP_ADR() throws ExecutionException {
+ int targetAddr = 0x12;
+
+ codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(targetAddr);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1L);
+
+ // targetAddr:
+ assertEquals(targetAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testSLP_DAT() throws ExecutionException {
+ int blockHeight = 12345;
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(blockHeight);
+ codeByteBuffer.put(OpCode.SLP_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isSleeping);
+ assertFalse(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Sleep-until block height incorrect", blockHeight, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testFIZ_DATtrue() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
+ codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.SLP_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.isSleeping);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testFIZ_DATfalse() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L);
+ codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.SLP_IMD.value);
+
+ simulate();
+
+ assertFalse(state.isFinished);
+ assertTrue(state.isSleeping);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testSTZ_DATtrue() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ int stopAddress = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isStopped);
+ assertFalse(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Program counter incorrect", stopAddress, state.programCounter);
+ }
+
+ @Test
+ public void testSTZ_DATfalse() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(1111L);
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertFalse(state.isStopped);
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testFIN_IMD() throws ExecutionException {
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+ codeByteBuffer.put(OpCode.STP_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.isStopped);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testSTP_IMD() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ int stopAddress = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.NOP.value);
+ codeByteBuffer.put(OpCode.STP_IMD.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isStopped);
+ assertFalse(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Program counter incorrect", stopAddress, state.programCounter);
+ }
+
+ @Test
+ public void testSLP_IMD() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SLP_IMD.value);
+ int nextAddress = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isSleeping);
+ assertFalse(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Program counter incorrect", nextAddress, state.programCounter);
+ }
+
+ @Test
+ public void testERR_ADR() throws ExecutionException {
+ // Note: non-fatal error because error handler IS set
+
+ int errorAddr = 0x29;
+
+ codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(12345L);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(0L);
+ codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(0).putInt(1); // divide by zero
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // errorAddr:
+ assertEquals(errorAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testPCS() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111"));
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222"));
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ int expectedStopAddress = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertEquals(expectedStopAddress, state.onStopAddress);
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testPCS2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111"));
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ int expectedStopAddress = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222"));
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertEquals(expectedStopAddress, state.onStopAddress);
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+}
diff --git a/Java/tests/SerializationTests.java b/Java/tests/SerializationTests.java
new file mode 100644
index 0000000..bd6e48b
--- /dev/null
+++ b/Java/tests/SerializationTests.java
@@ -0,0 +1,109 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.FunctionCode;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class SerializationTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(256).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private byte[] simulate() {
+ // version 0003, reserved 0000, code 0100 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0001" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ return executeAndCheck(state);
+ }
+
+ private byte[] continueSimulation(byte[] savedState) {
+ state = MachineState.fromBytes(api, logger, savedState);
+
+ // Pretend we're on next block
+ state.currentBlockHeight++;
+
+ return executeAndCheck(state);
+ }
+
+ private byte[] executeAndCheck(MachineState state) {
+ state.execute();
+
+ byte[] stateBytes = state.toBytes();
+ MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes);
+ byte[] restoredStateBytes = restoredState.toBytes();
+
+ assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
+
+ return stateBytes;
+ }
+
+ @Test
+ public void testPCS2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111"));
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222"));
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertEquals(0x0e, (int) state.onStopAddress);
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ }
+
+ @Test
+ public void testStopWithStacks() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(100); // 0000
+ codeByteBuffer.put(OpCode.SET_PCS.value); // 000d
+ codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(0x002a); // 000e
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(10); // 0013
+ codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(0).putInt(1); // 0020
+ codeByteBuffer.put(OpCode.STP_IMD.value); // 0029
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0); // 002a
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); // 0031
+ codeByteBuffer.put(OpCode.RET_SUB.value); // 0036
+
+ byte[] savedState = simulate();
+
+ assertEquals(0x0e, (int) state.onStopAddress);
+ assertTrue(state.isStopped);
+ assertFalse(state.hadFatalError);
+
+ savedState = continueSimulation(savedState);
+ savedState = continueSimulation(savedState);
+ }
+
+}
diff --git a/Java/tests/TestACCT.java b/Java/tests/TestACCT.java
new file mode 100644
index 0000000..37b8d4e
--- /dev/null
+++ b/Java/tests/TestACCT.java
@@ -0,0 +1,218 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Arrays;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.FunctionCode;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.ACCTAPI;
+import common.TestLogger;
+
+public class TestACCT {
+
+ public TestLogger logger;
+ public ACCTAPI api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+ public ByteBuffer dataByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new ACCTAPI();
+ codeByteBuffer = ByteBuffer.allocate(0x0200 * 1).order(ByteOrder.LITTLE_ENDIAN);
+ dataByteBuffer = ByteBuffer.allocate(0x0020 * 8).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ dataByteBuffer = null;
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private byte[] simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = dataByteBuffer.array();
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ return executeAndCheck(state);
+ }
+
+ private byte[] continueSimulation(byte[] savedState) {
+ state = MachineState.fromBytes(api, logger, savedState);
+
+ // Pretend we're on next block
+ state.currentBlockHeight++;
+
+ return executeAndCheck(state);
+ }
+
+ private byte[] executeAndCheck(MachineState state) {
+ state.execute();
+
+ byte[] stateBytes = state.toBytes();
+ MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes);
+ byte[] restoredStateBytes = restoredState.toBytes();
+
+ assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
+
+ return stateBytes;
+ }
+
+ @Test
+ public void testACCT() throws ExecutionException {
+ // DATA
+ final int addrHashPart1 = 0x0;
+ final int addrHashPart2 = 0x1;
+ final int addrHashPart3 = 0x2;
+ final int addrHashPart4 = 0x3;
+ final int addrAddressPart1 = 0x4;
+ final int addrAddressPart2 = 0x5;
+ final int addrAddressPart3 = 0x6;
+ final int addrAddressPart4 = 0x7;
+ final int addrRefundMinutes = 0x8;
+ final int addrRefundTimestamp = 0x9;
+ final int addrLastTimestamp = 0xa;
+ final int addrBlockTimestamp = 0xb;
+ final int addrTxType = 0xc;
+ final int addrComparator = 0xd;
+ final int addrAddressTemp1 = 0xe;
+ final int addrAddressTemp2 = 0xf;
+ final int addrAddressTemp3 = 0x10;
+ final int addrAddressTemp4 = 0x11;
+
+ byte[] secret = new byte[32];
+ new SecureRandom().nextBytes(secret);
+
+ try {
+ MessageDigest digester = MessageDigest.getInstance("SHA-256");
+ byte[] digest = digester.digest(secret);
+
+ dataByteBuffer.put(digest);
+ } catch (NoSuchAlgorithmException e) {
+ throw new ExecutionException("No SHA-256 message digest service available", e);
+ }
+
+ // Destination address (based on "R" for "Responder", where "R" is 0x52)
+ dataByteBuffer.put(hexToBytes("5200000000000000520000000000000052000000000000005200000000000000"));
+
+ // Expiry in minutes (but actually blocks in this test case)
+ dataByteBuffer.putLong(8L);
+
+ // Code labels
+ final int addrTxLoop = 0x36;
+ final int addrCheckTx = 0x4b;
+ final int addrCheckSender = 0x64;
+ final int addrCheckMessage = 0xab;
+ final int addrPayout = 0xdf;
+ final int addrRefund = 0x102;
+
+ int tempPC;
+
+ // init:
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp);
+ codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp)
+ .putInt(addrRefundTimestamp).putInt(addrRefundMinutes);
+ codeByteBuffer.put(OpCode.SET_PCS.value);
+
+ // loop:
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp);
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrTxLoop - tempPC));
+ codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrRefund);
+
+ // txloop:
+ assertEquals(addrTxLoop, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(addrLastTimestamp);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator);
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC));
+ codeByteBuffer.put(OpCode.STP_IMD.value);
+
+ // checkTx:
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType);
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrTxType).put((byte) (addrCheckSender - tempPC));
+ codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop);
+
+ // checkSender
+ assertEquals(addrCheckSender, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B1.value).putInt(addrAddressTemp1);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B2.value).putInt(addrAddressTemp2);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B3.value).putInt(addrAddressTemp3);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B4.value).putInt(addrAddressTemp4);
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC));
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp2).putInt(addrAddressPart2).put((byte) (addrTxLoop - tempPC));
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp3).putInt(addrAddressPart3).put((byte) (addrTxLoop - tempPC));
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC));
+
+ // checkMessage:
+ assertEquals(addrCheckMessage, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value);
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrHashPart1);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrHashPart2);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrHashPart3);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrHashPart4);
+ codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(addrComparator);
+ tempPC = codeByteBuffer.position();
+ codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC));
+ codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop);
+
+ // payout:
+ assertEquals(addrPayout, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrAddressPart1);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrAddressPart2);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrAddressPart3);
+ codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrAddressPart4);
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value);
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // refund:
+ assertEquals(addrRefund, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value);
+ codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ byte[] savedState = simulate();
+
+ while (!state.isFinished) {
+ ((ACCTAPI) state.api).generateNextBlock(secret);
+
+ savedState = continueSimulation(savedState);
+ }
+ }
+
+}
diff --git a/Java/tests/ToolchainTests.java b/Java/tests/ToolchainTests.java
new file mode 100644
index 0000000..64ec535
--- /dev/null
+++ b/Java/tests/ToolchainTests.java
@@ -0,0 +1,18 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class ToolchainTests {
+
+ @Test
+ public void testHexToBytes() {
+ assertTrue(Arrays.equals(new byte[] { 0x12 }, hexToBytes("12")));
+ assertTrue(Arrays.equals(new byte[] { 0x00, 0x00, 0x12 }, hexToBytes("000012")));
+ assertTrue(Arrays.equals(new byte[] { (byte) 0xff }, hexToBytes("ff")));
+ assertTrue(Arrays.equals(new byte[] { 0x00, 0x00, (byte) 0xee }, hexToBytes("0000ee")));
+ }
+
+}
diff --git a/Java/tests/UserStackOpCodeTests.java b/Java/tests/UserStackOpCodeTests.java
new file mode 100644
index 0000000..6c701ed
--- /dev/null
+++ b/Java/tests/UserStackOpCodeTests.java
@@ -0,0 +1,224 @@
+import static common.TestUtils.hexToBytes;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.OpCode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import common.TestAPI;
+import common.TestLogger;
+
+public class UserStackOpCodeTests {
+
+ public TestLogger logger;
+ public API api;
+ public MachineState state;
+ public ByteBuffer codeByteBuffer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.insertProviderAt(new BouncyCastleProvider(), 0);
+ }
+
+ @Before
+ public void beforeTest() {
+ logger = new TestLogger();
+ api = new TestAPI();
+ codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @After
+ public void afterTest() {
+ codeByteBuffer = null;
+ api = null;
+ logger = null;
+ }
+
+ private void execute() {
+ System.out.println("Starting execution:");
+ System.out.println("Current block height: " + state.currentBlockHeight);
+
+ state.execute();
+
+ System.out.println("After execution:");
+ System.out.println("Steps: " + state.steps);
+ System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
+ System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
+ System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
+ if (state.isSleeping)
+ System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
+ else
+ System.out.println("Sleeping: " + state.isSleeping);
+ System.out.println("Stopped: " + state.isStopped);
+ System.out.println("Finished: " + state.isFinished);
+ if (state.hadFatalError)
+ System.out.println("Finished due to fatal error!");
+ System.out.println("Frozen: " + state.isFrozen);
+ }
+
+ private void simulate() {
+ // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
+ byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
+ byte[] codeBytes = codeByteBuffer.array();
+ byte[] dataBytes = new byte[0];
+
+ state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
+
+ do {
+ execute();
+
+ // Bump block height
+ state.currentBlockHeight++;
+ } while (!state.isFinished);
+
+ }
+
+ @Test
+ public void testPSH_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedUserStackPosition = (state.numUserStackPages - 1) * MachineState.USER_STACK_PAGE_SIZE;
+ assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
+ assertEquals("Data does not match", 4444L, state.userStackByteBuffer.getLong(expectedUserStackPosition));
+ }
+
+ @Test
+ public void testPSH_DAT2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(3333L);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(1);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedUserStackPosition = (state.numUserStackPages - 2) * MachineState.USER_STACK_PAGE_SIZE;
+ assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
+ assertEquals("Data does not match", 3333L, state.userStackByteBuffer.getLong(expectedUserStackPosition));
+ }
+
+ @Test
+ public void testPSH_DAToverflow() throws ExecutionException {
+ // User stack is 0x0010 entries in size, so exceed this to test overflow
+ for (int i = 0; i < 20; ++i) {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(i).putLong(1000L * i);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(i);
+ }
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testPSH_DAToverflowWithOnError() throws ExecutionException {
+ int errorAddr = 0x16e;
+
+ codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr);
+
+ // User stack is 0x0010 entries in size, so exceed this to test overflow
+ for (int i = 0; i < 20; ++i) {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(i).putLong(1000L * i);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(i);
+ }
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // errorAddr:
+ assertEquals(errorAddr, codeByteBuffer.position());
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+ assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testPOP_DAT() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.POP_DAT.value).putInt(1);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedUserStackPosition = (state.numUserStackPages - 1 + 1) * MachineState.USER_STACK_PAGE_SIZE;
+ assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
+ assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+ assertEquals("Stack entry not cleared", 0L, state.userStackByteBuffer.getLong(expectedUserStackPosition - MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testPOP_DAT2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(3333L);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(1);
+ codeByteBuffer.put(OpCode.POP_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.POP_DAT.value).putInt(3);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertFalse(state.hadFatalError);
+
+ int expectedUserStackPosition = (state.numUserStackPages - 1 - 1 + 1 + 1) * MachineState.USER_STACK_PAGE_SIZE;
+ assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
+ assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+ assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+ }
+
+ @Test
+ public void testPOP_DAToverflow() throws ExecutionException {
+ codeByteBuffer.put(OpCode.POP_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+ @Test
+ public void testPOP_DAToverflow2() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
+ codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0);
+ codeByteBuffer.put(OpCode.POP_DAT.value).putInt(1);
+ codeByteBuffer.put(OpCode.POP_DAT.value).putInt(2);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ simulate();
+
+ assertTrue(state.isFinished);
+ assertTrue(state.hadFatalError);
+ }
+
+}
diff --git a/Java/tests/common/ACCTAPI.java b/Java/tests/common/ACCTAPI.java
new file mode 100644
index 0000000..3719c5e
--- /dev/null
+++ b/Java/tests/common/ACCTAPI.java
@@ -0,0 +1,328 @@
+package common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+import org.ciyam.at.API;
+import org.ciyam.at.ExecutionException;
+import org.ciyam.at.FunctionData;
+import org.ciyam.at.IllegalFunctionCodeException;
+import org.ciyam.at.MachineState;
+import org.ciyam.at.Timestamp;
+
+public class ACCTAPI implements API {
+
+ private class Account {
+ public String address;
+ public long balance;
+
+ public Account(String address, long amount) {
+ this.address = address;
+ this.balance = amount;
+ }
+ }
+
+ private class Transaction {
+ public int txType;
+ public String creator;
+ public String recipient;
+ public long amount;
+ public long[] message;
+ }
+
+ private class Block {
+ public List
+ * 0x0001 value
+ */
+ ECHO(0x0001, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ String message = String.valueOf(functionData.value1);
+ state.logger.echo(message);
+ }
+ },
+ /**
+ * 0x0100
+ * Returns A1 value
+ */
+ GET_A1(0x0100, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.a1;
+ }
+ },
+ /**
+ * 0x0101
+ * Returns A2 value
+ */
+ GET_A2(0x0101, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.a2;
+ }
+ },
+ /**
+ * 0x0102
+ * Returns A3 value
+ */
+ GET_A3(0x0102, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.a3;
+ }
+ },
+ /**
+ * 0x0103
+ * Returns A4 value
+ */
+ GET_A4(0x0103, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.a4;
+ }
+ },
+ /**
+ * 0x0104
+ * Returns B1 value
+ */
+ GET_B1(0x0104, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.b1;
+ }
+ },
+ /**
+ * 0x0105
+ * Returns B2 value
+ */
+ GET_B2(0x0105, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.b2;
+ }
+ },
+ /**
+ * 0x0106
+ * Returns B3 value
+ */
+ GET_B3(0x0106, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.b3;
+ }
+ },
+ /**
+ * 0x0107
+ * Returns B4 value
+ */
+ GET_B4(0x0107, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.b4;
+ }
+ },
+ /**
+ * Set A1
+ * 0x0110 value
+ */
+ SET_A1(0x0110, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = functionData.value1;
+ }
+ },
+ /**
+ * Set A2
+ * 0x0111 value
+ */
+ SET_A2(0x0111, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a2 = functionData.value1;
+ }
+ },
+ /**
+ * Set A3
+ * 0x0112 value
+ */
+ SET_A3(0x0112, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a3 = functionData.value1;
+ }
+ },
+ /**
+ * Set A4
+ * 0x0113 value
+ */
+ SET_A4(0x0113, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a4 = functionData.value1;
+ }
+ },
+ /**
+ * Set A1 and A2
+ * 0x0114 value value
+ */
+ SET_A1_A2(0x0114, 2, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = functionData.value1;
+ state.a2 = functionData.value2;
+ }
+ },
+ /**
+ * Set A3 and A4
+ * 0x0115 value value
+ */
+ SET_A3_A4(0x0115, 2, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a3 = functionData.value1;
+ state.a4 = functionData.value2;
+ }
+ },
+ /**
+ * Set B1
+ * 0x0116 value
+ */
+ SET_B1(0x0116, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b1 = functionData.value1;
+ }
+ },
+ /**
+ * Set B2
+ * 0x0117 value
+ */
+ SET_B2(0x0117, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b2 = functionData.value1;
+ }
+ },
+ /**
+ * Set B3
+ * 0x0118 value
+ */
+ SET_B3(0x0118, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b3 = functionData.value1;
+ }
+ },
+ /**
+ * Set B4
+ * 0x0119 value
+ */
+ SET_B4(0x0119, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b4 = functionData.value1;
+ }
+ },
+ /**
+ * Set B1 and B2
+ * 0x011a value value
+ */
+ SET_B1_B2(0x011a, 2, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b1 = functionData.value1;
+ state.b2 = functionData.value2;
+ }
+ },
+ /**
+ * Set B3 and B4
+ * 0x011b value value
+ */
+ SET_B3_B4(0x011b, 2, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b3 = functionData.value1;
+ state.b4 = functionData.value2;
+ }
+ },
+ /**
+ * Clear A
+ * 0x0120
+ */
+ CLEAR_A(0x0120, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = 0;
+ state.a2 = 0;
+ state.a3 = 0;
+ state.a4 = 0;
+ }
+ },
+ /**
+ * Clear B
+ * 0x0121
+ */
+ CLEAR_B(0x0121, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b1 = 0;
+ state.b2 = 0;
+ state.b3 = 0;
+ state.b4 = 0;
+ }
+ },
+ /**
+ * Clear A and B
+ * 0x0122
+ */
+ CLEAR_A_AND_B(0x0122, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = 0;
+ state.a2 = 0;
+ state.a3 = 0;
+ state.a4 = 0;
+ state.b1 = 0;
+ state.b2 = 0;
+ state.b3 = 0;
+ state.b4 = 0;
+ }
+ },
+ /**
+ * Copy A from B
+ * 0x0123
+ */
+ COPY_A_FROM_B(0x0123, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = state.b1;
+ state.a2 = state.b2;
+ state.a3 = state.b3;
+ state.a4 = state.b4;
+ }
+ },
+ /**
+ * Copy B from A
+ * 0x0124
+ */
+ COPY_B_FROM_A(0x0124, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b1 = state.a1;
+ state.b2 = state.a2;
+ state.b3 = state.a3;
+ state.b4 = state.a4;
+ }
+ },
+ /**
+ * Check A is zero
+ * 0x0125
+ * Returns 1 if true, 0 if false
+ */
+ CHECK_A_IS_ZERO(0x0125, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ if (state.a1 == 0 && state.a2 == 0 && state.a3 == 0 && state.a4 == 0)
+ functionData.returnValue = 1L; // true
+ else
+ functionData.returnValue = 0L; // false
+ }
+ },
+ /**
+ * Check B is zero
+ * 0x0126
+ * Returns 1 if true, 0 if false
+ */
+ CHECK_B_IS_ZERO(0x0126, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ if (state.b1 == 0 && state.b2 == 0 && state.b3 == 0 && state.b4 == 0)
+ functionData.returnValue = 1L; // true
+ else
+ functionData.returnValue = 0L; // false
+ }
+ },
+ /**
+ * Check A equals B
+ * 0x0127
+ * Returns 1 if true, 0 if false
+ */
+ CHECK_A_EQUALS_B(0x0127, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ if (state.a1 == state.b1 && state.a2 == state.b2 && state.a3 == state.b3 && state.a4 == state.b4)
+ functionData.returnValue = 1L; // true
+ else
+ functionData.returnValue = 0L; // false
+ }
+ },
+ /**
+ * Swap A with B
+ * 0x0128
+ */
+ SWAP_A_AND_B(0x0128, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ long tmp1 = state.a1;
+ long tmp2 = state.a2;
+ long tmp3 = state.a3;
+ long tmp4 = state.a4;
+
+ state.a1 = state.b1;
+ state.a2 = state.b2;
+ state.a3 = state.b3;
+ state.a4 = state.b4;
+
+ state.b1 = tmp1;
+ state.b2 = tmp2;
+ state.b3 = tmp3;
+ state.b4 = tmp4;
+ }
+ },
+ /**
+ * Bitwise-OR A with B
+ * 0x0129
+ */
+ OR_A_WITH_B(0x0129, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = state.a1 | state.b1;
+ state.a2 = state.a2 | state.b2;
+ state.a3 = state.a3 | state.b3;
+ state.a4 = state.a4 | state.b4;
+ }
+ },
+ /**
+ * Bitwise-OR B with A
+ * 0x012a
+ */
+ OR_B_WITH_A(0x012a, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b1 = state.a1 | state.b1;
+ state.b2 = state.a2 | state.b2;
+ state.b3 = state.a3 | state.b3;
+ state.b4 = state.a4 | state.b4;
+ }
+ },
+ /**
+ * Bitwise-AND A with B
+ * 0x012b
+ */
+ AND_A_WITH_B(0x012b, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = state.a1 & state.b1;
+ state.a2 = state.a2 & state.b2;
+ state.a3 = state.a3 & state.b3;
+ state.a4 = state.a4 & state.b4;
+ }
+ },
+ /**
+ * Bitwise-AND B with A
+ * 0x012c
+ */
+ AND_B_WITH_A(0x012c, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b1 = state.a1 & state.b1;
+ state.b2 = state.a2 & state.b2;
+ state.b3 = state.a3 & state.b3;
+ state.b4 = state.a4 & state.b4;
+ }
+ },
+ /**
+ * Bitwise-XOR A with B
+ * 0x012d
+ */
+ XOR_A_WITH_B(0x012d, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.a1 = state.a1 ^ state.b1;
+ state.a2 = state.a2 ^ state.b2;
+ state.a3 = state.a3 ^ state.b3;
+ state.a4 = state.a4 ^ state.b4;
+ }
+ },
+ /**
+ * Bitwise-XOR B with A
+ * 0x012e
+ */
+ XOR_B_WITH_A(0x012e, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.b1 = state.a1 ^ state.b1;
+ state.b2 = state.a2 ^ state.b2;
+ state.b3 = state.a3 ^ state.b3;
+ state.b4 = state.a4 ^ state.b4;
+ }
+ },
+ /**
+ * MD5 A into B
+ * 0x0200
+ */
+ MD5_A_TO_B(0x0200, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ ByteBuffer messageByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE);
+ messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ messageByteBuffer.putLong(state.a1);
+ messageByteBuffer.putLong(state.a2);
+
+ byte[] message = messageByteBuffer.array();
+
+ try {
+ MessageDigest digester = MessageDigest.getInstance("MD5");
+ byte[] digest = digester.digest(message);
+
+ ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest);
+ digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ state.b1 = digestByteBuffer.getLong();
+ state.b2 = digestByteBuffer.getLong();
+ state.b3 = 0L; // XXX Or do we leave B3 untouched?
+ state.b4 = 0L; // XXX Or do we leave B4 untouched?
+ } catch (NoSuchAlgorithmException e) {
+ throw new ExecutionException("No MD5 message digest service available", e);
+ }
+ }
+ },
+ /**
+ * Check MD5 of A matches B
+ * 0x0201
+ * Returns 1 if true, 0 if false
+ */
+ CHECK_MD5_A_WITH_B(0x0201, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ ByteBuffer messageByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE);
+ messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ messageByteBuffer.putLong(state.a1);
+ messageByteBuffer.putLong(state.a2);
+
+ byte[] message = messageByteBuffer.array();
+
+ try {
+ MessageDigest digester = MessageDigest.getInstance("MD5");
+ byte[] actualDigest = digester.digest(message);
+
+ ByteBuffer digestByteBuffer = ByteBuffer.allocate(2 * MachineState.VALUE_SIZE);
+ digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ digestByteBuffer.putLong(state.b1);
+ digestByteBuffer.putLong(state.b2);
+
+ byte[] expectedDigest = digestByteBuffer.array();
+
+ if (Arrays.equals(actualDigest, expectedDigest))
+ functionData.returnValue = 1L; // true
+ else
+ functionData.returnValue = 0L; // false
+ } catch (NoSuchAlgorithmException e) {
+ throw new ExecutionException("No MD5 message digest service available", e);
+ }
+ }
+ },
+ /**
+ * HASH160 A into B
+ * 0x0202
+ */
+ HASH160_A_TO_B(0x0202, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ ByteBuffer messageByteBuffer = ByteBuffer.allocate(3 * MachineState.VALUE_SIZE);
+ messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ messageByteBuffer.putLong(state.a1);
+ messageByteBuffer.putLong(state.a2);
+ messageByteBuffer.putLong(state.a3);
+
+ byte[] message = messageByteBuffer.array();
+
+ try {
+ MessageDigest digester = MessageDigest.getInstance("RIPEMD160");
+ byte[] digest = digester.digest(message);
+
+ ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest);
+ digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ state.b1 = digestByteBuffer.getLong();
+ state.b2 = digestByteBuffer.getLong();
+ state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL;
+ state.b4 = 0L; // XXX Or do we leave B4 untouched?
+ } catch (NoSuchAlgorithmException e) {
+ throw new ExecutionException("No RIPEMD160 message digest service available", e);
+ }
+ }
+ },
+ /**
+ * Check HASH160 of A matches B
+ * 0x0203
+ * Returns 1 if true, 0 if false
+ */
+ CHECK_HASH160_A_WITH_B(0x0203, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ ByteBuffer messageByteBuffer = ByteBuffer.allocate(3 * MachineState.VALUE_SIZE);
+ messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ messageByteBuffer.putLong(state.a1);
+ messageByteBuffer.putLong(state.a2);
+ messageByteBuffer.putLong(state.a3);
+
+ byte[] message = messageByteBuffer.array();
+
+ try {
+ MessageDigest digester = MessageDigest.getInstance("RIPEMD160");
+ byte[] actualDigest = digester.digest(message);
+
+ ByteBuffer digestByteBuffer = ByteBuffer.allocate(digester.getDigestLength());
+ digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ digestByteBuffer.putLong(state.b1);
+ digestByteBuffer.putLong(state.b2);
+ digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL));
+ // XXX: b4 ignored
+
+ byte[] expectedDigest = digestByteBuffer.array();
+
+ if (Arrays.equals(actualDigest, expectedDigest))
+ functionData.returnValue = 1L; // true
+ else
+ functionData.returnValue = 0L; // false
+ } catch (NoSuchAlgorithmException e) {
+ throw new ExecutionException("No RIPEMD160 message digest service available", e);
+ }
+ }
+ },
+ /**
+ * SHA256 A into B
+ * 0x0204
+ */
+ SHA256_A_TO_B(0x0204, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ ByteBuffer messageByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE);
+ messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ messageByteBuffer.putLong(state.a1);
+ messageByteBuffer.putLong(state.a2);
+ messageByteBuffer.putLong(state.a3);
+ messageByteBuffer.putLong(state.a4);
+
+ byte[] message = messageByteBuffer.array();
+
+ try {
+ MessageDigest digester = MessageDigest.getInstance("SHA-256");
+ byte[] digest = digester.digest(message);
+
+ ByteBuffer digestByteBuffer = ByteBuffer.wrap(digest);
+ digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ state.b1 = digestByteBuffer.getLong();
+ state.b2 = digestByteBuffer.getLong();
+ state.b3 = digestByteBuffer.getLong();
+ state.b4 = digestByteBuffer.getLong();
+ } catch (NoSuchAlgorithmException e) {
+ throw new ExecutionException("No SHA-256 message digest service available", e);
+ }
+ }
+ },
+ /**
+ * Check SHA256 of A matches B
+ * 0x0205
+ * Returns 1 if true, 0 if false
+ */
+ CHECK_SHA256_A_WITH_B(0x0205, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ ByteBuffer messageByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE);
+ messageByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ messageByteBuffer.putLong(state.a1);
+ messageByteBuffer.putLong(state.a2);
+ messageByteBuffer.putLong(state.a3);
+ messageByteBuffer.putLong(state.a4);
+
+ byte[] message = messageByteBuffer.array();
+
+ try {
+ MessageDigest digester = MessageDigest.getInstance("SHA-256");
+ byte[] actualDigest = digester.digest(message);
+
+ ByteBuffer digestByteBuffer = ByteBuffer.allocate(4 * MachineState.VALUE_SIZE);
+ digestByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ digestByteBuffer.putLong(state.b1);
+ digestByteBuffer.putLong(state.b2);
+ digestByteBuffer.putLong(state.b3);
+ digestByteBuffer.putLong(state.b4);
+
+ byte[] expectedDigest = digestByteBuffer.array();
+
+ if (Arrays.equals(actualDigest, expectedDigest))
+ functionData.returnValue = 1L; // true
+ else
+ functionData.returnValue = 0L; // false
+ } catch (NoSuchAlgorithmException e) {
+ throw new ExecutionException("No SHA256 message digest service available", e);
+ }
+ }
+ },
+ /**
+ * 0x0300
+ * Returns current block's "timestamp"
+ */
+ GET_BLOCK_TIMESTAMP(0x0300, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = Timestamp.toLong(state.api.getCurrentBlockHeight(), 0);
+ }
+ },
+ /**
+ * 0x0301
+ * Returns AT's creation block's "timestamp"
+ */
+ GET_CREATION_TIMESTAMP(0x0301, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = Timestamp.toLong(state.api.getATCreationBlockHeight(state), 0);
+ }
+ },
+ /**
+ * 0x0302
+ * Returns previous block's "timestamp"
+ */
+ GET_PREVIOUS_BLOCK_TIMESTAMP(0x0302, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = Timestamp.toLong(state.api.getPreviousBlockHeight(), 0);
+ }
+ },
+ /**
+ * 0x0303
+ * Put previous block's hash in A
+ */
+ PUT_PREVIOUS_BLOCK_HASH_IN_A(0x0303, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.putPreviousBlockHashInA(state);
+ }
+ },
+ /**
+ * 0x0304
+ * Put transaction after timestamp in A, or zero if none
+ * a-k-a "A_To_Tx_After_Timestamp"
+ */
+ PUT_TX_AFTER_TIMESTAMP_IN_A(0x0304, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.putTransactionAfterTimestampInA(new Timestamp(functionData.value1), state);
+ }
+ },
+ /**
+ * 0x0305
+ * Return transaction type from transaction in A
+ * Returns 0xffffffffffffffff in A not valid transaction
+ */
+ GET_TYPE_FROM_TX_IN_A(0x0305, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.api.getTypeFromTransactionInA(state);
+ }
+ },
+ /**
+ * 0x0306
+ * Return transaction amount from transaction in A
+ * Returns 0xffffffffffffffff in A not valid transaction
+ */
+ GET_AMOUNT_FROM_TX_IN_A(0x0306, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.api.getAmountFromTransactionInA(state);
+ }
+ },
+ /**
+ * 0x0307
+ * Return transaction timestamp from transaction in A
+ * Returns 0xffffffffffffffff in A not valid transaction
+ */
+ GET_TIMESTAMP_FROM_TX_IN_A(0x0307, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.api.getTimestampFromTransactionInA(state);
+ }
+ },
+ /**
+ * 0x0308
+ * Generate random number using transaction in A
+ * Returns 0xffffffffffffffff in A not valid transaction
+ * Can sleep to use next block as source of entropy
+ */
+ GENERATE_RANDOM_USING_TX_IN_A(0x0308, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.api.generateRandomUsingTransactionInA(state);
+
+ // If API set isSleeping then rewind program counter ready for being awoken
+ if (state.isSleeping) {
+ state.programCounter -= 1 + 2 + 4; // EXT_FUN_RET(1) + our function code(2) + address(4)
+
+ // If specific sleep height not set, default to next block
+ if (state.sleepUntilHeight == null)
+ state.sleepUntilHeight = state.currentBlockHeight + 1;
+ }
+ }
+ },
+ /**
+ * 0x0309
+ * Put 'message' from transaction in A into B
+ * If transaction has no 'message' then zero B
+ * Example 'message' could be 256-bit shared secret
+ */
+ PUT_MESSAGE_FROM_TX_IN_A_INTO_B(0x0309, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.putMessageFromTransactionInAIntoB(state);
+ }
+ },
+ /**
+ * 0x030a
+ * Put sender/creator address from transaction in A into B
+ */
+ PUT_ADDRESS_FROM_TX_IN_A_INTO_B(0x030a, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.putAddressFromTransactionInAIntoB(state);
+ }
+ },
+ /**
+ * 0x030b
+ * Put AT's creator's address into B
+ */
+ PUT_CREATOR_INTO_B(0x030b, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.putCreatorAddressIntoB(state);
+ }
+ },
+ /**
+ * 0x0400
+ * Returns AT's current balance
+ */
+ GET_CURRENT_BALANCE(0x0400, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.api.getCurrentBalance(state);
+ }
+ },
+ /**
+ * 0x0401
+ * Returns AT's previous balance at end of last execution round
+ * Does not include any amounts sent to AT since
+ */
+ GET_PREVIOUS_BALANCE(0x0401, 0, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.api.getPreviousBalance(state);
+ }
+ },
+ /**
+ * 0x0402
+ * Pay fee-inclusive amount to account address in B
+ * Reduces amount to current balance rather than failing due to insufficient funds
+ */
+ PAY_TO_ADDRESS_IN_B(0x0402, 1, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.payAmountToB(functionData.value1, state);
+ }
+ },
+ /**
+ * 0x0403
+ * Pay all remaining funds to account address in B
+ */
+ PAY_ALL_TO_ADDRESS_IN_B(0x0403, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.payCurrentBalanceToB(state);
+ }
+ },
+ /**
+ * 0x0404
+ * Pay previous balance to account address in B
+ * Reduces amount to current balance rather than failing due to insufficient funds
+ */
+ PAY_PREVIOUS_TO_ADDRESS_IN_B(0x0404, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.payPreviousBalanceToB(state);
+ }
+ },
+ /**
+ * 0x0405
+ * Send A as a message to address in B
+ */
+ MESSAGE_A_TO_ADDRESS_IN_B(0x0405, 0, false) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.messageAToB(state);
+ }
+ },
+ /**
+ * 0x0406
+ * Return 'timestamp' based on passed 'timestamp' plus minutes
+ */
+ ADD_MINUTES_TO_TIMESTAMP(0x0406, 2, true) {
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ functionData.returnValue = state.api.addMinutesToTimestamp(new Timestamp(functionData.value1), functionData.value2, state);
+ }
+ },
+ /**
+ * 0x0500 - 0x06ff
+ * Platform-specific functions.
+ * These are passed through to the API
+ */
+ API_PASSTHROUGH(0x0500, 0, false) {
+ @Override
+ public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.platformSpecificPreExecuteCheck(rawFunctionCode, paramCount, returnValueExpected);
+ }
+
+ @Override
+ protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
+ state.api.platformSpecificPostCheckExecute(rawFunctionCode, functionData, state);
+ }
+ };
+
+ public final short value;
+ public final int paramCount;
+ public final boolean returnsValue;
+
+ private final static Map
+ * 0x7f
+ * (Does nothing)
+ */
+ NOP(0x7f) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) {
+ // Do nothing
+ }
+ },
+ /**
+ * SET VALue
+ * 0x01 addr value
+ * @addr = value
+ */
+ SET_VAL(0x01, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = Utils.getCodeValue(codeByteBuffer);
+ dataByteBuffer.putLong(address, value);
+ }
+ },
+ /**
+ * SET DATa
+ * 0x02 addr1 addr2
+ * @addr1 = $addr2
+ */
+ SET_DAT(0x02, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address2);
+ dataByteBuffer.putLong(address1, value);
+ }
+ },
+ /**
+ * CLeaR DATa
+ * 0x03 addr
+ * @addr = 0
+ */
+ CLR_DAT(0x03, OpCodeParam.DEST_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ dataByteBuffer.putLong(address, 0L);
+ }
+ },
+ /**
+ * INCrement DATa
+ * 0x04 addr
+ * @addr += 1
+ */
+ INC_DAT(0x04, OpCodeParam.DEST_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address);
+ dataByteBuffer.putLong(address, value + 1);
+ }
+ },
+ /**
+ * DECrement DATa
+ * 0x05 addr
+ * @addr -= 1
+ */
+ DEC_DAT(0x05, OpCodeParam.DEST_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address);
+ dataByteBuffer.putLong(address, value - 1);
+ }
+ },
+ /**
+ * ADD DATa
+ * 0x06 addr1 addr2
+ * @addr1 += $addr2
+ */
+ ADD_DAT(0x06, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a + b);
+ }
+ },
+ /**
+ * SUBtract DATa
+ * 0x07 addr1 addr2
+ * @addr1 -= $addr2
+ */
+ SUB_DAT(0x07, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a - b);
+ }
+ },
+ /**
+ * MULtiply DATa
+ * 0x08 addr1 addr2
+ * @addr1 *= $addr2
+ */
+ MUL_DAT(0x08, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a * b);
+ }
+ },
+ /**
+ * DIVide DATa
+ * 0x09 addr1 addr2
+ * @addr1 /= $addr2
+ * Can also throw IllegealOperationException if divide-by-zero attempted.
+ */
+ DIV_DAT(0x09, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ try {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a / b);
+ } catch (ArithmeticException e) {
+ throw new IllegalOperationException("Divide by zero", e);
+ }
+ }
+ },
+ /**
+ * Binary-OR DATa
+ * 0x0a addr1 addr2
+ * @addr1 |= $addr2
+ */
+ BOR_DAT(0x0a, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a | b);
+ }
+ },
+ /**
+ * Binary-AND DATa
+ * 0x0b addr1 addr2
+ * @addr1 &= $addr2
+ */
+ AND_DAT(0x0b, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a & b);
+ }
+ },
+ /**
+ * EXclusive OR DATa
+ * 0x0c addr1 addr2
+ * @addr1 ^= $addr2
+ */
+ XOR_DAT(0x0c, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a ^ b);
+ }
+ },
+ /**
+ * Bitwise-NOT DATa
+ * 0x0d addr
+ * @addr = ~$addr
+ */
+ NOT_DAT(0x0d, OpCodeParam.DEST_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address);
+ dataByteBuffer.putLong(address, ~value);
+ }
+ },
+ /**
+ * SET using INDirect data
+ * 0x0e addr1 addr2
+ * @addr1 = $($addr2)
+ */
+ SET_IND(0x0e, OpCodeParam.DEST_ADDR, OpCodeParam.INDIRECT_SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long address3 = dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
+
+ if (address3 < 0 || address3 + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+ throw new InvalidAddressException("Data address out of bounds");
+
+ long value = dataByteBuffer.getLong((int) address3);
+ dataByteBuffer.putLong(address1, value);
+ }
+ },
+ /**
+ * SET using indirect InDeXed data
+ * 0x0f addr1 addr2 addr3
+ * @addr1 = $($addr2 + $addr3)
+ */
+ SET_IDX(0x0f, OpCodeParam.DEST_ADDR, OpCodeParam.INDIRECT_SRC_ADDR_WITH_INDEX, OpCodeParam.INDEX) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address3 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long baseAddress = dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
+ long offset = dataByteBuffer.getLong(address3) * MachineState.VALUE_SIZE;
+
+ long newAddress = baseAddress + offset;
+
+ if (newAddress < 0 || newAddress + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+ throw new InvalidAddressException("Data address out of bounds");
+
+ long value = dataByteBuffer.getLong((int) newAddress);
+ dataByteBuffer.putLong(address1, value);
+ }
+ },
+ /**
+ * PuSH DATa onto user stack
+ * 0x10 addr
+ * @--user_stack = $addr
+ * Can also throw StackBoundsException if user stack exhausted.
+ */
+ PSH_DAT(0x10, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address);
+
+ try {
+ // Simulate backwards-walking stack
+ int newPosition = userStackByteBuffer.position() - MachineState.VALUE_SIZE;
+ userStackByteBuffer.putLong(newPosition, value);
+ userStackByteBuffer.position(newPosition);
+ } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
+ throw new StackBoundsException("No room on user stack to push data", e);
+ }
+ }
+ },
+ /**
+ * POP DATa from user stack
+ * 0x11 addr
+ * @addr = $user_stack++
+ * Can also throw StackBoundsException if user stack empty.
+ */
+ POP_DAT(0x11, OpCodeParam.DEST_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ try {
+ long value = userStackByteBuffer.getLong();
+
+ // Clear old stack entry
+ userStackByteBuffer.putLong(userStackByteBuffer.position() - MachineState.VALUE_SIZE, 0L);
+
+ // Put popped value into data address
+ dataByteBuffer.putLong(address, value);
+ } catch (BufferUnderflowException e) {
+ throw new StackBoundsException("Empty user stack from which to pop data", e);
+ }
+ }
+ },
+ /**
+ * JuMP into SUBroutine
+ * 0x12 addr
+ * @--call_stack = PC after opcode & args, PC = addr
+ * Can also throw StackBoundsException if call stack exhausted.
+ */
+ JMP_SUB(0x12, OpCodeParam.CODE_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getCodeAddress(codeByteBuffer);
+
+ try {
+ // Simulate backwards-walking stack
+ int newPosition = callStackByteBuffer.position() - MachineState.ADDRESS_SIZE;
+ callStackByteBuffer.putInt(newPosition, codeByteBuffer.position());
+ callStackByteBuffer.position(newPosition);
+ } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
+ throw new StackBoundsException("No room on call stack to call subroutine", e);
+ }
+
+ codeByteBuffer.position(address);
+ }
+ },
+ /**
+ * RETurn from SUBroutine
+ * 0x13
+ * PC = $call_stack++
+ * Can also throw StackBoundsException if call stack empty.
+ */
+ RET_SUB(0x13) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ try {
+ int returnAddress = callStackByteBuffer.getInt();
+
+ // Clear old stack entry
+ callStackByteBuffer.putInt(callStackByteBuffer.position() - MachineState.ADDRESS_SIZE, 0);
+
+ codeByteBuffer.position(returnAddress);
+ } catch (BufferUnderflowException e) {
+ throw new StackBoundsException("Empty call stack missing return address from subroutine", e);
+ }
+ }
+ },
+ /**
+ * Store INDirect DATa
+ * 0x14 addr1 addr2
+ * @($addr1) = $addr2
+ */
+ IND_DAT(0x14, OpCodeParam.INDIRECT_DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long address3 = dataByteBuffer.getLong(address1) * MachineState.VALUE_SIZE;
+
+ if (address3 < 0 || address3 + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+ throw new InvalidAddressException("Data address out of bounds");
+
+ long value = dataByteBuffer.getLong(address2);
+ dataByteBuffer.putLong((int) address3, value);
+ }
+ },
+ /**
+ * Store indirect InDeXed DATa
+ * 0x15 addr1 addr2
+ * @($addr1 + $addr2) = $addr3
+ */
+ IDX_DAT(0x15, OpCodeParam.INDIRECT_DEST_ADDR_WITH_INDEX, OpCodeParam.INDEX, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address3 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long baseAddress = dataByteBuffer.getLong(address1) * MachineState.VALUE_SIZE;
+ long offset = dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
+
+ long newAddress = baseAddress + offset;
+
+ if (newAddress < 0 || newAddress + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+ throw new InvalidAddressException("Data address out of bounds");
+
+ long value = dataByteBuffer.getLong(address3);
+ dataByteBuffer.putLong((int) newAddress, value);
+ }
+ },
+ /**
+ * MODulo DATa
+ * 0x16 addr1 addr2
+ * @addr1 %= $addr2
+ */
+ MOD_DAT(0x16, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ try {
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a % b);
+ } catch (ArithmeticException e) {
+ throw new IllegalOperationException("Divide by zero", e);
+ }
+ }
+ },
+ /**
+ * SHift Left DATa
+ * 0x17 addr1 addr2
+ * @addr1 <<= $addr2
+ */
+ SHL_DAT(0x17, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8;
+
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ // If 2nd arg is more than value size (in bits) then return 0 to simulate all bits being shifted out of existence
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> b >= MAX_SHIFT ? 0 : a << b);
+ }
+ },
+ /**
+ * SHift Right DATa
+ * 0x18 addr1 addr2
+ * @addr1 >>= $addr2
+ * Note: new MSB bit will be zero
+ */
+ SHR_DAT(0x18, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8;
+
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ // If 2nd arg is more than value size (in bits) then return 0 to simulate all bits being shifted out of existence
+ executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> b >= MAX_SHIFT ? 0 : a >>> b);
+ }
+ },
+ /**
+ * JuMP to ADdRess
+ * 0x1a addr
+ * PC = addr
+ */
+ JMP_ADR(0x1a, OpCodeParam.CODE_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getCodeAddress(codeByteBuffer);
+
+ codeByteBuffer.position(address);
+ }
+ },
+ /**
+ * Branch if ZeRo
+ * 0x1b addr offset
+ * if ($addr == 0) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BZR_DAT(0x1b, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int opCodePosition = codeByteBuffer.position() - 1; // i.e. before this OpCode
+
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ byte offset = Utils.getCodeOffset(codeByteBuffer);
+
+ int branchTarget = opCodePosition + offset;
+
+ if (branchTarget < 0 || branchTarget >= codeByteBuffer.limit())
+ throw new InvalidAddressException("branch target out of bounds");
+
+ long value = dataByteBuffer.getLong(address);
+
+ if (value == 0)
+ codeByteBuffer.position(branchTarget);
+ }
+ },
+ /**
+ * Branch if Not Zero
+ * 0x1e addr offset
+ * if ($addr != 0) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BNZ_DAT(0x1e, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int opCodePosition = codeByteBuffer.position() - 1; // i.e. before this OpCode
+
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ byte offset = Utils.getCodeOffset(codeByteBuffer);
+
+ int branchTarget = opCodePosition + offset;
+
+ if (branchTarget < 0 || branchTarget >= codeByteBuffer.limit())
+ throw new InvalidAddressException("branch target out of bounds");
+
+ long value = dataByteBuffer.getLong(address);
+
+ if (value != 0)
+ codeByteBuffer.position(branchTarget);
+ }
+ },
+ /**
+ * Branch if Greater-Than DATa
+ * 0x1f addr1 addr2 offset
+ * if ($addr1 > $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BGT_DAT(0x1f, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a > b);
+ }
+ },
+ /**
+ * Branch if Less-Than DATa
+ * 0x20 addr1 addr2 offset
+ * if ($addr1 < $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BLT_DAT(0x20, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a < b);
+ }
+ },
+ /**
+ * Branch if Greater-or-Equal DATa
+ * 0x21 addr1 addr2 offset
+ * if ($addr1 >= $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BGE_DAT(0x21, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a >= b);
+ }
+ },
+ /**
+ * Branch if Less-or-Equal DATa
+ * 0x22 addr1 addr2 offset
+ * if ($addr1 <= $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BLE_DAT(0x22, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a <= b);
+ }
+ },
+ /**
+ * Branch if EQual DATa
+ * 0x23 addr1 addr2 offset
+ * if ($addr1 == $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BEQ_DAT(0x23, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a == b);
+ }
+ },
+ /**
+ * Branch if Not-Equal DATa
+ * 0x24 addr1 addr2 offset
+ * if ($addr1 != $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte.
+ */
+ BNE_DAT(0x24, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a != b);
+ }
+ },
+ /**
+ * SLeeP until DATa
+ * 0x25 addr
+ * sleep until $addr, then carry on from current PC
+ * Note: The value from $addr is considered to be a block height.
+ */
+ SLP_DAT(0x25, OpCodeParam.BLOCK_HEIGHT) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getCodeAddress(codeByteBuffer);
+ long value = codeByteBuffer.getLong(address);
+
+ state.sleepUntilHeight = (int) value;
+ state.isSleeping = true;
+ }
+ },
+ /**
+ * FInish if Zero DATa
+ * 0x26 addr
+ * if ($addr == 0) permanently stop
+ */
+ FIZ_DAT(0x26, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address);
+
+ if (value == 0)
+ state.isFinished = true;
+ }
+ },
+ /**
+ * STop if Zero DATa
+ * 0x27 addr
+ * if ($addr == 0) PC = PCS and stop
+ */
+ STZ_DAT(0x27, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address);
+
+ if (value == 0) {
+ state.programCounter = state.onStopAddress;
+ codeByteBuffer.position(state.onStopAddress);
+ state.isStopped = true;
+ }
+ }
+ },
+ /**
+ * FINish IMmeDiately
+ * 0x28
+ * permanently stop
+ */
+ FIN_IMD(0x28) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ state.isFinished = true;
+ }
+ },
+ /**
+ * SToP IMmeDiately
+ * 0x29
+ * stop
+ */
+ STP_IMD(0x29) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) {
+ state.isStopped = true;
+ }
+ },
+ /**
+ * SLeeP IMmeDiately
+ * 0x2a
+ * sleep until next block, then carry on from current PC
+ */
+ SLP_IMD(0x2a) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) {
+ state.sleepUntilHeight = state.currentBlockHeight + 1;
+ state.isSleeping = true;
+ }
+ },
+ /**
+ * Set ERRor ADdRess
+ * 0x2b addr
+ * PCE = addr
+ */
+ ERR_ADR(0x2b, OpCodeParam.CODE_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ int address = Utils.getCodeAddress(codeByteBuffer);
+
+ state.onErrorAddress = address;
+ }
+ },
+ /**
+ * SET PCS (stop address)
+ * 0x30
+ * PCS = PC
+ * Note: PC is considered to be immediately after this opcode byte.
+ */
+ SET_PCS(0x30) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) {
+ state.onStopAddress = codeByteBuffer.position();
+ }
+ },
+ /**
+ * Call EXTernal FUNction
+ * 0x32 func
+ * func()
+ */
+ EXT_FUN(0x32, OpCodeParam.FUNC) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ short rawFunctionCode = codeByteBuffer.getShort();
+ FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
+
+ if (functionCode == null)
+ throw new IllegalFunctionCodeException("Unknown function code 0x" + String.format("%04x", rawFunctionCode) + " encountered at EXT_FUN");
+
+ functionCode.preExecuteCheck(0, false, state, rawFunctionCode);
+
+ FunctionData functionData = new FunctionData(false);
+
+ executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+ }
+ },
+ /**
+ * Call EXTernal FUNction with DATa
+ * 0x33 func addr
+ * func($addr)
+ */
+ EXT_FUN_DAT(0x33, OpCodeParam.FUNC, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ short rawFunctionCode = codeByteBuffer.getShort();
+ FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
+
+ if (functionCode == null)
+ throw new IllegalFunctionCodeException("Unknown function code 0x" + String.format("%04x", rawFunctionCode) + " encountered at EXT_FUN_DAT");
+
+ functionCode.preExecuteCheck(1, false, state, rawFunctionCode);
+
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ long value = dataByteBuffer.getLong(address);
+
+ FunctionData functionData = new FunctionData(value, false);
+
+ executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+ }
+ },
+ /**
+ * Call EXTernal FUNction with DATa x2
+ * 0x34 func addr1 addr2
+ * func($addr1, $addr2)
+ */
+ EXT_FUN_DAT_2(0x34, OpCodeParam.FUNC, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ short rawFunctionCode = codeByteBuffer.getShort();
+ FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
+
+ if (functionCode == null)
+ throw new IllegalFunctionCodeException("Unknown function code 0x" + String.format("%04x", rawFunctionCode) + " encountered at EXT_FUN_DAT_2");
+
+ functionCode.preExecuteCheck(2, false, state, rawFunctionCode);
+
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long value1 = dataByteBuffer.getLong(address1);
+ long value2 = dataByteBuffer.getLong(address2);
+
+ FunctionData functionData = new FunctionData(value1, value2, false);
+
+ executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+ }
+ },
+ /**
+ * Call EXTernal FUNction expecting RETurn value
+ * 0x35 func addr
+ * @addr = func()
+ */
+ EXT_FUN_RET(0x35, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ short rawFunctionCode = codeByteBuffer.getShort();
+ FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
+
+ if (functionCode == null)
+ throw new IllegalFunctionCodeException("Unknown function code 0x" + String.format("%04x", rawFunctionCode) + " encountered at EXT_FUN_RET");
+
+ functionCode.preExecuteCheck(0, true, state, rawFunctionCode);
+
+ int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ FunctionData functionData = new FunctionData(true);
+
+ executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+
+ if (functionData.returnValue == null)
+ throw new ExecutionException("Function failed to return a value as expected of EXT_FUN_RET");
+
+ dataByteBuffer.putLong(address, functionData.returnValue);
+ }
+ },
+ /**
+ * Call EXTernal FUNction expecting RETurn value with DATa
+ * 0x36 func addr1 addr2
+ * @addr1 = func($addr2)
+ */
+ EXT_FUN_RET_DAT(0x36, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ short rawFunctionCode = codeByteBuffer.getShort();
+ FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
+
+ if (functionCode == null)
+ throw new IllegalFunctionCodeException("Unknown function code 0x" + String.format("%04x", rawFunctionCode) + " encountered at EXT_FUN_RET_DAT");
+
+ functionCode.preExecuteCheck(1, true, state, rawFunctionCode);
+
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long value = dataByteBuffer.getLong(address2);
+
+ FunctionData functionData = new FunctionData(value, true);
+
+ executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+
+ if (functionData.returnValue == null)
+ throw new ExecutionException("Function failed to return a value as expected of EXT_FUN_RET_DAT");
+
+ dataByteBuffer.putLong(address1, functionData.returnValue);
+ }
+ },
+ /**
+ * Call EXTernal FUNction expecting RETurn value with DATa x2
+ * 0x37 func addr1 addr2 addr3
+ * @addr1 = func($addr2, $addr3)
+ */
+ EXT_FUN_RET_DAT_2(0x37, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR) {
+ @Override
+ public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
+ MachineState state) throws ExecutionException {
+ short rawFunctionCode = codeByteBuffer.getShort();
+ FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
+
+ if (functionCode == null)
+ throw new IllegalFunctionCodeException(
+ "Unknown function code 0x" + String.format("%04x", rawFunctionCode) + " encountered at EXT_FUN_RET_DAT_2");
+
+ functionCode.preExecuteCheck(2, true, state, rawFunctionCode);
+
+ int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+ int address3 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+
+ long value1 = dataByteBuffer.getLong(address2);
+ long value2 = dataByteBuffer.getLong(address3);
+
+ FunctionData functionData = new FunctionData(value1, value2, true);
+
+ executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+
+ if (functionData.returnValue == null)
+ throw new ExecutionException("Function failed to return a value as expected of EXT_FUN_RET_DAT_2");
+
+ dataByteBuffer.putLong(address1, functionData.returnValue);
+ }
+ };
+
+ public final byte value;
+ public final OpCodeParam[] params;
+
+ // Create a map of opcode values to OpCode
+ private final static Map
+ * Adjusts codeByteBuffer position to programCounter after calling function.
+ *
+ * for example {@link FunctionCode#GENERATE_RANDOM_USING_TX_IN_A}
+ *
+ * @see FunctionCode#GENERATE_RANDOM_USING_TX_IN_A
+ *
+ * @param codeByteBuffer
+ * @param functionCode
+ * @param functionData
+ * @param state
+ * @throws ExecutionException
+ */
+ private static void executeFunction(ByteBuffer codeByteBuffer, FunctionCode functionCode, FunctionData functionData, MachineState state,
+ short rawFunctionCode) throws ExecutionException {
+ state.programCounter = codeByteBuffer.position();
+
+ functionCode.execute(functionData, state, rawFunctionCode);
+
+ codeByteBuffer.position(state.programCounter);
+ }
+
+}
diff --git a/Java/src/org/ciyam/at/OpCodeParam.java b/Java/src/org/ciyam/at/OpCodeParam.java
new file mode 100644
index 0000000..00b0e75
--- /dev/null
+++ b/Java/src/org/ciyam/at/OpCodeParam.java
@@ -0,0 +1,160 @@
+package org.ciyam.at;
+
+import java.nio.ByteBuffer;
+
+public enum OpCodeParam {
+
+ VALUE {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Long(Utils.getCodeValue(codeByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("#%016x", (Long) value);
+ }
+ },
+ DEST_ADDR {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("@%08x", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ },
+ INDIRECT_DEST_ADDR {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("@($%08x)", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ },
+ INDIRECT_DEST_ADDR_WITH_INDEX {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("@($%08x", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ },
+ SRC_ADDR {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("$%08x", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ },
+ INDIRECT_SRC_ADDR {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("$($%08x)", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ },
+ INDIRECT_SRC_ADDR_WITH_INDEX {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("$($%08x", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ },
+ INDEX {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("+ $%08x)", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ },
+ CODE_ADDR {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(Utils.getCodeAddress(codeByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("[%04x]", (Integer) value);
+ }
+ },
+ OFFSET {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Byte(Utils.getCodeOffset(codeByteBuffer));
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("PC+%02x=[%04x]", (int) ((Byte) value), postOpcodeProgramCounter - 1 + (Byte) value);
+ }
+ },
+ FUNC {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Short(codeByteBuffer.getShort());
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ FunctionCode functionCode = FunctionCode.valueOf((Short) value);
+
+ // generic/unknown form
+ if (functionCode == null)
+ return String.format("FN(%04x)", (Short) value);
+
+ // API pass-through
+ if (functionCode == FunctionCode.API_PASSTHROUGH)
+ return String.format("API-FN(%04x)", (Short) value);
+
+ return "\"" + functionCode.name() + "\"" + String.format("{%04x}", (Short) value);
+ }
+ },
+ BLOCK_HEIGHT {
+ @Override
+ public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
+ return new Integer(codeByteBuffer.getInt());
+ }
+
+ @Override
+ protected String toString(Object value, int postOpcodeProgramCounter) {
+ return String.format("height $%08x", ((Integer) value) / MachineState.VALUE_SIZE);
+ }
+ };
+
+ public abstract Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException;
+
+ public String disassemble(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, int postOpcodeProgramCounter) throws ExecutionException {
+ Object value = fetch(codeByteBuffer, dataByteBuffer);
+
+ return this.toString(value, postOpcodeProgramCounter);
+ }
+
+ protected abstract String toString(Object value, int postOpcodeProgramCounter);
+
+}
diff --git a/Java/src/org/ciyam/at/StackBoundsException.java b/Java/src/org/ciyam/at/StackBoundsException.java
new file mode 100644
index 0000000..8c394e9
--- /dev/null
+++ b/Java/src/org/ciyam/at/StackBoundsException.java
@@ -0,0 +1,21 @@
+package org.ciyam.at;
+
+@SuppressWarnings("serial")
+public class StackBoundsException extends ExecutionException {
+
+ public StackBoundsException() {
+ }
+
+ public StackBoundsException(String message) {
+ super(message);
+ }
+
+ public StackBoundsException(Throwable cause) {
+ super(cause);
+ }
+
+ public StackBoundsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/Java/src/org/ciyam/at/Timestamp.java b/Java/src/org/ciyam/at/Timestamp.java
new file mode 100644
index 0000000..55f6064
--- /dev/null
+++ b/Java/src/org/ciyam/at/Timestamp.java
@@ -0,0 +1,66 @@
+package org.ciyam.at;
+
+/**
+ * CIYAM-AT "Timestamp"
+ *