Skip to content

Commit 37e85da

Browse files
committed
feat: Add FFI bridge for WASM runtime integration
1 parent 35e2dd9 commit 37e85da

File tree

5 files changed

+327
-0
lines changed

5 files changed

+327
-0
lines changed

x/contracts/runtime/ffi/Makefile

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.PHONY: all clean
2+
3+
UNAME := $(shell uname)
4+
5+
ifeq ($(UNAME), Darwin)
6+
LIBEXT = dylib
7+
else
8+
LIBEXT = so
9+
endif
10+
11+
all: libhypersdk.$(LIBEXT)
12+
13+
libhypersdk.$(LIBEXT): bridge.go
14+
go build -buildmode=c-shared -o $@ $<
15+
16+
clean:
17+
rm -f libhypersdk.* *.h

x/contracts/runtime/ffi/bridge.go

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package main
2+
3+
/*
4+
#include <stdlib.h>
5+
#include <stdint.h>
6+
#include <string.h>
7+
8+
typedef unsigned char byte;
9+
typedef struct { byte data[32]; } contract_id_t;
10+
*/
11+
import "C"
12+
import (
13+
"encoding/json"
14+
"sync"
15+
"unsafe"
16+
17+
"github.com/bytecodealliance/wasmtime-go"
18+
)
19+
20+
var (
21+
runtime *Runtime
22+
mu sync.Mutex
23+
)
24+
25+
type Config struct {
26+
MaxMemory int `json:"max_memory"`
27+
MaxFuel int64 `json:"max_fuel"`
28+
MaxStackHeight int `json:"max_stack_height"`
29+
}
30+
31+
type Runtime struct {
32+
config Config
33+
contracts map[[32]byte][]byte
34+
states map[[32]byte][]byte
35+
engine *wasmtime.Engine
36+
}
37+
38+
func main() {} // Required for c-shared build mode
39+
40+
//export hypersdk_init_runtime
41+
func hypersdk_init_runtime(configC *C.char) C.int {
42+
mu.Lock()
43+
defer mu.Unlock()
44+
45+
if runtime != nil {
46+
return 0 // Already initialized
47+
}
48+
49+
configJSON := C.GoString(configC)
50+
var config Config
51+
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
52+
return -1
53+
}
54+
55+
// Create wasmtime engine with config
56+
engine := wasmtime.NewEngine()
57+
58+
runtime = &Runtime{
59+
config: config,
60+
contracts: make(map[[32]byte][]byte),
61+
states: make(map[[32]byte][]byte),
62+
engine: engine,
63+
}
64+
65+
return 0
66+
}
67+
68+
//export hypersdk_deploy_contract
69+
func hypersdk_deploy_contract(wasmBytes *C.byte, wasmLen C.size_t, contractIDOut *C.contract_id_t) C.int {
70+
if runtime == nil {
71+
return -1
72+
}
73+
74+
// Convert wasm bytes to Go slice
75+
bytes := C.GoBytes(unsafe.Pointer(wasmBytes), C.int(wasmLen))
76+
77+
// Validate WASM module
78+
_, err := wasmtime.NewModule(runtime.engine, bytes)
79+
if err != nil {
80+
return -2
81+
}
82+
83+
// Generate contract ID (using first 32 bytes as ID for now)
84+
var id [32]byte
85+
copy(id[:], bytes[:32])
86+
87+
// Store contract
88+
runtime.contracts[id] = bytes
89+
90+
// Copy ID to output parameter
91+
C.memcpy(unsafe.Pointer(&contractIDOut.data[0]), unsafe.Pointer(&id[0]), 32)
92+
93+
return 0
94+
}
95+
96+
//export hypersdk_call_contract
97+
func hypersdk_call_contract(
98+
contractID *C.contract_id_t,
99+
functionC *C.char,
100+
argsBytes *C.byte,
101+
argsLen C.size_t,
102+
resultPtr **C.byte,
103+
resultLen *C.size_t,
104+
) C.int {
105+
if runtime == nil {
106+
return -1
107+
}
108+
109+
// Convert contract ID to Go array
110+
var id [32]byte
111+
C.memcpy(unsafe.Pointer(&id[0]), unsafe.Pointer(&contractID.data[0]), 32)
112+
113+
function := C.GoString(functionC)
114+
args := C.GoBytes(unsafe.Pointer(argsBytes), C.int(argsLen))
115+
116+
// Get contract
117+
wasmBytes, ok := runtime.contracts[id]
118+
if !ok {
119+
return -2
120+
}
121+
122+
// Create execution context
123+
store := wasmtime.NewStore(runtime.engine)
124+
module, err := wasmtime.NewModule(runtime.engine, wasmBytes)
125+
if err != nil {
126+
return -3
127+
}
128+
129+
// Execute contract
130+
instance, err := wasmtime.NewInstance(store, module, []wasmtime.AsExtern{})
131+
if err != nil {
132+
return -4
133+
}
134+
135+
// Get function
136+
f := instance.GetExport(store, function).Func()
137+
if f == nil {
138+
return -5
139+
}
140+
141+
// Call function
142+
val, err := f.Call(store, args)
143+
if err != nil {
144+
return -6
145+
}
146+
147+
// Convert result to bytes
148+
result := val.([]byte)
149+
150+
// Allocate memory for result
151+
resultSize := C.size_t(len(result))
152+
resultBuf := C.malloc(resultSize)
153+
154+
// Copy result to C buffer
155+
C.memcpy(unsafe.Pointer(resultBuf), unsafe.Pointer(&result[0]), resultSize)
156+
157+
*resultPtr = (*C.byte)(resultBuf)
158+
*resultLen = resultSize
159+
160+
return 0
161+
}
162+
163+
//export hypersdk_get_state
164+
func hypersdk_get_state(
165+
contractID *C.contract_id_t,
166+
statePtr **C.byte,
167+
stateLen *C.size_t,
168+
) C.int {
169+
if runtime == nil {
170+
return -1
171+
}
172+
173+
// Convert contract ID to Go array
174+
var id [32]byte
175+
C.memcpy(unsafe.Pointer(&id[0]), unsafe.Pointer(&contractID.data[0]), 32)
176+
177+
// Get state
178+
state, ok := runtime.states[id]
179+
if !ok {
180+
// Return empty state if not found
181+
state = make([]byte, 0)
182+
}
183+
184+
// Allocate memory for state
185+
stateSize := C.size_t(len(state))
186+
stateBuf := C.malloc(stateSize)
187+
188+
// Copy state to C buffer
189+
C.memcpy(unsafe.Pointer(stateBuf), unsafe.Pointer(&state[0]), stateSize)
190+
191+
*statePtr = (*C.byte)(stateBuf)
192+
*stateLen = stateSize
193+
194+
return 0
195+
}
196+
197+
//export hypersdk_free_buffer
198+
func hypersdk_free_buffer(ptr unsafe.Pointer) {
199+
C.free(ptr)
200+
}

