Smart contracts require the ability to restrict access to and identify certain users or contracts. Unlike account-based blockchains, transactions in UTXO-based blockchains (i.e. Fuel) do not necessarily have a unique transaction sender. Additional logic is needed to handle this difference, and is provided by the standard library.
msg_sender
To deliver an experience akin to the EVM's access control, the std
library provides a msg_sender
function, which identifies a unique caller based upon the call and/or transaction input data.
contract;
abi MyOwnedContract {
fn receive(field_1: u64) -> bool;
}
const OWNER = Address::from(0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c);
impl MyOwnedContract for Contract {
fn receive(field_1: u64) -> bool {
let sender = msg_sender().unwrap();
if let Identity::Address(addr) = sender {
assert(addr == OWNER);
} else {
revert(0);
}
true
}
}
The msg_sender
function works as follows:
Ok(Sender)
is returned with the ContractId
sender variant. Ok(Sender)
is returned with the Address
sender variant. Err(AuthError)
is returned. Many contracts require some form of ownership for access control. To accomplish this, it is recommended that a storage variable of type Option<Identity>
is used to keep track of the owner. This allows setting and revoking ownership using the variants Some(..)
and None
respectively. This is better, safer, and more readable than using the Identity
type directly where revoking ownership has to be done using some magic value such as std::constants::ZERO_B256
or otherwise.
The following is an example of how to properly set ownership of a contract:
#[storage(write)]
fn set_owner(identity: Identity) {
storage.owner.write(Some(identity));
}
The following is an example of how to properly revoke ownership of a contract:
#[storage(write)]
fn revoke_ownership() {
storage.owner.write(None);
}