mirror of
https://github.com/status-im/evmc.git
synced 2025-02-23 00:18:24 +00:00
Initial implementation of Java bindings
Includes JNI bindings, tests and build system
This commit is contained in:
parent
1de783316a
commit
77f5747a5f
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning].
|
|||||||
|
|
||||||
### Added
|
### 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)
|
- 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.
|
which can be used to emulate Host behavior when testing VM implementations.
|
||||||
[#456](https://github.com/ethereum/evmc/pull/456)
|
[#456](https://github.com/ethereum/evmc/pull/456)
|
||||||
|
@ -25,6 +25,7 @@ Please visit the [documentation].
|
|||||||
| **C++** | C++11, C++14, C++17 | GCC 6+, clang 3.8+, MSVC 2015+
|
| **C++** | C++11, C++14, C++17 | GCC 6+, clang 3.8+, MSVC 2015+
|
||||||
| **Go** _(bindings)_ | 1.9 - 1.12 |
|
| **Go** _(bindings)_ | 1.9 - 1.12 |
|
||||||
| **Rust** _(bindings)_[¹](#n1) | 2018 edition | 1.37.0 and newer
|
| **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.
|
<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
23
bindings/java/.gitignore
vendored
Normal 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
46
bindings/java/Makefile
Normal 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/
|
40
bindings/java/build.gradle
Normal file
40
bindings/java/build.gradle
Normal 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
163
bindings/java/c/evmc-vm.c
Normal 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
584
bindings/java/c/host.c
Normal 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
21
bindings/java/c/host.h
Normal 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
|
26
bindings/java/java/README.md
Normal file
26
bindings/java/java/README.md
Normal 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
|
||||||
|
```
|
7
bindings/java/java/build.gradle
Normal file
7
bindings/java/java/build.gradle
Normal 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'
|
||||||
|
}
|
166
bindings/java/java/src/main/java/org/ethereum/evmc/EvmcVm.java
Normal file
166
bindings/java/java/src/main/java/org/ethereum/evmc/EvmcVm.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
150
bindings/java/java/src/main/java/org/ethereum/evmc/Host.java
Normal file
150
bindings/java/java/src/main/java/org/ethereum/evmc/Host.java
Normal 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<>());
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
247
bindings/java/java/src/test/java/org/ethereum/evmc/EvmcTest.java
Normal file
247
bindings/java/java/src/test/java/org/ethereum/evmc/EvmcTest.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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) {}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
2
bindings/java/settings.gradle
Normal file
2
bindings/java/settings.gradle
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
rootProject.name = 'evmc'
|
||||||
|
include 'java'
|
4
bindings/java/wrapper.gradle
Normal file
4
bindings/java/wrapper.gradle
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
task setup(type: Wrapper) {
|
||||||
|
description = "Download the gradle wrapper and requisite files. Overwrites existing wrapper files."
|
||||||
|
gradleVersion = "5.0"
|
||||||
|
}
|
22
circle.yml
22
circle.yml
@ -64,7 +64,7 @@ jobs:
|
|||||||
name: "Check code format"
|
name: "Check code format"
|
||||||
command: |
|
command: |
|
||||||
clang-format --version
|
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
|
git diff --color --exit-code
|
||||||
- run:
|
- run:
|
||||||
name: "Run codespell"
|
name: "Run codespell"
|
||||||
@ -182,6 +182,25 @@ jobs:
|
|||||||
- image: circleci/golang:1.9
|
- image: circleci/golang:1.9
|
||||||
steps: *bindings-go-steps
|
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:
|
bindings-rust:
|
||||||
docker:
|
docker:
|
||||||
- image: rust:1
|
- image: rust:1
|
||||||
@ -261,6 +280,7 @@ workflows:
|
|||||||
- build-gcc-32bit
|
- build-gcc-32bit
|
||||||
- bindings-go-latest
|
- bindings-go-latest
|
||||||
- bindings-go-min
|
- bindings-go-min
|
||||||
|
- bindings-java
|
||||||
- bindings-rust:
|
- bindings-rust:
|
||||||
requires:
|
requires:
|
||||||
- build-gcc8-cxx17
|
- build-gcc8-cxx17
|
||||||
|
Loading…
x
Reference in New Issue
Block a user