diff --git a/Java/src/org/ciyam/at/API.java b/Java/src/org/ciyam/at/API.java
index c012e27..6adecaf 100644
--- a/Java/src/org/ciyam/at/API.java
+++ b/Java/src/org/ciyam/at/API.java
@@ -10,33 +10,33 @@ package org.ciyam.at;
  * bits) with the second part being the number of the transaction if applicable (also 32 bits and zero if not applicable).
  *
  */
-public interface API {
+public abstract class API {
 
 	/** Returns current blockchain's height */
-	public int getCurrentBlockHeight();
+	public abstract int getCurrentBlockHeight();
 
 	/** Returns block height where AT was created */
-	public int getATCreationBlockHeight(MachineState state);
+	public abstract int getATCreationBlockHeight(MachineState state);
 
 	/** Returns previous block's height */
-	default public int getPreviousBlockHeight() {
+	public int getPreviousBlockHeight() {
 		return getCurrentBlockHeight() - 1;
 	}
 
 	/** Put previous block's signature hash in A */
-	public void putPreviousBlockHashInA(MachineState state);
+	public abstract void putPreviousBlockHashInA(MachineState state);
 
 	/** Put next transaction to AT after timestamp in A */
-	public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state);
+	public abstract void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state);
 
 	/** Return type from transaction in A, or 0xffffffffffffffff if A not valid transaction */
-	public long getTypeFromTransactionInA(MachineState state);
+	public abstract 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);
+	public abstract long getAmountFromTransactionInA(MachineState state);
 
 	/** Return timestamp from transaction in A, or 0xffffffffffffffff if A not valid transaction */
-	public long getTimestampFromTransactionInA(MachineState state);
+	public abstract long getTimestampFromTransactionInA(MachineState state);
 
 	/**
 	 * Generate pseudo-random number using transaction in A.
@@ -48,53 +48,92 @@ public interface API {
 	 * <p>
 	 * Returns 0xffffffffffffffff if A not valid transaction.
 	 */
-	public long generateRandomUsingTransactionInA(MachineState state);
+	public abstract long generateRandomUsingTransactionInA(MachineState state);
 
 	/** Put 'message' from transaction in A into B */
-	public void putMessageFromTransactionInAIntoB(MachineState state);
+	public abstract void putMessageFromTransactionInAIntoB(MachineState state);
 
 	/** Put sender/creator address from transaction in A into B */
-	public void putAddressFromTransactionInAIntoB(MachineState state);
+	public abstract void putAddressFromTransactionInAIntoB(MachineState state);
 
 	/** Put AT's creator's address into B */
-	public void putCreatorAddressIntoB(MachineState state);
+	public abstract void putCreatorAddressIntoB(MachineState state);
 
 	/** Return AT's current balance */
-	public long getCurrentBalance(MachineState state);
+	public abstract 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);
+	public abstract 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);
+	public abstract void payAmountToB(long value1, MachineState state);
 
 	/** Pay AT's current balance to address in B */
-	public void payCurrentBalanceToB(MachineState state);
+	public abstract void payCurrentBalanceToB(MachineState state);
 
 	/** Pay AT's previous balance to address in B */
-	public void payPreviousBalanceToB(MachineState state);
+	public abstract void payPreviousBalanceToB(MachineState state);
 
 	/** Send 'message' in A to address in B */
-	public void messageAToB(MachineState state);
+	public abstract 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);
+	public abstract 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);
+	public abstract 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;
+	public abstract 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;
+	public abstract void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException;
+
+	/** Convenience method to allow subclasses to access package-scoped MachineState.setIsSleeping */
+	protected void setIsSleeping(MachineState state, boolean isSleeping) {
+		state.setIsSleeping(isSleeping);
+	}
+
+	/** Convenience methods to allow subclasses to access package-scoped a1-a4, b1-b4 variables */
+	protected void setA1(MachineState state, long value) {
+		state.a1 = value;
+	}
+
+	protected void setA2(MachineState state, long value) {
+		state.a2 = value;
+	}
+
+	protected void setA3(MachineState state, long value) {
+		state.a3 = value;
+	}
+
+	protected void setA4(MachineState state, long value) {
+		state.a4 = value;
+	}
+
+	protected void setB1(MachineState state, long value) {
+		state.b1 = value;
+	}
+
+	protected void setB2(MachineState state, long value) {
+		state.b2 = value;
+	}
+
+	protected void setB3(MachineState state, long value) {
+		state.b3 = value;
+	}
+
+	protected void setB4(MachineState state, long value) {
+		state.b4 = value;
+	}
 
 }
diff --git a/Java/src/org/ciyam/at/FunctionCode.java b/Java/src/org/ciyam/at/FunctionCode.java
index 1aa3aeb..c3c4e37 100644
--- a/Java/src/org/ciyam/at/FunctionCode.java
+++ b/Java/src/org/ciyam/at/FunctionCode.java
@@ -31,7 +31,7 @@ public enum FunctionCode {
 		@Override
 		protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
 			String message = String.valueOf(functionData.value1);
-			state.logger.echo(message);
+			state.getLogger().echo(message);
 		}
 	},
 	/**
@@ -671,7 +671,7 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = Timestamp.toLong(state.getAPI().getCurrentBlockHeight(), 0);
 		}
 	},
 	/**
@@ -681,7 +681,7 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = Timestamp.toLong(state.getAPI().getATCreationBlockHeight(state), 0);
 		}
 	},
 	/**
@@ -691,7 +691,7 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = Timestamp.toLong(state.getAPI().getPreviousBlockHeight(), 0);
 		}
 	},
 	/**
@@ -701,7 +701,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().putPreviousBlockHashInA(state);
 		}
 	},
 	/**
@@ -712,7 +712,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().putTransactionAfterTimestampInA(new Timestamp(functionData.value1), state);
 		}
 	},
 	/**
@@ -723,7 +723,7 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = state.getAPI().getTypeFromTransactionInA(state);
 		}
 	},
 	/**
@@ -734,7 +734,7 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = state.getAPI().getAmountFromTransactionInA(state);
 		}
 	},
 	/**
@@ -745,7 +745,7 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = state.getAPI().getTimestampFromTransactionInA(state);
 		}
 	},
 	/**
@@ -757,15 +757,17 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = state.getAPI().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 API set isSleeping then rewind program counter (actually codeByteBuffer) ready for being awoken
+			if (state.getIsSleeping()) {
+				// EXT_FUN_RET(1) + our function code(2) + address(4)
+				int newPosition = state.codeByteBuffer.position() - MachineState.OPCODE_SIZE - MachineState.FUNCTIONCODE_SIZE - MachineState.ADDRESS_SIZE;
+				state.codeByteBuffer.position(newPosition);
 
 				// If specific sleep height not set, default to next block
-				if (state.sleepUntilHeight == null)
-					state.sleepUntilHeight = state.currentBlockHeight + 1;
+				if (state.getSleepUntilHeight() == null)
+					state.setSleepUntilHeight(state.getCurrentBlockHeight() + 1);
 			}
 		}
 	},
@@ -778,7 +780,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().putMessageFromTransactionInAIntoB(state);
 		}
 	},
 	/**
@@ -788,7 +790,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().putAddressFromTransactionInAIntoB(state);
 		}
 	},
 	/**
@@ -798,7 +800,7 @@ public enum FunctionCode {
 	PUT_CREATOR_INTO_B(0x030b, 0, false) {
 		@Override
 		protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
-			state.api.putCreatorAddressIntoB(state);
+			state.getAPI().putCreatorAddressIntoB(state);
 		}
 	},
 	/**
@@ -808,7 +810,7 @@ public enum FunctionCode {
 	GET_CURRENT_BALANCE(0x0400, 0, true) {
 		@Override
 		protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
-			functionData.returnValue = state.api.getCurrentBalance(state);
+			functionData.returnValue = state.getAPI().getCurrentBalance(state);
 		}
 	},
 	/**
@@ -819,7 +821,7 @@ public enum FunctionCode {
 	GET_PREVIOUS_BALANCE(0x0401, 0, true) {
 		@Override
 		protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
-			functionData.returnValue = state.api.getPreviousBalance(state);
+			functionData.returnValue = state.getAPI().getPreviousBalance(state);
 		}
 	},
 	/**
@@ -830,7 +832,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().payAmountToB(functionData.value1, state);
 		}
 	},
 	/**
@@ -840,7 +842,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().payCurrentBalanceToB(state);
 		}
 	},
 	/**
@@ -851,7 +853,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().payPreviousBalanceToB(state);
 		}
 	},
 	/**
@@ -861,7 +863,7 @@ public enum FunctionCode {
 	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);
+			state.getAPI().messageAToB(state);
 		}
 	},
 	/**
@@ -871,7 +873,7 @@ public enum FunctionCode {
 	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);
+			functionData.returnValue = state.getAPI().addMinutesToTimestamp(new Timestamp(functionData.value1), functionData.value2, state);
 		}
 	},
 	/**
@@ -882,12 +884,12 @@ public enum FunctionCode {
 	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);
+			state.getAPI().platformSpecificPreExecuteCheck(rawFunctionCode, paramCount, returnValueExpected);
 		}
 
 		@Override
 		protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
-			state.api.platformSpecificPostCheckExecute(rawFunctionCode, functionData, state);
+			state.getAPI().platformSpecificPostCheckExecute(rawFunctionCode, functionData, state);
 		}
 	};
 
@@ -943,7 +945,7 @@ public enum FunctionCode {
 		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() + "\"");
+		state.getLogger().debug("Function \"" + this.name() + "\"");
 
 		postCheckExecute(functionData, state, rawFunctionCode);
 	}
diff --git a/Java/src/org/ciyam/at/MachineState.java b/Java/src/org/ciyam/at/MachineState.java
index 1175d42..011e1f4 100644
--- a/Java/src/org/ciyam/at/MachineState.java
+++ b/Java/src/org/ciyam/at/MachineState.java
@@ -4,12 +4,21 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 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 one OpCode - typically 1 byte (byte) */
+	public static final int OPCODE_SIZE = 1;
+
+	/** Size of one FunctionCode - typically 2 bytes (short) */
+	public static final int FUNCTIONCODE_SIZE = 2;
+
 	/** Size of value stored in data segment - typically 8 bytes (long) */
 	public static final int VALUE_SIZE = 8;
 
