Browse Source

Initial upload of Java re-implementation

Note that this is unfinished, requiring fee-per-opcode support
and some refactoring.
master
catbref 6 years ago
parent
commit
f0e031599d
  1. 3
      .gitignore
  2. 27
      Java/.classpath
  3. 23
      Java/.project
  4. 28
      Java/pom.xml
  5. 100
      Java/src/org/ciyam/at/API.java
  6. 21
      Java/src/org/ciyam/at/CodeSegmentException.java
  7. 21
      Java/src/org/ciyam/at/ExecutionException.java
  8. 956
      Java/src/org/ciyam/at/FunctionCode.java
  9. 29
      Java/src/org/ciyam/at/FunctionData.java
  10. 21
      Java/src/org/ciyam/at/IllegalFunctionCodeException.java
  11. 21
      Java/src/org/ciyam/at/IllegalOperationException.java
  12. 21
      Java/src/org/ciyam/at/InvalidAddressException.java
  13. 11
      Java/src/org/ciyam/at/LoggerInterface.java
  14. 445
      Java/src/org/ciyam/at/MachineState.java
  15. 1014
      Java/src/org/ciyam/at/OpCode.java
  16. 160
      Java/src/org/ciyam/at/OpCodeParam.java
  17. 21
      Java/src/org/ciyam/at/StackBoundsException.java
  18. 66
      Java/src/org/ciyam/at/Timestamp.java
  19. 7
      Java/src/org/ciyam/at/TwoValueComparator.java
  20. 7
      Java/src/org/ciyam/at/TwoValueOperator.java
  21. 126
      Java/src/org/ciyam/at/Utils.java
  22. 496
      Java/tests/BranchingOpCodeTests.java
  23. 255
      Java/tests/CallStackOpCodeTests.java
  24. 816
      Java/tests/DataOpCodeTests.java
  25. 116
      Java/tests/DisassemblyTests.java
  26. 311
      Java/tests/FunctionCodeTests.java
  27. 111
      Java/tests/MiscTests.java
  28. 292
      Java/tests/OpCodeTests.java
  29. 109
      Java/tests/SerializationTests.java
  30. 218
      Java/tests/TestACCT.java
  31. 18
      Java/tests/ToolchainTests.java
  32. 224
      Java/tests/UserStackOpCodeTests.java
  33. 328
      Java/tests/common/ACCTAPI.java
  34. 193
      Java/tests/common/TestAPI.java
  35. 21
      Java/tests/common/TestLogger.java
  36. 21
      Java/tests/common/TestUtils.java

3
.gitignore vendored

@ -0,0 +1,3 @@
/Java/bin/
/Java/target/
/Java/.settings/

27
Java/.classpath

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="tests">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

23
Java/.project

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>CIYAM-AT-Java</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

28
Java/pom.xml

@ -0,0 +1,28 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>CIYAM-AT-Java</groupId>
<artifactId>CIYAM-AT-Java</artifactId>
<version>1.0</version>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>tests</testSourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

100
Java/src/org/ciyam/at/API.java

