Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add map integrity tests #1227

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions soroban-env-host/src/test/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use soroban_env_common::{
},
MapObject, U32Val,
};
use soroban_test_wasms::LINEAR_MEMORY;

use crate::{
xdr::{ScMap, ScMapEntry, ScVal, ScVec, VecM},
Expand Down Expand Up @@ -338,3 +339,62 @@ fn map_build_bad_element_integrity() -> Result<(), HostError> {

Ok(())
}

#[test]
fn initialization_invalid() -> Result<(), HostError> {
use crate::EnvBase;
let host = observe_host!(Host::default());

// Out of order keys
assert!(host
.map_new_from_slices(&["b", "a"], &[1u32.into(), 2u32.into()])
.is_err());

// Duplicate
assert!(host
.map_new_from_slices(&["a", "a"], &[1u32.into(), 2u32.into()])
.is_err());

let buf = vec![0; 7000000];
let scv_bytes = ScVal::Bytes(buf.try_into().unwrap());
let bytes_val = host.to_host_val(&scv_bytes)?;

// roughly consumes 7*5=42MiB (> 40 MiB is the budget limit)
let vals = vec![bytes_val; 5];
let keys = ["a", "b", "c", "d", "e"];

let res = host.map_new_from_slices(&keys, &vals.as_slice())?;
assert!(host.from_host_val(res.to_val()).is_err());
graydon marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you expect budget error, please add the specific assert for that. It would make test intent more clear.


Ok(())
}

#[test]
fn linear_memory_operations() -> Result<(), HostError> {
let host = observe_host!(Host::test_host_with_recording_footprint());
host.enable_debug()?;
let id_obj = host.register_test_contract_wasm(LINEAR_MEMORY);

{
let args = host.vec_new()?;
let res = host.call(
id_obj,
Symbol::try_from_val(&*host, &"map_mem_ok").unwrap(),
args,
);

assert!(res.is_ok());
}

{
let args = host.vec_new()?;
let res = host.call(
id_obj,
Symbol::try_from_val(&*host, &"map_mem_bad").unwrap(),
args,
);
assert!(res.is_err());
assert!(res.unwrap_err().error.is_code(ScErrorCode::InvalidInput));
}
Ok(())
}
24 changes: 23 additions & 1 deletion soroban-test-wasms/wasm-workspace/linear_memory/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![no_std]
use soroban_sdk::{contract, contractimpl, Bytes, Env};
use soroban_env_common::{VecObject, Val, EnvBase, Error};
use soroban_env_common::{VecObject, MapObject, Val, EnvBase, Error};

#[contract]
pub struct Contract;
Expand Down Expand Up @@ -51,4 +51,26 @@ impl Contract {
assert!(e.vec_unpack_to_slice(vec, &mut long_buf).is_err());
Ok(())
}

// Bounce a map of vals off the host, successfully
pub fn map_mem_ok(e: Env) -> Result<(), Error> {
let map: MapObject = e.map_new_from_slices(&["a", "b"], &[1u32.into(), 2u32.into()])?;

let key_buf = ["a", "b"];
let mut val_buf: [Val; 2] = [Val::from(0); 2];

e.map_unpack_to_slice(map, &key_buf, &mut val_buf)?;
Ok(())
}

// Same but with out of order keys
pub fn map_mem_bad(e: Env) -> Result<(), Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this return Result?

// out of order
assert!(e.map_new_from_slices(&["b", "a"], &[1u32.into(), 2u32.into()]).is_err());
Copy link
Contributor

@dmkozh dmkozh Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like almost every function in this Wasm is set up incorrectly. Host functions don't return errors in guest environment, they cause the contract to trap. Functions like map_new_from_slices should be changed in SDK to be infallible (probably open an issue for that?).
Besides that, functions that perform assert! check can only be meaningfully tested when the expected invocation result is Ok (e.g. map_mem_bad expects an error, so even if you could assert on host fns, your test would still pass if either first or second assertion fails).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the assert part. The contract would've already trapped and the assert here does nothing (and the line after this will never get executed).
But I'm not sure about making map_new_from_slices and other functions infallible in the sdk. They are part of the EnvBase crate, and simply calls the host functions from the guest env. So I guess it doesn't really matter? If you have an opinion of what to do better in the sdk, can you create an issue there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'm not sure about making map_new_from_slices and other functions infallible in the sdk. They are part of the EnvBase crate, and simply calls the host functions from the guest env. So I guess it doesn't really matter?

It does matter, because it creates a false impression that these functions can return an Err result. They cannot though, as they either cause a trap, or return Ok. This test is a live evidence of how misleading this is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the issue with EnvBase though, so I'm not sure how exactly we should address this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally familiar with the structuring of the sdk, maybe @graydon or @leighmcculloch can chime in here.

The EnvBase has same as the guest::Env here, where all functions have fallible interface but Infallible error. The Env functions are not directly exposed to the outside (unless via one of the public modules), but the EnvBase functions are. So maybe the answer here is not to expose the EnvBase methods, or expose only selected ones in other modules (and giving them infallible signatures)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe the answer here is not to expose the EnvBase methods, or expose only selected ones in other modules (and giving them infallible signatures)?

Yes, probably the least confusing thing to do would be to add a public wrapper over all the EnvBase functions and hide the fallible implementation, so that it can't be used by accident.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// duplicate
assert!(e.map_new_from_slices(&["a", "a"], &[1u32.into(), 2u32.into()]).is_err());

Ok(())
}
}
Binary file not shown.
Loading