@@ -19,162 +28,336 @@ public class MachineState {
 	/** 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;
+	private static class VersionedConstants {
+		/** Bytes per code page */
+		public final int CODE_PAGE_SIZE;
+		/** Bytes per data page */
+		public final int DATA_PAGE_SIZE;
+		/** Bytes per call stack page */
+		public final int CALL_STACK_PAGE_SIZE;
+		/** Bytes per user stack page */
+		public final int USER_STACK_PAGE_SIZE;
 
-	/** 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;
+		public VersionedConstants(int codePageSize, int dataPageSize, int callStackPageSize, int userStackPageSize) {
+			CODE_PAGE_SIZE = codePageSize;
+			DATA_PAGE_SIZE = dataPageSize;
+			CALL_STACK_PAGE_SIZE = callStackPageSize;
+			USER_STACK_PAGE_SIZE = userStackPageSize;
 		}
 	}
 
+	/** Map of constants (e.g. CODE_PAGE_SIZE) by AT version */
+	private static final Map<Short, VersionedConstants> VERSIONED_CONSTANTS = new HashMap<Short, VersionedConstants>();
+	static {
+		VERSIONED_CONSTANTS.put((short) 1, new VersionedConstants(256, 256, 256, 256));
+		VERSIONED_CONSTANTS.put((short) 3, new VersionedConstants(OPCODE_SIZE, VALUE_SIZE, ADDRESS_SIZE, VALUE_SIZE));
+	}
+
+	// Set during construction
+	public final short version;
+	public final short reserved;
+	public final short numCodePages;
+	public final short numDataPages;
+	public final short numCallStackPages;
+	public final short numUserStackPages;
+
+	private final byte[] headerBytes;
+
+	/** Constants set in effect */
+	private final VersionedConstants constants;
+
+	/** Program Counter: offset into code to point of current execution */
+	private int programCounter;
+
+	/** Initial program counter value to use on next block after current block's execution has stopped. 0 by default */
+	private int onStopAddress;
+
+	/** Program counter value to use if an error occurs during execution. If null upon error, refund all funds to creator and finish */
+	private Integer onErrorAddress;
+
+	/** Execution for current block has stopped. Continue at current program counter on next/specific block */
+	private boolean isSleeping;
+
+	/** Block height required to wake from sleeping, or null if not in use */
+	private Integer sleepUntilHeight;
+
+	/** Execution for current block has stopped. Restart at onStopAddress on next block */
+	private boolean isStopped;
+
+	/** Execution stopped due to lack of funds for processing. Restart at onStopAddress if frozenBalance increases */
+	private boolean isFrozen;
+
+	/** Balance at which there were not enough funds, or null if not in use */
+	private Long frozenBalance;
+
+	/** Execution permanently stopped */
+	private boolean isFinished;
+
+	/** Execution permanently stopped due to fatal error */
+	private boolean hadFatalError;
+
+	// 256-bit pseudo-registers
+	// NOTE: These are package-scope to allow easy access/operations in FunctionCodes.
+	// Outside classes (e.g. unit tests) can use getters
+	/* package */ long a1;
+	/* package */ long a2;
+	/* package */ long a3;
+	/* package */ long a4;
+
+	/* package */ long b1;
+	/* package */ long b2;
+	/* package */ long b3;
+	/* package */ long b4;
+
+	private int currentBlockHeight;
+
+	/** Number of opcodes processed this execution */
+	private int steps;
+
+	private API api;
+	private LoggerInterface logger;
+
+	// NOTE: These are package-scope to allow easy access/operations in Opcode/FunctionCode.
+	/* package */ ByteBuffer codeByteBuffer;
+	/* package */ ByteBuffer dataByteBuffer;
+	/* package */ ByteBuffer callStackByteBuffer;
+	/* package */ ByteBuffer userStackByteBuffer;
+
+	// Constructors
+
 	/** 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);
+		// Parsing header bytes
+		ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes);
+		byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 
-		this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+		this.version = byteBuffer.getShort();
+		if (this.version < 1)
+			throw new IllegalArgumentException("Version must be >= 0");
 
-		this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * CALL_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+		this.constants = VERSIONED_CONSTANTS.get(this.version);
+		if (this.constants == null)
+			throw new IllegalArgumentException("Version " + this.version + " unsupported");
+
+		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");
+
+		// Header OK - set up code and data buffers
+		this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+		this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+
+		// Set up stacks
+		this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.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 = ByteBuffer.allocate(this.numUserStackPages * this.constants.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.currentBlockHeight = 0;
 		this.steps = 0;
 		this.logger = logger;
 	}
 
+	/** For creating a new machine state */
+	public MachineState(byte[] creationBytes) {
+		this(null, null, Arrays.copyOfRange(creationBytes, 0, HEADER_LENGTH));
+
+		int expectedLength = HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE + this.numDataPages + this.constants.DATA_PAGE_SIZE;
+		if (creationBytes.length != expectedLength)
+			throw new IllegalArgumentException("Creation bytes length does not match header values");
+
+		System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, this.numCodePages * this.constants.CODE_PAGE_SIZE);
+
+		System.arraycopy(creationBytes, HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE, this.dataByteBuffer.array(), 0,
+				this.numDataPages + this.constants.DATA_PAGE_SIZE);
+
+		commonFinalConstruction();
+	}
+
 	/** 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)
+		if (codeBytes.length > this.numCodePages * this.constants.CODE_PAGE_SIZE)
 			throw new IllegalArgumentException("Number of code pages too small to hold code bytes");
 
-		if (dataBytes.length > this.numDataPages * DATA_PAGE_SIZE)
+		if (dataBytes.length > this.numDataPages * this.constants.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);
 
+		commonFinalConstruction();
+	}
+
+	private void commonFinalConstruction() {
 		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;
+		this.isFinished = false;
+		this.hadFatalError = false;
 	}
 
+	// Getters / setters
+
+	// NOTE: Many setters have package-scope (i.e. org.ciyam.at only) to allow changes
+	// during execution but not by outside classes.
+
+	public int getProgramCounter() {
+		return this.programCounter;
+	}
+
+	public int getOnStopAddress() {
+		return this.onStopAddress;
+	}
+
+	/* package */ void setOnStopAddress(int address) {
+		this.onStopAddress = address;
+	}
+
+	public Integer getOnErrorAddress() {
+		return this.onErrorAddress;
+	}
+
+	/* package */ void setOnErrorAddress(Integer address) {
+		this.onErrorAddress = address;
+	}
+
+	public boolean getIsSleeping() {
+		return this.isSleeping;
+	}
+
+	/* package */ void setIsSleeping(boolean isSleeping) {
+		this.isSleeping = isSleeping;
+	}
+
+	public Integer getSleepUntilHeight() {
+		return this.sleepUntilHeight;
+	}
+
+	/* package */ void setSleepUntilHeight(Integer address) {
+		this.sleepUntilHeight = address;
+	}
+
+	public boolean getIsStopped() {
+		return this.isStopped;
+	}
+
+	/* package */ void setIsStopped(boolean isStopped) {
+		this.isStopped = isStopped;
+	}
+
+	public boolean getIsFrozen() {
+		return this.isFrozen;
+	}
+
+	/* package */ void setIsFrozen(boolean isFrozen) {
+		this.isFrozen = isFrozen;
+	}
+
+	public Long getFrozenBalance() {
+		return this.frozenBalance;
+	}
+
+	/* package */ void setFrozenBalance(Long frozenBalance) {
+		this.frozenBalance = frozenBalance;
+	}
+
+	public boolean getIsFinished() {
+		return this.isFinished;
+	}
+
+	/* package */ void setIsFinished(boolean isFinished) {
+		this.isFinished = isFinished;
+	}
+
+	public boolean getHadFatalError() {
+		return this.hadFatalError;
+	}
+
+	/* package */ void setHadFatalError(boolean hadFatalError) {
+		this.hadFatalError = hadFatalError;
+	}
+
+	// No corresponding setters due to package-scope - see above
+	public long getA1() {
+		return this.a1;
+	}
+
+	public long getA2() {
+		return this.a2;
+	}
+
+	public long getA3() {
+		return this.a3;
+	}
+
+	public long getA4() {
+		return this.a4;
+	}
+
+	public long getB1() {
+		return this.b1;
+	}
+
+	public long getB2() {
+		return this.b2;
+	}
+
+	public long getB3() {
+		return this.b3;
+	}
+
+	public long getB4() {
+		return this.b4;
+	}
+	// End of package-scope pseudo-registers
+
+	public int getCurrentBlockHeight() {
+		return this.currentBlockHeight;
+	}
+
+	public int getSteps() {
+		return this.steps;
+	}
+
+	public API getAPI() {
+		return this.api;
+	}
+
+	public LoggerInterface getLogger() {
+		return this.logger;
+	}
+
+	// Serialization
+
 	/** For serializing a machine state */
 	public byte[] toBytes() {
 		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@@ -192,13 +375,13 @@ public class MachineState {
 			// Call stack length (32bit unsigned int)
 			int callStackLength = this.callStackByteBuffer.limit() - this.callStackByteBuffer.position();
 			bytes.write(toByteArray(callStackLength));
-			// Call stack
+			// Call stack (only the bytes actually in use)
 			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
+			// User stack (only the bytes actually in use)
 			bytes.write(this.userStackByteBuffer.array(), this.userStackByteBuffer.position(), userStackLength);
 
 			// Actual state
@@ -333,6 +516,34 @@ public class MachineState {
 		return state;
 	}
 
+	/** Class for pushing/popping boolean flags onto/from an int */
+	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;
+		}
+	}
+
 	/** 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) };
@@ -344,32 +555,7 @@ public class MachineState {
 				(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");
-	}
+	// Actual execution
 
 	public void execute() {
 		// Set byte buffer position using program counter
@@ -382,6 +568,7 @@ public class MachineState {
 		this.isFrozen = false;
 		this.frozenBalance = null;
 		this.steps = 0;
+		this.currentBlockHeight = api.getCurrentBlockHeight();
 
 		while (!this.isSleeping && !this.isStopped && !this.isFinished && !this.isFrozen) {
 			byte rawOpCode = codeByteBuffer.get();
@@ -393,7 +580,12 @@ public class MachineState {
 
 				this.logger.debug("[PC: " + String.format("%04x", this.programCounter) + "] " + nextOpCode.name());
 
-				nextOpCode.execute(codeByteBuffer, dataByteBuffer, userStackByteBuffer, callStackByteBuffer, this);
+				// TODO: Request cost from API, apply cost to balance, etc.
+
+				// At this point, programCounter is BEFORE opcode (and args).
+				nextOpCode.execute(this);
+
+				// Synchronize programCounter with codeByteBuffer in case of JMPs, branches, etc.
 				this.programCounter = codeByteBuffer.position();
 			} catch (ExecutionException e) {
 				this.logger.debug("Error at PC " + String.format("%04x", this.programCounter) + ": " + e.getMessage());
@@ -401,6 +593,8 @@ public class MachineState {
 				if (this.onErrorAddress == null) {
 					this.isFinished = true;
 					this.hadFatalError = true;
+
+					// Ask API to refund remaining funds back to AT's creator
 					this.api.onFatalError(this, e);
 					break;
 				}
@@ -418,7 +612,7 @@ public class MachineState {
 		}
 	}
 
-	// public String disassemble(List<String> dataLabels, Map<Integer, String> codeLabels) {
+	/** Return disassembly of code bytes */
 	public String disassemble() throws ExecutionException {
 		String output = "";
 
diff --git a/Java/src/org/ciyam/at/OpCode.java b/Java/src/org/ciyam/at/OpCode.java
index 8e69040..3fbfcbb 100644
--- a/Java/src/org/ciyam/at/OpCode.java
+++ b/Java/src/org/ciyam/at/OpCode.java
@@ -2,7 +2,9 @@ package org.ciyam.at;
 
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -26,7 +28,7 @@ import java.util.stream.Collectors;
  * <tt>$($addr1 + $addr2)</tt> means "fetch from address fetched from <tt>addr1</tt> plus offset fetched from <tt>addr2</tt>", i.e. indirect indexed
  * 
  * @see OpCode#valueOf(int)
- * @see OpCode#execute(ByteBuffer, ByteBuffer, ByteBuffer, ByteBuffer, MachineState)
+ * @see OpCode#executeWithParams(MachineState, Object...)
  */
 public enum OpCode {
 
@@ -37,8 +39,7 @@ public enum OpCode {
 	 */
 	NOP(0x7f) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) {
+		public void executeWithParams(MachineState state, Object... args) {
 			// Do nothing
 		}
 	},
@@ -49,11 +50,11 @@ public enum OpCode {
 	 */
 	SET_VAL(0x01, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = Utils.getCodeValue(codeByteBuffer);
-			dataByteBuffer.putLong(address, value);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+			long value = (long) args[1];
+
+			state.dataByteBuffer.putLong(address, value);
 		}
 	},
 	/**
@@ -63,12 +64,12 @@ public enum OpCode {
 	 */
 	SET_DAT(0x02, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address2);
-			dataByteBuffer.putLong(address1, value);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address1 = (int) args[0];
+			int address2 = (int) args[1];
+
+			long value = state.dataByteBuffer.getLong(address2);
+			state.dataByteBuffer.putLong(address1, value);
 		}
 	},
 	/**
@@ -78,10 +79,10 @@ public enum OpCode {
 	 */
 	CLR_DAT(0x03, OpCodeParam.DEST_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			dataByteBuffer.putLong(address, 0L);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+
+			state.dataByteBuffer.putLong(address, 0L);
 		}
 	},
 	/**
@@ -91,11 +92,11 @@ public enum OpCode {
 	 */
 	INC_DAT(0x04, OpCodeParam.DEST_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address);
