Skip to content

Commit c02538a

Browse files
Add v25 contracttrait migration notes (#1696)
### What Add migration documentation for the `contracttrait` macro introduced in v23.4.0. The documentation covers the macro's purpose (defining reusable contract interfaces as Rust traits with default implementations), what it generates (Client, Args, Spec types), when to use it, and includes three examples: basic trait definition and implementation, overriding default implementations, and using the generated client. ### Why Document a feature that was released in v23.4.0 but sort of in stealth, not yet covered in migration notes, making it discoverable for developers upgrading to v25. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent f79c364 commit c02538a

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed

soroban-sdk/src/_migrating.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
//! Available via `CryptoHazmat` under the `hazmat-crypto` feature for advanced
1212
//! cryptographic use cases.
1313
//!
14+
//! 4. [`contracttrait` macro added for reusable contract interfaces][v25_contracttrait].
15+
//! Define traits with default implementations using `#[contracttrait]`, then implement them
16+
//! in contracts using `#[contractimpl(contracttrait)]`.
17+
//!
18+
//! [v25_contracttrait]: v25_contracttrait
19+
//!
1420
//! # Migrating from v22 to v23
1521
//!
1622
//! 1. [`contractevent` replaces `Events::publish`][v23_contractevent]
@@ -263,5 +269,6 @@
263269
pub mod v23_archived_testing;
264270
pub mod v23_contractevent;
265271
pub mod v25_bn254;
272+
pub mod v25_contracttrait;
266273
pub mod v25_event_testing;
267274
pub mod v25_poseidon;

soroban-sdk/src/_migrating/v25_bn254.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,8 @@
131131
//! // result will be true or false depending on the pairing equation
132132
//! # }
133133
//! ```
134+
//!
135+
//! [`Bn254G1Affine`]: crate::crypto::bn254::Bn254G1Affine
136+
//! [`Bn254G2Affine`]: crate::crypto::bn254::Bn254G2Affine
137+
//! [`Fr`]: crate::crypto::bn254::Fr
138+
//! [`Bn254Fp`]: crate::crypto::bn254::Bn254Fp
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
//! [`contracttrait`] macro for reusable contract interfaces.
2+
//!
3+
//! _Note: This feature was released in v23.4.0 but is being included in the migration notes for the
4+
//! next major version, v25._
5+
//!
6+
//! The [`contracttrait`] macro enables defining reusable contract interfaces as Rust traits with
7+
//! default implementations. Contracts can implement these traits, and the default implementations
8+
//! are automatically exported as contract functions.
9+
//!
10+
//! ## Generated Functionality
11+
//!
12+
//! When applied to a trait, [`contracttrait`] generates:
13+
//!
14+
//! - `{TraitName}Client` - A client for invoking the trait's functions on a contract
15+
//! - `{TraitName}Args` - An enum of function arguments for the trait's functions
16+
//! - `{TraitName}Spec` - The contract specification for the trait's functions
17+
//!
18+
//! These names can be customized using macro arguments (e.g., `client_name`, `args_name`,
19+
//! `spec_name`).
20+
//!
21+
//! ## When to Use
22+
//!
23+
//! Use [`contracttrait`] when you want the trait to represent a contract interface.
24+
//!
25+
//! The [`contracttrait`] will make it possible to:
26+
//! - Access a generated client for any contract implementing the interface.
27+
//! - Automatically export default implementations that contracts can optionally override
28+
//! - Share common functionality across contracts
29+
//!
30+
//! ## How It Works
31+
//!
32+
//! 1. **Define a trait** with [`contracttrait`]
33+
//!
34+
//! 2. **Implement the trait** with [`contractimpl`], including the `contracttrait` option:
35+
//! `#[contractimpl(contracttrait)]`
36+
//!
37+
//! 3. **Override default functions as needed** - Contracts can provide their own implementations of
38+
//! any function with default implementations.
39+
//!
40+
//! ## Patterns For Use
41+
//!
42+
//! Place traits that use [`contracttrait`] into a library crate, to share and make those traits
43+
//! available to other crates and developers.
44+
//!
45+
//! [`contracttrait`]: crate::contracttrait
46+
//! [`contractimpl`]: crate::contractimpl
47+
//!
48+
//! ## Example: Defining and Implementing a Trait
49+
//!
50+
//! ```
51+
//! use soroban_sdk::{contract, contractimpl, contracttrait, Env};
52+
//!
53+
//! // Define a trait with default implementations
54+
//! #[contracttrait]
55+
//! pub trait Pausable {
56+
//! fn is_paused(env: &Env) -> bool {
57+
//! env.storage().instance().has(&"paused")
58+
//! }
59+
//!
60+
//! fn pause(env: &Env) {
61+
//! env.storage().instance().set(&"paused", &true);
62+
//! }
63+
//!
64+
//! fn unpause(env: &Env) {
65+
//! env.storage().instance().remove(&"paused");
66+
//! }
67+
//! }
68+
//!
69+
//! #[contract]
70+
//! pub struct MyContract;
71+
//!
72+
//! // Implement the trait - default functions are automatically exported
73+
//! #[contractimpl(contracttrait)]
74+
//! impl Pausable for MyContract {}
75+
//!
76+
//! #[contractimpl]
77+
//! impl MyContract {
78+
//! pub fn do_something(env: &Env) {
79+
//! if Self::is_paused(env) {
80+
//! panic!("contract is paused");
81+
//! }
82+
//! // ... rest of the function
83+
//! }
84+
//! }
85+
//! # fn main() { }
86+
//! ```
87+
//!
88+
//! ## Example: Overriding Default Implementations
89+
//!
90+
//! Contracts can override specific functions while keeping the defaults for others:
91+
//!
92+
//! ```
93+
//! use soroban_sdk::{contract, contractimpl, contracttrait, Env};
94+
//!
95+
//! // Define a trait with default implementations
96+
//! #[contracttrait]
97+
//! pub trait Pausable {
98+
//! fn is_paused(env: &Env) -> bool {
99+
//! env.storage().instance().has(&"paused")
100+
//! }
101+
//!
102+
//! fn pause(env: &Env) {
103+
//! env.storage().instance().set(&"paused", &true);
104+
//! }
105+
//!
106+
//! fn unpause(env: &Env) {
107+
//! env.storage().instance().remove(&"paused");
108+
//! }
109+
//! }
110+
//!
111+
//! #[contract]
112+
//! pub struct MyContract;
113+
//!
114+
//! // Implement the trait - override default implementations as needed
115+
//! #[contractimpl(contracttrait)]
116+
//! impl Pausable for MyContract {
117+
//! // Override is_paused with custom logic that returns false when not set
118+
//! fn is_paused(env: &Env) -> bool {
119+
//! env.storage().instance().get(&"paused").unwrap_or(false)
120+
//! }
121+
//! // pause() and unpause() use the default implementations
122+
//! }
123+
//!
124+
//! #[contractimpl]
125+
//! impl MyContract {
126+
//! pub fn do_something(env: &Env) {
127+
//! if Self::is_paused(env) {
128+
//! panic!("contract is paused");
129+
//! }
130+
//! // ... rest of the function
131+
//! }
132+
//! }
133+
//! # fn main() { }
134+
//! ```
135+
//!
136+
//! ## Example: Using the Generated Client
137+
//!
138+
//! The generated `{TraitName}Client` can be used to call any contract that implements the trait:
139+
//!
140+
//! ```
141+
//! use soroban_sdk::{contract, contractimpl, contracttrait, Env};
142+
//!
143+
//! // Define a trait with default implementations
144+
//! #[contracttrait]
145+
//! pub trait Pausable {
146+
//! fn is_paused(env: &Env) -> bool {
147+
//! env.storage().instance().has(&"paused")
148+
//! }
149+
//!
150+
//! fn pause(env: &Env) {
151+
//! env.storage().instance().set(&"paused", &true);
152+
//! }
153+
//!
154+
//! fn unpause(env: &Env) {
155+
//! env.storage().instance().remove(&"paused");
156+
//! }
157+
//! }
158+
//!
159+
//! #[contract]
160+
//! pub struct MyContract;
161+
//!
162+
//! // Implement the trait - default functions are automatically exported
163+
//! #[contractimpl(contracttrait)]
164+
//! impl Pausable for MyContract {}
165+
//!
166+
//! #[contractimpl]
167+
//! impl MyContract {
168+
//! pub fn do_something(env: &Env) {
169+
//! if Self::is_paused(env) {
170+
//! panic!("contract is paused");
171+
//! }
172+
//! // ... rest of the function
173+
//! }
174+
//! }
175+
//!
176+
//! #[test]
177+
//! fn test() {
178+
//! # }
179+
//! # #[cfg(feature = "testutils")]
180+
//! # fn main() {
181+
//! let env = Env::default();
182+
//! let contract_id = env.register(MyContract, ());
183+
//! let client = PausableClient::new(&env, &contract_id);
184+
//!
185+
//! assert!(!client.is_paused());
186+
//! client.pause();
187+
//! assert!(client.is_paused());
188+
//! client.unpause();
189+
//! assert!(!client.is_paused());
190+
//! }
191+
//! # #[cfg(not(feature = "testutils"))]
192+
//! # fn main() { }
193+
//! ```

0 commit comments

Comments
 (0)