Skip to content

Commit bd1d962

Browse files
Post-review follow-up (#246)
* Post-review follow-up * Revert the hard cap * Improvements * no need to commit cargo lock for this * CR follow ups
1 parent 8cfd34d commit bd1d962

18 files changed

Lines changed: 333 additions & 139 deletions

File tree

packages/cre-rust-inject-alpha/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use javy_plugin_api::javy::quickjs::prelude::*;
33
use javy_plugin_api::javy::quickjs::{Ctx, Object};
44

55
pub fn register(ctx: &Ctx<'_>) {
6-
let obj = Object::new(ctx.clone()).unwrap();
6+
let obj = Object::new(ctx.clone()).expect("failed to create rustAlpha export object");
77
obj.set(
88
"greet",
99
Func::from(|| -> String { "Hello from alpha".to_string() }),
1010
)
11-
.unwrap();
11+
.expect("failed to set rustAlpha.greet export");
1212
extend_wasm_exports(ctx, "rustAlpha", obj);
1313
}

packages/cre-rust-inject-beta/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use javy_plugin_api::javy::quickjs::prelude::*;
33
use javy_plugin_api::javy::quickjs::{Ctx, Object};
44

55
pub fn register(ctx: &Ctx<'_>) {
6-
let obj = Object::new(ctx.clone()).unwrap();
6+
let obj = Object::new(ctx.clone()).expect("failed to create rustBeta export object");
77
obj.set(
88
"greet",
99
Func::from(|| -> String { "Hello from beta".to_string() }),
1010
)
11-
.unwrap();
11+
.expect("failed to set rustBeta.greet export");
1212
extend_wasm_exports(ctx, "rustBeta", obj);
1313
}

packages/cre-sdk-javy-plugin/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules
22
src/javy_chainlink_sdk/target/
3+
src/cre_wasm_exports/target/
34
.cargo-target/
45
.turbo
56

-1.12 KB
Binary file not shown.

packages/cre-sdk-javy-plugin/scripts/generate-host-crate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ pub unsafe extern "C" fn initialize_runtime() {
175175
runtime
176176
},
177177
)
178-
.unwrap();
178+
.expect("failed to initialize CRE Javy runtime");
179179
}
180180
`
181181

packages/cre-sdk-javy-plugin/src/cre_wasm_exports/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ pub fn extend_wasm_exports<'js, V: IntoJs<'js>>(ctx: &Ctx<'js>, name: &'static s
2323
panic!("Duplicate WASM export: '{name}' is already registered");
2424
}
2525
});
26-
ctx.globals().set(name, value).unwrap();
26+
ctx.globals()
27+
.set(name, value)
28+
.unwrap_or_else(|err| panic!("failed to register WASM export '{name}': {err:?}"));
2729
}
2830

2931
/// Resets the export registry for a new initialization cycle.
@@ -53,7 +55,8 @@ mod tests {
5355
#[should_panic(expected = "Duplicate WASM export")]
5456
fn rejects_duplicate_export_name() {
5557
REGISTERED.with(|cell| cell.borrow_mut().clear());
56-
let runtime = Runtime::new(Config::default()).unwrap();
58+
let runtime =
59+
Runtime::new(Config::default()).expect("failed to create QuickJS runtime for test");
5760
runtime.context().with(|ctx| {
5861
extend_wasm_exports(&ctx, "duplicatedName", true);
5962
extend_wasm_exports(&ctx, "duplicatedName", true);

packages/cre-sdk-javy-plugin/src/javy_chainlink_sdk/src/lib.rs

Lines changed: 115 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
use cre_wasm_exports::{__clear_registry, extend_wasm_exports};
22
use javy_plugin_api::{
3-
import_namespace,
3+
Config, import_namespace,
44
javy::{Runtime, quickjs::prelude::*},
5-
Config,
65
};
76

7+
use base64::Engine;
88
use javy_plugin_api::javy::quickjs::{
99
ArrayBuffer, Ctx, Error, Exception, FromJs, TypedArray, Value,
1010
};
11+
use rand::{Rng, SeedableRng};
12+
use rand_chacha::ChaCha8Rng;
1113
use std::collections::HashMap;
1214
use std::env;
1315
use std::sync::{Mutex, OnceLock};
14-
use base64::Engine;
15-
use rand::{Rng, SeedableRng};
16-
use rand_chacha::ChaCha8Rng;
1716

1817
static CURRENT_MODE: Mutex<i32> = Mutex::new(0);
1918
static RANDOM_GENERATORS: OnceLock<Mutex<HashMap<i32, ChaCha8Rng>>> = OnceLock::new();
19+
const MAX_RESPONSE_LEN_BYTES: i32 = 64 * 1024 * 1024;
2020

2121
// ✅ Host imports: implemented in Go
2222
#[link(wasm_import_module = "env")]
@@ -96,8 +96,63 @@ pub fn config() -> Config {
9696
config
9797
}
9898

99-
/// Applies CRE plugin globals and host bindings. Used by the default plugin build and by generated host crates that add `--cre-exports` extensions.
100-
///
99+
fn validate_max_response_len(max_len: i32) -> Result<usize, &'static str> {
100+
if max_len < 0 {
101+
return Err("maxLen < 0");
102+
}
103+
104+
if max_len > MAX_RESPONSE_LEN_BYTES {
105+
return Err("maxLen exceeds maximum allowed response size");
106+
}
107+
108+
Ok(max_len as usize)
109+
}
110+
111+
fn checked_response_buffer_len(ctx: &Ctx<'_>, max_len: i32) -> Result<usize, Error> {
112+
validate_max_response_len(max_len).map_err(|message| Exception::throw_range(ctx, message))
113+
}
114+
115+
/// Invokes a host fn with shape `(req_ptr, req_len, buf_ptr, max_len) -> i64`,
116+
/// validates `max_len`, returns the populated response slice or maps host errors
117+
/// to JS exceptions. `op_name` appears in the empty-error fallback; `capacity_msg`
118+
/// is static because `Error::new_into_js` requires `&'static str`.
119+
fn dispatch_host_call(
120+
ctx: &Ctx<'_>,
121+
req: ArgBytes,
122+
max_len: i32,
123+
op_name: &str,
124+
capacity_msg: &'static str,
125+
host_fn: unsafe extern "C" fn(*const u8, i32, *mut u8, i32) -> i64,
126+
) -> Result<Vec<u8>, Error> {
127+
let max_len_usize = checked_response_buffer_len(ctx, max_len)?;
128+
let req_bytes = req.0;
129+
let mut buf = vec![0u8; max_len_usize];
130+
131+
let n = unsafe {
132+
host_fn(
133+
req_bytes.as_ptr(),
134+
req_bytes.len() as i32,
135+
buf.as_mut_ptr(),
136+
max_len,
137+
)
138+
};
139+
if n < 0 {
140+
let error_len = (-n) as usize;
141+
let error_msg =
142+
String::from_utf8_lossy(&buf[..error_len.min(max_len_usize)]).into_owned();
143+
let error_msg = if error_msg.is_empty() {
144+
format!("{op_name} failed")
145+
} else {
146+
error_msg
147+
};
148+
return Err(Exception::throw_message(ctx, &error_msg));
149+
}
150+
if n > max_len_usize as i64 {
151+
return Err(Error::new_into_js("Error", capacity_msg));
152+
}
153+
Ok(buf[..n as usize].to_vec())
154+
}
155+
101156
/// Duplicate export names are caught eagerly by `extend_wasm_exports`.
102157
pub fn modify_runtime(runtime: Runtime) -> Runtime {
103158
__clear_registry();
@@ -118,118 +173,44 @@ pub fn modify_runtime(runtime: Runtime) -> Runtime {
118173
&ctx,
119174
"awaitCapabilities",
120175
Func::from(|ctx: Ctx<'_>, req: ArgBytes, max_len: i32| {
121-
if max_len < 0 {
122-
return Err(Exception::throw_range(&ctx, "maxLen < 0"));
123-
}
124-
let req_bytes = req.0;
125-
let mut buf = vec![0u8; max_len as usize];
126-
127-
let n = unsafe {
128-
await_capabilities(
129-
req_bytes.as_ptr(),
130-
req_bytes.len() as i32,
131-
buf.as_mut_ptr(),
132-
max_len,
133-
)
134-
};
135-
if n < 0 {
136-
let error_len = (-n) as usize;
137-
let error_msg =
138-
String::from_utf8_lossy(&buf[..error_len.min(max_len as usize)]).into_owned();
139-
let error_msg_static: &'static str = Box::leak(error_msg.into_boxed_str());
140-
return Err(Error::new_into_js("Error", error_msg_static));
141-
}
142-
if n > max_len as i64 {
143-
return Err(Error::new_into_js(
144-
"Error",
145-
"await_capabilities: host returned length exceeding buffer capacity",
146-
));
147-
}
148-
149-
let out = &buf[..n as usize];
150-
Ok::<Vec<u8>, Error>(out.to_vec())
176+
dispatch_host_call(
177+
&ctx,
178+
req,
179+
max_len,
180+
"await_capabilities",
181+
"await_capabilities: host returned length exceeding buffer capacity",
182+
await_capabilities,
183+
)
151184
}),
152185
);
153186