-			dataByteBuffer.putLong(address, value + 1);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+
+			long value = state.dataByteBuffer.getLong(address);
+			state.dataByteBuffer.putLong(address, value + 1);
 		}
 	},
 	/**
@@ -105,11 +106,11 @@ public enum OpCode {
 	 */
 	DEC_DAT(0x05, OpCodeParam.DEST_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address);
-			dataByteBuffer.putLong(address, value - 1);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+
+			long value = state.dataByteBuffer.getLong(address);
+			state.dataByteBuffer.putLong(address, value - 1);
 		}
 	},
 	/**
@@ -119,9 +120,8 @@ public enum OpCode {
 	 */
 	ADD_DAT(0x06, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a + b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeDataOperation(state, (a, b) -> a + b, args);
 		}
 	},
 	/**
@@ -131,9 +131,8 @@ public enum OpCode {
 	 */
 	SUB_DAT(0x07, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a - b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeDataOperation(state, (a, b) -> a - b, args);
 		}
 	},
 	/**
@@ -143,9 +142,8 @@ public enum OpCode {
 	 */
 	MUL_DAT(0x08, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a * b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeDataOperation(state, (a, b) -> a * b, args);
 		}
 	},
 	/**
@@ -156,10 +154,9 @@ public enum OpCode {
 	 */
 	DIV_DAT(0x09, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
 			try {
-				executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a / b);
+				executeDataOperation(state, (a, b) -> a / b, args);
 			} catch (ArithmeticException e) {
 				throw new IllegalOperationException("Divide by zero", e);
 			}
@@ -172,9 +169,8 @@ public enum OpCode {
 	 */
 	BOR_DAT(0x0a, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a | b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeDataOperation(state, (a, b) -> a | b, args);
 		}
 	},
 	/**
@@ -184,9 +180,8 @@ public enum OpCode {
 	 */
 	AND_DAT(0x0b, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a & b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeDataOperation(state, (a, b) -> a & b, args);
 		}
 	},
 	/**
@@ -196,9 +191,8 @@ public enum OpCode {
 	 */
 	XOR_DAT(0x0c, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a ^ b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeDataOperation(state, (a, b) -> a ^ b, args);
 		}
 	},
 	/**
@@ -208,11 +202,11 @@ public enum OpCode {
 	 */
 	NOT_DAT(0x0d, OpCodeParam.DEST_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address);
-			dataByteBuffer.putLong(address, ~value);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+
+			long value = state.dataByteBuffer.getLong(address);
+			state.dataByteBuffer.putLong(address, ~value);
 		}
 	},
 	/**
@@ -222,18 +216,17 @@ public enum OpCode {
 	 */
 	SET_IND(0x0e, OpCodeParam.DEST_ADDR, OpCodeParam.INDIRECT_SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address1 = (int) args[0];
+			int address2 = (int) args[1];
 
-			long address3 = dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
+			long address3 = state.dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
 
-			if (address3 < 0 || address3 + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+			if (address3 < 0 || address3 + MachineState.VALUE_SIZE >= state.dataByteBuffer.limit())
 				throw new InvalidAddressException("Data address out of bounds");
 
-			long value = dataByteBuffer.getLong((int) address3);
-			dataByteBuffer.putLong(address1, value);
+			long value = state.dataByteBuffer.getLong((int) address3);
+			state.dataByteBuffer.putLong(address1, value);
 		}
 	},
 	/**
@@ -243,22 +236,21 @@ public enum OpCode {
 	 */
 	SET_IDX(0x0f, OpCodeParam.DEST_ADDR, OpCodeParam.INDIRECT_SRC_ADDR_WITH_INDEX, OpCodeParam.INDEX) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address3 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address1 = (int) args[0];
+			int address2 = (int) args[1];
+			int address3 = (int) args[2];
 
-			long baseAddress = dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
-			long offset = dataByteBuffer.getLong(address3) * MachineState.VALUE_SIZE;
+			long baseAddress = state.dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
+			long offset = state.dataByteBuffer.getLong(address3) * MachineState.VALUE_SIZE;
 
 			long newAddress = baseAddress + offset;
 
