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);
}
}