Initial implementation of Java bindings

Includes JNI bindings, tests and build system
This commit is contained in:
jonny rhea 2019-11-14 16:10:16 -06:00 committed by Alex Beregszaszi
parent 1de783316a
commit 77f5747a5f
19 changed files with 1783 additions and 1 deletions

View File

@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning].
### Added
- Added Java bindings.
[#455](https://github.com/ethereum/evmc/pull/455)
- Added `MockedHost` C++ class (in form of header-only `evmc::mocked_host` library)
which can be used to emulate Host behavior when testing VM implementations.
[#456](https://github.com/ethereum/evmc/pull/456)

View File

@ -25,6 +25,7 @@ Please visit the [documentation].
| **C++** | C++11, C++14, C++17 | GCC 6+, clang 3.8+, MSVC 2015+
| **Go** _(bindings)_ | 1.9 - 1.12 |
| **Rust** _(bindings)_[¹](#n1) | 2018 edition | 1.37.0 and newer
| **Java** _(bindings)_ | 11 |
<b id="n1">1</b>. Rust support is limited and not complete yet, but it is mostly functional already. Breaking changes are possible at this stage.

23
bindings/java/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.idea/
.vscode
java/build/*
java/out/*
c/build/*
*.o
*.dylib
.DS_Store
.gradle
build
.classpath
.project
.settings/
java/.classpath
java/.project
java/.settings/
java/bin/
java/hs_err_pid*
gradle/
gradlew
gradlew.bat
c/evmc-vm.h
*.class

46
bindings/java/Makefile Normal file
View File

@ -0,0 +1,46 @@
OS:=$(shell uname -s | tr '[:upper:]' '[:lower:]')
ifeq ($(OS), linux)
EXT:=so
OS_LFLAGS:=
JAVA_HOME:=/usr/local/openjdk-11
else ifeq ($(OS), darwin)
EXT:=so
OS_LFLAGS:=-mmacosx-version-min=$(shell defaults read loginwindow SystemVersionStampAsString) -framework CoreFoundation -framework Security
JAVA_HOME:=$(shell java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | sed 's/\s*java.home = //' | sed 's/\/jre//')
endif
INCLUDES = -I../../include
JAVA_INCLUDES = -I$(JAVA_HOME)/include/$(OS) -I$(JAVA_HOME)/include
JAVA_LIBS = -L$(JAVA_HOME)/lib/server -ljvm
CFLAGS = -O2 -fPIC
LFLAGS = -shared
OUT_DIR = ./c/build
gradlew:
gradle setup
build: gradlew
mkdir -p $(OUT_DIR)
javac ./java/src/main/java/org/ethereum/evmc/EvmcVm.java -h ./c --class-path ./java/src/main/java -s ./java/build
mv c/org_ethereum_evmc_EvmcVm.h c/evmc-vm.h
gcc $(DEBUG_FLAG) $(CFLAGS) -c $(INCLUDES) -o $(OUT_DIR)/loader.o ../../lib/loader/loader.c
gcc $(DEBUG_FLAG) $(CFLAGS) -c $(INCLUDES) $(JAVA_INCLUDES) -o $(OUT_DIR)/host.o ./c/host.c
gcc $(DEBUG_FLAG) $(CFLAGS) ./c/evmc-vm.c $(INCLUDES) $(JAVA_INCLUDES) $(JAVA_LIBS) $(CFLAGS) $(LFLAGS) -o $(OUT_DIR)/evmc.$(EXT) $(OUT_DIR)/host.o $(OUT_DIR)/loader.o
gcc $(DEBUG_FLAG) -shared ../../examples/example_vm/example_vm.c $(INCLUDES) -o $(OUT_DIR)/example_vm.$(EXT)
mkdir -p ./java/build
./gradlew --no-daemon clean spotlessApply build
debug: DEBUG_FLAG = -D DEBUG
debug: build
test: build
./gradlew --no-daemon test
format:
clang-format -i c/evmc-vm.c c/host.c c/host.h
clean:
rm -rf build
rm -rf ./java/build/
rm -rf ./c/build/

View File

@ -0,0 +1,40 @@
plugins {
id 'com.diffplug.gradle.spotless' version '3.16.0'
}
apply from: "${rootDir}/wrapper.gradle"
allprojects {
apply plugin: 'java-library'
repositories {
google()
jcenter()
mavenCentral()
}
sourceCompatibility = '11'
targetCompatibility = '11'
apply plugin: 'com.diffplug.gradle.spotless'
spotless {
java {
// This path needs to be relative to each project
target fileTree('.') {
include '**/*.java'
exclude '**/.gradle/**'
}
importOrder 'org.ethereum', 'java', ''
trimTrailingWhitespace()
endWithNewline()
googleJavaFormat('1.7')
}
}
}
subprojects {
tasks.withType(Test) {
testLogging.showStandardStreams = true
// If GRADLE_MAX_TEST_FORKS is not set, use half the available processors
maxParallelForks = (System.getenv('GRADLE_MAX_TEST_FORKS') ?: (Runtime.runtime.availableProcessors().intdiv(2) ?: 1)).toInteger()
useJUnitPlatform()
reports {
junitXml.enabled = true
}
}
}

