Dummy Context

This example illustrates how to create a TxContext for object testing without using the Test Scenario framework.

/// This module contains an example of a simple application with a simple set of
/// rules: a letter can only be read once, and then it is burned.
///
/// The tests in this module demonstrate how to use the `tx_context` module to
/// create a custom `TxContext` for testing purposes without using a heavier
/// `test_scenario` module.
///
/// The UIDs of objects generated with dummy context will remain the same in
/// every test, so the consistency of the tests is guaranteed. However, for a
/// single test, there should be only one `tx_context::dummy()` call, otherwise
/// it is possible to create objects with the same UID in the same test.
module examples::burn_after_reading {
    use std::string::String;

    /// Attempt to burn before reading.
    const ENotReadYet: u64 = 0;

    /// A simple letter Object which can be read and then burned.
    public struct Letter has key, store {
        id: UID,
        content: String,
        is_read: bool,
        sender: address,
    }

    /// Write a new letter, signed by the sender.
    public fun new(content: String, ctx: &mut TxContext): Letter {
        Letter {
            id: object::new(ctx),
            content,
            is_read: false,
            sender: ctx.sender()
        }
    }

    /// Read the letter and get the contents.
    public fun read(l: &mut Letter): String {
        l.is_read = true;
        l.content
    }

    /// Burn once read. Only sender address can be recovered from the ashes, the
    /// letter is gone forever.
    public fun burn(l: Letter): address {
        let Letter { id, content: _, is_read, sender } = l;
        assert!(is_read, ENotReadYet);
        id.delete();
        sender
    }

    #[test]
    // This test uses the dummy context to create and read a letter. By using
    // the `tx_context::dummy()` function, we can create a dummy context which
    // has the `sender = 0x0` and `epoch = 0`. This allows us to create objects
    // without using a more complex test scenario.
    fun test_new_read_letter() {

        // This is where the magic happens! Because context has `drop`, we can
        // assign it directly as `&mut` reference and use in the test.
        let ctx = &mut tx_context::dummy();
        let mut letter = new(b"Directed by Ethan and Joel Coen".to_string(), ctx);

        // burn after reading
        read(&mut letter);
        burn(letter);
    }

    #[test]
    // It is also possible to create custom contexts for testing. Especially if
    // there's a need to test the behavior of the contract based on the sender
    // or the epoch.
    //
    // The `tx_context::new_from_hint` function can create a context with tx
    // hash generated from a number. Alternatively, you can use `tx_context::new`
    // and provide 32-byte hash manually.
    fun test_fan_letter() {

        // this is the best way to create a custom `TxContext`
        let ctx = &mut tx_context::new_from_hint(
            @0xC4AD,  // sender
            0u64,     // hint, used to generate tx hash
            1,        // epoch
            0,        // epoch_timestamp_ms
            0,        // `ids_created` (normally should be `0`)
        );

        let mut letter = new(b"Uhhh... Osbourne?... Osbourne Cox?".to_string(), ctx);

        // read, burn, but keep the sender
        read(&mut letter);
        let sender = burn(letter);

        // check the sender is correct
        assert!(sender == @0xC4AD, 0);
    }

    #[test, expected_failure(abort_code = ENotReadYet)]
    // This test makes sure the letter cannot be burned before reading. And
    // expects the transaction to fail with `ENotReadYet` abort code.
    //
    // It uses the `tx_context::new` method with a custom tx hash.
    fun test_burn_before_read() {
        use sui::hash::blake2b256;

        let tx_hash = blake2b256(&b"some seed for hashing");
        let ctx = &mut tx_context::new(
            @0x0,    // sender
            tx_hash, // tx hash
            0,       // epoch
            0,       // epoch_timestamp_ms
            0,       // `ids_created` (normally should be `0`)
        );

        let letter = new(b"What did we learn, Palmer?".to_string(), ctx);
        burn(letter);
    }
}