154187
extend_wasm_exports(
155188
&ctx,
156189
"getSecrets",
157190
Func::from(|ctx: Ctx<'_>, req: ArgBytes, max_len: i32| {
158-
if max_len < 0 {
159-
return Err(Exception::throw_range(&ctx, "maxLen < 0"));
160-
}
161-
let req_bytes = req.0;
162-
let mut buf = vec![0u8; max_len as usize];
163-
164-
let n = unsafe {
165-
get_secrets(
166-
req_bytes.as_ptr(),
167-
req_bytes.len() as i32,
168-
buf.as_mut_ptr(),
169-
max_len,
170-
)
171-
};
172-
if n < 0 {
173-
let error_len = (-n) as usize;
174-
let error_msg =
175-
String::from_utf8_lossy(&buf[..error_len.min(max_len as usize)]).into_owned();
176-
let error_msg = if error_msg.is_empty() {
177-
"get_secrets failed".to_string()
178-
} else {
179-
error_msg
180-
};
181-
return Err(Exception::throw_message(&ctx, &error_msg));
182-
}
183-
if n > max_len as i64 {
184-
return Err(Error::new_into_js(
185-
"Error",
186-
"get_secrets: host returned length exceeding buffer capacity",
187-
));
188-
}
189-
190-
let out = &buf[..n as usize];
191-
Ok::<Vec<u8>, Error>(out.to_vec())
191+
dispatch_host_call(
192+
&ctx,
193+
req,
194+
max_len,
195+
"get_secrets",
196+
"get_secrets: host returned length exceeding buffer capacity",
197+
get_secrets,
198+
)
192199
}),
193200
);
194201

