Skip to content

Commit

Permalink
Test update_contract_wasm with rollbacks. (stellar#1195)
Browse files Browse the repository at this point in the history
### What

This makes sure the correct events are recorded in case if contract
fails after update, and also that contract isn't updated after
`try_call` fails.


### Why

Improving test coverage.

### Known limitations

N/A
  • Loading branch information
dmkozh authored Nov 11, 2023
1 parent 47c8824 commit 8674be3
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 6 deletions.
122 changes: 119 additions & 3 deletions soroban-env-host/src/test/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,32 +267,58 @@ fn test_contract_wasm_update() {

let contract_addr_obj = host.register_test_contract_wasm(UPDATEABLE_CONTRACT);

// Try updating with non-existing hash first.
let non_existent_hash = [0u8; 32];
let non_existent_contract_wasm = host.bytes_new_from_slice(&non_existent_hash).unwrap();
let non_existent_wasm_res = host.call(
contract_addr_obj,
Symbol::try_from_small_str("update").unwrap(),
host_vec![&host, &non_existent_contract_wasm].into(),
host_vec![&host, &non_existent_contract_wasm, &false].into(),
);
assert!(non_existent_wasm_res.is_err());
let non_existent_wasm_err = non_existent_wasm_res.err().unwrap().error;
assert!(non_existent_wasm_err.is_type(ScErrorType::Storage));
assert!(non_existent_wasm_err.is_code(ScErrorCode::MissingValue));
assert!(host.get_events().unwrap().0.is_empty());

let updated_wasm = ADD_I32;

let updated_wasm_hash_obj: Val = host
.invoke_function(HostFunction::UploadContractWasm(
updated_wasm.to_vec().try_into().unwrap(),
))
.unwrap()
.try_into_val(&host)
.unwrap();

// Now do a successful update, but fail the contract after that.
let failed_call_res = host.call(
contract_addr_obj,
Symbol::try_from_small_str("update").unwrap(),
host_vec![&host, &updated_wasm_hash_obj, &true].into(),
);
assert!(failed_call_res.is_err());
let failed_call_err = failed_call_res.err().unwrap().error;
assert!(failed_call_err.is_type(ScErrorType::WasmVm));
assert!(failed_call_err.is_code(ScErrorCode::InvalidAction));
// The update now has happened, but then got rolled back. Make sure
// that it got converted to a failed system event.
let failed_call_events = host.get_events().unwrap().0;
assert_eq!(failed_call_events.len(), 1);
match failed_call_events.last() {
Some(he) => {
assert!(he.failed_call);
assert_eq!(he.event.type_, ContractEventType::System);
}
_ => {
panic!("unexpected event");
}
}

let res: i32 = host
.call(
contract_addr_obj,
Symbol::try_from_small_str("update").unwrap(),
host_vec![&host, &updated_wasm_hash_obj].into(),
host_vec![&host, &updated_wasm_hash_obj, &false].into(),
)
.unwrap()
.try_into_val(&host)
Expand Down Expand Up @@ -330,8 +356,10 @@ fn test_contract_wasm_update() {
updated_wasm_hash_obj.try_into_val(&host).unwrap(),
)
.unwrap();
assert_eq!(events.len(), 2);
match events.last() {
Some(he) => {
assert!(!he.failed_call);
assert_eq!(
he.event,
ContractEvent {
Expand Down Expand Up @@ -382,6 +410,94 @@ fn test_contract_wasm_update() {
assert_eq!(updated_res, 30);
}

#[test]
fn test_contract_wasm_update_with_try_call() {
let host = Host::test_host_with_recording_footprint();
let contract_addr_obj = host.register_test_contract_wasm(UPDATEABLE_CONTRACT);
let updated_contract_addr_obj = host.register_test_contract_wasm(UPDATEABLE_CONTRACT);
let updated_wasm = ADD_I32;
let updated_wasm_hash_obj: Val = host
.invoke_function(HostFunction::UploadContractWasm(
updated_wasm.to_vec().try_into().unwrap(),
))
.unwrap()
.try_into_val(&host)
.unwrap();

// Run `update` that fails via external contract that does `try_call`.
// The overall call succeeds, but the internal contract stays unchanged.
let failed_call_res: Option<i32> = host
.call(
contract_addr_obj,
Symbol::try_from_small_str("try_upd").unwrap(),
host_vec![
&host,
&updated_contract_addr_obj,
&updated_wasm_hash_obj,
&true
]
.into(),
)
.unwrap()
.try_into_val(&host)
.unwrap();
assert_eq!(failed_call_res, None);

// Make sure failure event is recorded.
let failed_call_events = host.get_events().unwrap().0;
assert_eq!(failed_call_events.len(), 1);
match failed_call_events.last() {
Some(he) => {
assert!(he.failed_call);
assert_eq!(he.event.type_, ContractEventType::System);
}
_ => {
panic!("unexpected event");
}
}

let res: Option<i32> = host
.call(
contract_addr_obj,
Symbol::try_from_small_str("try_upd").unwrap(),
host_vec![
&host,
&updated_contract_addr_obj,
&updated_wasm_hash_obj,
&false
]
.into(),
)
.unwrap()
.try_into_val(&host)
.unwrap();
assert_eq!(res, Some(123));
let success_call_events = host.get_events().unwrap().0;
assert_eq!(success_call_events.len(), 2);
// Make sure event is recorded.
match success_call_events.last() {
Some(he) => {
assert!(!he.failed_call);
assert_eq!(he.event.type_, ContractEventType::System);
}
_ => {
panic!("unexpected event");
}
}

// Make sure internal contract has been updated.
let updated_res: i32 = host
.call(
updated_contract_addr_obj,
Symbol::try_from_small_str("add").unwrap(),
host_vec![&host, 10_i32, 20_i32].into(),
)
.unwrap()
.try_into_val(&host)
.unwrap();
assert_eq!(updated_res, 30);
}

#[test]
fn test_create_contract_from_source_account_recording_auth() {
let host = Host::test_host_with_recording_footprint();
Expand Down
Binary file not shown.
23 changes: 20 additions & 3 deletions soroban-test-wasms/wasm-workspace/update/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, BytesN, Env};

use soroban_sdk::{contract, contractimpl, symbol_short, Address, BytesN, Env, Error, IntoVal};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
pub fn update(env: Env, wasm_hash: BytesN<32>) -> i32 {
pub fn update(env: Env, wasm_hash: BytesN<32>, fail: bool) -> i32 {
// Modify instance storage to make sure that both instance storage and
// executable are updated.
env.storage().instance().set(&symbol_short!("foo"), &111);
env.deployer().update_current_contract_wasm(wasm_hash);
env.deployer().update_current_contract_wasm(wasm_hash);
if fail {
panic!();
}
123
}

pub fn try_upd(env: Env, contract: Address, wasm_hash: BytesN<32>, fail: bool) -> Option<i32> {
let res = env.try_invoke_contract::<i32, Error>(
&contract,
&symbol_short!("update"),
(wasm_hash, fail).into_val(&env),
);
if let Ok(v) = res {
Some(v.unwrap())
} else {
None
}
}
}

0 comments on commit 8674be3

Please sign in to comment.