DEV Community

yuzurush
yuzurush

Posted on • Updated on

Soroban Contracts 101 : Events

Hi there! Welcome to my fourth post of my series called "Soroban Contracts 101", where I'll be explaining the basics of Soroban contracts, such as data storage, authentication, custom types, and more. All the code that we're gonna explain throughout this series will mostly come from soroban-contracts-101 github repository.

In this fourth post of the series, I'll be covering soroban contract events funcionalities.So events allow a contract to notify the outside world that some important state change occurred, and pass along details about what changed. This allows building more complex decentralized systems that react to on-chain events, and also provides transparency into what a contract is doing.

The Contract Code

const COUNTER: Symbol = symbol!("COUNTER");

pub struct IncrementContract;

#[contractimpl]
impl IncrementContract {
    pub fn increment(env: Env) -> u32 {
        let mut count: u32 = env
            .storage()
            .get(COUNTER)
            .unwrap_or(Ok(0)) // If no value set, assume 0.
            .unwrap(); // Panic if the value of COUNTER is not u32.
        count += 1;
        env.storage().set(COUNTER, count);
        env.events().publish((COUNTER, symbol!("increment")), count);
        count
    }
}
Enter fullscreen mode Exit fullscreen mode

This code is evolved version of the Storing Data Contract from the second post, it now :

  • Gets the current count from storage (or defaults to 0)
  • Increments it
  • Stores it back to storage
  • Publishes an increment event containing the count
  • Returns the new count

Line of code that published the event :

env.events().publish((COUNTER, symbol!("increment")), count);
Enter fullscreen mode Exit fullscreen mode
  • Calls env.events().publish() - this is how you publish an event from a contract
  • Passes in a tuple of (COUNTER, symbol!("increment")) - this is the event type/category. We're using our COUNTER symbol and an increment symbol to categorize this event.
  • Passes in count - this is the event data. In this case we're passing through the current count value.

So altogether, this line is publishing an "increment" event under the COUNTER category, and including the current count value as the event data.
This allows system watching for events from this contract to detect that an increment occurred, and access the current count value.

The Test Code

#[test]
fn test() {
    let env = Env::default();
    let contract_id = env.register_contract(None, IncrementContract);
    let client = IncrementContractClient::new(&env, &contract_id);

    assert_eq!(client.increment(), 1);
    assert_eq!(client.increment(), 2);
    assert_eq!(client.increment(), 3);

    assert_eq!(
        env.events().all(),
        vec![
            &env,
            (
                contract_id.clone(),
                (symbol!("COUNTER"), symbol!("increment")).into_val(&env),
                1u32.into_val(&env)
            ),
            (
                contract_id.clone(),
                (symbol!("COUNTER"), symbol!("increment")).into_val(&env),
                2u32.into_val(&env)
            ),
            (
                contract_id.clone(),
                (symbol!("COUNTER"), symbol!("increment")).into_val(&env),
                3u32.into_val(&env)
            ),
        ]
    );
}
Enter fullscreen mode Exit fullscreen mode

This code is a unit test for our contract. It does the following:

  • Creates an Env and registers the contract like before
  • Calls increment three times
  • Then checks that three increment events were published by the contract, with the expected data (contract ID, event category, and increment count)

So this test is verifying that the contract is correctly publishing an event on each increment, with the right data.
This demonstrates how you can test event publishing in a unit test - by checking the list of published events at the end.

Running Contract Tests

To ensure that the contract functions as intended, you can run the contract tests using the following command:

cargo test 
Enter fullscreen mode Exit fullscreen mode

If the tests are successful, you should see an output similar to:

running 1 test
test test::test ... ok
Enter fullscreen mode Exit fullscreen mode

Building The Contract

To build the contract, use the following command:

cargo build --target wasm32-unknown-unknown --release
Enter fullscreen mode Exit fullscreen mode

This should output a .wasm file in the ../target directory:

../target/wasm32-unknown-unknown/release/soroban_events_contract.wasm
Enter fullscreen mode Exit fullscreen mode

Invoking The Contract

To invoke the increment function of the contract, use the following command with Soroban-CLI:

soroban contract invoke \
    --wasm ../target/wasm32-unknown-unknown/release/soroban_events_contract.wasm \
    --id 1 \
    -- \
    increment
Enter fullscreen mode Exit fullscreen mode

You should see the following output:

1
#0: event: {"ext":"v0","contractId":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],"type":"contract","body":{"v0":{"topics":[{"symbol":[67,79,85,78,84,69,82]},{"symbol":[105,110,99,114,101,109,101,110,116]}],"data":{"u32":1}}}}
Enter fullscreen mode Exit fullscreen mode

That output is the event that our contract emitted being logged. It contains:

  • The contract ID (all 0s here since this is a test)
  • The event category - our COUNTER and increment symbols
  • The event data - the count value (1)

So this is just showing that our test correctly emitted the expected increment event with a count of 1. In a real deployment, you would see the actual contract ID and increment values here.

Conclusion

We explored an increment contract that emits an event on each increment. This has a few key benefits:

  • It allows other systems to be notified when something happens on-chain (an increment in this case)
  • It enables reactive systems that can respond to on-chain events
  • It provides a transparent log of key operations a contract performs

So events are a useful tool in blockchain development to enable complex, reactive systems and provide transparency. Our simple increment contract shows how easy it is to include event publishing in a Soroban contract.

We hope this article has given you an idea of how Soroban events works. Stay tuned for more post in this "Soroban Contracts 101" Series where we will dive deeper into Soroban Contracts and their functionalities.

Top comments (1)

Collapse
 
fyodorio profile image
Fyodor

Cool! And how and where can we actually listen to the events? (subscribe to them?) Other contracts or clients or both?