#[contract]Expand description
Expose methods for cross-contract calls.
This macro may be applied to:
- a trait
- a trait implementation
- an inherent implementation
- a free function
Usage examples are in the top-level documentation.
§Interface vs implementation
The easiest mental model is:
- Put
#[contract]on a trait when you want to define a reusable interface. - Put
#[contract]on a trait impl when you want to export an implementation of that interface. - Put
#[contract]on an inherent impl when you want a self-contained contract with methods defined directly on the type. - Put
#[contract]on a free function when you only want an exported entrypoint and do not need a generated client.
§Client objects
Generated clients default to the interface name plus Client:
#[contract(interface = "IERC20")] trait TokengeneratesIERC20Client#[contract] trait FoogeneratesFooClient#[contract] impl Foo { ... }generatesFooClient
Use client = CustomClient only when you want to override that default.
For trait impls, the trait is authoritative for the client name. Adding an
explicit client = ... attribute to the impl is rejected to avoid
double-generation.
The macro reserves new, with_gas_limit, with_commit_locking,
with_read_only, and the try_ prefix for method/function identifiers on
exported contract surfaces. #[contract]
rejects trait methods, trait-impl methods, public inherent-impl methods, and
free #[contract] fn names that use them to avoid collisions with generated
client helpers and keep naming consistent across contract surfaces.
§Contract exports
The generated exports module is an internal implementation detail. The
exports = "custom_name" attribute only overrides that internal module name
when you need a disambiguating name during macro expansion; it is not a public
API surface. Free-function contracts still support exports = ..., but they do
not generate a client or <Interface>Entrypoints wrapper.
Generated entrypoint metadata lives on <Interface>Entrypoints:
- trait definitions generate the wrapper alongside the client
- bare impls generate the wrapper together with the exports module
For bare impls, the default interface name and domain separator are derived from the self type name.
§Events
Declare emitted event types with emits(MyEvent, OtherEvent) on a trait
definition or inherent impl. This retains their definitions in the contract
spec. For a trait impl, declare events on the trait instead. Runtime event
logging alone does not retain an event’s spec.
§Errors
If the macro includes throws = ErrorType, the implementation may pass values
of that type to throw. throws accepts a single type path:
#[contract(throws = errors::MyError)]or a list of type paths:
#[contract(throws(errors::AlphaError, errors::BetaError))]throws belongs to the interface:
- use it on a trait definition
- or on an inherent impl
- not on a trait impl
throws declares the interface’s error contract: it is published in the
contract spec and documents what callers should expect. It does not restrict
what a caller can catch — exceptions are identified on the wire by a selector
derived from the type, so a caller can match any exception type it knows
about, declared or not. Two types sharing a last path segment (foo::Error
and bar::Error) are fine.
Generated clients expose:
client.method(...)for the success-oriented pathclient.try_method(...) -> Result<_, CallError>for inspecting exceptions, matched withmatch_exn!
§Storage
If a method takes self or &mut self, the SDK reconstructs the contract state
from storage before calling the method.
If the receiver is &mut self, the updated state is written back only when the
method completes successfully. Exceptions and faults revert the frame.
The state type should derive Storage (or use
storage).
§Endpoint identifier customization
Each exported contract entrypoint is named with an identifier derived from:
- the interface name
- a version
- the method name
interface = "InterfaceName" overrides the interface-name portion.
For bare impls, omitting interface = "..." uses the self type name for this
metadata by default.
For trait impls, a plain #[contract] inherits the interface name, full
version, and entrypoint IDs from the trait, including across crates and through
trait aliases or re-exports. The implementing crate’s version does not affect
that interface’s identity. There is no need to repeat interface or version
on the impl.
version = "0.3.0" overrides the version on a trait definition, bare impl, or
free function; otherwise those declarations use their crate’s own version.
Entrypoint IDs use the version through its first nonzero component: 1.2.3
uses 1, 0.2.3 uses 0.2, and 0.0.3 uses 0.0.3. For 0.0.0, the
entire version string is used, including any prerelease or build suffix.
version is not allowed on trait impls, even if it matches the trait. Remove
the argument to inherit the trait’s version.
For compatibility, an explicit interface argument on a trait impl is accepted
as an assertion that the name matches the trait. A mismatch is rejected at
compile time; the trait remains authoritative.
Turn a trait, impl block, or free function into contract entrypoints,
generating exports, a typed client, and spec registration.
§State
Methods taking &self/&mut self load the contract’s state from
storage: the self type must be a storage struct (annotate it with
#[storage]; a unit struct like struct Token; works for contracts
that keep no state in the struct). Contracts with only associated
functions need no storage annotation.
§Method visibility (bare impl blocks)
In a bare (non-trait) impl block, pub methods become entrypoints
and private methods are skipped (use them for helpers). Restricted
visibility (pub(crate), pub(super), pub(in ...)) is a compile
error because the intent is ambiguous. Trait impls export every
trait method regardless of visibility.
§Arguments
version = "x.y.z", interface = "Name", exports = "mod_name",
client = ClientIdent, throws = Error / throws(E1, E2),
emits(Event1, Event2).
Trait implementations inherit the trait’s interface name, version, and
entrypoint IDs, including when the trait comes from another crate or is
imported under an alias. A plain #[contract] impl Trait for Contract
is sufficient. version is not allowed on trait impls. An explicit
interface argument is an optional assertion that the name matches the trait.
For trait definitions, bare impls, and free functions, the version still
defaults to the declaring crate’s CARGO_PKG_VERSION.
No throws(...) type may contain a thruput_sdk::signer::Signed: abort
frames carry no auth-token handles, so such a payload could only decode
to an uninitialized sentinel. Spec canonicalization rejects it when the
contract is built. See #[contract_exception].
§Declared method effects
On trait methods and public methods of bare impls, use
#[contract(effect = "pure")], #[contract(effect = "readOnly")], or
#[contract(effect = "stateChanging")]. Omission leaves the effect absent, never
read-only. Trait implementations inherit declarations from the trait;
annotating an implementation method is rejected.
Effects cover the whole invocation, including nested calls. pure uses
only arguments and constants, without state or execution-context reads.
readOnly permits those reads but no application effects. stateChanging
permits storage deltas, events, transfers and deployment; it need not
change state on every call. Event emission is an application effect.
These are author declarations. The macro checks syntax and duplicates, not method behavior; neither the compiler nor runtime enforces the declared effects. Metadata does not change entrypoint IDs, execution paths, authorization, gas accounting, or whether a call can fail.
§Limitations
Generic parameters — including explicit lifetime parameters — are not
supported on #[contract] items or their methods: generated clients,
entrypoint wrappers, and spec consts are non-generic, so a spliced
T or 'a cannot resolve there. Elided borrow lifetimes (&self,
&[u8]) are fine, and #[derive(Event)] types may carry lifetime
parameters (folded to 'static in the spec). Associated types are not
supported in #[contract] traits.
A struct must carry only one #[contract] impl block: depending on
configuration a second block either collides on generated items
(E0428 on names you never wrote) or compiles while silently merging
the entrypoint surface — neither is supported.
§Interface id
A #[contract] trait gains an associated
INTERFACE_ID: thruput_sdk::InterfaceId, derived from the effective
interface name and major version (the pair that also namespaces its
entrypoint IDs), and the generated <Iface>Entrypoints wrapper carries
the same constant. The name is reserved on contract traits. Every
#[contract] impl block also records its interface’s id in the
contract’s interface table (thruput_sdk::interface_table), which is what
thruput-erc165-if answers supports_interface from.
§Storage-domain collision detection
When a contract uses multiple storage structs, #[contract] generates a
lib test that verifies their domain strings are all distinct. This check
runs as a #[cfg(test)] lib test; a crate that sets [lib] test = false
in its Cargo.toml, or that only runs an external test harness without
compiling lib tests, will silently skip this collision check at build time.