@ -0,0 +1,100 @@
package org.ciyam.at;
/**
* API for CIYAM AT "Function Codes" for blockchain-specific interactions.
* <p>
* For more information, see the specification document at:<br>
* <a href="http://ciyam.org/at/at_api.html">Automated Transactions API Specification</a>
* <p>
* 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.
* <p>
* AT should sleep so it can use next block as source of entropy.
* <p>
* Set <tt>state.isSleeping = true</tt> before exit on first call.<br>
* <tt>state.steps</tt> will be zero on second call after wake-up.
* <p>
* 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 <tt>minutes</tt> of blocks added to 'timestamp'
* <p>
* <tt>minutes</tt> 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;
}

21
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);
}
}

21
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);
}
}

956
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.
* <p>
* Function codes are represented by a short. Functions can take 0 to 2 additional long values and optionally return a value too.
* <p>
* FunctionCode instances can be obtained via the default <tt>FunctionCode.valueOf(String)</tt> or the additional <tt>FunctionCode.valueOf(int)</tt>.
* <p>
* Use the <tt>FunctionCode.execute</tt> method to perform the operation.
* <p>
* For more details, view the <a href="http://ciyam.org/at/at_api.html">API Specification</a>.
*
* @see FunctionCode#valueOf(int)
* @see FunctionCode#execute(FunctionData, MachineState)
*/
public enum FunctionCode {
/**
* <b>ECHO</b> value to logger<br>
* <tt>0x0001 value</tt>
*/
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);
}
},
/**
* <tt>0x0100</tt><br>
* Returns A1 value
*/
GET_A1(0x0100, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.a1;
}
},
/**
* <tt>0x0101</tt><br>
* Returns A2 value
*/
GET_A2(0x0101, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.a2;
}
},
/**
* <tt>0x0102</tt><br>
* Returns A3 value
*/
GET_A3(0x0102, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.a3;
}
},
/**
* <tt>0x0103</tt><br>
* Returns A4 value
*/
GET_A4(0x0103, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.a4;
}
},
/**
* <tt>0x0104</tt><br>
* Returns B1 value
*/
GET_B1(0x0104, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.b1;
}
},
/**
* <tt>0x0105</tt><br>
* Returns B2 value
*/
GET_B2(0x0105, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.b2;
}
},
/**
* <tt>0x0106</tt><br>
* Returns B3 value
*/
GET_B3(0x0106, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.b3;
}
},
/**
* <tt>0x0107</tt><br>
* 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<br>
* <tt>0x0110 value</tt>
*/
SET_A1(0x0110, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.a1 = functionData.value1;
}
},
/**
* Set A2<br>
* <tt>0x0111 value</tt>
*/
SET_A2(0x0111, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.a2 = functionData.value1;
}
},
/**
* Set A3<br>
* <tt>0x0112 value</tt>
*/
SET_A3(0x0112, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.a3 = functionData.value1;
}
},
/**
* Set A4<br>
* <tt>0x0113 value</tt>
*/
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<br>
* <tt>0x0114 value value</tt>
*/
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<br>
* <tt>0x0115 value value</tt>
*/
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<br>
* <tt>0x0116 value</tt>
*/
SET_B1(0x0116, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.b1 = functionData.value1;
}
},
/**
* Set B2<br>
* <tt>0x0117 value</tt>
*/
SET_B2(0x0117, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.b2 = functionData.value1;
}
},
/**
* Set B3<br>
* <tt>0x0118 value</tt>
*/
SET_B3(0x0118, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.b3 = functionData.value1;
}
},
/**
* Set B4<br>
* <tt>0x0119 value</tt>
*/
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<br>
* <tt>0x011a value value</tt>
*/
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<br>
* <tt>0x011b value value</tt>
*/
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<br>
* <tt>0x0120</tt>
*/
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<br>
* <tt>0x0121</tt>
*/
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<br>
* <tt>0x0122</tt>
*/
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<br>
* <tt>0x0123</tt>
*/
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<br>
* <tt>0x0124</tt>
*/
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<br>
* <tt>0x0125</tt><br>
* 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<br>
* <tt>0x0126</tt><br>
* 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<br>
* <tt>0x0127</tt><br>
* 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<br>
* <tt>0x0128</tt>
*/
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<br>
* <tt>0x0129</tt>
*/
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<br>
* <tt>0x012a</tt>
*/
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<br>
* <tt>0x012b</tt>
*/
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<br>
* <tt>0x012c</tt>
*/
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<br>
* <tt>0x012d</tt>
*/
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<br>
* <tt>0x012e</tt>
*/
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<br>
* <tt>0x0200</tt>
*/
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<br>
* <tt>0x0201</tt><br>
* 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<br>
* <tt>0x0202</tt>
*/
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<br>
* <tt>0x0203</tt><br>
* 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<br>
* <tt>0x0204</tt>
*/
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<br>
* <tt>0x0205</tt><br>
* 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);
}
}
},
/**
* <tt>0x0300</tt><br>
* 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);
}
},
/**
* <tt>0x0301</tt><br>
* 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);
}
},
/**
* <tt>0x0302</tt><br>
* 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);
}
},
/**
* <tt>0x0303</tt><br>
* 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);
}
},
/**
* <tt>0x0304</tt><br>
* Put transaction after timestamp in A, or zero if none<br>
* 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);
}
},
/**
* <tt>0x0305</tt><br>
* Return transaction type from transaction in A<br>
* 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);
}
},
/**
* <tt>0x0306</tt><br>
* Return transaction amount from transaction in A<br>
* 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);
}
},
/**
* <tt>0x0307</tt><br>
* Return transaction timestamp from transaction in A<br>
* 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);
}
},
/**
* <tt>0x0308</tt><br>
* Generate random number using transaction in A<br>
* Returns 0xffffffffffffffff in A not valid transaction<br>
* 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;
}
}
},
/**
* <tt>0x0309</tt><br>
* Put 'message' from transaction in A into B<br>
* If transaction has no 'message' then zero B<br>
* 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);
}
},
/**
* <tt>0x030a</tt><br>
* 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);
}
},
/**
* <tt>0x030b</tt><br>
* 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);
}
},
/**
* <tt>0x0400</tt><br>
* 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);
}
},
/**
* <tt>0x0401</tt><br>
* Returns AT's previous balance at end of last execution round<br>
* 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);
}
},
/**
* <tt>0x0402</tt><br>
* Pay fee-inclusive amount to account address in B<br>
* 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);
}
},
/**
* <tt>0x0403</tt><br>
* 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);
}
},
/**
* <tt>0x0404</tt><br>
* Pay previous balance to account address in B<br>
* 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);
}
},
/**
* <tt>0x0405</tt><br>
* 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);
}
},
/**
* <tt>0x0406</tt><br>
* 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);
}
},
/**
* <tt>0x0500 - 0x06ff</tt><br>
* Platform-specific functions.<br>
* 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<Short, FunctionCode> map = Arrays.stream(FunctionCode.values())
.collect(Collectors.toMap(functionCode -> functionCode.value, functionCode -> functionCode));
private FunctionCode(int value, int paramCount, boolean returnsValue) {
this.value = (short) value;
this.paramCount = paramCount;
this.returnsValue = returnsValue;
}
public static FunctionCode valueOf(int value) {
// Platform-specific?
if (value >= 0x0500 && value <= 0x06ff)
return API_PASSTHROUGH;
return map.get((short) value);
}
public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws ExecutionException {
if (paramCount != this.paramCount)
throw new IllegalFunctionCodeException(
"Passed paramCount (" + paramCount + ") does not match function's required paramCount (" + this.paramCount + ")");
if (returnValueExpected != this.returnsValue)
throw new IllegalFunctionCodeException(
"Passed returnValueExpected (" + returnValueExpected + ") does not match function's return signature (" + this.returnsValue + ")");
}
/**
* Execute Function
* <p>
* Can modify various fields of <tt>state</tt>, including <tt>programCounter</tt>.
* <p>
* Throws a subclass of <tt>ExecutionException</tt> on error, e.g. <tt>InvalidAddressException</tt>.
*
* @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();
}

29
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);
}
}

21
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);
}
}

21
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);
}
}

21
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);
}
}

11
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);
}

445
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<String> dataLabels, Map<Integer, String> codeLabels) {
public String disassemble() throws ExecutionException {
String output = "";
codeByteBuffer.position(0);
while (codeByteBuffer.hasRemaining()) {
byte rawOpCode = codeByteBuffer.get();
if (rawOpCode == 0)
continue;
OpCode nextOpCode = OpCode.valueOf(rawOpCode);
if (nextOpCode == null)
throw new IllegalOperationException("OpCode 0x" + String.format("%02x", rawOpCode) + " not recognised");
if (!output.isEmpty())
output += "\n";
output += "[PC: " + String.format("%04x", codeByteBuffer.position() - 1) + "] " + nextOpCode.disassemble(codeByteBuffer, dataByteBuffer);
}
return output;
}
}

1014
Java/src/org/ciyam/at/OpCode.java

File diff suppressed because it is too large Load Diff

160
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);
}

21
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);
}
}

66
Java/src/org/ciyam/at/Timestamp.java

@ -0,0 +1,66 @@
package org.ciyam.at;
/**
* CIYAM-AT "Timestamp"
* <p>
* 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.
* <p>
*
* @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;
}
}

7
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);
}

7
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);
}

126
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.
* <p>
* Initial position is <tt>codeByteBuffer.position()</tt> 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.
* <p>
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 4.
* <p>
* <b>Note:</b> address is not scaled by <tt>Constants.VALUE_SIZE</tt> 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.
* <p>
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 4.
* <p>
* <b>Note:</b> address is returned scaled by <tt>Constants.VALUE_SIZE</tt>.
*
* @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.
* <p>
* Initial position is <tt>codeByteBuffer.position()</tt> but on return is incremented by 1.
* <p>
* <b>Note:</b> offset is not scaled by <tt>Constants.VALUE_SIZE</tt> 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.
* <p>
* Initial position is <tt>codeByteBuffer.position()</tt> 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);
}
}
}

496
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));
}
}

255
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);
}
}

816
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));
}
}

116
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());
}
}

311
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);
}
}

111
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);
}
}

292
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);
}
}

109
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);
}
}

218
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);
}
}
}

18
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")));
}
}

224
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);
}
}

328
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<Transaction> transactions;
public Block() {
this.transactions = new ArrayList<Transaction>();
}
}
//
private List<Block> blockchain;
private Map<String, Account> accounts;
private long balanceAT;
private long previousBalanceAT;
//
public ACCTAPI() {
// build blockchain
this.blockchain = new ArrayList<Block>();
Block genesisBlock = new Block();
this.blockchain.add(genesisBlock);
// generate accounts
this.accounts = new HashMap<String, Account>();
Account initiator = new Account("Initiator", 0);
this.accounts.put(initiator.address, initiator);
Account responder = new Account("Responder", 10000);
this.accounts.put(responder.address, responder);
Account bystander = new Account("Bystander", 999);
this.accounts.put(bystander.address, bystander);
this.balanceAT = 50000;
this.previousBalanceAT = this.balanceAT;
}
public void generateNextBlock(byte[] secret) {
Random random = new Random();
Block block = new Block();
System.out.println("Block " + (this.blockchain.size() + 1));
int transactionCount = random.nextInt(5);
for (int i = 0; i < transactionCount; ++i) {
Transaction transaction = new Transaction();
transaction.txType = random.nextInt(2);
switch (transaction.txType) {
case 0: // payment
transaction.amount = random.nextInt(1000);
System.out.print("Payment Tx [" + transaction.amount + "]");
break;
case 1: // message
System.out.print("Message Tx [");
transaction.message = new long[4];
if (random.nextInt(3) == 0) {
// correct message
transaction.message[0] = fromBytes(secret, 0);
transaction.message[1] = fromBytes(secret, 8);
transaction.message[2] = fromBytes(secret, 16);
transaction.message[3] = fromBytes(secret, 24);
} else {
// incorrect message
transaction.message[0] = 0xdeadbeefdeadbeefL;
transaction.message[1] = 0xdeadbeefdeadbeefL;
transaction.message[2] = 0xdeadbeefdeadbeefL;
transaction.message[3] = 0xdeadbeefdeadbeefL;
}
System.out.print(String.format("%016x", transaction.message[0]));
System.out.print(String.format("%016x", transaction.message[1]));
System.out.print(String.format("%016x", transaction.message[2]));
System.out.print(String.format("%016x", transaction.message[3]));
System.out.print("]");
break;
}
transaction.creator = getRandomAccount();
transaction.recipient = getRandomAccount();
System.out.println(" from " + transaction.creator + " to " + transaction.recipient);
block.transactions.add(transaction);
}
this.blockchain.add(block);
this.previousBalanceAT = this.balanceAT;
}
/** 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) };
}
/** Convert part of little-endian byte[] to long */
private long fromBytes(byte[] bytes, int start) {
return (bytes[start] & 0xffL) | (bytes[start + 1] & 0xffL) << 8 | (bytes[start + 2] & 0xffL) << 16 | (bytes[start + 3] & 0xffL) << 24
| (bytes[start + 4] & 0xffL) << 32 | (bytes[start + 5] & 0xffL) << 40 | (bytes[start + 6] & 0xffL) << 48 | (bytes[start + 7] & 0xffL) << 56;
}
private String getRandomAccount() {
int numAccounts = this.accounts.size();
int accountIndex = new Random().nextInt(numAccounts);
List<Account> accounts = this.accounts.values().stream().collect(Collectors.toList());
return accounts.get(accountIndex).address;
}
@Override
public int getCurrentBlockHeight() {
return this.blockchain.size();
}
@Override
public int getATCreationBlockHeight(MachineState state) {
return 1;
}
@Override
public void putPreviousBlockHashInA(MachineState state) {
state.a1 = this.blockchain.size() - 1;
state.a2 = state.a1;
state.a3 = state.a1;
state.a4 = state.a1;
}
@Override
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) {
int blockHeight = timestamp.blockHeight;
int transactionSequence = timestamp.transactionSequence + 1;
while (blockHeight <= this.blockchain.size()) {
Block block = this.blockchain.get(blockHeight - 1);
List<Transaction> transactions = block.transactions;
if (transactionSequence > transactions.size() - 1) {
// No more transactions at this height
++blockHeight;
transactionSequence = 0;
continue;
}
Transaction transaction = transactions.get(transactionSequence);
if (transaction.recipient.equals("Initiator")) {
// Found a transaction
System.out.println("Found transaction at height " + blockHeight + " sequence " + transactionSequence);
// Generate pseudo-hash of transaction
state.a1 = new Timestamp(blockHeight, transactionSequence).longValue();
state.a2 = state.a1;
state.a3 = state.a1;
state.a4 = state.a1;
return;
}
++transactionSequence;
}
// Nothing found
state.a1 = 0L;
state.a2 = 0L;
state.a3 = 0L;
state.a4 = 0L;
}
@Override
public long getTypeFromTransactionInA(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
return transaction.txType;
}
@Override
public long getAmountFromTransactionInA(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
return transaction.amount;
}
@Override
public long getTimestampFromTransactionInA(MachineState state) {
// Transaction hash in A is actually just 4 copies of transaction's "timestamp"
Timestamp timestamp = new Timestamp(state.a1);
return timestamp.longValue();
}
@Override
public long generateRandomUsingTransactionInA(MachineState state) {
// NOT USED
return 0L;
}
@Override
public void putMessageFromTransactionInAIntoB(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
state.b1 = transaction.message[0];
state.b2 = transaction.message[1];
state.b3 = transaction.message[2];
state.b4 = transaction.message[3];
}
@Override
public void putAddressFromTransactionInAIntoB(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
state.b1 = transaction.creator.charAt(0);
state.b2 = state.b1;
state.b3 = state.b1;
state.b4 = state.b1;
}
@Override
public void putCreatorAddressIntoB(MachineState state) {
// Dummy creator
state.b1 = "C".charAt(0);
state.b2 = state.b1;
state.b3 = state.b1;
state.b4 = state.b1;
}
@Override
public long getCurrentBalance(MachineState state) {
return this.balanceAT;
}
@Override
public long getPreviousBalance(MachineState state) {
return this.previousBalanceAT;
}
@Override
public void payAmountToB(long value1, MachineState state) {
char firstChar = String.format("%c", state.b1).charAt(0);
Account recipient = this.accounts.values().stream().filter((account) -> account.address.charAt(0) == firstChar).findFirst().get();
recipient.balance += value1;
System.out.println("Paid " + value1 + " to " + recipient.address + ", their balance now: " + recipient.balance);
this.balanceAT -= value1;
System.out.println("Our balance now: " + this.balanceAT);
}
@Override
public void payCurrentBalanceToB(MachineState state) {
// NOT USED
}
@Override
public void payPreviousBalanceToB(MachineState state) {
// NOT USED
}
@Override
public void messageAToB(MachineState state) {
// NOT USED
}
@Override
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state) {
timestamp.blockHeight += (int) minutes;
return timestamp.longValue();
}
@Override
public void onFatalError(MachineState state, ExecutionException e) {
System.out.println("Fatal error: " + e.getMessage());
System.out.println("No error address set - refunding to creator and finishing");
}
@Override
public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException {
// NOT USED
}
@Override
public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException {
// NOT USED
}
}

