Smart contract state not updating after receive function finishes

Hey everyone please I need some help troubleshooting. I have a smart contract function entry point that looks like this

#[receive(
    contract = "first_game_room",
    name = "join_game_room",
    parameter = "JoinGameRoomParameter",
    error = "Error",
    return_value="Vec<AccountAddress>",
    mutable
)]
fn join_game_room(ctx: &ReceiveContext, host: &mut Host<State>) -> Result<Vec<AccountAddress>, Error> {
    // check if invoker is the smart contract owner, else reject
    // Get the contract owner
    let owner = ctx.owner();
    let sender = ctx.sender();
    ensure!(sender.matches_account(&owner), Error::Unauthorized);

    // check if game room alredy started,  throw error geam arleeady started
    let block_time = ctx.metadata().block_time(); // current time
    ensure!(block_time > host.state().start, Error::JoinBeforeStartError);

    // check if game room already ended, throw error game room already ended
    // ensure!(block_time <= host.state().end, Error::JoinAfterEndError);

    // check if participant already entered, throw error participnat already entered
    let param: JoinGameRoomParameter = ctx.parameter_cursor().get()?;

    host.state_mut().participants.insert(param.potential_participant);
    
    let mut participants_vec: Vec<AccountAddress> = Vec::new();
    for addr in host.state().participants.iter() {
        participants_vec.push(*addr);
    }
    Ok(participants_vec)
}

when I call this join_game_room entrypoint everything seem to work well and the participants state update seems successful as I’m able to get back a Vector that contains the address that just got added to the participants. The problem is that I have another view_participant entry point that looks like this

/// View function that returns the content of the state.
#[receive(contract = "first_game_room", name = "view_participants", return_value = "Vec<AccountAddress>")]
fn view_participants(_ctx: &ReceiveContext, host: &Host<State>) -> ReceiveResult<Vec<AccountAddress>> {
    let mut participants_vec: Vec<AccountAddress> = Vec::new();
    
    for addr in host.state().participants.iter() {
        participants_vec.push(*addr);
    }
    Ok(participants_vec)
}

when I call this view_participant entry point after a successful join_game_room invocation, the view_partipicipant function returns an empty vector, so it seems as if the participant state update only happened within the join_game_room function. Any idea what might be wrong?

Hi @jerry

I don’t see anything wrong with the code that you have just shared, could you share a bit more? Like the type of participants in the state and what you do when testing it.

@limemloh this how my state looks

#[derive(Serial, DeserialWithState, Debug)]
#[concordium(state_parameter = "S")]
pub struct State<S: HasStateApi = StateApi> {
    id: u32, // game room id
    start: Timestamp,
    end: Timestamp,
    prize: ContractTokenAmount,
    /// A mapping including all addresses that have joined this game room contract
    participants: StateSet<AccountAddress, S>,
    /// The cis2 token contract. Its tokens can be used to bid for items in this contract.
    cis2_contract: ContractAddress,
    winner: Option<AccountAddress>,
    token_id: ContractTokenId,
    entry_fee: TokenAmountU32,
}

participants a StateSet of AccountAddresses

when testing I call the join_game_room entry point with a potential_participant which seems to be successfully entered as a participant, as I get back a response that looks like this

- Return value:
      [
          "3zdLnv2NRjEEJ53PsUENXF8mhPFtgnD9TmfQMJGaHiQpeFcz3j"
      ]

but when I call my view_participants entrypoint it comes back empty

 Return value:
      []

when I call another entry point "process_payout" which takes an address (3zdLnv2NRjEEJ53PsUENXF8mhPFtgnD9TmfQMJGaHiQpeFcz3j) and checks if it can be found in host.state().participants
host.state().participants.contains(&params.potential_winner), this returns false too. therefore it appears as if the state update only happened inside join_game_room

So far, everything seems right.
My best guess is that it is related to how you test this.
Do you run the test manually on testnet? or are you testing using the smart contract testing library?

1 Like

I’m testing manually, will put up some test with the smart contract testing library and see.

The behavior makes sense, if you are only using invoke_instance, since this is only dry running the update and does not submit it as an update on chain.
You need to send an update transaction for the join_game_room entrypoint instead, if this is the case.

2 Likes

ooh my bad, thanks for this pointing this out @limemloh, problem solved