-			if (newAddress < 0 || newAddress + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+			if (newAddress < 0 || newAddress + MachineState.VALUE_SIZE >= state.dataByteBuffer.limit())
 				throw new InvalidAddressException("Data address out of bounds");
 
-			long value = dataByteBuffer.getLong((int) newAddress);
-			dataByteBuffer.putLong(address1, value);
+			long value = state.dataByteBuffer.getLong((int) newAddress);
+			state.dataByteBuffer.putLong(address1, value);
 		}
 	},
 	/**
@@ -269,16 +261,16 @@ public enum OpCode {
 	 */
 	PSH_DAT(0x10, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+
+			long value = state.dataByteBuffer.getLong(address);
 
 			try {
 				// Simulate backwards-walking stack
-				int newPosition = userStackByteBuffer.position() - MachineState.VALUE_SIZE;
-				userStackByteBuffer.putLong(newPosition, value);
-				userStackByteBuffer.position(newPosition);
+				int newPosition = state.userStackByteBuffer.position() - MachineState.VALUE_SIZE;
+				state.userStackByteBuffer.putLong(newPosition, value);
+				state.userStackByteBuffer.position(newPosition);
 			} catch (IndexOutOfBoundsException | IllegalArgumentException e) {
 				throw new StackBoundsException("No room on user stack to push data", e);
 			}
@@ -292,18 +284,17 @@ public enum OpCode {
 	 */
 	POP_DAT(0x11, OpCodeParam.DEST_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
 
 			try {
-				long value = userStackByteBuffer.getLong();
+				long value = state.userStackByteBuffer.getLong();
 
 				// Clear old stack entry
-				userStackByteBuffer.putLong(userStackByteBuffer.position() - MachineState.VALUE_SIZE, 0L);
+				state.userStackByteBuffer.putLong(state.userStackByteBuffer.position() - MachineState.VALUE_SIZE, 0L);
 
 				// Put popped value into data address
-				dataByteBuffer.putLong(address, value);
+				state.dataByteBuffer.putLong(address, value);
 			} catch (BufferUnderflowException e) {
 				throw new StackBoundsException("Empty user stack from which to pop data", e);
 			}
@@ -317,20 +308,19 @@ public enum OpCode {
 	 */
 	JMP_SUB(0x12, OpCodeParam.CODE_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getCodeAddress(codeByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
 
 			try {
 				// Simulate backwards-walking stack
-				int newPosition = callStackByteBuffer.position() - MachineState.ADDRESS_SIZE;
-				callStackByteBuffer.putInt(newPosition, codeByteBuffer.position());
-				callStackByteBuffer.position(newPosition);
+				int newPosition = state.callStackByteBuffer.position() - MachineState.ADDRESS_SIZE;
+				state.callStackByteBuffer.putInt(newPosition, state.codeByteBuffer.position());
+				state.callStackByteBuffer.position(newPosition);
 			} catch (IndexOutOfBoundsException | IllegalArgumentException e) {
 				throw new StackBoundsException("No room on call stack to call subroutine", e);
 			}
 
-			codeByteBuffer.position(address);
+			state.codeByteBuffer.position(address);
 		}
 	},
 	/**
@@ -341,15 +331,14 @@ public enum OpCode {
 	 */
 	RET_SUB(0x13) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
 			try {
-				int returnAddress = callStackByteBuffer.getInt();
+				int returnAddress = state.callStackByteBuffer.getInt();
 
 				// Clear old stack entry
-				callStackByteBuffer.putInt(callStackByteBuffer.position() - MachineState.ADDRESS_SIZE, 0);
+				state.callStackByteBuffer.putInt(state.callStackByteBuffer.position() - MachineState.ADDRESS_SIZE, 0);
 
-				codeByteBuffer.position(returnAddress);
+				state.codeByteBuffer.position(returnAddress);
 			} catch (BufferUnderflowException e) {
 				throw new StackBoundsException("Empty call stack missing return address from subroutine", e);
 			}
@@ -362,18 +351,17 @@ public enum OpCode {
 	 */
 	IND_DAT(0x14, OpCodeParam.INDIRECT_DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address1 = (int) args[0];
+			int address2 = (int) args[1];
 
-			long address3 = dataByteBuffer.getLong(address1) * MachineState.VALUE_SIZE;
+			long address3 = state.dataByteBuffer.getLong(address1) * MachineState.VALUE_SIZE;
 
-			if (address3 < 0 || address3 + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+			if (address3 < 0 || address3 + MachineState.VALUE_SIZE >= state.dataByteBuffer.limit())
 				throw new InvalidAddressException("Data address out of bounds");
 
-			long value = dataByteBuffer.getLong(address2);
-			dataByteBuffer.putLong((int) address3, value);
+			long value = state.dataByteBuffer.getLong(address2);
+			state.dataByteBuffer.putLong((int) address3, value);
 		}
 	},
 	/**
@@ -383,22 +371,21 @@ public enum OpCode {
 	 */
 	IDX_DAT(0x15, OpCodeParam.INDIRECT_DEST_ADDR_WITH_INDEX, OpCodeParam.INDEX, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address3 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address1 = (int) args[0];
+			int address2 = (int) args[1];
+			int address3 = (int) args[2];
 
-			long baseAddress = dataByteBuffer.getLong(address1) * MachineState.VALUE_SIZE;
-			long offset = dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
+			long baseAddress = state.dataByteBuffer.getLong(address1) * MachineState.VALUE_SIZE;
+			long offset = state.dataByteBuffer.getLong(address2) * MachineState.VALUE_SIZE;
 
 			long newAddress = baseAddress + offset;
 
-			if (newAddress < 0 || newAddress + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
+			if (newAddress < 0 || newAddress + MachineState.VALUE_SIZE >= state.dataByteBuffer.limit())
 				throw new InvalidAddressException("Data address out of bounds");
 
-			long value = dataByteBuffer.getLong(address3);
-			dataByteBuffer.putLong((int) newAddress, value);
+			long value = state.dataByteBuffer.getLong(address3);
+			state.dataByteBuffer.putLong((int) newAddress, value);
 		}
 	},
 	/**
@@ -408,10 +395,9 @@ public enum OpCode {
 	 */
 	MOD_DAT(0x16, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
 			try {
-				executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> a % b);
+				executeDataOperation(state, (a, b) -> a % b, args);
 			} catch (ArithmeticException e) {
 				throw new IllegalOperationException("Divide by zero", e);
 			}
@@ -426,10 +412,9 @@ public enum OpCode {
 		private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8;
 
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
 			// If 2nd arg is more than value size (in bits) then return 0 to simulate all bits being shifted out of existence
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> b >= MAX_SHIFT ? 0 : a << b);
+			executeDataOperation(state, (a, b) -> b >= MAX_SHIFT ? 0 : a << b, args);
 		}
 	},
 	/**
@@ -442,10 +427,9 @@ public enum OpCode {
 		private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8;
 
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
 			// If 2nd arg is more than value size (in bits) then return 0 to simulate all bits being shifted out of existence
-			executeDataOperation(codeByteBuffer, dataByteBuffer, (a, b) -> b >= MAX_SHIFT ? 0 : a >>> b);
+			executeDataOperation(state, (a, b) -> b >= MAX_SHIFT ? 0 : a >>> b, args);
 		}
 	},
 	/**
@@ -455,11 +439,10 @@ public enum OpCode {
 	 */
 	JMP_ADR(0x1a, OpCodeParam.CODE_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getCodeAddress(codeByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
 
-			codeByteBuffer.position(address);
+			state.codeByteBuffer.position(address);
 		}
 	},
 	/**
@@ -470,22 +453,19 @@ public enum OpCode {
 	 */
 	BZR_DAT(0x1b, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int opCodePosition = codeByteBuffer.position() - 1; // i.e. before this OpCode
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+			byte offset = (byte) args[1];
 
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			byte offset = Utils.getCodeOffset(codeByteBuffer);
+			int branchTarget = state.getProgramCounter() + offset;
 
-			int branchTarget = opCodePosition + offset;
-
-			if (branchTarget < 0 || branchTarget >= codeByteBuffer.limit())
+			if (branchTarget < 0 || branchTarget >= state.codeByteBuffer.limit())
 				throw new InvalidAddressException("branch target out of bounds");
 
-			long value = dataByteBuffer.getLong(address);
+			long value = state.dataByteBuffer.getLong(address);
 
 			if (value == 0)
-				codeByteBuffer.position(branchTarget);
+				state.codeByteBuffer.position(branchTarget);
 		}
 	},
 	/**
@@ -496,22 +476,19 @@ public enum OpCode {
 	 */
 	BNZ_DAT(0x1e, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int opCodePosition = codeByteBuffer.position() - 1; // i.e. before this OpCode
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+			byte offset = (byte) args[1];
 
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			byte offset = Utils.getCodeOffset(codeByteBuffer);
+			int branchTarget = state.getProgramCounter() + offset;
 
-			int branchTarget = opCodePosition + offset;
-
-			if (branchTarget < 0 || branchTarget >= codeByteBuffer.limit())
+			if (branchTarget < 0 || branchTarget >= state.codeByteBuffer.limit())
 				throw new InvalidAddressException("branch target out of bounds");
 
-			long value = dataByteBuffer.getLong(address);
+			long value = state.dataByteBuffer.getLong(address);
 
 			if (value != 0)
-				codeByteBuffer.position(branchTarget);
+				state.codeByteBuffer.position(branchTarget);
 		}
 	},
 	/**
@@ -522,9 +499,8 @@ public enum OpCode {
 	 */
 	BGT_DAT(0x1f, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a > b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeBranchConditional(state, (a, b) -> a > b, args);
 		}
 	},
 	/**
@@ -535,9 +511,8 @@ public enum OpCode {
 	 */
 	BLT_DAT(0x20, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a < b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeBranchConditional(state, (a, b) -> a < b, args);
 		}
 	},
 	/**
@@ -548,9 +523,8 @@ public enum OpCode {
 	 */
 	BGE_DAT(0x21, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a >= b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeBranchConditional(state, (a, b) -> a >= b, args);
 		}
 	},
 	/**
@@ -561,9 +535,8 @@ public enum OpCode {
 	 */
 	BLE_DAT(0x22, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a <= b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeBranchConditional(state, (a, b) -> a <= b, args);
 		}
 	},
 	/**
@@ -574,9 +547,8 @@ public enum OpCode {
 	 */
 	BEQ_DAT(0x23, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a == b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeBranchConditional(state, (a, b) -> a == b, args);
 		}
 	},
 	/**
@@ -587,9 +559,8 @@ public enum OpCode {
 	 */
 	BNE_DAT(0x24, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			executeBranchConditional(codeByteBuffer, dataByteBuffer, state, (a, b) -> a != b);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			executeBranchConditional(state, (a, b) -> a != b, args);
 		}
 	},
 	/**
@@ -600,13 +571,13 @@ public enum OpCode {
 	 */
 	SLP_DAT(0x25, OpCodeParam.BLOCK_HEIGHT) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getCodeAddress(codeByteBuffer);
-			long value = codeByteBuffer.getLong(address);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
 
-			state.sleepUntilHeight = (int) value;
-			state.isSleeping = true;
+			long value = state.codeByteBuffer.getLong(address);
+
+			state.setSleepUntilHeight((int) value);
+			state.setIsSleeping(true);
 		}
 	},
 	/**
@@ -616,13 +587,13 @@ public enum OpCode {
 	 */
 	FIZ_DAT(0x26, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+
+			long value = state.dataByteBuffer.getLong(address);
 
 			if (value == 0)
-				state.isFinished = true;
+				state.setIsFinished(true);
 		}
 	},
 	/**
@@ -632,15 +603,14 @@ public enum OpCode {
 	 */
 	STZ_DAT(0x27, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
+
+			long value = state.dataByteBuffer.getLong(address);
 
 			if (value == 0) {
-				state.programCounter = state.onStopAddress;
-				codeByteBuffer.position(state.onStopAddress);
-				state.isStopped = true;
+				state.codeByteBuffer.position(state.getOnStopAddress());
+				state.setIsStopped(true);
 			}
 		}
 	},
@@ -651,9 +621,8 @@ public enum OpCode {
 	 */
 	FIN_IMD(0x28) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			state.isFinished = true;
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			state.setIsFinished(true);
 		}
 	},
 	/**
@@ -663,9 +632,8 @@ public enum OpCode {
 	 */
 	STP_IMD(0x29) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) {
-			state.isStopped = true;
+		public void executeWithParams(MachineState state, Object... args) {
+			state.setIsStopped(true);
 		}
 	},
 	/**
@@ -675,10 +643,9 @@ public enum OpCode {
 	 */
 	SLP_IMD(0x2a) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) {
-			state.sleepUntilHeight = state.currentBlockHeight + 1;
-			state.isSleeping = true;
+		public void executeWithParams(MachineState state, Object... args) {
+			state.setSleepUntilHeight(state.getCurrentBlockHeight() + 1);
+			state.setIsSleeping(true);
 		}
 	},
 	/**
@@ -688,11 +655,10 @@ public enum OpCode {
 	 */
 	ERR_ADR(0x2b, OpCodeParam.CODE_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			int address = Utils.getCodeAddress(codeByteBuffer);
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			int address = (int) args[0];
 
-			state.onErrorAddress = address;
+			state.setOnErrorAddress(address);
 		}
 	},
 	/**
@@ -703,9 +669,8 @@ public enum OpCode {
 	 */
 	SET_PCS(0x30) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) {
-			state.onStopAddress = codeByteBuffer.position();
+		public void executeWithParams(MachineState state, Object... args) {
+			state.setOnStopAddress(state.codeByteBuffer.position());
 		}
 	},
 	/**
@@ -715,9 +680,9 @@ public enum OpCode {
 	 */
 	EXT_FUN(0x32, OpCodeParam.FUNC) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			short rawFunctionCode = codeByteBuffer.getShort();
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			short rawFunctionCode = (short) args[0];
+
 			FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
 
 			if (functionCode == null)
