diff --git a/piecrust/CHANGELOG.md b/piecrust/CHANGELOG.md index a7019dde..d67c3a73 100644 --- a/piecrust/CHANGELOG.md +++ b/piecrust/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Change `owner` import to accept the contract ID as argument and return + non-zero upon success + ## [0.14.1] - 2024-01-11 ### Fixed diff --git a/piecrust/src/imports.rs b/piecrust/src/imports.rs index 2773fc85..230036f1 100644 --- a/piecrust/src/imports.rs +++ b/piecrust/src/imports.rs @@ -72,7 +72,10 @@ impl Imports { "limit" => Func::wrap(store, limit), "spent" => Func::wrap(store, spent), "panic" => Func::wrap(store, panic), - "owner" => Func::wrap(store, owner), + "owner" => match is_64 { + false => Func::wrap(store, wasm32::owner), + true => Func::wrap(store, wasm64::owner), + }, "self_id" => Func::wrap(store, self_id), #[cfg(feature = "debug")] "hdebug" => Func::wrap(store, hdebug), @@ -386,16 +389,47 @@ fn panic(fenv: Caller, arg_len: u32) -> WasmtimeResult<()> { })?) } -fn owner(fenv: Caller) { +fn owner(fenv: Caller, mod_id_ofs: usize) -> WasmtimeResult { + check_ptr(fenv.data().self_instance(), mod_id_ofs, CONTRACT_ID_BYTES)?; + let env = fenv.data(); - let self_id = env.self_contract_id(); - let contract_metadata = env - .contract_metadata(self_id) - .expect("contract metadata should exist"); - let slice = contract_metadata.owner.as_slice(); - let len = slice.len(); - env.self_instance() - .with_arg_buf_mut(|arg| arg[..len].copy_from_slice(slice)); + + // The null pointer is always zero, so we can use this to check if the + // caller wants their own ID. + let metadata = if mod_id_ofs == 0 { + let self_id = env.self_contract_id(); + + let contract_metadata = env + .contract_metadata(self_id) + .expect("contract metadata should exist"); + + Some(contract_metadata) + } else { + let instance = env.self_instance(); + + let mod_id = instance.with_memory(|memory| { + let mut mod_id = ContractId::uninitialized(); + mod_id.as_bytes_mut().copy_from_slice( + &memory[mod_id_ofs..][..std::mem::size_of::()], + ); + mod_id + }); + + env.contract_metadata(&mod_id) + }; + + match metadata { + None => Ok(0), + Some(metadata) => { + let owner = metadata.owner.as_slice(); + + env.self_instance().with_arg_buf_mut(|arg| { + arg[..owner.len()].copy_from_slice(owner) + }); + + Ok(1) + } + } } fn self_id(fenv: Caller) { diff --git a/piecrust/src/imports/wasm32.rs b/piecrust/src/imports/wasm32.rs index ead8479c..4963f461 100644 --- a/piecrust/src/imports/wasm32.rs +++ b/piecrust/src/imports/wasm32.rs @@ -52,3 +52,7 @@ pub(crate) fn emit( ) -> WasmtimeResult<()> { imports::emit(fenv, topic_ofs as usize, topic_len, arg_len) } + +pub(crate) fn owner(fenv: Caller, mod_id_ofs: u32) -> WasmtimeResult { + imports::owner(fenv, mod_id_ofs as usize) +} diff --git a/piecrust/src/imports/wasm64.rs b/piecrust/src/imports/wasm64.rs index 95ce70cd..06f729db 100644 --- a/piecrust/src/imports/wasm64.rs +++ b/piecrust/src/imports/wasm64.rs @@ -52,3 +52,7 @@ pub(crate) fn emit( ) -> WasmtimeResult<()> { imports::emit(fenv, topic_ofs as usize, topic_len, arg_len) } + +pub(crate) fn owner(fenv: Caller, mod_id_ofs: u64) -> WasmtimeResult { + imports::owner(fenv, mod_id_ofs as usize) +} diff --git a/piecrust/tests/metadata.rs b/piecrust/tests/metadata.rs index bba837f4..6239ac02 100644 --- a/piecrust/tests/metadata.rs +++ b/piecrust/tests/metadata.rs @@ -47,3 +47,75 @@ fn metadata() -> Result<(), Error> { Ok(()) } + +#[test] +fn owner_of() -> Result<(), Error> { + const EXPECTED_OWNER_0: [u8; 33] = [3u8; 33]; + const EXPECTED_OWNER_1: [u8; 33] = [4u8; 33]; + + const CONTRACT_ID_0: ContractId = ContractId::from_bytes([1; 32]); + const CONTRACT_ID_1: ContractId = ContractId::from_bytes([2; 32]); + const CONTRACT_ID_2: ContractId = ContractId::from_bytes([3; 32]); + + let vm = VM::ephemeral()?; + + let mut session = vm.session(SessionData::builder())?; + + session.deploy( + contract_bytecode!("metadata"), + ContractData::builder(EXPECTED_OWNER_0).contract_id(CONTRACT_ID_0), + LIMIT, + )?; + session.deploy( + contract_bytecode!("metadata"), + ContractData::builder(EXPECTED_OWNER_1).contract_id(CONTRACT_ID_1), + LIMIT, + )?; + + let owner = session + .call::<_, Option<[u8; 33]>>( + CONTRACT_ID_0, + "read_owner_of", + &CONTRACT_ID_1, + LIMIT, + )? + .data; + + assert_eq!( + owner, + Some(EXPECTED_OWNER_1), + "The first contract should think the second contract has the correct owner" + ); + + let owner = session + .call::<_, Option<[u8; 33]>>( + CONTRACT_ID_1, + "read_owner_of", + &CONTRACT_ID_0, + LIMIT, + )? + .data; + + assert_eq!( + owner, + Some(EXPECTED_OWNER_0), + "The second contract should think the first contract has the correct owner" + ); + + let owner = session + .call::<_, Option<[u8; 33]>>( + CONTRACT_ID_0, + "read_owner_of", + &CONTRACT_ID_2, + LIMIT, + )? + .data; + + assert_eq!( + owner, + None, + "The first contract should think that the owner of a non-existing contract in None" + ); + + Ok(()) +}