Address System
Every Dubhe DApp stores data indexed by a resource address — a string key that uniquely identifies the owner of a storage slot. The address_system module is responsible for deriving this key from the transaction context.
ensure_origin
use dubhe::address_system;
let sender: String = address_system::ensure_origin(ctx);ensure_origin reads ctx and returns the caller’s address as a lowercase hex string (without the 0x prefix).
For native Sui transactions this is always a 64-character hex string derived from the 32-byte Sui address:
ctx.sender() = 0x1462cab50fe5998f8161378e5265f7920bfd9fbce604d602619962f608837217
ensure_origin → "1462cab50fe5998f8161378e5265f7920bfd9fbce604d602619962f608837217"The resource_address convention
Rule: Every DApp system function that writes user data must derive
resource_addressfromaddress_system::ensure_origin(ctx). Do not accept the address as a free caller-supplied argument.
Violating this rule means any caller can supply an arbitrary string and overwrite another user’s storage slot.
The counter template follows this convention correctly:
module counter::counter_system {
use dubhe::dapp_service::DappHub;
use dubhe::address_system;
use counter::value;
public entry fun inc(dapp_hub: &mut DappHub, number: u32, ctx: &mut TxContext) {
// Derive the caller's namespace key — never accept this as a parameter.
let sender = address_system::ensure_origin(ctx);
if (value::has(dapp_hub, sender)) {
value::set(dapp_hub, sender, value::get(dapp_hub, sender) + number, ctx);
} else {
value::set(dapp_hub, sender, number, ctx);
}
}
}All code generated by dubhe schemagen follows this convention. When writing hand-coded system functions, apply the same pattern.
Address conversion utilities
The module also provides helpers for converting between chain address formats:
use dubhe::address_system;
use std::ascii::string;
// Convert an EVM hex address to its mapped Sui address
let sui_addr: address = address_system::evm_to_sui(
string(b"0x9168765ee952de7c6f8fc6fad5ec209b960b7622")
);
// Convert a Solana Base58 address to its mapped Sui address
let sui_addr: address = address_system::solana_to_sui(
string(b"3vy8k1NAc3Q9EPvqrAuS4DG4qwbgVqfxznEdtcrL743L")
);Both functions abort if the input is malformed (wrong length or invalid encoding).
Multi-chain support
ensure_origin is designed to support transactions relayed from EVM and Solana chains via Dubhe Channel (the Dubhe bridge relay service, currently under development). When a cross-chain transaction is received, ensure_origin automatically returns the original EVM or Solana address string instead of the Sui address, keeping user namespace isolation intact across chains.
For native Sui DApps this is transparent — ensure_origin behaves exactly like a typed ctx.sender().
Testing
Use the built-in test helpers to simulate different chain contexts in unit tests:
#[test]
public fun test_inc_records_by_sender() {
let sender = @0x1462cab50fe5998f8161378e5265f7920bfd9fbce604d602619962f608837217;
let mut scenario = test_scenario::begin(sender);
let ctx = test_scenario::ctx(&mut scenario);
let origin = address_system::ensure_origin(ctx);
assert!(origin == string(b"1462cab50fe5998f8161378e5265f7920bfd9fbce604d602619962f608837217"));
scenario.end();
}
// Simulate an EVM relay context (requires Dubhe Channel for production use)
#[test]
public fun test_evm_context() {
let mut scenario = test_scenario::begin(@0x1);
address_system::setup_evm_scenario(
&mut scenario,
b"0x9168765EE952de7C6f8fC6FaD5Ec209B960b7622"
);
let ctx = test_scenario::ctx(&mut scenario);
// Returns the original 40-char EVM address (lowercase, no 0x)
let origin = address_system::ensure_origin(ctx);
assert!(origin == string(b"9168765ee952de7c6f8fc6fad5ec209b960b7622"));
scenario.end();
}The setup_evm_scenario and setup_solana_scenario helpers are #[test_only] and are never available in production builds.