Defines the Java methods capable of accessing the evm implementation. + */ +public final class EvmcVm implements AutoCloseable { + private static EvmcVm evmcVm; + private static boolean isEvmcLibraryLoaded = false; + private ByteBuffer nativeVm; + /** + * This method loads the specified evm shared library and loads/initializes the jni bindings. + * + * @param filename /path/filename of the evm shared object + */ + public static EvmcVm create(String filename) { + if (!EvmcVm.isEvmcLibraryLoaded) { + try { + // load so containing the jni bindings to evmc + System.load(System.getProperty("user.dir") + "/../c/build/evmc.so"); + EvmcVm.isEvmcLibraryLoaded = true; + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load.\n" + e); + System.exit(1); + } + } + if (Objects.isNull(evmcVm)) { + evmcVm = new EvmcVm(filename); + } + return evmcVm; + } + + private EvmcVm(String filename) { + // initialize jni and load EVM shared library + nativeVm = init(filename); + } + + /** + * This method loads the specified evm implementation and initializes jni + * + * @param filename path + filename of the evm shared object to load + * @return + */ + public native ByteBuffer init(String filename); + + /** + * EVMC ABI version implemented by the VM instance. + * + *
Can be used to detect ABI incompatibilities. The EVMC ABI version represented by this file + * is in ::EVMC_ABI_VERSION. + */ + public native int abi_version(); + + /** + * The name of the EVMC VM implementation. + * + *
It MUST be a NULL-terminated not empty string. The content MUST be UTF-8 encoded (this + * implies ASCII encoding is also allowed). + */ + native String name(ByteBuffer nativeVm); + + /** Function is a wrapper around native name(). */ + public String name() { + return name(nativeVm); + } + + /** + * The version of the EVMC VM implementation, e.g. "1.2.3b4". + * + *
It MUST be a NULL-terminated not empty string. The content MUST be UTF-8 encoded (this + * implies ASCII encoding is also allowed). + */ + native String version(ByteBuffer nativeVm); + + /** Function is a wrapper around native version(). */ + public String version() { + return version(nativeVm); + } + + /** + * Function to destroy the VM instance. + * + *
This is a mandatory method and MUST NOT be set to NULL. + */ + native void destroy(ByteBuffer nativeVm); + + /** + * Function to execute a code by the VM instance. + * + *
This is a mandatory method and MUST NOT be set to NULL. + */ + native void execute( + ByteBuffer nativeVm, + int context_index, + int rev, + ByteBuffer msg, + ByteBuffer code, + int size, + ByteBuffer result); + + /** + * Function is a wrapper around native execute. + * + *
This allows the context to managed in one method + */ + public synchronized ByteBuffer execute( + HostContext context, int rev, ByteBuffer msg, ByteBuffer code, int size) { + int context_index = addContext(context); + int resultSize = get_result_size(); + ByteBuffer result = ByteBuffer.allocateDirect(resultSize); + execute(nativeVm, context_index, rev, msg, code, size, result); + removeContext(context_index); + return result; + } + + /** + * A method returning capabilities supported by the VM instance. + * + *
The value returned MAY change when different options are set via the set_option() method. + * + *
A Client SHOULD only rely on the value returned if it has queried it after it has called the + * set_option(). + * + *
This is a mandatory method and MUST NOT be set to NULL. + */ + native int get_capabilities(ByteBuffer nativeVm); + + /** Function is a wrapper around native get_capabilities(). */ + public int get_capabilities() { + return get_capabilities(nativeVm); + } + + /** + * Function that modifies VM's options. + * + *
If the VM does not support this feature the pointer can be NULL. + */ + native int set_option(ByteBuffer nativeVm, String name, String value); + + /** Function is a wrapper around native set_option(). */ + public int set_option(String name, String value) { + return set_option(nativeVm, name, value); + } + + /** get size of result struct */ + native int get_result_size(); + + /** + * This method cleans up resources + * + * @throws Exception + */ + @Override + public void close() throws Exception { + destroy(nativeVm); + isEvmcLibraryLoaded = false; + evmcVm = null; + } +} diff --git a/bindings/java/java/src/main/java/org/ethereum/evmc/Host.java b/bindings/java/java/src/main/java/org/ethereum/evmc/Host.java new file mode 100644 index 000000000..209acfe63 --- /dev/null +++ b/bindings/java/java/src/main/java/org/ethereum/evmc/Host.java @@ -0,0 +1,150 @@ +package org.ethereum.evmc; + +import static java.util.Objects.requireNonNull; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * The Host interface. + * + *
The set of all callback functions expected by VM instances.
+ */
+final class Host {
+ /** Check account existence callback function. */
+ static int account_exists(int context_index, byte[] address) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.accountExists(address) ? 1 : 0;
+ }
+
+ /** Get storage callback function. */
+ static ByteBuffer get_storage(int context_index, byte[] address, byte[] key) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.getStorage(address, key);
+ }
+
+ /** Set storage callback function. */
+ static int set_storage(int context_index, byte[] address, byte[] key, byte[] value) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.setStorage(address, key, value);
+ }
+ /** Get balance callback function. */
+ static ByteBuffer get_balance(int context_index, byte[] address) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.getBalance(address);
+ }
+
+ /** Get code size callback function. */
+ static int get_code_size(int context_index, byte[] address) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.getCodeSize(address);
+ }
+
+ /** Get code hash callback function. */
+ static ByteBuffer get_code_hash(int context_index, byte[] address) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.getCodeHash(address);
+ }
+
+ /** Copy code callback function. */
+ static ByteBuffer copy_code(int context_index, byte[] address, int code_offset) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ byte[] code = context.getCode(address).array();
+
+ if (code != null && code_offset > 0 && code_offset < code.length) {
+ int length = code.length - code_offset;
+ return ByteBuffer.wrap(code, 0, length);
+ }
+
+ return ByteBuffer.wrap(new byte[0]);
+ }
+
+ /** Selfdestruct callback function. */
+ static void selfdestruct(int context_index, byte[] address, byte[] beneficiary) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ context.selfdestruct(address, beneficiary);
+ }
+
+ /** Call callback function. */
+ static ByteBuffer call(int context_index, ByteBuffer msg) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.call(msg);
+ }
+
+ /** Get transaction context callback function. */
+ static ByteBuffer get_tx_context(int context_index) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.getTxContext();
+ }
+
+ /** Get block hash callback function. */
+ static ByteBuffer get_block_hash_fn(int context_index, long number) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ return context.getBlockHash(number);
+ }
+
+ /** Emit log callback function. */
+ static void emit_log(
+ int context_index,
+ byte[] address,
+ byte[] data,
+ int data_size,
+ byte[][] topics,
+ int topic_count) {
+ HostContext context =
+ requireNonNull(
+ getContext(context_index),
+ "HostContext does not exist for context_index: " + context_index);
+ context.emitLog(address, data, data_size, topics, topic_count);
+ }
+
+ static HostContext getContext(int index) {
+ return contextList.get(index);
+ }
+
+ static synchronized int addContext(HostContext context) {
+ contextList.add(context);
+ return contextList.size() - 1;
+ }
+
+ static void removeContext(int index) {
+ contextList.remove(index);
+ }
+
+ private static List This function is used by the VM to check if there exists an account at given address.
+ *
+ * @param address The address of the account the query is about.
+ * @return true if exists, false otherwise.
+ */
+ boolean accountExists(byte[] address);
+
+ /**
+ * Get storage function.
+ *
+ * This function is used by a VM to query the given account storage entry.
+ *
+ * @param address The address of the account.
+ * @param key The index of the account's storage entry.
+ * @return The storage value at the given storage key or null bytes if the account does not exist.
+ */
+ ByteBuffer getStorage(byte[] address, byte[] key);
+
+ /**
+ * Set storage function.
+ *
+ * This function is used by a VM to update the given account storage entry. The VM MUST make
+ * sure that the account exists. This requirement is only a formality because VM implementations
+ * only modify storage of the account of the current execution context (i.e. referenced by
+ * evmc_message::destination).
+ *
+ * @param address The address of the account.
+ * @param key The index of the storage entry.
+ * @param value The value to be stored.
+ * @return The effect on the storage item.
+ */
+ int setStorage(byte[] address, byte[] key, byte[] value);
+
+ /**
+ * Get balance function.
+ *
+ * This function is used by a VM to query the balance of the given account.
+ *
+ * @param address The address of the account.
+ * @return The balance of the given account or 0 if the account does not exist.
+ */
+ ByteBuffer getBalance(byte[] address);
+
+ /**
+ * Get code size function.
+ *
+ * This function is used by a VM to get the size of the code stored in the account at the given
+ * address.
+ *
+ * @param address The address of the account.
+ * @return The size of the code in the account or 0 if the account does not exist.
+ */
+ int getCodeSize(byte[] address);
+
+ /**
+ * Get code hash function.
+ *
+ * This function is used by a VM to get the keccak256 hash of the code stored in the account at
+ * the given address. For existing accounts not having a code, this function returns keccak256
+ * hash of empty data.
+ *
+ * @param address The address of the account.
+ * @return The hash of the code in the account or null bytes if the account does not exist.
+ */
+ ByteBuffer getCodeHash(byte[] address);
+
+ /**
+ * Copy code function.
+ *
+ * This function is used by an EVM to request a copy of the code of the given account to the
+ * memory buffer provided by the EVM. The Client MUST copy the requested code, starting with the
+ * given offset, to the provided memory buffer up to the size of the buffer or the size of the
+ * code, whichever is smaller.
+ *
+ * @param address The address of the account.
+ * @return A copy of the requested code.
+ */
+ ByteBuffer getCode(byte[] address);
+
+ /**
+ * Selfdestruct function.
+ *
+ * This function is used by an EVM to SELFDESTRUCT given contract. The execution of the
+ * contract will not be stopped, that is up to the EVM.
+ *
+ * @param address The address of the contract to be selfdestructed.
+ * @param beneficiary The address where the remaining ETH is going to be transferred.
+ */
+ void selfdestruct(byte[] address, byte[] beneficiary);
+
+ /**
+ * This function supports EVM calls.
+ *
+ * @param msg The call parameters.
+ * @return The result of the call.
+ */
+ ByteBuffer call(ByteBuffer msg);
+
+ /**
+ * Get transaction context function.
+ *
+ * This function is used by an EVM to retrieve the transaction and block context.
+ *
+ * @return The transaction context.
+ */
+ ByteBuffer getTxContext();
+
+ /**
+ * Get block hash function.
+ *
+ * This function is used by a VM to query the hash of the header of the given block. If the
+ * information about the requested block is not available, then this is signalled by returning
+ * null bytes.
+ *
+ * @param number The block number.
+ * @return The block hash or null bytes if the information about the block is not available.
+ */
+ ByteBuffer getBlockHash(long number);
+
+ /**
+ * Log function.
+ *
+ * This function is used by an EVM to inform about a LOG that happened during an EVM bytecode
+ * execution.
+ *
+ * @param address The address of the contract that generated the log.
+ * @param data The unindexed data attached to the log.
+ * @param dataSize The length of the data.
+ * @param topics The the array of topics attached to the log.
+ * @param topicCount The number of the topics. Valid values are between 0 and 4 inclusively.
+ */
+ void emitLog(byte[] address, byte[] data, int dataSize, byte[][] topics, int topicCount);
+}
diff --git a/bindings/java/java/src/test/java/org/ethereum/evmc/EvmcTest.java b/bindings/java/java/src/test/java/org/ethereum/evmc/EvmcTest.java
new file mode 100644
index 000000000..0dcdb3893
--- /dev/null
+++ b/bindings/java/java/src/test/java/org/ethereum/evmc/EvmcTest.java
@@ -0,0 +1,247 @@
+package org.ethereum.evmc;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+final class EvmcTest {
+ @Test
+ void testInitCloseDestroy() throws Exception {
+ Assertions.assertDoesNotThrow(
+ () -> {
+ try (EvmcVm vm =
+ EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {}
+ });
+ }
+
+ @Test
+ void testAbiVersion() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ int abiVersion = vm.abi_version();
+ assert (abiVersion > 0);
+ }
+ }
+
+ @Test
+ void testName() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ String name = vm.name();
+ assert (name.length() > 0);
+ assert (name.equals("example_vm"));
+ }
+ }
+
+ @Test
+ void testVersion() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ String version = vm.version();
+ assert (version.length() > 0);
+ assert (version.equals("0.0.0"));
+ }
+ }
+
+ @Test
+ void testExecute_returnAddress() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ HostContext context = new TestHostContext();
+ int BYZANTIUM = 4;
+ int EVMC_CALL = 0;
+ int kind = EVMC_CALL;
+ char[] sender = "39bf71de1b7d7be3b51\0".toCharArray();
+ char[] destination = "53cf77204eEef952e25\0".toCharArray();
+ char[] value = "1\0".toCharArray();
+ char[] inputData = "hello w\0".toCharArray();
+ long gas = 200000;
+ int depth = 0;
+ ByteBuffer msg =
+ new TestMessage(kind, sender, destination, value, inputData, gas, depth).toByteBuffer();
+
+ byte[] code = {0x30, 0x60, 0x00, 0x52, 0x59, 0x60, 0x00, (byte) 0xf3}; // return_address
+ ByteBuffer bbcode = ByteBuffer.allocateDirect(code.length).put(code);
+
+ ByteBuffer result =
+ vm.execute(context, BYZANTIUM, msg, bbcode, code.length).order(ByteOrder.nativeOrder());
+ int statusCode = result.getInt();
+ result.getInt(); // padding
+ long gasLeft = result.getLong();
+ assert (statusCode == 0);
+ assert (gasLeft == 0);
+ }
+ }
+
+ /** Tests callbacks: get_storage_fn & set_storage_fn */
+ @Test
+ void testExecute_counter() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ HostContext context = new TestHostContext();
+ int BYZANTIUM = 4;
+ int EVMC_CALL = 0;
+ int kind = EVMC_CALL;
+ char[] sender = "39bf71de1b7d7be3b51\0".toCharArray();
+ char[] destination = "53cf77204eEef952e25\0".toCharArray();
+ char[] value = "1\0".toCharArray();
+ char[] inputData = "hello w\0".toCharArray();
+ long gas = 200000;
+ int depth = 0;
+ ByteBuffer msg =
+ new TestMessage(kind, sender, destination, value, inputData, gas, depth).toByteBuffer();
+
+ byte[] code = {0x60, 0x01, 0x60, 0x00, 0x54, 0x01, 0x60, 0x00, 0x55}; // counter
+ ByteBuffer bbcode = ByteBuffer.allocateDirect(code.length).put(code);
+
+ ByteBuffer result =
+ vm.execute(context, BYZANTIUM, msg, bbcode, code.length).order(ByteOrder.nativeOrder());
+ int statusCode = result.getInt();
+ result.getInt(); // padding
+ long gasLeft = result.getLong();
+ assert (statusCode == 0);
+ assert (gasLeft == 0);
+ }
+ }
+
+ /** Tests callbacks: get_tx_context_fn */
+ @Test
+ void testExecute_returnBlockNumber() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ HostContext context = new TestHostContext();
+ int BYZANTIUM = 4;
+ int EVMC_CALL = 0;
+ int kind = EVMC_CALL;
+ char[] sender = "39bf71de1b7d7be3b51\0".toCharArray();
+ char[] destination = "53cf77204eEef952e25\0".toCharArray();
+ char[] value = "1\0".toCharArray();
+ char[] inputData = "hello w\0".toCharArray();
+ long gas = 200000;
+ int depth = 0;
+ ByteBuffer msg =
+ new TestMessage(kind, sender, destination, value, inputData, gas, depth).toByteBuffer();
+
+ byte[] code = {0x43, 0x60, 0x00, 0x52, 0x59, 0x60, 0x00, (byte) 0xf3}; // return_block_number(
+ ByteBuffer bbcode = ByteBuffer.allocateDirect(code.length).put(code);
+
+ ByteBuffer result =
+ vm.execute(context, BYZANTIUM, msg, bbcode, code.length).order(ByteOrder.nativeOrder());
+ int statusCode = result.getInt();
+ result.getInt(); // padding
+ long gasLeft = result.getLong();
+ assert (statusCode == 0);
+ assert (gasLeft == gas / 2);
+ }
+ }
+
+ /** Tests callbacks: get_tx_context_fn & set_storage_fn */
+ @Test
+ void testExecute_saveReturnBlockNumber() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ HostContext context = new TestHostContext();
+ int BYZANTIUM = 4;
+ int EVMC_CALL = 0;
+ int kind = EVMC_CALL;
+ char[] sender = "39bf71de1b7d7be3b51\0".toCharArray();
+ char[] destination = "53cf77204eEef952e25\0".toCharArray();
+ char[] value = "1\0".toCharArray();
+ char[] inputData = "hello w\0".toCharArray();
+ long gas = 200000;
+ int depth = 0;
+ ByteBuffer msg =
+ new TestMessage(kind, sender, destination, value, inputData, gas, depth).toByteBuffer();
+
+ byte[] code = {
+ 0x43, 0x60, 0x00, 0x55, 0x43, 0x60, 0x00, 0x52, 0x59, 0x60, 0x00, (byte) 0xf3
+ }; // save_return_block_number
+ ByteBuffer bbcode = ByteBuffer.allocateDirect(code.length).put(code);
+
+ ByteBuffer result =
+ vm.execute(context, BYZANTIUM, msg, bbcode, code.length).order(ByteOrder.nativeOrder());
+ int statusCode = result.getInt();
+ result.getInt(); // padding
+ long gasLeft = result.getLong();
+ assert (statusCode == 0);
+ assert (gasLeft == gas / 2);
+ }
+ }
+
+ /** Tests callbacks: call_fn */
+ @Test
+ void testExecute_makeCall() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ HostContext context = new TestHostContext();
+ int BYZANTIUM = 4;
+ int EVMC_CALL = 0;
+ int kind = EVMC_CALL;
+ char[] sender = "39bf71de1b7d7be3b51\0".toCharArray();
+ char[] destination = "53cf77204eEef952e25\0".toCharArray();
+ char[] value = "1\0".toCharArray();
+ char[] inputData = "hello w\0".toCharArray();
+ long gas = 200000;
+ int depth = 0;
+ ByteBuffer msg =
+ new TestMessage(kind, sender, destination, value, inputData, gas, depth).toByteBuffer();
+ byte[] code = {
+ 0x60,
+ 0x00,
+ (byte) 0x80,
+ (byte) 0x80,
+ (byte) 0x80,
+ (byte) 0x80,
+ (byte) 0x80,
+ (byte) 0x80,
+ (byte) 0xf1
+ }; // make_a_call(
+ ByteBuffer bbcode = ByteBuffer.allocateDirect(code.length).put(code);
+
+ ByteBuffer result =
+ vm.execute(context, BYZANTIUM, msg, bbcode, code.length).order(ByteOrder.nativeOrder());
+ int statusCode = result.getInt();
+ result.getInt(); // padding
+ long gasLeft = result.getLong();
+ assert (statusCode == 0);
+ assert (gasLeft == 0); // gas - gas / 64);
+ }
+ }
+
+ @Test
+ void testExecute_EVMC_CREATE() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ HostContext context = new TestHostContext();
+ int BYZANTIUM = 4;
+ int EVMC_CREATE = 3;
+ int kind = EVMC_CREATE;
+ char[] sender = "39bf71de1b7d7be3b51\\0".toCharArray();
+ char[] destination = "53cf77204eEef952e25\0".toCharArray();
+ char[] value = "1\0".toCharArray();
+ char[] inputData = "hello w\0".toCharArray();
+ long gas = 200000;
+ int depth = 0;
+ ByteBuffer msg =
+ new TestMessage(kind, sender, destination, value, inputData, gas, depth).toByteBuffer();
+ byte[] code = {0x00};
+ ByteBuffer bbcode = ByteBuffer.allocateDirect(code.length).put(code);
+
+ ByteBuffer result =
+ vm.execute(context, BYZANTIUM, msg, bbcode, code.length).order(ByteOrder.nativeOrder());
+ int statusCode = result.getInt();
+ result.getInt(); // padding
+ long gasLeft = result.getLong();
+ assert (statusCode == 0);
+ assert (gasLeft == gas / 10);
+ }
+ }
+
+ @Test
+ void testGetCapabilities() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ int capabilities = vm.get_capabilities();
+ assert (capabilities > 0);
+ }
+ }
+
+ @Test
+ void testSetOption() throws Exception {
+ try (EvmcVm vm = EvmcVm.create(System.getProperty("user.dir") + "/../c/build/example_vm.so")) {
+ int result = vm.set_option("verbose", "1");
+ assert (result == 0);
+ }
+ }
+}
diff --git a/bindings/java/java/src/test/java/org/ethereum/evmc/TestHostContext.java b/bindings/java/java/src/test/java/org/ethereum/evmc/TestHostContext.java
new file mode 100644
index 000000000..e4cbb87ee
--- /dev/null
+++ b/bindings/java/java/src/test/java/org/ethereum/evmc/TestHostContext.java
@@ -0,0 +1,61 @@
+package org.ethereum.evmc;
+
+import java.nio.ByteBuffer;
+
+class TestHostContext implements HostContext {
+ @Override
+ public boolean accountExists(byte[] address) {
+ return true;
+ }
+
+ @Override
+ public ByteBuffer getStorage(byte[] address, byte[] key) {
+ return ByteBuffer.allocateDirect(64).put(new byte[64]);
+ }
+
+ @Override
+ public int setStorage(byte[] address, byte[] key, byte[] value) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getBalance(byte[] address) {
+ return ByteBuffer.allocateDirect(64).put(new byte[64]);
+ }
+
+ @Override
+ public int getCodeSize(byte[] address) {
+ return address.length;
+ }
+
+ @Override
+ public ByteBuffer getCodeHash(byte[] address) {
+ return ByteBuffer.allocateDirect(64).put(new byte[64]);
+ }
+
+ @Override
+ public ByteBuffer getCode(byte[] address) {
+ return ByteBuffer.allocateDirect(64).put(new byte[64]);
+ }
+
+ @Override
+ public void selfdestruct(byte[] address, byte[] beneficiary) {}
+
+ @Override
+ public ByteBuffer call(ByteBuffer msg) {
+ return ByteBuffer.allocateDirect(64).put(new byte[64]);
+ }
+
+ @Override
+ public ByteBuffer getTxContext() {
+ return ByteBuffer.allocateDirect(64).put(new byte[64]);
+ }
+
+ @Override
+ public ByteBuffer getBlockHash(long number) {
+ return ByteBuffer.allocateDirect(64).put(new byte[64]);
+ }
+
+ @Override
+ public void emitLog(byte[] address, byte[] data, int dataSize, byte[][] topics, int topicCount) {}
+}
diff --git a/bindings/java/java/src/test/java/org/ethereum/evmc/TestMessage.java b/bindings/java/java/src/test/java/org/ethereum/evmc/TestMessage.java
new file mode 100644
index 000000000..faee0cba7
--- /dev/null
+++ b/bindings/java/java/src/test/java/org/ethereum/evmc/TestMessage.java
@@ -0,0 +1,74 @@
+package org.ethereum.evmc;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+
+public class TestMessage {
+ int kind;
+ int flags;
+ int depth;
+ long gas;
+ char[] destination;
+ char[] sender;
+ char[] inputData;
+ long inputSize;
+ char[] value;
+ byte[] createSalt;
+
+ public TestMessage(
+ int kind,
+ char[] sender,
+ char[] destination,
+ char[] value,
+ char[] inputData,
+ long gas,
+ int depth) {
+ this.kind = kind;
+ this.flags = 0;
+ this.depth = depth;
+ this.gas = gas;
+ this.destination = destination;
+ this.sender = sender;
+ this.inputData = inputData;
+ this.inputSize = (long) inputData.length;
+ this.value = value;
+ this.createSalt = new byte[32];
+ }
+
+ public TestMessage(ByteBuffer msg) {
+ this.kind = msg.getInt();
+ this.flags = msg.getInt();
+ this.depth = msg.getInt();
+ msg.getInt(); // padding
+ this.gas = msg.getLong();
+ ByteBuffer tmpbuf = msg.get(new byte[20]);
+ this.destination = StandardCharsets.ISO_8859_1.decode(tmpbuf).array();
+ tmpbuf = msg.get(new byte[20]);
+ this.sender = StandardCharsets.ISO_8859_1.decode(tmpbuf).array();
+ tmpbuf = msg.get(new byte[8]);
+ this.inputData = StandardCharsets.ISO_8859_1.decode(tmpbuf).array();
+ this.inputSize = msg.getLong();
+ tmpbuf = msg.get(new byte[32]);
+ this.value = StandardCharsets.ISO_8859_1.decode(tmpbuf).array();
+ this.createSalt = msg.get(new byte[32]).array();
+ }
+
+ public ByteBuffer toByteBuffer() {
+
+ return ByteBuffer.allocateDirect(152)
+ .order(ByteOrder.nativeOrder())
+ .putInt(kind) // 4
+ .putInt(flags) // 4
+ .putInt(depth) // 4
+ .put(new byte[4]) // 4 (padding)
+ .putLong(gas) // 8
+ .put(StandardCharsets.ISO_8859_1.encode(CharBuffer.wrap(destination))) // 20
+ .put(StandardCharsets.ISO_8859_1.encode(CharBuffer.wrap(sender))) // 20
+ .put(StandardCharsets.ISO_8859_1.encode(CharBuffer.wrap(inputData))) // 8
+ .putLong(inputSize) // 8
+ .put(StandardCharsets.ISO_8859_1.encode(CharBuffer.wrap(value))) // 32
+ .put(createSalt); // 32
+ }
+}
diff --git a/bindings/java/settings.gradle b/bindings/java/settings.gradle
new file mode 100644
index 000000000..395ce3e0a
--- /dev/null
+++ b/bindings/java/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'evmc'
+include 'java'
diff --git a/bindings/java/wrapper.gradle b/bindings/java/wrapper.gradle
new file mode 100644
index 000000000..3dd555611
--- /dev/null
+++ b/bindings/java/wrapper.gradle
@@ -0,0 +1,4 @@
+task setup(type: Wrapper) {
+ description = "Download the gradle wrapper and requisite files. Overwrites existing wrapper files."
+ gradleVersion = "5.0"
+}
diff --git a/circle.yml b/circle.yml
index 1f6a48fb9..fad610790 100644
--- a/circle.yml
+++ b/circle.yml
@@ -64,7 +64,7 @@ jobs:
name: "Check code format"
command: |
clang-format --version
- find examples include lib test -name '*.hpp' -o -name '*.cpp' -o -name '*.h' -o -name '*.c' | xargs clang-format -i
+ find bindings/java examples include lib test -name '*.hpp' -o -name '*.cpp' -o -name '*.h' -o -name '*.c' | xargs clang-format -i
git diff --color --exit-code
- run:
name: "Run codespell"
@@ -182,6 +182,25 @@ jobs:
- image: circleci/golang:1.9
steps: *bindings-go-steps
+ bindings-java:
+ docker:
+ - image: circleci/openjdk:11-stretch
+ steps:
+ - checkout
+ - run:
+ name: Update environment
+ command: |
+ sudo apt -qq update
+ sudo apt -yq install build-essential
+ - run:
+ name: "Java Build"
+ command: |
+ cd bindings/java && make clean build
+ - run:
+ name: "Java Test"
+ command: |
+ cd bindings/java && make test
+
bindings-rust:
docker:
- image: rust:1
@@ -261,6 +280,7 @@ workflows:
- build-gcc-32bit
- bindings-go-latest
- bindings-go-min
+ - bindings-java
- bindings-rust:
requires:
- build-gcc8-cxx17