195202
extend_wasm_exports(
196203
&ctx,
197204
"awaitSecrets",
198205
Func::from(|ctx: Ctx<'_>, req: ArgBytes, max_len: i32| {
199-
if max_len < 0 {
200-
return Err(Exception::throw_range(&ctx, "maxLen < 0"));
201-
}
202-
let req_bytes = req.0;
203-
let mut buf = vec![0u8; max_len as usize];
204-
205-
let n = unsafe {
206-
await_secrets(
207-
req_bytes.as_ptr(),
208-
req_bytes.len() as i32,
209-
buf.as_mut_ptr(),
210-
max_len,
211-
)
212-
};
213-
if n < 0 {
214-
let error_len = (-n) as usize;
215-
let error_msg =
216-
String::from_utf8_lossy(&buf[..error_len.min(max_len as usize)]).into_owned();
217-
let error_msg = if error_msg.is_empty() {
218-
"await_secrets failed".to_string()
219-
} else {
220-
error_msg
221-
};
222-
return Err(Exception::throw_message(&ctx, &error_msg));
223-
}
224-
if n > max_len as i64 {
225-
return Err(Error::new_into_js(
226-
"Error",
227-
"await_secrets: host returned length exceeding buffer capacity",
228-
));
229-
}
230-
231-
let out = &buf[..n as usize];
232-
Ok::<Vec<u8>, Error>(out.to_vec())
206+
dispatch_host_call(
207+
&ctx,
208+
req,
209+
max_len,
210+
"await_secrets",
211+
"await_secrets: host returned length exceeding buffer capacity",
212+
await_secrets,
213+
)
233214
}),
234215
);
235216