@@ -727,7 +692,7 @@ public enum OpCode {
 
 			FunctionData functionData = new FunctionData(false);
 
-			executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+			functionCode.execute(functionData, state, rawFunctionCode);
 		}
 	},
 	/**
@@ -737,9 +702,10 @@ public enum OpCode {
 	 */
 	EXT_FUN_DAT(0x33, OpCodeParam.FUNC, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			short rawFunctionCode = codeByteBuffer.getShort();
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			short rawFunctionCode = (short) args[0];
+			int address = (int) args[1];
+
 			FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
 
 			if (functionCode == null)
@@ -747,12 +713,11 @@ public enum OpCode {
 
 			functionCode.preExecuteCheck(1, false, state, rawFunctionCode);
 
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			long value = dataByteBuffer.getLong(address);
+			long value = state.dataByteBuffer.getLong(address);
 
 			FunctionData functionData = new FunctionData(value, false);
 
-			executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+			functionCode.execute(functionData, state, rawFunctionCode);
 		}
 	},
 	/**
@@ -762,9 +727,11 @@ public enum OpCode {
 	 */
 	EXT_FUN_DAT_2(0x34, OpCodeParam.FUNC, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			short rawFunctionCode = codeByteBuffer.getShort();
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			short rawFunctionCode = (short) args[0];
+			int address1 = (int) args[1];
+			int address2 = (int) args[2];
+
 			FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
 
 			if (functionCode == null)
@@ -772,15 +739,12 @@ public enum OpCode {
 
 			functionCode.preExecuteCheck(2, false, state, rawFunctionCode);
 
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-
-			long value1 = dataByteBuffer.getLong(address1);
-			long value2 = dataByteBuffer.getLong(address2);
+			long value1 = state.dataByteBuffer.getLong(address1);
+			long value2 = state.dataByteBuffer.getLong(address2);
 
 			FunctionData functionData = new FunctionData(value1, value2, false);
 
-			executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+			functionCode.execute(functionData, state, rawFunctionCode);
 		}
 	},
 	/**
@@ -790,9 +754,10 @@ public enum OpCode {
 	 */
 	EXT_FUN_RET(0x35, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			short rawFunctionCode = codeByteBuffer.getShort();
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			short rawFunctionCode = (short) args[0];
+			int address = (int) args[1];
+
 			FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
 
 			if (functionCode == null)
@@ -800,16 +765,14 @@ public enum OpCode {
 
 			functionCode.preExecuteCheck(0, true, state, rawFunctionCode);
 
-			int address = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-
 			FunctionData functionData = new FunctionData(true);
 
-			executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+			functionCode.execute(functionData, state, rawFunctionCode);
 
 			if (functionData.returnValue == null)
 				throw new ExecutionException("Function failed to return a value as expected of EXT_FUN_RET");
 
-			dataByteBuffer.putLong(address, functionData.returnValue);
+			state.dataByteBuffer.putLong(address, functionData.returnValue);
 		}
 	},
 	/**
@@ -819,9 +782,11 @@ public enum OpCode {
 	 */
 	EXT_FUN_RET_DAT(0x36, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			short rawFunctionCode = codeByteBuffer.getShort();
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			short rawFunctionCode = (short) args[0];
+			int address1 = (int) args[1];
+			int address2 = (int) args[2];
+
 			FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
 
 			if (functionCode == null)
@@ -829,19 +794,16 @@ public enum OpCode {
 
 			functionCode.preExecuteCheck(1, true, state, rawFunctionCode);
 
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-
-			long value = dataByteBuffer.getLong(address2);
+			long value = state.dataByteBuffer.getLong(address2);
 
 			FunctionData functionData = new FunctionData(value, true);
 
-			executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+			functionCode.execute(functionData, state, rawFunctionCode);
 
 			if (functionData.returnValue == null)
 				throw new ExecutionException("Function failed to return a value as expected of EXT_FUN_RET_DAT");
 
-			dataByteBuffer.putLong(address1, functionData.returnValue);
+			state.dataByteBuffer.putLong(address1, functionData.returnValue);
 		}
 	},
 	/**
@@ -851,9 +813,12 @@ public enum OpCode {
 	 */
 	EXT_FUN_RET_DAT_2(0x37, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR) {
 		@Override
-		public void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-				MachineState state) throws ExecutionException {
-			short rawFunctionCode = codeByteBuffer.getShort();
+		public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+			short rawFunctionCode = (short) args[0];
+			int address1 = (int) args[1];
+			int address2 = (int) args[2];
+			int address3 = (int) args[3];
+
 			FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
 
 			if (functionCode == null)
@@ -862,21 +827,17 @@ public enum OpCode {
 
 			functionCode.preExecuteCheck(2, true, state, rawFunctionCode);
 
-			int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-			int address3 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-
-			long value1 = dataByteBuffer.getLong(address2);
-			long value2 = dataByteBuffer.getLong(address3);
+			long value1 = state.dataByteBuffer.getLong(address2);
+			long value2 = state.dataByteBuffer.getLong(address3);
 
 			FunctionData functionData = new FunctionData(value1, value2, true);
 
-			executeFunction(codeByteBuffer, functionCode, functionData, state, rawFunctionCode);
+			functionCode.execute(functionData, state, rawFunctionCode);
 
 			if (functionData.returnValue == null)
 				throw new ExecutionException("Function failed to return a value as expected of EXT_FUN_RET_DAT_2");
 
-			dataByteBuffer.putLong(address1, functionData.returnValue);
+			state.dataByteBuffer.putLong(address1, functionData.returnValue);
 		}
 	};
 
@@ -896,26 +857,32 @@ public enum OpCode {
 	}
 
 	/**
-	 * Execute OpCode
+	 * Execute OpCode with args fetched from code bytes
 	 * <p>
-	 * Assumes <tt>codeByteBuffer.position()</tt> is already placed immediately after opcode.
+	 * Assumes <tt>codeByteBuffer.position()</tt> is already placed immediately after opcode and params.<br>
+	 * <tt>state.getProgramCounter()</tt> is available to return position immediately before opcode and params.
 	 * <p>
-	 * Updates <tt>codeByteBuffer.position()</tt> as arguments are fetched, so caller should update <tt>state.programCounter</tt> using
-	 * <tt>codeByteBuffer.position()</tt> on return.
+	 * OpCode execution can modify <tt>codeByteBuffer.position()</tt> in cases like jumps, branches, etc.
 	 * <p>
 	 * Can also modify <tt>userStackByteBuffer</tt> and various fields of <tt>state</tt>.
 	 * <p>
 	 * Throws a subclass of <tt>ExecutionException</tt> on error, e.g. <tt>InvalidAddressException</tt>.
 	 * 
-	 * @param codeByteBuffer
-	 * @param dataByteBuffer
-	 * @param userStackByteBuffer
-	 * @param callStackByteBuffer
 	 * @param state
+	 * @param args
+	 * 
 	 * @throws ExecutionException
 	 */
-	public abstract void execute(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, ByteBuffer userStackByteBuffer, ByteBuffer callStackByteBuffer,
-			MachineState state) throws ExecutionException;
+	public abstract void executeWithParams(MachineState state, Object... args) throws ExecutionException;
+
+	public void execute(MachineState state) throws ExecutionException {
+		List<Object> args = new ArrayList<Object>();
+
+		for (OpCodeParam param : this.params)
+			args.add(param.fetch(state.codeByteBuffer, state.dataByteBuffer));
+
+		this.executeWithParams(state, args.toArray());
+	}
 
 	/**
 	 * Returns string representing disassembled OpCode and parameters
@@ -930,9 +897,8 @@ public enum OpCode {
 
 		int postOpcodeProgramCounter = codeByteBuffer.position();
 
-		for (OpCodeParam param : this.params) {
+		for (OpCodeParam param : this.params)
 			output += " " + param.disassemble(codeByteBuffer, dataByteBuffer, postOpcodeProgramCounter);
-		}
 
 		return output;
 	}
@@ -946,16 +912,16 @@ public enum OpCode {
 	 *            - typically a lambda operating on two <tt>long</tt> params, e.g. <tt>(a, b) -> a + b</tt>
 	 * @throws ExecutionException
 	 */
-	private static void executeDataOperation(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, TwoValueOperator operator) throws ExecutionException {
-		int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-		int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
+	private static void executeDataOperation(MachineState state, TwoValueOperator operator, Object... args) throws ExecutionException {
+		int address1 = (int) args[0];
+		int address2 = (int) args[1];
 
-		long value1 = dataByteBuffer.getLong(address1);
-		long value2 = dataByteBuffer.getLong(address2);
+		long value1 = state.dataByteBuffer.getLong(address1);
+		long value2 = state.dataByteBuffer.getLong(address2);
 
 		long newValue = operator.apply(value1, value2);
 
-		dataByteBuffer.putLong(address1, newValue);
+		state.dataByteBuffer.putLong(address1, newValue);
 	}
 
 	/**
@@ -968,47 +934,18 @@ public enum OpCode {
 	 *            - typically a lambda comparing two <tt>long</tt> params, e.g. <tt>(a, b) -> a == b</tt>
 	 * @throws ExecutionException
 	 */
-	private static void executeBranchConditional(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, MachineState state, TwoValueComparator comparator)
-			throws ExecutionException {
-		int opCodePosition = codeByteBuffer.position() - 1; // i.e. before this OpCode
+	private static void executeBranchConditional(MachineState state, TwoValueComparator comparator, Object... args) throws ExecutionException {
+		int address1 = (int) args[0];
+		int address2 = (int) args[1];
+		byte offset = (byte) args[2];
 
-		int address1 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-		int address2 = Utils.getDataAddress(codeByteBuffer, dataByteBuffer);
-		byte offset = Utils.getCodeOffset(codeByteBuffer);
+		int branchTarget = state.getProgramCounter() + offset;
 
-		int branchTarget = opCodePosition + offset;
-
-		long value1 = dataByteBuffer.getLong(address1);
-		long value2 = dataByteBuffer.getLong(address2);
+		long value1 = state.dataByteBuffer.getLong(address1);
+		long value2 = state.dataByteBuffer.getLong(address2);
 
 		if (comparator.compare(value1, value2))
-			codeByteBuffer.position(branchTarget);
-	}
-
-	/**
-	 * Common code for executing a function.
-	 * <p>
-	 * Updates <tt>programCounter</tt> to <tt>codeByteBuffer</tt>'s position before calling function.<br>
-	 * Adjusts <tt>codeByteBuffer</tt> position to <tt>programCounter</tt> after calling function.
-	 * <p>
-	 * This is needed for functions that might use/alter the programCounter during their execution,<br>
-	 * for example {@link FunctionCode#GENERATE_RANDOM_USING_TX_IN_A}
-	 * 
-	 * @see FunctionCode#GENERATE_RANDOM_USING_TX_IN_A
-	 *
-	 * @param codeByteBuffer
-	 * @param functionCode
-	 * @param functionData
-	 * @param state
-	 * @throws ExecutionException
-	 */
-	private static void executeFunction(ByteBuffer codeByteBuffer, FunctionCode functionCode, FunctionData functionData, MachineState state,
-			short rawFunctionCode) throws ExecutionException {
-		state.programCounter = codeByteBuffer.position();
-
-		functionCode.execute(functionData, state, rawFunctionCode);
-
-		codeByteBuffer.position(state.programCounter);
+			state.codeByteBuffer.position(branchTarget);
 	}
 
 }
diff --git a/Java/tests/BranchingOpCodeTests.java b/Java/tests/BranchingOpCodeTests.java
index 1f03606..87ab5e2 100644
--- a/Java/tests/BranchingOpCodeTests.java
+++ b/Java/tests/BranchingOpCodeTests.java
@@ -1,87 +1,12 @@
-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;
+import common.ExecutableTest;
 
-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);
-
-	}
+public class BranchingOpCodeTests extends ExecutableTest {
 
 	@Test
 	public void testBZR_DATtrue() throws ExecutionException {
@@ -98,11 +23,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(1));
 	}
 
 	@Test
@@ -120,11 +45,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(1));
 	}
 
 	@Test
@@ -142,11 +67,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(1));
 	}
 
 	@Test
@@ -164,11 +89,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(1));
 	}
 
 	@Test
@@ -187,11 +112,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -210,11 +135,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(2));
 	}
 
 	@Test
@@ -233,11 +158,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -256,11 +181,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(2));
 	}
 
 	@Test
@@ -279,11 +204,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -302,11 +227,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -325,11 +250,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(2));
 	}
 
 	@Test
@@ -348,11 +273,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -371,11 +296,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -394,11 +319,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(2));
 	}
 
 	@Test
@@ -417,11 +342,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -440,11 +365,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(2));
 	}
 
 	@Test
@@ -463,11 +388,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(2));
 	}
 
 	@Test
@@ -486,11 +411,11 @@ public class BranchingOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 1L, getData(2));
 	}
 
 }
diff --git a/Java/tests/CallStackOpCodeTests.java b/Java/tests/CallStackOpCodeTests.java
index d4afa8c..c010070 100644
--- a/Java/tests/CallStackOpCodeTests.java
+++ b/Java/tests/CallStackOpCodeTests.java
@@ -1,87 +1,14 @@
-import static common.TestUtils.hexToBytes;
+import static common.TestUtils.*;
 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;