163
bindings/java/c/evmc-vm.c Normal file
View File

@ -0,0 +1,163 @@
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include "evmc-vm.h"
#include "evmc/helpers.h"
#include "evmc/loader.h"
#include "host.h"
JNIEXPORT jobject JNICALL Java_org_ethereum_evmc_EvmcVm_init(JNIEnv* jenv,
jclass jcls,
jstring jfilename)
{
struct evmc_vm* evm;
jint rs = set_jvm(jenv);
assert(rs == JNI_OK);
// load the EVM
const char* filename = (*jenv)->GetStringUTFChars(jenv, jfilename, 0);
if (filename != NULL)
{
enum evmc_loader_error_code loader_error;
evm = evmc_load_and_create(filename, &loader_error);
if (evm == NULL || loader_error != EVMC_LOADER_SUCCESS)
{
const char* error_msg = evmc_last_error_msg();
jclass jclazz = (*jenv)->FindClass(jenv, "java/lang/AssertionError");
(*jenv)->ThrowNew(jenv, jclazz, error_msg);
}
(*jenv)->ReleaseStringUTFChars(jenv, jfilename, filename);
}
else
{
jclass jclazz = (*jenv)->FindClass(jenv, "java/lang/AssertionError");
(*jenv)->ThrowNew(jenv, jclazz, "JNI Error: filename cannot be NULL. \n");
}
jobject jresult = (*jenv)->NewDirectByteBuffer(jenv, (void*)evm, sizeof(struct evmc_vm));
assert(jresult != NULL);
return jresult;
}
JNIEXPORT jint JNICALL Java_org_ethereum_evmc_EvmcVm_abi_1version(JNIEnv* jenv, jclass jcls)
{
return EVMC_ABI_VERSION;
}
JNIEXPORT jstring JNICALL Java_org_ethereum_evmc_EvmcVm_name(JNIEnv* jenv,
jclass jcls,
jobject jevm)
{
struct evmc_vm* evm = (struct evmc_vm*)(*jenv)->GetDirectBufferAddress(jenv, jevm);
assert(evm != NULL);
const char* evm_name = evmc_vm_name(evm);
return (*jenv)->NewStringUTF(jenv, evm_name);
}
JNIEXPORT jstring JNICALL Java_org_ethereum_evmc_EvmcVm_version(JNIEnv* jenv,
jclass jcls,
jobject jevm)
{
struct evmc_vm* evm = (struct evmc_vm*)(*jenv)->GetDirectBufferAddress(jenv, jevm);
assert(evm != NULL);
const char* evm_version = evmc_vm_version(evm);
return (*jenv)->NewStringUTF(jenv, evm_version);
}
JNIEXPORT void JNICALL Java_org_ethereum_evmc_EvmcVm_destroy(JNIEnv* jenv,
jclass jcls,
jobject jevm)
{
struct evmc_vm* evm = (struct evmc_vm*)(*jenv)->GetDirectBufferAddress(jenv, jevm);
assert(evm != NULL);
evmc_destroy(evm);
}
JNIEXPORT void JNICALL Java_org_ethereum_evmc_EvmcVm_execute(JNIEnv* jenv,
jclass jcls,
jobject jevm,
jint jcontext_index,
jint jrev,
jobject jmsg,
jobject jcode,
jint jsize,
jobject jresult)
{
struct evmc_message* cmsg = (struct evmc_message*)(*jenv)->GetDirectBufferAddress(jenv, jmsg);
assert(cmsg != NULL);
const uint8_t* ccode = (uint8_t*)(*jenv)->GetDirectBufferAddress(jenv, jcode);
assert(ccode != NULL);
struct evmc_host_context context = {jcontext_index};
struct evmc_vm* evm = (struct evmc_vm*)(*jenv)->GetDirectBufferAddress(jenv, jevm);
assert(evm != NULL);
#ifdef DEBUG
printf("********************before execute*******************\n");
printf("struct: evmc_message=%p\n", cmsg);
printf("sizeof(evmc_message): %lu\n", sizeof(struct evmc_message));
printf("kind=%p\n", &cmsg->kind);
printf("flags=%p\n", &cmsg->flags);
printf("depth=%p\n", &cmsg->depth);
printf("gas=%p\n", &cmsg->gas);
printf("destination=%p\n", &cmsg->destination.bytes);
printf("sender=%p\n", &cmsg->sender.bytes);
printf("input_data=%p\n", &cmsg->input_data);
printf("input_size=%p\n", &cmsg->input_size);
printf("value=%p\n", &cmsg->value.bytes);
printf("create2_salt=%p\n\n", &cmsg->create2_salt.bytes);
printf("kind=%d\n", cmsg->kind);
printf("flags=%d\n", cmsg->flags);
printf("depth=%d\n", cmsg->depth);
printf("gas=%lld\n", cmsg->gas);
printf("destination=%s\n", cmsg->destination.bytes);
printf("sender=%s\n", cmsg->sender.bytes);
printf("input_size=%zu\n", cmsg->input_size);
printf("value=%s\n\n", cmsg->value.bytes);
#endif
const struct evmc_host_interface* host = get_host_interface();
struct evmc_result* result =
(struct evmc_result*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(result != NULL);
*result = evmc_execute(evm, host, &context, jrev, cmsg, ccode, jsize);
#ifdef DEBUG
printf("********************after execute*******************\n");
printf("sizeof(evmc_result): %lu\n", sizeof(struct evmc_result));
printf("status_code=%p\n", &result->status_code);
printf("gas_left=%p\n", &result->gas_left);
printf("output_data=%p\n\n", &result->output_data);
printf("status_code=%d\n", result->status_code);
printf("gas_left=%llu\n", result->gas_left);
printf("output_data=%s\n\n", result->output_data);
#endif
}
JNIEXPORT jint JNICALL Java_org_ethereum_evmc_EvmcVm_get_1capabilities(JNIEnv* jenv,
jclass jcls,
jobject jevm)
{
struct evmc_vm* evm = (struct evmc_vm*)(*jenv)->GetDirectBufferAddress(jenv, jevm);
assert(evm != NULL);
return evm->get_capabilities(evm);
}
JNIEXPORT jint JNICALL Java_org_ethereum_evmc_EvmcVm_set_1option(JNIEnv* jenv,
jclass jcls,
jobject jevm,
jstring jname,
jstring jvalue)
{
struct evmc_vm* evm = (struct evmc_vm*)(*jenv)->GetDirectBufferAddress(jenv, jevm);
assert(evm != NULL);
const char* name = (*jenv)->GetStringUTFChars(jenv, jname, 0);
const char* value = (*jenv)->GetStringUTFChars(jenv, jvalue, 0);
enum evmc_set_option_result option_result = evmc_set_option(evm, name, value);
(*jenv)->ReleaseStringUTFChars(jenv, jname, name);
(*jenv)->ReleaseStringUTFChars(jenv, jvalue, value);
return option_result;
}
JNIEXPORT jint JNICALL Java_org_ethereum_evmc_EvmcVm_get_1result_1size(JNIEnv* jenv, jclass jcls)
{
return sizeof(struct evmc_result);
}

584
bindings/java/c/host.c Normal file
View File

@ -0,0 +1,584 @@
#include <assert.h>
#include <stdlib.h>
#include "host.h"
static JavaVM* jvm;
int set_jvm(JNIEnv* jenv)
{
return (*jenv)->GetJavaVM(jenv, &jvm);
}
static JNIEnv* attach()
{
JNIEnv* jenv;
jint rs = (*jvm)->AttachCurrentThread(jvm, (void**)&jenv, NULL);
assert(rs == JNI_OK);
return jenv;
}
static bool account_exists_fn(struct evmc_host_context* context, const evmc_address* address)
{
bool result = false;
const char java_method_name[] = "account_exists";
const char java_method_signature[] = "(I[B)I";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
assert(jaddress != NULL);
// call java method
jint jresult =
(*jenv)->CallStaticIntMethod(jenv, host_class, method, jcontext_index, jaddress);
result = !!jresult;
}
return result;
}
static evmc_bytes32 get_storage_fn(struct evmc_host_context* context,
const evmc_address* address,
const evmc_bytes32* key)
{
evmc_bytes32 result;
const char java_method_name[] = "get_storage";
const char java_method_signature[] = "(I[B[B)Ljava/nio/ByteBuffer;";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
jbyteArray jkey;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
jkey = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_bytes32));
assert(jkey != NULL);
(*jenv)->SetByteArrayRegion(jenv, jkey, 0, sizeof(struct evmc_bytes32), (jbyte*)key);
// call java method
jobject jresult = (*jenv)->CallStaticObjectMethod(jenv, host_class, method, jcontext_index,
jaddress, jkey);
assert(jresult != NULL);
evmc_bytes32* result_ptr =
(struct evmc_bytes32*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(result_ptr != NULL);
result = *result_ptr;
}
return result;
}
static enum evmc_storage_status set_storage_fn(struct evmc_host_context* context,
const evmc_address* address,
const evmc_bytes32* key,
const evmc_bytes32* value)
{
enum evmc_storage_status result;
const char java_method_name[] = "set_storage";
const char java_method_signature[] = "(I[B[B[B)I";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
jbyteArray jkey;
jbyteArray jvalue;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
jkey = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_bytes32));
assert(jkey != NULL);
(*jenv)->SetByteArrayRegion(jenv, jkey, 0, sizeof(struct evmc_bytes32), (jbyte*)key);
jvalue = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_bytes32));
assert(jvalue != NULL);
(*jenv)->SetByteArrayRegion(jenv, jvalue, 0, sizeof(struct evmc_bytes32), (jbyte*)value);
// call java method
jint jresult = (*jenv)->CallStaticIntMethod(jenv, host_class, method, jcontext_index,
jaddress, jkey, jvalue);
result = (enum evmc_storage_status)jresult;
}
return result;
}
static evmc_uint256be get_balance_fn(struct evmc_host_context* context, const evmc_address* address)
{
evmc_uint256be result;
char java_method_name[] = "get_balance";
char java_method_signature[] = "(I[B)Ljava/nio/ByteBuffer;";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
// call java method
jobject jresult =
(*jenv)->CallStaticObjectMethod(jenv, host_class, method, jcontext_index, jaddress);
assert(jresult != NULL);
evmc_uint256be* result_ptr =
(evmc_uint256be*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(result_ptr != NULL);
result = *result_ptr;
(*jenv)->ReleaseByteArrayElements(jenv, jaddress, (jbyte*)address, 0);
}
return result;
}
static size_t get_code_size_fn(struct evmc_host_context* context, const evmc_address* address)
{
size_t result = 0;
char java_method_name[] = "get_code_size";
char java_method_signature[] = "(I[B)I";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
// call java method
jint jresult =
(*jenv)->CallStaticIntMethod(jenv, host_class, method, jcontext_index, jaddress);
result = jresult;
}
return result;
}
static evmc_bytes32 get_code_hash_fn(struct evmc_host_context* context, const evmc_address* address)
{
evmc_bytes32 result;
char java_method_name[] = "get_code_hash";
char java_method_signature[] = "(I[B)Ljava/nio/ByteBuffer;";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
// call java method
jobject jresult =
(*jenv)->CallStaticObjectMethod(jenv, host_class, method, jcontext_index, jaddress);
assert(jresult != NULL);
evmc_bytes32* result_ptr =
(struct evmc_bytes32*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(result_ptr != NULL);
result = *result_ptr;
(*jenv)->ReleaseByteArrayElements(jenv, jaddress, (jbyte*)address, 0);
}
return result;
}
static size_t copy_code_fn(struct evmc_host_context* context,
const evmc_address* address,
size_t code_offset,
uint8_t* buffer_data,
size_t buffer_size)
{
size_t result;
const char java_method_name[] = "copy_code";
const char java_method_signature[] = "(I[BI)Ljava/nio/ByteBuffer;";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
jint jcode_offset;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
jcode_offset = code_offset;
// call java method
jobject jresult = (*jenv)->CallStaticObjectMethod(jenv, host_class, method, jcontext_index,
jaddress, jcode_offset);
assert(jresult != NULL);
// copy jresult back to buffer_data
buffer_data = (uint8_t*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(buffer_data != NULL);
result = get_code_size_fn(context, address) - code_offset;
(*jenv)->ReleaseByteArrayElements(jenv, jaddress, (jbyte*)address, 0);
}
return result;
}
static void selfdestruct_fn(struct evmc_host_context* context,
const evmc_address* address,
const evmc_address* beneficiary)
{
const char java_method_name[] = "selfdestruct";
const char java_method_signature[] = "(I[B[B)V";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
jbyteArray jbeneficiary;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
jbeneficiary = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jbeneficiary != NULL);
(*jenv)->SetByteArrayRegion(jenv, jbeneficiary, 0, sizeof(struct evmc_address),
(jbyte*)beneficiary);
// call java method
(*jenv)->CallStaticIntMethod(jenv, host_class, method, jcontext_index, jaddress,
jbeneficiary);
}
return;
}
static struct evmc_result call_fn(struct evmc_host_context* context, const struct evmc_message* msg)
{
struct evmc_result result;
const char java_method_name[] = "call";
const char java_method_signature[] = "(ILjava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jobject jmsg;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jmsg = (*jenv)->NewDirectByteBuffer(jenv, (void*)msg, sizeof(struct evmc_message));
assert(jmsg != NULL);
// call java method
jobject jresult =
(*jenv)->CallStaticObjectMethod(jenv, host_class, method, jcontext_index, jmsg);
assert(jresult != NULL);
struct evmc_result* result_ptr =
(struct evmc_result*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(result_ptr != NULL);
result = *result_ptr;
}
return result;
}
static struct evmc_tx_context get_tx_context_fn(struct evmc_host_context* context)
{
struct evmc_tx_context result;
const char java_method_name[] = "get_tx_context";
const char java_method_signature[] = "(I)Ljava/nio/ByteBuffer;";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
// call java method
jobject jresult = (*jenv)->CallStaticObjectMethod(jenv, host_class, method, jcontext_index);
assert(jresult != NULL);
struct evmc_tx_context* result_ptr =
(struct evmc_tx_context*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(result_ptr != NULL);
result = *result_ptr;
}
return result;
}
static evmc_bytes32 get_block_hash_fn(struct evmc_host_context* context, int64_t number)
{
evmc_bytes32 result;
char java_method_name[] = "get_code_hash";
char java_method_signature[] = "(IJ)Ljava/nio/ByteBuffer;";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jlong jnumber;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jnumber = (jlong)number;
// call java method
jobject jresult =
(*jenv)->CallStaticObjectMethod(jenv, host_class, method, jcontext_index, jnumber);
assert(jresult != NULL);
evmc_bytes32* result_ptr =
(struct evmc_bytes32*)(*jenv)->GetDirectBufferAddress(jenv, jresult);
assert(result_ptr != NULL);
result = *result_ptr;
}
return result;
}
static void emit_log_fn(struct evmc_host_context* context,
const evmc_address* address,
const uint8_t* data,
size_t data_size,
const evmc_bytes32 topics[],
size_t topics_count)
{
const char java_method_name[] = "emit_log";
const char java_method_signature[] = "(I[B[BI[[BI)V";
JNIEnv* jenv = attach();
if (jenv != NULL)
{
jclass host_class;
jmethodID method;
jint jcontext_index;
jbyteArray jaddress;
jbyteArray jdata;
jobjectArray jtopics;
// get java class
host_class = (*jenv)->FindClass(jenv, "org/ethereum/evmc/Host");
assert(host_class != NULL);
// get java method
method =
(*jenv)->GetStaticMethodID(jenv, host_class, java_method_name, java_method_signature);
assert(method != NULL);
// set java method params
assert(context != NULL);
jcontext_index = context->index;
jaddress = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_address));
assert(jaddress != NULL);
(*jenv)->SetByteArrayRegion(jenv, jaddress, 0, sizeof(struct evmc_address),
(jbyte*)address);
jdata = (*jenv)->NewByteArray(jenv, data_size);
assert(jdata != NULL);
(*jenv)->SetByteArrayRegion(jenv, jdata, 0, data_size, (jbyte*)data);
jclass byte_type = (*jenv)->FindClass(jenv, "[B");
jtopics = (*jenv)->NewObjectArray(jenv, (jsize)topics_count, byte_type, NULL);
assert(jtopics != NULL);
for (int i = 0; i < topics_count; i++)
{
jbyteArray jtopic = (*jenv)->NewByteArray(jenv, sizeof(struct evmc_bytes32));
assert(jtopic != NULL);
(*jenv)->SetByteArrayRegion(jenv, jtopic, 0, sizeof(struct evmc_bytes32),
(jbyte*)topics[i].bytes);
(*jenv)->SetObjectArrayElement(jenv, jtopics, i, jtopic);
(*jenv)->DeleteLocalRef(jenv, jtopic);
}
// call java method
(*jenv)->CallStaticIntMethod(jenv, host_class, method, jcontext_index, jaddress, jdata,
data_size, jtopics, topics_count);
}
return;
}
const struct evmc_host_interface* get_host_interface()
{
static const struct evmc_host_interface host = {
account_exists_fn, get_storage_fn, set_storage_fn, get_balance_fn,
get_code_size_fn, get_code_hash_fn, copy_code_fn, selfdestruct_fn,
call_fn, get_tx_context_fn, get_block_hash_fn, emit_log_fn,
};
return &host;
}

