Skip to content

Commit 1757cc2

Browse files
authored
contracts: Change define_env! to expect a Result<T, DispatchError> for every function (#7762)
* Make host functions return TrapReason This avoids the need to manually store any trap reasons to the `Runtime` from the host function. This adds the following benefits: * It properly composes with the upcoming chain extensions * Missing to set a trap value is now a compile error * review: Remove superflous .into()
1 parent 8d0d255 commit 1757cc2

File tree

5 files changed

+146
-156
lines changed

5 files changed

+146
-156
lines changed

src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,14 @@ decl_error! {
378378
/// on the call stack. Those actions are contract self destruction and restoration
379379
/// of a tombstone.
380380
ReentranceDenied,
381+
/// `seal_input` was called twice from the same contract execution context.
382+
InputAlreadyRead,
383+
/// The subject passed to `seal_random` exceeds the limit.
384+
RandomSubjectTooLong,
385+
/// The amount of topics passed to `seal_deposit_events` exceeds the limit.
386+
TooManyTopics,
387+
/// The topics passed to `seal_deposit_events` contains at least one duplicate.
388+
DuplicateTopics,
381389
}
382390
}
383391

src/wasm/env_def/macros.rs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ macro_rules! unmarshall_then_body {
9696
#[inline(always)]
9797
pub fn constrain_closure<R, F>(f: F) -> F
9898
where
99-
F: FnOnce() -> Result<R, sp_sandbox::HostError>,
99+
F: FnOnce() -> Result<R, crate::wasm::runtime::TrapReason>,
100100
{
101101
f
102102
}
@@ -109,14 +109,20 @@ macro_rules! unmarshall_then_body_then_marshall {
109109
>(|| {
110110
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
111111
});
112-
let r = body()?;
112+
let r = body().map_err(|reason| {
113+
$ctx.set_trap_reason(reason);
114+
sp_sandbox::HostError
115+
})?;
113116
return Ok(sp_sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() }))
114117
});
115118
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
116119
let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| {
117120
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
118121
});
119-
body()?;
122+
body().map_err(|reason| {
123+
$ctx.set_trap_reason(reason);
124+
sp_sandbox::HostError
125+
})?;
120126
return Ok(sp_sandbox::ReturnValue::Unit)
121127
})
122128
}
@@ -207,15 +213,24 @@ mod tests {
207213
use parity_wasm::elements::ValueType;
208214
use sp_runtime::traits::Zero;
209215
use sp_sandbox::{ReturnValue, Value};
210-
use crate::wasm::tests::MockExt;
211-
use crate::wasm::Runtime;
212-
use crate::exec::Ext;
213-
use crate::gas::Gas;
216+
use crate::{
217+
wasm::{Runtime, runtime::TrapReason, tests::MockExt},
218+
exec::Ext,
219+
gas::Gas,
220+
};
221+
222+
struct TestRuntime {
223+
value: u32,
224+
}
225+
226+
impl TestRuntime {
227+
fn set_trap_reason(&mut self, _reason: TrapReason) {}
228+
}
214229

215230
#[test]
216231
fn macro_unmarshall_then_body_then_marshall_value_or_trap() {
217232
fn test_value(
218-
_ctx: &mut u32,
233+
_ctx: &mut TestRuntime,
219234
args: &[sp_sandbox::Value],
220235
) -> Result<ReturnValue, sp_sandbox::HostError> {
221236
let mut args = args.iter();
@@ -224,15 +239,15 @@ mod tests {
224239
_ctx,
225240
(a: u32, b: u32) -> u32 => {
226241
if b == 0 {
227-
Err(sp_sandbox::HostError)
242+
Err(crate::wasm::runtime::TrapReason::Termination)
228243
} else {
229244
Ok(a / b)
230245
}
231246
}
232247
)
233248
}
234249

235-
let ctx = &mut 0;
250+
let ctx = &mut TestRuntime { value: 0 };
236251
assert_eq!(
237252
test_value(ctx, &[Value::I32(15), Value::I32(3)]).unwrap(),
238253
ReturnValue::Value(Value::I32(5)),
@@ -243,24 +258,24 @@ mod tests {
243258
#[test]
244259
fn macro_unmarshall_then_body_then_marshall_unit() {
245260
fn test_unit(
246-
ctx: &mut u32,
261+
ctx: &mut TestRuntime,
247262
args: &[sp_sandbox::Value],
248263
) -> Result<ReturnValue, sp_sandbox::HostError> {
249264
let mut args = args.iter();
250265
unmarshall_then_body_then_marshall!(
251266
args,
252267
ctx,
253268
(a: u32, b: u32) => {
254-
*ctx = a + b;
269+
ctx.value = a + b;
255270
Ok(())
256271
}
257272
)
258273
}
259274

260-
let ctx = &mut 0;
275+
let ctx = &mut TestRuntime { value: 0 };
261276
let result = test_unit(ctx, &[Value::I32(2), Value::I32(3)]).unwrap();
262277
assert_eq!(result, ReturnValue::Unit);
263-
assert_eq!(*ctx, 5);
278+
assert_eq!(ctx.value, 5);
264279
}
265280

266281
#[test]
@@ -270,7 +285,7 @@ mod tests {
270285
if !amount.is_zero() {
271286
Ok(())
272287
} else {
273-
Err(sp_sandbox::HostError)
288+
Err(TrapReason::Termination)
274289
}
275290
});
276291
let _f: fn(&mut Runtime<MockExt>, &[sp_sandbox::Value])
@@ -322,7 +337,7 @@ mod tests {
322337
if !amount.is_zero() {
323338
Ok(())
324339
} else {
325-
Err(sp_sandbox::HostError)
340+
Err(crate::wasm::runtime::TrapReason::Termination)
326341
}
327342
},
328343
);

src/wasm/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ mod tests {
15371537
&mut gas_meter
15381538
),
15391539
Err(ExecError {
1540-
error: Error::<Test>::ContractTrapped.into(),
1540+
error: Error::<Test>::TooManyTopics.into(),
15411541
origin: ErrorOrigin::Caller,
15421542
})
15431543
);
@@ -1582,7 +1582,7 @@ mod tests {
15821582
&mut gas_meter
15831583
),
15841584
Err(ExecError {
1585-
error: Error::<Test>::ContractTrapped.into(),
1585+
error: Error::<Test>::DuplicateTopics.into(),
15861586
origin: ErrorOrigin::Caller,
15871587
})
15881588
);

