Host Communication
One of the things that your contract will probably do is to query the host for some information - what is the current time? Who called me? Following example shows how to do this:
use odra::prelude::*;
#[odra::module]
pub struct HostContract {
name: Var<String>,
created_at: Var<u64>,
created_by: Var<Address>
}
#[odra::module]
impl HostContract {
pub fn init(&mut self, name: String) {
self.name.set(name);
self.created_at.set(self.env().get_block_time());
self.created_by.set(self.env().caller())
}
pub fn name(&self) -> String {
self.name.get_or_default()
}
pub fn created_at(&self) -> u64 {
self.created_at.get().unwrap()
}
pub fn created_by(&self) -> Address {
self.created_by.get().unwrap()
}
}
The two new getters use get().unwrap() rather than the get_or_default() used for name.
get_or_default() requires the stored type to implement Default, and Address does not - so for a
Var<Address> you must use get() (and deal with the Option) or get_or_revert_with(...).
As you can see, we are using self.env(). It is an implementation of Module::env(), autogenerated
by #[odra::module] attribute. The function returns a reference to the ContractEnv (you can read more in
the Backend section). This is a structure that provides access to the host functions and variables.
In this example, we use two of them:
get_block_time()- returns the current block time as u64.caller()- returns an OdraAddressof the caller (this can be an external caller or another contract).
Call stack
caller() is the address right behind the contract: an account, or another contract that called it.
Sometimes that is not enough. A token with a dedicated burner contract wants to burn from the
account that called the burner, not from the burner itself. self.env().call_stack() returns
every address on the way, from the account that sent the transaction to the current contract, and
self.env().nth_caller(n) walks it: nth_caller(0) is caller(), nth_caller(1) is the caller
of the caller, and so on, None when the stack is not that deep.
#[odra::module]
impl CallStackProbe {
/// Returns the immediate caller, the caller of the caller (if any) and the call stack from
/// the initiating account to this contract.
pub fn inspect(&self) -> (Address, Option<Address>, Vec<Address>) {
let env = self.env();
(env.caller(), env.nth_caller(1), env.call_stack())
}
}
Called by an account directly, inspect answers (account, None, [account, probe]). Called
through another contract, it answers (relay, Some(account), [account, relay, probe]). The same on
OdraVM, CasperVM and livenet.
Trusting an address further up the stack has the usual tx.origin caveats: a user can be tricked
into calling a malicious contract that then calls yours. Check the immediate caller first, as the
burner example does, and only then look behind it.
Debug output
self.env().debug(message) prints a message on the host that runs the contract - the quickest way to
see what a contract does in a test:
self.env().debug(format!("transfer of {amount} from {from:?}"));
On OdraVM it always prints. For the Casper VM the contract has to be built with the test-support
feature of odra, which compiles the call into Casper's casper_print host function:
odra = { version = "2.10.0", features = ["test-support"] }
Run the tests with cargo odra test -b casper -- --nocapture to see the output. Without the feature
the call compiles to nothing (the message arguments are still evaluated). Build production wasm
without test-support: on a real network the messages would only end up in the node's log, at the
cost of gas for every call.
You will learn more functions that Odra exposes from host and types it uses in further articles.
What's next
In the next article, we'll dive into testing your contracts with Odra, so you can check that the code we presented in fact works!