Documentation Index
Fetch the complete documentation index at: https://docs.kontor.network/llms.txt
Use this file to discover all available pages before exploring further.
This example demonstrates proxying (a common pattern used in contract upgrades), utilizing the fallback hook and introducing the foreign::call built-in and aliased imports.
WIT Interface
The proxy contract exports functions for delegation and configuration:
package root:component;
world root {
include kontor:built-in/built-in;
use kontor:built-in/context.{fall-context, proc-context, view-context, signer};
use kontor:built-in/foreign.{contract-address};
export fallback: func(ctx: borrow<fall-context>, expr: string) -> string;
export init: func(ctx: borrow<proc-context>);
export get-contract-address: func(ctx: borrow<view-context>) -> option<contract-address>;
export set-contract-address: func(ctx: borrow<proc-context>, contract-address: contract-address);
}
Rust Implementation
The proxy contract stores a target contract address and forwards all calls to it using the fallback hook:
use stdlib::*;
contract!(name = "proxy");
#[derive(Clone, StorageRoot, Default)]
struct ProxyStorage {
contract_address: Option<ContractAddress>,
}
impl Guest for Proxy {
fn fallback(ctx: &FallContext, expr: String) -> String {
let ctx_ = &ctx.view_context();
if let Some(contract_address) = ctx_.model().contract_address() {
foreign::call(ctx.signer(), &contract_address, &expr)
} else {
"".to_string()
}
}
fn init(ctx: &ProcContext) {
ProxyStorage::default().init(ctx)
}
fn get_contract_address(ctx: &ViewContext) -> Option<ContractAddress> {
ctx.model().contract_address()
}
fn set_contract_address(ctx: &ProcContext, contract_address: ContractAddress) {
ctx.model().set_contract_address(Some(contract_address));
}
}
Key points:
- Storage accessed via
ctx.model()
fallback handles missing function calls by forwarding to the target contract
- Uses
if let Some(...) to safely handle the Option<ContractAddress>
FallContext can be converted to ViewContext for read-only access
foreign::call is the low-level API used by interface! and import! macros
Testing
The test demonstrates configuring the proxy and forwarding calls to a shared-account contract:
#[cfg(test)]
mod tests {
use testlib::*;
interface!(name = "proxy");
interface!(name = "token", path = "../../token/contract/wit");
interface!(name = "shared-account", path = "../../shared-account/contract/wit");
#[testlib::test(contracts_dir = "../../")]
async fn test_shared_account_contract() -> Result<()> {
let alice = runtime.identity().await?;
let bob = runtime.identity().await?;
let claire = runtime.identity().await?;
let dara = runtime.identity().await?;
let proxy = runtime.publish(&alice, "proxy").await?;
let token = runtime.publish(&alice, "token").await?;
let shared_account = runtime.publish(&alice, "shared-account").await?;
// Configure proxy to forward to shared-account
proxy::set_contract_address(runtime, &proxy, &alice, shared_account.clone()).await?;
let result = proxy::get_contract_address(runtime, &proxy).await?;
assert_eq!(result, Some(shared_account.clone()));
// Use token contract
token::mint(runtime, &token, &alice, 100.into()).await??;
// Call shared-account THROUGH the proxy using the fallback function
let account_id = shared_account::open(
runtime,
&proxy,
&alice,
token.clone(),
50.into(),
vec![&bob, &dara],
)
.await??;
let result = shared_account::balance(runtime, &proxy, &account_id).await?;
assert_eq!(result, Some(50.into()));
// Further deposits and withdrawals through proxy
shared_account::deposit(runtime, &proxy, &alice, token.clone(), &account_id, 25.into()).await??;
let result = shared_account::balance(runtime, &proxy, &account_id).await?;
assert_eq!(result, Some(75.into()));
// Test withdrawals
shared_account::withdraw(runtime, &proxy, &bob, token.clone(), &account_id, 25.into()).await??;
let result = shared_account::balance(runtime, &proxy, &account_id).await?;
assert_eq!(result, Some(50.into()));
// Test error handling
let result = shared_account::withdraw(runtime, &proxy, &claire, token.clone(), &account_id, 1.into()).await?;
assert_eq!(result, Err(Error::Message("unauthorized".to_string())));
Ok(())
}
}
Key points:
- Uses
#[testlib::test] with auto-injected runtime
- Publishes all three contracts (proxy, token, shared-account)
- Configures proxy to point to shared-account
- Calls shared-account functions through the proxy address
- The proxy’s
fallback function automatically forwards calls