src/wasm/prepare.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -491,18 +491,24 @@ mod tests {
491491
}
492492
}
493493

494-
// Define test environment for tests. We need ImportSatisfyCheck
495-
// implementation from it. So actual implementations doesn't matter.
496-
define_env!(TestEnv, <E: Ext>,
497-
panic(_ctx) => { unreachable!(); },
494+
/// Using unreachable statements triggers unreachable warnings in the generated code
495+
#[allow(unreachable_code)]
496+
mod env {
497+
use super::*;
498498

499-
// gas is an implementation defined function and a contract can't import it.
500-
gas(_ctx, _amount: u32) => { unreachable!(); },
499+
// Define test environment for tests. We need ImportSatisfyCheck
500+
// implementation from it. So actual implementations doesn't matter.
501+
define_env!(Test, <E: Ext>,
502+
panic(_ctx) => { unreachable!(); },
501503

502-
nop(_ctx, _unused: u64) => { unreachable!(); },
504+
// gas is an implementation defined function and a contract can't import it.
505+
gas(_ctx, _amount: u32) => { unreachable!(); },
503506

504-
seal_println(_ctx, _ptr: u32, _len: u32) => { unreachable!(); },
505-
);
507+
nop(_ctx, _unused: u64) => { unreachable!(); },
508+
509+
seal_println(_ctx, _ptr: u32, _len: u32) => { unreachable!(); },
510+
);
511+
}
506512

507513
macro_rules! prepare_test {
508514
($name:ident, $wat:expr, $($expected:tt)*) => {
@@ -520,7 +526,7 @@ mod tests {
520526
},
521527
.. Default::default()
522528
};
523-
let r = prepare_contract::<TestEnv, crate::tests::Test>(wasm.as_ref(), &schedule);
529+
let r = prepare_contract::<env::Test, crate::tests::Test>(wasm.as_ref(), &schedule);
524530
assert_matches!(r, $($expected)*);
525531
}
526532
};
@@ -931,7 +937,7 @@ mod tests {
931937
).unwrap();
932938
let mut schedule = Schedule::default();
933939
schedule.enable_println = true;
934-
let r = prepare_contract::<TestEnv, crate::tests::Test>(wasm.as_ref(), &schedule);
940+
let r = prepare_contract::<env::Test, crate::tests::Test>(wasm.as_ref(), &schedule);
935941
assert_matches!(r, Ok(_));
936942
}
937943
}

0 commit comments

Comments
 (0)