x/contracts/runtime/ffi/go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module hypersdk/runtime/ffi
2+
3+
go 1.23.5
4+
5+
require github.com/bytecodealliance/wasmtime-go v1.0.0

x/contracts/runtime/ffi/go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/bytecodealliance/wasmtime-go v1.0.0 h1:9u9gqaUiaJeN5IoD1L7egD8atOnTGyJcNp8BhkL9cUU=
2+
github.com/bytecodealliance/wasmtime-go v1.0.0/go.mod h1:jjlqQbWUfVSbehpErw3UoWFndBXRRMvfikYH6KsCwOg=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7+
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
8+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

x/contracts/runtime/ffi/libhypersdk.h

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* Code generated by cmd/cgo; DO NOT EDIT. */
2+
3+
/* package command-line-arguments */
4+
5+
6+
#line 1 "cgo-builtin-export-prolog"
7+
8+
#include <stddef.h>
9+
10+
#ifndef GO_CGO_EXPORT_PROLOGUE_H
11+
#define GO_CGO_EXPORT_PROLOGUE_H
12+
13+
#ifndef GO_CGO_GOSTRING_TYPEDEF
14+
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
15+
#endif
16+
17+
#endif
18+
19+
/* Start of preamble from import "C" comments. */
20+
21+
22+
#line 3 "bridge.go"
23+
24+
#include <stdlib.h>
25+
#include <stdint.h>
26+
#include <string.h>
27+
28+
typedef unsigned char byte;
29+
typedef struct { byte data[32]; } contract_id_t;
30+
31+
#line 1 "cgo-generated-wrapper"
32+
33+
34+
/* End of preamble from import "C" comments. */
35+
36+
37+
/* Start of boilerplate cgo prologue. */
38+
#line 1 "cgo-gcc-export-header-prolog"
39+
40+
#ifndef GO_CGO_PROLOGUE_H
41+
#define GO_CGO_PROLOGUE_H
42+
43+
typedef signed char GoInt8;
44+
typedef unsigned char GoUint8;
45+
typedef short GoInt16;
46+
typedef unsigned short GoUint16;
47+
typedef int GoInt32;
48+
typedef unsigned int GoUint32;
49+
typedef long long GoInt64;
50+
typedef unsigned long long GoUint64;
51+
typedef GoInt64 GoInt;
52+
typedef GoUint64 GoUint;
53+
typedef size_t GoUintptr;
54+
typedef float GoFloat32;
55+
typedef double GoFloat64;
56+
#ifdef _MSC_VER
57+
#include <complex.h>
58+
typedef _Fcomplex GoComplex64;
59+
typedef _Dcomplex GoComplex128;
60+
#else
61+
typedef float _Complex GoComplex64;
62+
typedef double _Complex GoComplex128;
63+
#endif
64+
65+
/*
66+
static assertion to make sure the file is being used on architecture
67+
at least with matching size of GoInt.
68+
*/
69+
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
70+
71+
#ifndef GO_CGO_GOSTRING_TYPEDEF
72+
typedef _GoString_ GoString;
73+
#endif
74+
typedef void *GoMap;
75+
typedef void *GoChan;
76+
typedef struct { void *t; void *v; } GoInterface;
77+
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
78+
79+
#endif
80+
81+
/* End of boilerplate cgo prologue. */
82+
83+
#ifdef __cplusplus
84+
extern "C" {
85+
#endif
86+
87+
extern int hypersdk_init_runtime(char* configC);
88+
extern int hypersdk_deploy_contract(byte* wasmBytes, size_t wasmLen, contract_id_t* contractIDOut);
89+
extern int hypersdk_call_contract(contract_id_t* contractID, char* functionC, byte* argsBytes, size_t argsLen, byte** resultPtr, size_t* resultLen);
90+
extern int hypersdk_get_state(contract_id_t* contractID, byte** statePtr, size_t* stateLen);
91+
extern void hypersdk_free_buffer(void* ptr);
92+
93+
#ifdef __cplusplus
94+
}
95+
#endif

0 commit comments

Comments
 (0)