mirror of
https://github.com/Qortal/AT.git
synced 2025-01-30 10:52:14 +00:00
Initial upload of Java re-implementation
Note that this is unfinished, requiring fee-per-opcode support and some refactoring.
This commit is contained in:
parent
2d7555be98
commit
f0e031599d
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/Java/bin/
|
||||
/Java/target/
|
||||
/Java/.settings/
|
27
Java/.classpath
Normal file
27
Java/.classpath
Normal file
@ -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
Normal file
23
Java/.project
Normal file
@ -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
Normal file
28
Java/pom.xml
Normal file
@ -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
Normal file
100
Java/src/org/ciyam/at/API.java
Normal file
@ -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
Normal file
21
Java/src/org/ciyam/at/CodeSegmentException.java
Normal file
@ -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
Normal file
21
Java/src/org/ciyam/at/ExecutionException.java
Normal file
@ -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
Normal file
956
Java/src/org/ciyam/at/FunctionCode.java
Normal file
@ -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
Normal file
29
Java/src/org/ciyam/at/FunctionData.java
Normal file
@ -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
Normal file
21
Java/src/org/ciyam/at/IllegalFunctionCodeException.java
Normal file
@ -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
Normal file
21
Java/src/org/ciyam/at/IllegalOperationException.java
Normal file
@ -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
Normal file
21
Java/src/org/ciyam/at/InvalidAddressException.java
Normal file
@ -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
Normal file
11
Java/src/org/ciyam/at/LoggerInterface.java
Normal file
@ -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
Normal file
445
Java/src/org/ciyam/at/MachineState.java
Normal file
@ -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
Normal file
1014
Java/src/org/ciyam/at/OpCode.java
Normal file
File diff suppressed because it is too large
Load Diff
160
Java/src/org/ciyam/at/OpCodeParam.java
Normal file
160
Java/src/org/ciyam/at/OpCodeParam.java
Normal file
@ -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
Normal file
21
Java/src/org/ciyam/at/StackBoundsException.java
Normal file
@ -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
Normal file
66
Java/src/org/ciyam/at/Timestamp.java
Normal file
@ -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
Normal file
7
Java/src/org/ciyam/at/TwoValueComparator.java
Normal file
@ -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
Normal file
7
Java/src/org/ciyam/at/TwoValueOperator.java
Normal file
@ -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
Normal file
126
Java/src/org/ciyam/at/Utils.java
Normal file
@ -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
Normal file
496
Java/tests/BranchingOpCodeTests.java
Normal file
@ -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
Normal file
255
Java/tests/CallStackOpCodeTests.java
Normal file
@ -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
Normal file
816
Java/tests/DataOpCodeTests.java
Normal file
@ -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
Normal file
116
Java/tests/DisassemblyTests.java
Normal file
@ -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
Normal file
311
Java/tests/FunctionCodeTests.java
Normal file
@ -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
Normal file
111
Java/tests/MiscTests.java
Normal file
@ -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
Normal file
292
Java/tests/OpCodeTests.java
Normal file
@ -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
Normal file
109
Java/tests/SerializationTests.java
Normal file
@ -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
Normal file
218
Java/tests/TestACCT.java
Normal file
@ -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
Normal file
18
Java/tests/ToolchainTests.java
Normal file
@ -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
Normal file
224
Java/tests/UserStackOpCodeTests.java
Normal file
@ -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
Normal file
328
Java/tests/common/ACCTAPI.java
Normal file
@ -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
Normal file
193
Java/tests/common/TestAPI.java
Normal file
@ -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
Normal file
21
Java/tests/common/TestLogger.java
Normal file
@ -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
Normal file
21
Java/tests/common/TestUtils.java
Normal file
@ -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…
Reference in New Issue
Block a user