+import common.ExecutableTest;
 
-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);
-
-	}
+public class CallStackOpCodeTests extends ExecutableTest {
 
 	@Test
 	public void testJMP_SUB() throws ExecutionException {
@@ -96,17 +23,17 @@ public class CallStackOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		int expectedCallStackPosition = (state.numCallStackPages - 1) * MachineState.CALL_STACK_PAGE_SIZE;
-		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+		int expectedCallStackPosition = (state.numCallStackPages - 1) * CALL_STACK_PAGE_SIZE;
+		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
 
-		assertEquals("Return address does not match", returnAddress, state.callStackByteBuffer.getInt(expectedCallStackPosition));
+		assertEquals("Return address does not match", returnAddress, getCallStackEntry(expectedCallStackPosition));
 
-		assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+		assertEquals("Data does not match", 4444L, getData(0));
 	}
 
 	@Test
@@ -130,19 +57,19 @@ public class CallStackOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(5555L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * MachineState.CALL_STACK_PAGE_SIZE;
-		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+		int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * CALL_STACK_PAGE_SIZE;
+		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
 
-		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("Return address does not match", returnAddress2, getCallStackEntry(expectedCallStackPosition));
+		assertEquals("Return address does not match", returnAddress1, getCallStackEntry(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));
+		assertEquals("Data does not match", 4444L, getData(0));
+		assertEquals("Data does not match", 5555L, getData(1));
 	}
 
 	@Test
@@ -154,10 +81,10 @@ public class CallStackOpCodeTests {
 		}
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -174,18 +101,18 @@ public class CallStackOpCodeTests {
 		codeByteBuffer.put(OpCode.RET_SUB.value);
 		codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE;
-		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+		int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * CALL_STACK_PAGE_SIZE;
+		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
 
-		assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
+		assertEquals("Return address not cleared", 0L, getCallStackEntry(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", 4444L, getData(0));
+		assertEquals("Data does not match", 7777L, getData(1));
 	}
 
 	@Test
@@ -211,20 +138,20 @@ public class CallStackOpCodeTests {
 		codeByteBuffer.put(OpCode.RET_SUB.value);
 		codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE;
-		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
+		int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * CALL_STACK_PAGE_SIZE;
+		assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
 
-		assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
+		assertEquals("Return address not cleared", 0L, getCallStackEntry(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));
+		assertEquals("Data does not match", 4444L, getData(0));
+		assertEquals("Data does not match", 7777L, getData(1));
+		assertEquals("Data does not match", 2222L, getData(2));
+		assertEquals("Data does not match", 3333L, getData(3));
 	}
 
 	@Test
@@ -232,10 +159,10 @@ public class CallStackOpCodeTests {
 		codeByteBuffer.put(OpCode.RET_SUB.value);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -246,10 +173,10 @@ public class CallStackOpCodeTests {
 		codeByteBuffer.put(OpCode.RET_SUB.value);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 }
diff --git a/Java/tests/DataOpCodeTests.java b/Java/tests/DataOpCodeTests.java
index 6bdf312..9382146 100644
--- a/Java/tests/DataOpCodeTests.java
+++ b/Java/tests/DataOpCodeTests.java
@@ -1,98 +1,23 @@
-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;
+import common.ExecutableTest;
 
-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);
-
-	}
+public class DataOpCodeTests extends ExecutableTest {
 
 	@Test
 	public void testSET_VAL() throws ExecutionException {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L, getData(2));
 	}
 
 	@Test
@@ -100,10 +25,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -112,11 +37,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L, getData(1));
 	}
 
 	@Test
@@ -124,10 +49,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_DAT.value).putInt(9999).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -135,10 +60,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(9999);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -147,15 +72,14 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
 		// Check data all zero
-		state.dataByteBuffer.position(0);
-		while (state.dataByteBuffer.hasRemaining())
-			assertEquals((byte) 0, state.dataByteBuffer.get());
+		for (int i = 0; i < 0x0020; ++i)
+			assertEquals(0L, getData(i));
 	}
 
 	@Test
@@ -163,10 +87,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(9999);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -175,11 +99,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L + 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L + 1L, getData(2));
 	}
 
 	@Test
@@ -187,10 +111,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.INC_DAT.value).putInt(9999);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -199,11 +123,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 0L, getData(2));
 	}
 
 	@Test
@@ -212,11 +136,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L - 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L - 1L, getData(2));
 	}
 
 	@Test
@@ -224,9 +148,9 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(9999);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		execute(true);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -235,11 +159,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 0xffffffffffffffffL, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 0xffffffffffffffffL, getData(2));
 	}
 
 	@Test
@@ -249,11 +173,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L + 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L + 3333L, getData(2));
 	}
 
 	@Test
@@ -261,10 +185,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(9999).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -272,10 +196,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(9999);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -285,11 +209,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 0x0000000000000098L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 0x0000000000000098L, getData(2));
 	}
 
 	@Test
@@ -299,11 +223,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SUB_DAT.value).putInt(3).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 3333L - 2222L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 3333L - 2222L, getData(3));
 	}
 
 	@Test
@@ -313,11 +237,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.MUL_DAT.value).putInt(3).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", (3333L * 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", (3333L * 2222L), getData(3));
 	}
 
 	@Test
@@ -329,11 +253,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", (3333L / 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", (3333L / 2222L), getData(3));
 	}
 
 	@Test
@@ -354,11 +278,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Error flag not set", 1L, getData(1));
 	}
 
 	@Test
@@ -368,11 +292,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.BOR_DAT.value).putInt(3).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", (3333L | 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", (3333L | 2222L), getData(3));
 	}
 
 	@Test
@@ -382,11 +306,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.AND_DAT.value).putInt(3).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", (3333L & 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", (3333L & 2222L), getData(3));
 	}
 
 	@Test
@@ -396,11 +320,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.XOR_DAT.value).putInt(3).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", (3333L ^ 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", (3333L ^ 2222L), getData(3));
 	}
 
 	@Test
@@ -409,11 +333,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.NOT_DAT.value).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", ~2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", ~2222L, getData(2));
 	}
 
 	@Test
@@ -428,11 +352,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(6 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 3333L, getData(6));
 	}
 
 	@Test
@@ -447,10 +371,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(9999);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -465,10 +389,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -484,11 +408,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 4444L, getData(0));
 	}
 
 	@Test
@@ -504,10 +428,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(9999).putInt(7);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -523,10 +447,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -542,10 +466,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -561,10 +485,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(9999);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -579,11 +503,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.IND_DAT.value).putInt(0).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 5555L, getData(3));
 	}
 
 	@Test
@@ -598,10 +522,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IND.value).putInt(9999).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -616,10 +540,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_IND.value).putInt(0).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -635,11 +559,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(4 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 5555L, getData(4));
 	}
 
 	@Test
@@ -655,10 +579,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(9999).putInt(7).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -674,10 +598,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -693,10 +617,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -712,10 +636,10 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(9999).putInt(5);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -725,11 +649,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.MOD_DAT.value).putInt(2).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L % 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L % 3333L, getData(2));
 	}
 
 	@Test
@@ -750,11 +674,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Error flag not set", 1L, getData(1));
 	}
 
 	@Test
@@ -764,11 +688,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L << 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L << 3, getData(2));
 	}
 
 	@Test
@@ -778,11 +702,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 0L, getData(2));
 	}
 
 	@Test
@@ -792,11 +716,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2222L >> 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2222L >> 3, getData(2));
 	}
 
 	@Test
@@ -806,11 +730,11 @@ public class DataOpCodeTests {
 		codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 0L, getData(2));
 	}
 
 }
