Skip to content

Commit c8cd59e

Browse files
Add access control to Pausable trait examples (#1764)
### What `pause()` and `unpause()` default implementations in the v25 `contracttrait` migration guide now call `require_auth` on an admin address retrieved from instance storage. A `__constructor` is added to each example to initialize the admin address. Inline string literals for storage keys are replaced with named constants `ADMIN` and `PAUSED`. ### Why The previous examples exported `pause()` and `unpause()` as publicly callable contract functions without any authorization check. Developers following the pattern as written would ship contracts with open pause entrypoints. This ensures the examples start from a secure baseline. Closes #1763. Close #1763
1 parent 3423e93 commit c8cd59e

File tree

1 file changed

+154
-12
lines changed

1 file changed

+154
-12
lines changed

soroban-sdk/src/_migrating/v25_contracttrait.rs

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,40 +48,93 @@
4848
//! ## Example: Defining and Implementing a Trait
4949
//!
5050
//! ```
51-
//! use soroban_sdk::{contract, contractimpl, contracttrait, Env};
51+
//! use soroban_sdk::{contract, contractimpl, contracttrait, Address, Env};
5252
//!
53-
//! // Define a trait with default implementations
53+
//! // A regular trait for admin access control - not exported as contract functions
54+
//! pub trait RequireAuthForPause {
55+
//! fn require_auth_for_pause(env: &Env);
56+
//! }
57+
//!
58+
//! // Define a contracttrait with default implementations that require RequireAuthForPause
5459
//! #[contracttrait]
55-
//! pub trait Pausable {
60+
//! pub trait Pausable: RequireAuthForPause {
5661
//! fn is_paused(env: &Env) -> bool {
5762
//! env.storage().instance().has(&"paused")
5863
//! }
5964
//!
6065
//! fn pause(env: &Env) {
66+
//! Self::require_auth_for_pause(env);
6167
//! env.storage().instance().set(&"paused", &true);
6268
//! }
6369
//!
6470
//! fn unpause(env: &Env) {
71+
//! Self::require_auth_for_pause(env);
6572
//! env.storage().instance().remove(&"paused");
6673
//! }
6774
//! }
6875
//!
6976
//! #[contract]
7077
//! pub struct MyContract;
7178
//!
79+
//! impl RequireAuthForPause for MyContract {
80+
//! fn require_auth_for_pause(env: &Env) {
81+
//! let admin: Address = env.storage().instance().get(&"admin").unwrap();
82+
//! admin.require_auth();
83+
//! }
84+
//! }
85+
//!
7286
//! // Implement the trait - default functions are automatically exported
7387
//! #[contractimpl(contracttrait)]
7488
//! impl Pausable for MyContract {}
7589
//!
7690
//! #[contractimpl]
7791
//! impl MyContract {
92+
//! pub fn __constructor(env: &Env, admin: Address) {
93+
//! env.storage().instance().set(&"admin", &admin);
94+
//! }
95+
//!
7896
//! pub fn do_something(env: &Env) {
7997
//! if Self::is_paused(env) {
8098
//! panic!("contract is paused");
8199
//! }
82100
//! // ... rest of the function
83101
//! }
84102
//! }
103+
//!
104+
//! #[test]
105+
//! fn test() {
106+
//! # }
107+
//! # #[cfg(feature = "testutils")]
108+
//! # fn main() {
109+
//! use soroban_sdk::{testutils::{Address as _, MockAuth, MockAuthInvoke}, IntoVal};
110+
//! let env = Env::default();
111+
//! let admin = Address::generate(&env);
112+
//! let contract_id = env.register(MyContract, (&admin,));
113+
//! let client = PausableClient::new(&env, &contract_id);
114+
//!
115+
//! assert!(!client.is_paused());
116+
//! client.mock_auths(&[MockAuth {
117+
//! address: &admin,
118+
//! invoke: &MockAuthInvoke {
119+
//! contract: &contract_id,
120+
//! fn_name: "pause",
121+
//! args: ().into_val(&env),
122+
//! sub_invokes: &[],
123+
//! },
124+
//! }]).pause();
125+
//! assert!(client.is_paused());
126+
//! client.mock_auths(&[MockAuth {
127+
//! address: &admin,
128+
//! invoke: &MockAuthInvoke {
129+
//! contract: &contract_id,
130+
//! fn_name: "unpause",
131+
//! args: ().into_val(&env),
132+
//! sub_invokes: &[],
133+
//! },
134+
//! }]).unpause();
135+
//! assert!(!client.is_paused());
136+
//! }
137+
//! # #[cfg(not(feature = "testutils"))]
85138
//! # fn main() { }
86139
//! ```
87140
//!
@@ -90,27 +143,41 @@
90143
//! Contracts can override specific functions while keeping the defaults for others:
91144
//!
92145
//! ```
93-
//! use soroban_sdk::{contract, contractimpl, contracttrait, Env};
146+
//! use soroban_sdk::{contract, contractimpl, contracttrait, Address, Env};
147+
//!
148+
//! // A regular trait for admin access control - not exported as contract functions
149+
//! pub trait RequireAuthForPause {
150+
//! fn require_auth_for_pause(env: &Env);
151+
//! }
94152
//!
95-
//! // Define a trait with default implementations
153+
//! // Define a contracttrait with default implementations that require RequireAuthForPause
96154
//! #[contracttrait]
97-
//! pub trait Pausable {
155+
//! pub trait Pausable: RequireAuthForPause {
98156
//! fn is_paused(env: &Env) -> bool {
99157
//! env.storage().instance().has(&"paused")
100158
//! }
101159
//!
102160
//! fn pause(env: &Env) {
161+
//! Self::require_auth_for_pause(env);
103162
//! env.storage().instance().set(&"paused", &true);
104163
//! }
105164
//!
106165
//! fn unpause(env: &Env) {
166+
//! Self::require_auth_for_pause(env);
107167
//! env.storage().instance().remove(&"paused");
108168
//! }
109169
//! }
110170
//!
111171
//! #[contract]
112172
//! pub struct MyContract;
113173
//!
174+
//! impl RequireAuthForPause for MyContract {
175+
//! fn require_auth_for_pause(env: &Env) {
176+
//! let admin: Address = env.storage().instance().get(&"admin").unwrap();
177+
//! admin.require_auth();
178+
//! }
179+
//! }
180+
//!
114181
//! // Implement the trait - override default implementations as needed
115182
//! #[contractimpl(contracttrait)]
116183
//! impl Pausable for MyContract {
@@ -123,13 +190,52 @@
123190
//!
124191
//! #[contractimpl]
125192
//! impl MyContract {
193+
//! pub fn __constructor(env: &Env, admin: Address) {
194+
//! env.storage().instance().set(&"admin", &admin);
195+
//! }
196+
//!
126197
//! pub fn do_something(env: &Env) {
127198
//! if Self::is_paused(env) {
128199
//! panic!("contract is paused");
129200
//! }
130201
//! // ... rest of the function
131202
//! }
132203
//! }
204+
//!
205+
//! #[test]
206+
//! fn test() {
207+
//! # }
208+
//! # #[cfg(feature = "testutils")]
209+
//! # fn main() {
210+
//! use soroban_sdk::{testutils::{Address as _, MockAuth, MockAuthInvoke}, IntoVal};
211+
//! let env = Env::default();
212+
//! let admin = Address::generate(&env);
213+
//! let contract_id = env.register(MyContract, (&admin,));
214+
//! let client = PausableClient::new(&env, &contract_id);
215+
//!
216+
//! assert!(!client.is_paused());
217+
//! client.mock_auths(&[MockAuth {
218+
//! address: &admin,
219+
//! invoke: &MockAuthInvoke {
220+
//! contract: &contract_id,
221+
//! fn_name: "pause",
222+
//! args: ().into_val(&env),
223+
//! sub_invokes: &[],
224+
//! },
225+
//! }]).pause();
226+
//! assert!(client.is_paused());
227+
//! client.mock_auths(&[MockAuth {
228+
//! address: &admin,
229+
//! invoke: &MockAuthInvoke {
230+
//! contract: &contract_id,
231+
//! fn_name: "unpause",
232+
//! args: ().into_val(&env),
233+
//! sub_invokes: &[],
234+
//! },
235+
//! }]).unpause();
236+
//! assert!(!client.is_paused());
237+
//! }
238+
//! # #[cfg(not(feature = "testutils"))]
133239
//! # fn main() { }
134240
//! ```
135241
//!
@@ -138,33 +244,51 @@
138244
//! The generated `{TraitName}Client` can be used to call any contract that implements the trait:
139245
//!
140246
//! ```
141-
//! use soroban_sdk::{contract, contractimpl, contracttrait, Env};
247+
//! use soroban_sdk::{contract, contractimpl, contracttrait, Address, Env};
248+
//!
249+
//! // A regular trait for admin access control - not exported as contract functions
250+
//! pub trait RequireAuthForPause {
251+
//! fn require_auth_for_pause(env: &Env);
252+
//! }
142253
//!
143-
//! // Define a trait with default implementations
254+
//! // Define a contracttrait with default implementations that require RequireAuthForPause
144255
//! #[contracttrait]
145-
//! pub trait Pausable {
256+
//! pub trait Pausable: RequireAuthForPause {
146257
//! fn is_paused(env: &Env) -> bool {
147258
//! env.storage().instance().has(&"paused")
148259
//! }
149260
//!
150261
//! fn pause(env: &Env) {
262+
//! Self::require_auth_for_pause(env);
151263
//! env.storage().instance().set(&"paused", &true);
152264
//! }
153265
//!
154266
//! fn unpause(env: &Env) {
267+
//! Self::require_auth_for_pause(env);
155268
//! env.storage().instance().remove(&"paused");
156269
//! }
157270
//! }
158271
//!
159272
//! #[contract]
160273
//! pub struct MyContract;
161274
//!
275+
//! impl RequireAuthForPause for MyContract {
276+
//! fn require_auth_for_pause(env: &Env) {
277+
//! let admin: Address = env.storage().instance().get(&"admin").unwrap();
278+
//! admin.require_auth();
279+
//! }
280+
//! }
281+
//!
162282
//! // Implement the trait - default functions are automatically exported
163283
//! #[contractimpl(contracttrait)]
164284
//! impl Pausable for MyContract {}
165285
//!
166286
//! #[contractimpl]
167287
//! impl MyContract {
288+
//! pub fn __constructor(env: &Env, admin: Address) {
289+
//! env.storage().instance().set(&"admin", &admin);
290+
//! }
291+
//!
168292
//! pub fn do_something(env: &Env) {
169293
//! if Self::is_paused(env) {
170294
//! panic!("contract is paused");
@@ -178,14 +302,32 @@
178302
//! # }
179303
//! # #[cfg(feature = "testutils")]
180304
//! # fn main() {
305+
//! use soroban_sdk::{testutils::{Address as _, MockAuth, MockAuthInvoke}, IntoVal};
181306
//! let env = Env::default();
182-
//! let contract_id = env.register(MyContract, ());
307+
//! let admin = Address::generate(&env);
308+
//! let contract_id = env.register(MyContract, (&admin,));
183309
//! let client = PausableClient::new(&env, &contract_id);
184310
//!
185311
//! assert!(!client.is_paused());
186-
//! client.pause();
312+
//! client.mock_auths(&[MockAuth {
313+
//! address: &admin,
314+
//! invoke: &MockAuthInvoke {
315+
//! contract: &contract_id,
316+
//! fn_name: "pause",
317+
//! args: ().into_val(&env),
318+
//! sub_invokes: &[],
319+
//! },
320+
//! }]).pause();
187321
//! assert!(client.is_paused());
188-
//! client.unpause();
322+
//! client.mock_auths(&[MockAuth {
323+
//! address: &admin,
324+
//! invoke: &MockAuthInvoke {
325+
//! contract: &contract_id,
326+
//! fn_name: "unpause",
327+
//! args: ().into_val(&env),
328+
//! sub_invokes: &[],
329+
//! },
330+
//! }]).unpause();
189331
//! assert!(!client.is_paused());
190332
//! }
191333
//! # #[cfg(not(feature = "testutils"))]

0 commit comments

Comments
 (0)