@@ -312,9 +293,8 @@ pub fn modify_runtime(runtime: Runtime) -> Runtime {
312293
"getWasiArgs",
313294
Func::from(|_ctx: Ctx<'_>| -> Result<String, Error> {
314295
let args: Vec<String> = env::args().collect();
315-
let args_json = serde_json::to_string(&args).map_err(|_| {
316-
Error::new_into_js("Error", "Failed to serialize args to JSON")
317-
})?;
296+
let args_json = serde_json::to_string(&args)
297+
.map_err(|_| Error::new_into_js("Error", "Failed to serialize args to JSON"))?;
318298
Ok(args_json)
319299
}),
320300
);
@@ -346,3 +326,30 @@ pub fn modify_runtime(runtime: Runtime) -> Runtime {
346326

347327
runtime
348328
}
329+
330+
#[cfg(test)]
331+
mod tests {
332+
use super::*;
333+
334+
#[test]
335+
fn validate_max_response_len_accepts_values_within_cap() {
336+
assert_eq!(validate_max_response_len(0), Ok(0));
337+
assert_eq!(
338+
validate_max_response_len(MAX_RESPONSE_LEN_BYTES),
339+
Ok(MAX_RESPONSE_LEN_BYTES as usize)
340+
);
341+
}
342+
343+
#[test]
344+
fn validate_max_response_len_rejects_negative_values() {
345+
assert_eq!(validate_max_response_len(-1), Err("maxLen < 0"));
346+
}
347+
348+
#[test]
349+
fn validate_max_response_len_rejects_values_above_cap() {
350+
assert_eq!(
351+
validate_max_response_len(MAX_RESPONSE_LEN_BYTES + 1),
352+
Err("maxLen exceeds maximum allowed response size")
353+
);
354+
}
355+
}

packages/cre-sdk/src/sdk/impl/runtime-impl.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,44 @@ describe('test getSecret', () => {
441441
expect(result.value).toEqual('value-456')
442442
})
443443

444+
test('normalizes missing secret namespace to default for JSON and protobuf requests', () => {
445+
const observedNamespaces: string[] = []
446+
const helpers = createRuntimeHelpersMock({
447+
getSecrets: mock((request) => {
448+
expect(request.requests.length).toEqual(1)
449+
observedNamespaces.push(request.requests[0].namespace)
450+
}),
451+
awaitSecrets: mock((request) => {
452+
const id = request.ids[0]
453+
return create(AwaitSecretsResponseSchema, {
454+
responses: {
455+
[id]: create(SecretResponsesSchema, {
456+
responses: [
457+
create(SecretResponseSchema, {
458+
response: {
459+
case: 'secret',
460+
value: {
461+
id: 'secret',
462+
namespace: 'main',
463+
owner: 'owner',
464+
value: 'value',
465+
},
466+
},
467+
}),
468+
],
469+
}),
470+
},
471+
})
472+
}),
473+
})
474+
475+
const runtime = new RuntimeImpl<unknown>({}, 1, helpers, anyMaxSize)
476+
runtime.getSecret({ id: 'json-secret' }).result()
477+
runtime.getSecret(create(SecretRequestSchema, { id: 'proto-secret' })).result()
478+
479+
expect(observedNamespaces).toEqual(['main', 'main'])
480+
})
481+
444482
test('getSecrets throws → wrapped as SecretsError', () => {
445483
const secretRequest = create(SecretRequestSchema, {
446484
id: 'test-secret',

0 commit comments

Comments
 (0)