diff --git a/Java/tests/DisassemblyTests.java b/Java/tests/DisassemblyTests.java
index 0812ec2..01b0fc3 100644
--- a/Java/tests/DisassemblyTests.java
+++ b/Java/tests/DisassemblyTests.java
@@ -44,28 +44,6 @@ public class DisassemblyTests {
 		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
diff --git a/Java/tests/FunctionCodeTests.java b/Java/tests/FunctionCodeTests.java
index e9fd8df..f1e3ee6 100644
--- a/Java/tests/FunctionCodeTests.java
+++ b/Java/tests/FunctionCodeTests.java
@@ -1,89 +1,15 @@
 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;
+import common.ExecutableTest;
 
-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);
-
-	}
+public class FunctionCodeTests extends ExecutableTest {
 
 	@Test
 	public void testMD5() throws ExecutionException {
@@ -109,11 +35,11 @@ public class FunctionCodeTests {
 
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("MD5 hashes do not match", 1L, getData(1));
 	}
 
 	@Test
@@ -134,11 +60,11 @@ public class FunctionCodeTests {
 
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("MD5 hashes do not match", 1L, getData(1));
 	}
 
 	@Test
@@ -165,11 +91,11 @@ public class FunctionCodeTests {
 
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
 	}
 
 	@Test
@@ -192,11 +118,11 @@ public class FunctionCodeTests {
 
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -223,11 +149,11 @@ public class FunctionCodeTests {
 
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
 	}
 
 	@Test
@@ -252,11 +178,11 @@ public class FunctionCodeTests {
 
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -266,11 +192,11 @@ public class FunctionCodeTests {
 		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();
+		execute(false);
 
-		assertNotEquals("Random wasn't generated", 0L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertNotEquals("Random wasn't generated", 0L, getData(1));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -278,10 +204,10 @@ public class FunctionCodeTests {
 		codeByteBuffer.put(OpCode.EXT_FUN.value).putShort((short) 0xaaaa);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -290,10 +216,10 @@ public class FunctionCodeTests {
 		codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort((short) 0x0501).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -302,10 +228,10 @@ public class FunctionCodeTests {
 		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();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 }
diff --git a/Java/tests/MiscTests.java b/Java/tests/MiscTests.java
index c0a0d87..48241ae 100644
--- a/Java/tests/MiscTests.java
+++ b/Java/tests/MiscTests.java
@@ -1,88 +1,13 @@
-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;
+import common.ExecutableTest;
 
-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);
-
-	}
+public class MiscTests extends ExecutableTest {
 
 	@Test
 	public void testSimpleCode() throws ExecutionException {
@@ -91,21 +16,21 @@ public class MiscTests {
 		codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", testValue, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", testValue, getData(0));
 	}
 
 	@Test
 	public void testInvalidOpCode() throws ExecutionException {
 		codeByteBuffer.put((byte) 0xdd);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 }
diff --git a/Java/tests/OpCodeTests.java b/Java/tests/OpCodeTests.java
index 7ee530d..1327310 100644
--- a/Java/tests/OpCodeTests.java
+++ b/Java/tests/OpCodeTests.java
@@ -1,102 +1,27 @@
 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;
+import common.ExecutableTest;
 
-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);
-
-	}
+public class OpCodeTests extends ExecutableTest {
 
 	@Test
 	public void testNOP() throws ExecutionException {
 		codeByteBuffer.put(OpCode.NOP.value);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
 		// Check data unchanged
-		state.dataByteBuffer.position(0);
-		while (state.dataByteBuffer.hasRemaining())
-			assertEquals((byte) 0, state.dataByteBuffer.get());
+		for (int i = 0; i < 0x0020; ++i)
+			assertEquals(0L, getData(i));
 	}
 
 	@Test
@@ -111,11 +36,11 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Data does not match", 2L, getData(0));
 	}
 
 	@Test
@@ -126,12 +51,12 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.SLP_DAT.value).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isSleeping);
-		assertFalse(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Sleep-until block height incorrect", blockHeight, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsSleeping());
+		assertFalse(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Sleep-until block height incorrect", blockHeight, getData(0));
 	}
 
 	@Test
@@ -140,11 +65,11 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0);
 		codeByteBuffer.put(OpCode.SLP_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.isSleeping);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getIsSleeping());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -153,11 +78,11 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0);
 		codeByteBuffer.put(OpCode.SLP_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertFalse(state.isFinished);
-		assertTrue(state.isSleeping);
-		assertFalse(state.hadFatalError);
+		assertFalse(state.getIsFinished());
+		assertTrue(state.getIsSleeping());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -168,12 +93,12 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isStopped);
-		assertFalse(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Program counter incorrect", stopAddress, state.programCounter);
+		assertTrue(state.getIsStopped());
+		assertFalse(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Program counter incorrect", stopAddress, state.getProgramCounter());
 	}
 
 	@Test
@@ -183,11 +108,11 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertFalse(state.isStopped);
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertFalse(state.getIsStopped());
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -195,11 +120,11 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 		codeByteBuffer.put(OpCode.STP_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.isStopped);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getIsStopped());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -210,12 +135,12 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.STP_IMD.value);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isStopped);
-		assertFalse(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Program counter incorrect", stopAddress, state.programCounter);
+		assertTrue(state.getIsStopped());
+		assertFalse(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Program counter incorrect", stopAddress, state.getProgramCounter());
 	}
 
 	@Test
@@ -224,12 +149,12 @@ public class OpCodeTests {
 		int nextAddress = codeByteBuffer.position();
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isSleeping);
-		assertFalse(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Program counter incorrect", nextAddress, state.programCounter);
+		assertTrue(state.getIsSleeping());
+		assertFalse(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Program counter incorrect", nextAddress, state.getProgramCounter());
 	}
 
 	@Test
@@ -250,11 +175,11 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Error flag not set", 1L, getData(2));
 	}
 
 	@Test
@@ -267,11 +192,11 @@ public class OpCodeTests {
 		int expectedStopAddress = codeByteBuffer.position();
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertEquals(expectedStopAddress, state.onStopAddress);
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertEquals(expectedStopAddress, state.getOnStopAddress());
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -282,11 +207,11 @@ public class OpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222"));
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertEquals(expectedStopAddress, state.onStopAddress);
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertEquals(expectedStopAddress, state.getOnStopAddress());
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 }
diff --git a/Java/tests/SerializationTests.java b/Java/tests/SerializationTests.java
index bd6e48b..7f08d6a 100644
--- a/Java/tests/SerializationTests.java
+++ b/Java/tests/SerializationTests.java
@@ -5,7 +5,6 @@ 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;
@@ -20,7 +19,7 @@ import common.TestLogger;
 public class SerializationTests {
 
 	public TestLogger logger;
-	public API api;
+	public TestAPI api;
 	public MachineState state;
 	public ByteBuffer codeByteBuffer;
 
@@ -53,7 +52,7 @@ public class SerializationTests {
 		state = MachineState.fromBytes(api, logger, savedState);
 
 		// Pretend we're on next block
-		state.currentBlockHeight++;
+		api.bumpCurrentBlockHeight();
 
 		return executeAndCheck(state);
 	}
@@ -79,9 +78,9 @@ public class SerializationTests {
 
 		simulate();
 
-		assertEquals(0x0e, (int) state.onStopAddress);
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertEquals(0x0e, (int) state.getOnStopAddress());
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 	}
 
 	@Test
@@ -98,9 +97,9 @@ public class SerializationTests {
 
 		byte[] savedState = simulate();
 
-		assertEquals(0x0e, (int) state.onStopAddress);
-		assertTrue(state.isStopped);
-		assertFalse(state.hadFatalError);
+		assertEquals(0x0e, (int) state.getOnStopAddress());
+		assertTrue(state.getIsStopped());
+		assertFalse(state.getHadFatalError());
 
 		savedState = continueSimulation(savedState);
 		savedState = continueSimulation(savedState);
diff --git a/Java/tests/TestACCT.java b/Java/tests/TestACCT.java
index 37b8d4e..c974f63 100644
--- a/Java/tests/TestACCT.java
+++ b/Java/tests/TestACCT.java
@@ -65,9 +65,6 @@ public class TestACCT {
 	private byte[] continueSimulation(byte[] savedState) {
 		state = MachineState.fromBytes(api, logger, savedState);
 
-		// Pretend we're on next block
-		state.currentBlockHeight++;
-
 		return executeAndCheck(state);
 	}
 
@@ -208,8 +205,8 @@ public class TestACCT {
 
 		byte[] savedState = simulate();
 
-		while (!state.isFinished) {
-			((ACCTAPI) state.api).generateNextBlock(secret);
+		while (!state.getIsFinished()) {
+			((ACCTAPI) state.getAPI()).generateNextBlock(secret);
 
 			savedState = continueSimulation(savedState);
 		}
diff --git a/Java/tests/UserStackOpCodeTests.java b/Java/tests/UserStackOpCodeTests.java
index 6c701ed..38ef017 100644
--- a/Java/tests/UserStackOpCodeTests.java
+++ b/Java/tests/UserStackOpCodeTests.java
@@ -1,87 +1,13 @@
-import static common.TestUtils.hexToBytes;
+import static common.TestUtils.*;
 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;
+import common.ExecutableTest;
 
-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);
-
-	}
+public class UserStackOpCodeTests extends ExecutableTest {
 
 	@Test
 	public void testPSH_DAT() throws ExecutionException {
@@ -89,14 +15,14 @@ public class UserStackOpCodeTests {
 		codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		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));
+		int expectedUserStackPosition = (state.numUserStackPages - 1) * USER_STACK_PAGE_SIZE;
+		assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
+		assertEquals("Data does not match", 4444L, getUserStackEntry(expectedUserStackPosition));
 	}
 
 	@Test
@@ -107,14 +33,14 @@ public class UserStackOpCodeTests {
 		codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(1);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		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));
+		int expectedUserStackPosition = (state.numUserStackPages - 2) * USER_STACK_PAGE_SIZE;
+		assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
+		assertEquals("Data does not match", 3333L, getUserStackEntry(expectedUserStackPosition));
 	}
 
 	@Test
@@ -126,10 +52,10 @@ public class UserStackOpCodeTests {
 		}
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -150,11 +76,11 @@ public class UserStackOpCodeTests {
 		codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
-		assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
+		assertEquals("Error flag not set", 1L, getData(1));
 	}
 
 	@Test
@@ -164,15 +90,16 @@ public class UserStackOpCodeTests {
 		codeByteBuffer.put(OpCode.POP_DAT.value).putInt(1);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		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));
+		int expectedUserStackPosition = (state.numUserStackPages - 1 + 1) * USER_STACK_PAGE_SIZE;
+		assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
+		assertEquals("Data does not match", 4444L, getData(1));
+		// Following test is not applicable when using serialized state:
+		// assertEquals("Stack entry not cleared", 0L, getUserStackEntry(expectedUserStackPosition - MachineState.VALUE_SIZE));
 	}
 
 	@Test
@@ -185,15 +112,15 @@ public class UserStackOpCodeTests {
 		codeByteBuffer.put(OpCode.POP_DAT.value).putInt(3);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertFalse(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertFalse(state.getHadFatalError());
 
-		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));
+		int expectedUserStackPosition = (state.numUserStackPages - 1 - 1 + 1 + 1) * USER_STACK_PAGE_SIZE;
+		assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
+		assertEquals("Data does not match", 3333L, getData(2));
+		assertEquals("Data does not match", 4444L, getData(3));
 	}
 
 	@Test
@@ -201,10 +128,10 @@ public class UserStackOpCodeTests {
 		codeByteBuffer.put(OpCode.POP_DAT.value).putInt(0);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 	@Test
@@ -215,10 +142,10 @@ public class UserStackOpCodeTests {
 		codeByteBuffer.put(OpCode.POP_DAT.value).putInt(2);
 		codeByteBuffer.put(OpCode.FIN_IMD.value);
 
-		simulate();
+		execute(true);
 
-		assertTrue(state.isFinished);
-		assertTrue(state.hadFatalError);
+		assertTrue(state.getIsFinished());
+		assertTrue(state.getHadFatalError());
 	}
 
 }
diff --git a/Java/tests/common/ACCTAPI.java b/Java/tests/common/ACCTAPI.java
index 3719c5e..4eef496 100644
--- a/Java/tests/common/ACCTAPI.java
+++ b/Java/tests/common/ACCTAPI.java
@@ -14,7 +14,7 @@ import org.ciyam.at.IllegalFunctionCodeException;
 import org.ciyam.at.MachineState;
 import org.ciyam.at.Timestamp;
 