193
Java/tests/common/TestAPI.java

@ -0,0 +1,193 @@
package common;
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 TestAPI implements API {
private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds
@Override
public int getCurrentBlockHeight() {
return 10;
}
@Override
public int getATCreationBlockHeight(MachineState state) {
return 5;
}
@Override
public void putPreviousBlockHashInA(MachineState state) {
state.a1 = 9L;
state.a2 = 9L;
state.a3 = 9L;
state.a4 = 9L;
}
@Override
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) {
// Cycle through transactions: 1 -> 2 -> 3 -> 0 -> 1 ...
state.a1 = (timestamp.transactionSequence + 1) % 4;
state.a2 = state.a1;
state.a3 = state.a1;
state.a4 = state.a1;
}
@Override
public long getTypeFromTransactionInA(MachineState state) {
return 0L;
}
@Override
public long getAmountFromTransactionInA(MachineState state) {
return 123L;
}
@Override
public long getTimestampFromTransactionInA(MachineState state) {
return 1536227162000L;
}
@Override
public long generateRandomUsingTransactionInA(MachineState state) {
if (state.steps != 0) {
// First call
System.out.println("generateRandomUsingTransactionInA: first call - sleeping");
// Perform init?
state.isSleeping = true;
return 0L; // not used
} else {
// Second call
System.out.println("generateRandomUsingTransactionInA: second call - returning random");
// HASH(A and new block hash)
return (state.a1 ^ 9L) << 3 ^ (state.a2 ^ 9L) << 12 ^ (state.a3 ^ 9L) << 5 ^ (state.a4 ^ 9L);
}
}
@Override
public void putMessageFromTransactionInAIntoB(MachineState state) {
state.b1 = state.a4;
state.b2 = state.a3;
state.b3 = state.a2;
state.b4 = state.a1;
}
@Override
public void putAddressFromTransactionInAIntoB(MachineState state) {
// Dummy address
state.b1 = 0xaaaaaaaaaaaaaaaaL;
state.b2 = 0xaaaaaaaaaaaaaaaaL;
state.b3 = 0xaaaaaaaaaaaaaaaaL;
state.b4 = 0xaaaaaaaaaaaaaaaaL;
}
@Override
public void putCreatorAddressIntoB(MachineState state) {
// Dummy creator
state.b1 = 0xccccccccccccccccL;
state.b2 = 0xccccccccccccccccL;
state.b3 = 0xccccccccccccccccL;
state.b4 = 0xccccccccccccccccL;
}
@Override
public long getCurrentBalance(MachineState state) {
return 12345L;
}
@Override
public long getPreviousBalance(MachineState state) {
return 10000L;
}
@Override
public void payAmountToB(long value1, MachineState state) {
}
@Override
public void payCurrentBalanceToB(MachineState state) {
}
@Override
public void payPreviousBalanceToB(MachineState state) {
}
@Override
public void messageAToB(MachineState state) {
}
@Override
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state) {
timestamp.blockHeight = ((int) minutes * 60) / BLOCK_PERIOD;
return timestamp.longValue();
}
@Override
public void onFatalError(MachineState state, ExecutionException e) {
System.out.println("Fatal error: " + e.getMessage());
System.out.println("No error address set - refunding to creator and finishing");
}
@Override
public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException {
Integer requiredParamCount;
Boolean returnsValue;
switch (functionCodeValue) {
case 0x0501:
// take one arg, no return value
requiredParamCount = 1;
returnsValue = false;
break;
case 0x0502:
// take no arg, return a value
requiredParamCount = 0;
returnsValue = true;
break;
default:
// Unrecognised platform-specific function code
throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", functionCodeValue));
}
if (requiredParamCount == null || returnsValue == null)
throw new IllegalFunctionCodeException("Error during platform-specific function pre-execute check");
if (paramCount != requiredParamCount)
throw new IllegalFunctionCodeException("Passed paramCount (" + paramCount + ") does not match platform-specific function code 0x"
+ String.format("%04x", functionCodeValue) + " required paramCount (" + requiredParamCount + ")");
if (returnValueExpected != returnsValue)
throw new IllegalFunctionCodeException("Passed returnValueExpected (" + returnValueExpected + ") does not match platform-specific function code 0x"
+ String.format("%04x", functionCodeValue) + " return signature (" + returnsValue + ")");
}
@Override
public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException {
switch (functionCodeValue) {
case 0x0501:
System.out.println("Platform-specific function 0x0501 called with 0x" + String.format("%016x", functionData.value1));
break;
case 0x0502:
System.out.println("Platform-specific function 0x0502 called!");
functionData.returnValue = 0x0502L;
break;
default:
// Unrecognised platform-specific function code
throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", functionCodeValue));
}
}
}

21
Java/tests/common/TestLogger.java

@ -0,0 +1,21 @@
package common;
import org.ciyam.at.LoggerInterface;
public class TestLogger implements LoggerInterface {
@Override
public void error(String message) {
System.err.println("ERROR: " + message);
}
@Override
public void debug(String message) {
System.err.println("DEBUG: " + message);
}
@Override
public void echo(String message) {
System.err.println("ECHO: " + message);
}
}

21
Java/tests/common/TestUtils.java

@ -0,0 +1,21 @@
package common;
import java.math.BigInteger;
public class TestUtils {
public static byte[] hexToBytes(String hex) {
byte[] output = new byte[hex.length() / 2];
byte[] converted = new BigInteger("00" + hex, 16).toByteArray();
int convertedLength = Math.min(output.length, converted.length);
int convertedStart = converted.length - convertedLength;
int outputStart = output.length - convertedLength;
System.arraycopy(converted, convertedStart, output, outputStart, convertedLength);
return output;
}
}
Loading…
Cancel
Save