21
bindings/java/c/host.h Normal file
View File

@ -0,0 +1,21 @@
#include "evmc/evmc.h"
#include <jni.h>
#ifndef _Included_org_ethereum_evmc_Host
#define _Included_org_ethereum_evmc_Host
#ifdef __cplusplus
extern "C" {
#endif
struct evmc_host_context
{
int index;
};
int set_jvm(JNIEnv*);
const struct evmc_host_interface* get_host_interface();
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,26 @@
## Java Bindings
This bindings have been tested with `openjdk64-11.0.1` on OSX and debian:latest.
For examples of how to use these bindings take a look at the JUnit tests.
### Prerequisites
You need to have [Gradle](https://www.gradle.org/installation) and [Java](https://www.oracle.com/technetwork/java/javase/downloads/index.html) installed.
> Note: Requires Gradle 5.x
### Build and test
from project root:
```bash
cd bindings/java && make clean build test
```
### Build and test (debug with debug printouts)
from project root:
```bash
cd bindings/java && make clean debug test
```

View File

@ -0,0 +1,7 @@
dependencies {
testImplementation 'org.apache.commons:commons-lang3:3.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.3.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}

View File

@ -0,0 +1,166 @@
package org.ethereum.evmc;
import static org.ethereum.evmc.Host.addContext;
import static org.ethereum.evmc.Host.removeContext;
import java.nio.ByteBuffer;
import java.util.Objects;
/**
* The Java interface to the evm instance.
*
* <p>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.
*
* <p>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.
*
* <p>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".
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>The value returned MAY change when different options are set via the set_option() method.
*
* <p>A Client SHOULD only rely on the value returned if it has queried it after it has called the
* set_option().
*
* <p>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.
*
* <p>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;
}
}

View File

@ -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.
*
* <p>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<HostContext> contextList = Collections.synchronizedList(new ArrayList<>());
}

View File

@ -0,0 +1,145 @@
package org.ethereum.evmc;
import java.nio.ByteBuffer;
/**
* This interface represents the callback functions must be implemented in order to interface with
* the EVM.
*/
public interface HostContext {
/**
* Check account existence function.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>This function is used by an EVM to retrieve the transaction and block context.
*
* @return The transaction context.
*/
ByteBuffer getTxContext();
/**
* Get block hash function.
*
* <p>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.
*
* <p>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);
}

View File

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

View File

@ -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) {}
}

View File

@ -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
}
}

View File

@ -0,0 +1,2 @@
rootProject.name = 'evmc'
include 'java'

View File

@ -0,0 +1,4 @@
task setup(type: Wrapper) {
description = "Download the gradle wrapper and requisite files. Overwrites existing wrapper files."
gradleVersion = "5.0"
}

View File

@ -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