-public class ACCTAPI implements API {
+public class ACCTAPI extends API {
 
 	private class Account {
 		public String address;
@@ -130,6 +130,7 @@ public class ACCTAPI implements API {
 	}
 
 	/** Convert long to little-endian byte array */
+	@SuppressWarnings("unused")
 	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) };
@@ -161,10 +162,10 @@ public class ACCTAPI implements API {
 
 	@Override
 	public void putPreviousBlockHashInA(MachineState state) {
-		state.a1 = this.blockchain.size() - 1;
-		state.a2 = state.a1;
-		state.a3 = state.a1;
-		state.a4 = state.a1;
+		this.setA1(state, this.blockchain.size() - 1);
+		this.setA2(state, state.getA1());
+		this.setA3(state, state.getA1());
+		this.setA4(state, state.getA1());
 	}
 
 	@Override
@@ -191,10 +192,10 @@ public class ACCTAPI implements API {
 				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;
+				this.setA1(state, new Timestamp(blockHeight, transactionSequence).longValue());
+				this.setA2(state, state.getA1());
+				this.setA3(state, state.getA1());
+				this.setA4(state, state.getA1());
 				return;
 			}
 
@@ -202,15 +203,15 @@ public class ACCTAPI implements API {
 		}
 
 		// Nothing found
-		state.a1 = 0L;
-		state.a2 = 0L;
-		state.a3 = 0L;
-		state.a4 = 0L;
+		this.setA1(state, 0L);
+		this.setA2(state, 0L);
+		this.setA3(state, 0L);
+		this.setA4(state, 0L);
 	}
 
 	@Override
 	public long getTypeFromTransactionInA(MachineState state) {
-		Timestamp timestamp = new Timestamp(state.a1);
+		Timestamp timestamp = new Timestamp(state.getA1());
 		Block block = this.blockchain.get(timestamp.blockHeight - 1);
 		Transaction transaction = block.transactions.get(timestamp.transactionSequence);
 		return transaction.txType;
@@ -218,7 +219,7 @@ public class ACCTAPI implements API {
 
 	@Override
 	public long getAmountFromTransactionInA(MachineState state) {
-		Timestamp timestamp = new Timestamp(state.a1);
+		Timestamp timestamp = new Timestamp(state.getA1());
 		Block block = this.blockchain.get(timestamp.blockHeight - 1);
 		Transaction transaction = block.transactions.get(timestamp.transactionSequence);
 		return transaction.amount;
@@ -227,7 +228,7 @@ public class ACCTAPI implements API {
 	@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);
+		Timestamp timestamp = new Timestamp(state.getA1());
 		return timestamp.longValue();
 	}
 
@@ -239,33 +240,33 @@ public class ACCTAPI implements API {
 
 	@Override
 	public void putMessageFromTransactionInAIntoB(MachineState state) {
-		Timestamp timestamp = new Timestamp(state.a1);
+		Timestamp timestamp = new Timestamp(state.getA1());
 		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];
+		this.setB1(state, transaction.message[0]);
+		this.setB2(state, transaction.message[1]);
+		this.setB3(state, transaction.message[2]);
+		this.setB4(state, transaction.message[3]);
 	}
 
 	@Override
 	public void putAddressFromTransactionInAIntoB(MachineState state) {
-		Timestamp timestamp = new Timestamp(state.a1);
+		Timestamp timestamp = new Timestamp(state.getA1());
 		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;
+		this.setB1(state, transaction.creator.charAt(0));
+		this.setB2(state, state.getB1());
+		this.setB3(state, state.getB1());
+		this.setB4(state, state.getB1());
 	}
 
 	@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;
+		this.setB1(state, "C".charAt(0));
+		this.setB2(state, state.getB1());
+		this.setB3(state, state.getB1());
+		this.setB4(state, state.getB1());
 	}
 
 	@Override
@@ -280,7 +281,7 @@ public class ACCTAPI implements API {
 
 	@Override
 	public void payAmountToB(long value1, MachineState state) {
-		char firstChar = String.format("%c", state.b1).charAt(0);
+		char firstChar = String.format("%c", state.getB1()).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);
diff --git a/Java/tests/common/ExecutableTest.java b/Java/tests/common/ExecutableTest.java
new file mode 100644
index 0000000..6a2144c
--- /dev/null
+++ b/Java/tests/common/ExecutableTest.java
@@ -0,0 +1,124 @@
+package common;
+
+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.MachineState;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+public abstract class ExecutableTest {
+
+	public static final int CODE_OFFSET = 6 * 2;
+	public static final int DATA_OFFSET = CODE_OFFSET + 0x0200;
+	public static final int CALL_STACK_OFFSET = DATA_OFFSET + 0x0020 * 8;
+
+	public TestLogger logger;
+	public TestAPI api;
+	public MachineState state;
+	public ByteBuffer codeByteBuffer;
+	public ByteBuffer stateByteBuffer;
+	public int callStackSize;
+	public int userStackOffset;
+	public int userStackSize;
+
+	@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);
+		stateByteBuffer = null;
+	}
+
+	@After
+	public void afterTest() {
+		stateByteBuffer = null;
+		codeByteBuffer = null;
+		api = null;
+		logger = null;
+	}
+
+	protected void execute(boolean onceOnly) {
+		// 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 {
+			System.out.println("Starting execution:");
+			System.out.println("Current block height: " + api.getCurrentBlockHeight());
+
+			// Actual execution
+			state.execute();
+
+			System.out.println("After execution:");
+			System.out.println("Steps: " + state.getSteps());
+			System.out.println("Program Counter: " + String.format("%04x", state.getProgramCounter()));
+			System.out.println("Stop Address: " + String.format("%04x", state.getOnStopAddress()));
+			System.out.println("Error Address: " + (state.getOnErrorAddress() == null ? "not set" : String.format("%04x", state.getOnErrorAddress())));
+
+			if (state.getIsSleeping())
+				System.out.println("Sleeping until current block height (" + state.getCurrentBlockHeight() + ") reaches " + state.getSleepUntilHeight());
+			else
+				System.out.println("Sleeping: " + state.getIsSleeping());
+
+			System.out.println("Stopped: " + state.getIsStopped());
+			System.out.println("Finished: " + state.getIsFinished());
+
+			if (state.getHadFatalError())
+				System.out.println("Finished due to fatal error!");
+
+			System.out.println("Frozen: " + state.getIsFrozen());
+
+			// Bump block height
+			api.bumpCurrentBlockHeight();
+		} while (!onceOnly && !state.getIsFinished());
+
+		// Ready for diagnosis
+		byte[] stateBytes = state.toBytes();
+
+		// We know how the state will be serialized so we can extract values
+		// header(6) + code(0x0200) + data(0x0020 * 8) + callStack length(4) + callStack + userStack length(4) + userStack
+
+		stateByteBuffer = ByteBuffer.wrap(stateBytes).order(ByteOrder.LITTLE_ENDIAN);
+		callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET);
+		userStackOffset = CALL_STACK_OFFSET + 4 + callStackSize;
+		userStackSize = stateByteBuffer.getInt(userStackOffset);
+	}
+
+	protected long getData(int address) {
+		int index = DATA_OFFSET + address * MachineState.VALUE_SIZE;
+		return stateByteBuffer.getLong(index);
+	}
+
+	protected int getCallStackPosition() {
+		return 0x0010 * MachineState.ADDRESS_SIZE - callStackSize;
+	}
+
+	protected int getCallStackEntry(int address) {
+		int index = CALL_STACK_OFFSET + 4 + address - 0x0010 * MachineState.ADDRESS_SIZE + callStackSize;
+		return stateByteBuffer.getInt(index);
+	}
+
+	protected int getUserStackPosition() {
+		return 0x0010 * MachineState.VALUE_SIZE - userStackSize;
+	}
+
+	protected long getUserStackEntry(int address) {
+		int index = userStackOffset + 4 + address - 0x0010 * MachineState.VALUE_SIZE + userStackSize;
+		return stateByteBuffer.getLong(index);
+	}
+
+}
diff --git a/Java/tests/common/TestAPI.java b/Java/tests/common/TestAPI.java
index c2aa819..fb711b1 100644
--- a/Java/tests/common/TestAPI.java
+++ b/Java/tests/common/TestAPI.java
@@ -7,13 +7,23 @@ import org.ciyam.at.IllegalFunctionCodeException;
 import org.ciyam.at.MachineState;
 import org.ciyam.at.Timestamp;
 
-public class TestAPI implements API {
+public class TestAPI extends API {
 
 	private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds
 
+	private int currentBlockHeight;
+
+	public TestAPI() {
+		this.currentBlockHeight = 10;
+	}
+
+	public void bumpCurrentBlockHeight() {
+		++this.currentBlockHeight;
+	}
+
 	@Override
 	public int getCurrentBlockHeight() {
-		return 10;
+		return this.currentBlockHeight;
 	}
 
 	@Override
@@ -23,19 +33,19 @@ public class TestAPI implements API {
 
 	@Override
 	public void putPreviousBlockHashInA(MachineState state) {
-		state.a1 = 9L;
-		state.a2 = 9L;
-		state.a3 = 9L;
-		state.a4 = 9L;
+		this.setA1(state, 9L);
+		this.setA2(state, 9L);
+		this.setA3(state, 9L);
+		this.setA4(state, 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;
+		this.setA1(state, (timestamp.transactionSequence + 1) % 4);
+		this.setA2(state, state.getA1());
+		this.setA3(state, state.getA1());
+		this.setA4(state, state.getA1());
 	}
 
 	@Override
@@ -55,13 +65,13 @@ public class TestAPI implements API {
 
 	@Override
 	public long generateRandomUsingTransactionInA(MachineState state) {
-		if (state.steps != 0) {
+		if (state.getSteps() != 0) {
 			// First call
 			System.out.println("generateRandomUsingTransactionInA: first call - sleeping");
 
-			// Perform init?
+			// first-call initialization would go here
 
-			state.isSleeping = true;
+			this.setIsSleeping(state, true);
 
 			return 0L; // not used
 		} else {
@@ -69,34 +79,34 @@ public class TestAPI implements API {
 			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);
+			return (state.getA1() ^ 9L) << 3 ^ (state.getA2() ^ 9L) << 12 ^ (state.getA3() ^ 9L) << 5 ^ (state.getA4() ^ 9L);
 		}
 	}
 
 	@Override
 	public void putMessageFromTransactionInAIntoB(MachineState state) {
-		state.b1 = state.a4;
-		state.b2 = state.a3;
-		state.b3 = state.a2;
-		state.b4 = state.a1;
+		this.setB1(state, state.getA4());
+		this.setB2(state, state.getA3());
+		this.setB3(state, state.getA2());
+		this.setB4(state, state.getA1());
 	}
 
 	@Override
 	public void putAddressFromTransactionInAIntoB(MachineState state) {
 		// Dummy address
-		state.b1 = 0xaaaaaaaaaaaaaaaaL;
-		state.b2 = 0xaaaaaaaaaaaaaaaaL;
-		state.b3 = 0xaaaaaaaaaaaaaaaaL;
-		state.b4 = 0xaaaaaaaaaaaaaaaaL;
+		this.setB1(state, 0xaaaaaaaaaaaaaaaaL);
+		this.setB2(state, 0xaaaaaaaaaaaaaaaaL);
+		this.setB3(state, 0xaaaaaaaaaaaaaaaaL);
+		this.setB4(state, 0xaaaaaaaaaaaaaaaaL);
 	}
 
 	@Override
 	public void putCreatorAddressIntoB(MachineState state) {
 		// Dummy creator
-		state.b1 = 0xccccccccccccccccL;
-		state.b2 = 0xccccccccccccccccL;
-		state.b3 = 0xccccccccccccccccL;
-		state.b4 = 0xccccccccccccccccL;
+		this.setB1(state, 0xccccccccccccccccL);
+		this.setB2(state, 0xccccccccccccccccL);
+		this.setB3(state, 0xccccccccccccccccL);
+		this.setB4(state, 0xccccccccccccccccL);
 	}
 
 	@Override
diff --git a/Java/tests/common/TestUtils.java b/Java/tests/common/TestUtils.java
index 40a4cea..ca1eaf8 100644
--- a/Java/tests/common/TestUtils.java
+++ b/Java/tests/common/TestUtils.java
@@ -2,8 +2,16 @@ package common;
 
 import java.math.BigInteger;
 
+import org.ciyam.at.MachineState;
+
 public class TestUtils {
 
+	// v3 constants replicated due to private cope in MachineState
+	public static final int CODE_PAGE_SIZE = 1;
+	public static final int DATA_PAGE_SIZE = MachineState.VALUE_SIZE;
+	public static final int CALL_STACK_PAGE_SIZE = MachineState.ADDRESS_SIZE;
+	public static final int USER_STACK_PAGE_SIZE = MachineState.VALUE_SIZE;
+
 	public static byte[] hexToBytes(String hex) {
 		byte[] output = new byte[hex.length() / 2];
 		byte[] converted = new BigInteger("00" + hex, 16).toByteArray();