@layerzerolabs/protocol-stellar-v2 0.2.40 → 0.2.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +226 -313
- package/.turbo/turbo-lint.log +98 -227
- package/.turbo/turbo-test.log +1803 -1954
- package/contracts/common-macros/src/lib.rs +38 -15
- package/contracts/common-macros/src/lz_contract.rs +12 -21
- package/contracts/common-macros/src/tests/lz_contract.rs +17 -8
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__lz_contract__snapshot_generated_lz_contract_code.snap +20 -0
- package/contracts/common-macros/src/upgradeable.rs +37 -30
- package/contracts/endpoint-v2/src/endpoint_v2.rs +4 -3
- package/contracts/endpoint-v2/src/errors.rs +2 -2
- package/contracts/endpoint-v2/src/messaging_channel.rs +11 -0
- package/contracts/endpoint-v2/src/messaging_composer.rs +1 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +12 -25
- package/contracts/endpoint-v2/src/tests/endpoint_v2/initializable.rs +4 -4
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verifiable.rs +50 -10
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verify.rs +6 -35
- package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +2 -2
- package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +50 -1
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +78 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/insert_and_drain_pending_nonces.rs +272 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/mod.rs +1 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +10 -5
- package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +30 -0
- package/contracts/macro-integration-tests/tests/runtime/oapp/mod.rs +22 -1
- package/contracts/macro-integration-tests/tests/runtime/oapp/oapp_core.rs +13 -11
- package/contracts/macro-integration-tests/tests/runtime/oapp/options_type3.rs +13 -10
- package/contracts/macro-integration-tests/tests/runtime/oapp/receiver.rs +15 -11
- package/contracts/macro-integration-tests/tests/runtime/oapp/sender.rs +3 -2
- package/contracts/macro-integration-tests/tests/runtime/ownable/two_step_transfer.rs +14 -12
- package/contracts/macro-integration-tests/tests/runtime/upgradeable/migrate_guard_and_state.rs +3 -9
- package/contracts/macro-integration-tests/tests/ui/lz_contract/fail/upgradeable_invalid_inner_option.stderr +24 -1
- package/contracts/macro-integration-tests/tests/ui/lz_contract/fail/upgradeable_missing_internal.stderr +3 -3
- package/contracts/macro-integration-tests/tests/ui/lz_contract/pass/upgradeable_rbac.rs +44 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_all.rs +3 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_single_trait.rs +3 -0
- package/contracts/macro-integration-tests/tests/ui/ownable/pass/basic.rs +1 -1
- package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/attr_args.stderr +1 -1
- package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_auth_trait.stderr +2 -2
- package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_upgradeable_internal.stderr +2 -2
- package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/rbac.rs +44 -0
- package/contracts/oapps/counter/integration_tests/utils.rs +5 -3
- package/contracts/oapps/counter/src/tests/mod.rs +16 -1
- package/contracts/oapps/counter/src/tests/test_counter.rs +5 -2
- package/contracts/oapps/oapp/src/oapp_core.rs +21 -7
- package/contracts/oapps/oapp/src/oapp_options_type3.rs +7 -5
- package/contracts/oapps/oapp/src/tests/mod.rs +21 -0
- package/contracts/oapps/oapp/src/tests/oapp_core.rs +12 -10
- package/contracts/oapps/oapp/src/tests/oapp_options_type3.rs +11 -7
- package/contracts/oapps/oapp/src/tests/oapp_receiver.rs +4 -2
- package/contracts/oapps/oapp/src/tests/oapp_sender.rs +3 -2
- package/contracts/oapps/oapp/src/tests/test_macros.rs +15 -0
- package/contracts/oapps/oapp-macros/src/generators.rs +6 -0
- package/contracts/oapps/oapp-macros/src/tests/snapshots/oapp_macros__tests__oapp__snapshot_generate_oapp.snap +15 -0
- package/contracts/oapps/oft/integration-tests/setup.rs +22 -4
- package/contracts/oapps/oft/integration-tests/utils.rs +94 -13
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +23 -10
- package/contracts/oapps/oft/src/extensions/pausable.rs +31 -10
- package/contracts/oapps/oft/src/extensions/rate_limiter.rs +9 -4
- package/contracts/oapps/oft/src/oft.rs +1 -2
- package/contracts/oapps/oft/src/tests/extensions/oft_fee.rs +39 -27
- package/contracts/oapps/oft/src/tests/extensions/pausable.rs +38 -24
- package/contracts/oapps/oft/src/tests/extensions/rate_limiter.rs +87 -69
- package/contracts/oapps/oft-core/integration-tests/setup.rs +27 -3
- package/contracts/oapps/oft-core/src/oft_core.rs +10 -5
- package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +20 -20
- package/contracts/oapps/oft-core/src/tests/test_utils.rs +31 -3
- package/contracts/upgrader/src/lib.rs +67 -30
- package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract3.wasm +0 -0
- package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract4.wasm +0 -0
- package/contracts/upgrader/src/tests/test_upgrader.rs +50 -4
- package/contracts/utils/src/ownable.rs +16 -5
- package/contracts/utils/src/tests/ownable.rs +39 -39
- package/contracts/utils/src/upgradeable.rs +60 -17
- package/docs/oapp-guide.md +4 -4
- package/package.json +3 -4
- package/sdk/.turbo/turbo-test.log +381 -366
- package/sdk/dist/generated/bml.d.ts +4 -4
- package/sdk/dist/generated/bml.js +6 -6
- package/sdk/dist/generated/counter.d.ts +158 -12
- package/sdk/dist/generated/counter.js +32 -12
- package/sdk/dist/generated/dvn.d.ts +4 -6
- package/sdk/dist/generated/dvn.js +8 -8
- package/sdk/dist/generated/dvn_fee_lib.d.ts +8 -10
- package/sdk/dist/generated/dvn_fee_lib.js +8 -8
- package/sdk/dist/generated/endpoint.d.ts +9 -9
- package/sdk/dist/generated/endpoint.js +9 -9
- package/sdk/dist/generated/executor.d.ts +9 -11
- package/sdk/dist/generated/executor.js +11 -11
- package/sdk/dist/generated/executor_fee_lib.d.ts +9 -11
- package/sdk/dist/generated/executor_fee_lib.js +11 -11
- package/sdk/dist/generated/executor_helper.d.ts +4 -4
- package/sdk/dist/generated/executor_helper.js +6 -6
- package/sdk/dist/generated/layerzero_view.d.ts +9 -11
- package/sdk/dist/generated/layerzero_view.js +11 -11
- package/sdk/dist/generated/oft.d.ts +194 -27
- package/sdk/dist/generated/oft.js +44 -22
- package/sdk/dist/generated/price_feed.d.ts +8 -10
- package/sdk/dist/generated/price_feed.js +8 -8
- package/sdk/dist/generated/sac_manager.d.ts +8 -8
- package/sdk/dist/generated/sac_manager.js +6 -6
- package/sdk/dist/generated/sml.d.ts +9 -9
- package/sdk/dist/generated/sml.js +9 -9
- package/sdk/dist/generated/treasury.d.ts +9 -9
- package/sdk/dist/generated/treasury.js +9 -9
- package/sdk/dist/generated/uln302.d.ts +9 -9
- package/sdk/dist/generated/uln302.js +9 -9
- package/sdk/dist/generated/upgrader.d.ts +25 -16
- package/sdk/dist/generated/upgrader.js +5 -5
- package/sdk/package.json +1 -1
- package/sdk/test/counter-sml.test.ts +20 -0
- package/sdk/test/counter-uln.test.ts +20 -0
- package/sdk/test/oft-sml.test.ts +22 -0
- package/sdk/test/upgrader.test.ts +1 -0
- package/turbo.json +1 -8
|
@@ -18,8 +18,10 @@ use soroban_sdk::{
|
|
|
18
18
|
contract, contractimpl, contracttype, log, symbol_short,
|
|
19
19
|
testutils::{Address as _, MockAuth, MockAuthInvoke},
|
|
20
20
|
token::{StellarAssetClient, TokenClient},
|
|
21
|
-
Address, Bytes, BytesN, Env, IntoVal,
|
|
21
|
+
Address, Bytes, BytesN, Env, IntoVal, Symbol,
|
|
22
22
|
};
|
|
23
|
+
use oapp::oapp_core::OAPP_ADMIN_ROLE;
|
|
24
|
+
use utils::rbac::grant_role_no_auth;
|
|
23
25
|
|
|
24
26
|
// ============================================================================
|
|
25
27
|
// Test OFT Contract
|
|
@@ -178,6 +180,11 @@ fn setup_chain<'a>(env: &Env) -> ChainSetup<'a> {
|
|
|
178
180
|
let delegate = owner.clone();
|
|
179
181
|
let shared_decimals: u32 = 6; // Default shared decimals
|
|
180
182
|
let oft_address = env.register(TestOFT, (&oft_token, &owner, &endpoint_address, &delegate, &shared_decimals));
|
|
183
|
+
|
|
184
|
+
// Grant OAPP_ADMIN_ROLE to owner so they can call set_peer, set_delegate, set_msg_inspector, etc.
|
|
185
|
+
env.as_contract(&oft_address, || {
|
|
186
|
+
grant_role_no_auth(env, &owner, &Symbol::new(env, OAPP_ADMIN_ROLE), &owner);
|
|
187
|
+
});
|
|
181
188
|
let composer_address = env.register(DummyComposer, (&endpoint_address,));
|
|
182
189
|
|
|
183
190
|
let endpoint = EndpointV2Client::new(env, &endpoint_address);
|
|
@@ -255,17 +262,34 @@ pub fn wire_oft(env: &Env, chains: &[&ChainSetup<'_>]) {
|
|
|
255
262
|
}
|
|
256
263
|
}
|
|
257
264
|
|
|
265
|
+
fn grant_oapp_admin(env: &Env, contract: &Address, owner: &Address) {
|
|
266
|
+
let role = soroban_sdk::Symbol::new(env, oapp::oapp_core::OAPP_ADMIN_ROLE);
|
|
267
|
+
env.mock_auths(&[MockAuth {
|
|
268
|
+
address: owner,
|
|
269
|
+
invoke: &MockAuthInvoke {
|
|
270
|
+
contract,
|
|
271
|
+
fn_name: "grant_role",
|
|
272
|
+
args: (owner, &role, owner).into_val(env),
|
|
273
|
+
sub_invokes: &[],
|
|
274
|
+
},
|
|
275
|
+
}]);
|
|
276
|
+
utils::rbac::RoleBasedAccessControlClient::new(env, contract).grant_role(owner, &role, owner);
|
|
277
|
+
}
|
|
278
|
+
|
|
258
279
|
pub fn set_peer(env: &Env, owner: &Address, oft: &OFTClient<'_>, dst_eid: u32, peer: &BytesN<32>) {
|
|
280
|
+
grant_oapp_admin(env, &oft.address, owner);
|
|
281
|
+
|
|
282
|
+
let peer_option = Some(peer.clone());
|
|
259
283
|
env.mock_auths(&[MockAuth {
|
|
260
284
|
address: owner,
|
|
261
285
|
invoke: &MockAuthInvoke {
|
|
262
286
|
contract: &oft.address,
|
|
263
287
|
fn_name: "set_peer",
|
|
264
|
-
args: (&dst_eid, &
|
|
288
|
+
args: (&dst_eid, &peer_option, owner).into_val(env),
|
|
265
289
|
sub_invokes: &[],
|
|
266
290
|
},
|
|
267
291
|
}]);
|
|
268
|
-
oapp::oapp_core::OAppCoreClient::new(env, &oft.address).set_peer(&dst_eid, &
|
|
292
|
+
oapp::oapp_core::OAppCoreClient::new(env, &oft.address).set_peer(&dst_eid, &peer_option, owner);
|
|
269
293
|
}
|
|
270
294
|
|
|
271
295
|
pub fn register_library(env: &Env, owner: &Address, endpoint: &EndpointV2Client<'_>, lib: &Address) {
|
|
@@ -55,10 +55,10 @@ use crate::{
|
|
|
55
55
|
types::{OFTFeeDetail, OFTLimit, OFTReceipt, SendParam, SEND, SEND_AND_CALL},
|
|
56
56
|
utils as oft_utils,
|
|
57
57
|
};
|
|
58
|
-
use common_macros::{contract_trait,
|
|
58
|
+
use common_macros::{contract_trait, only_role};
|
|
59
59
|
use endpoint_v2::{MessagingComposerClient, MessagingFee, MessagingReceipt};
|
|
60
60
|
use oapp::{
|
|
61
|
-
oapp_core::initialize_oapp,
|
|
61
|
+
oapp_core::{initialize_oapp, OAPP_ADMIN_ROLE},
|
|
62
62
|
oapp_options_type3::OAppOptionsType3,
|
|
63
63
|
oapp_receiver::OAppReceiver,
|
|
64
64
|
oapp_sender::{FeePayer, OAppSenderInternal},
|
|
@@ -448,12 +448,17 @@ pub trait OFTCore: OFTInternal {
|
|
|
448
448
|
/// Pass `None` to remove the inspector and disable outbound validation.
|
|
449
449
|
///
|
|
450
450
|
/// # Authorization
|
|
451
|
-
/// Requires
|
|
451
|
+
/// Requires the caller to have `OAPP_ADMIN_ROLE`.
|
|
452
452
|
///
|
|
453
453
|
/// # Arguments
|
|
454
454
|
/// * `inspector` - Address of the inspector contract, or `None` to remove it
|
|
455
|
-
|
|
456
|
-
|
|
455
|
+
/// * `operator` - The address that must have OAPP_ADMIN_ROLE
|
|
456
|
+
#[only_role(operator, OAPP_ADMIN_ROLE)]
|
|
457
|
+
fn set_msg_inspector(
|
|
458
|
+
env: &soroban_sdk::Env,
|
|
459
|
+
inspector: &Option<soroban_sdk::Address>,
|
|
460
|
+
operator: &soroban_sdk::Address,
|
|
461
|
+
) {
|
|
457
462
|
Self::__set_msg_inspector(env, inspector);
|
|
458
463
|
}
|
|
459
464
|
|
|
@@ -60,17 +60,17 @@ fn test_set_msg_inspector() {
|
|
|
60
60
|
// Deploy a passing inspector
|
|
61
61
|
let inspector_address = env.register(PassingInspector, ());
|
|
62
62
|
|
|
63
|
-
// Owner sets the inspector
|
|
63
|
+
// Owner (with OAPP_ADMIN_ROLE) sets the inspector
|
|
64
64
|
env.mock_auths(&[MockAuth {
|
|
65
65
|
address: &setup.owner,
|
|
66
66
|
invoke: &MockAuthInvoke {
|
|
67
67
|
contract: &setup.oft.address,
|
|
68
68
|
fn_name: "set_msg_inspector",
|
|
69
|
-
args: (&Some(inspector_address.clone()),).into_val(&env),
|
|
69
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
70
70
|
sub_invokes: &[],
|
|
71
71
|
},
|
|
72
72
|
}]);
|
|
73
|
-
setup.oft.set_msg_inspector(&Some(inspector_address.clone()));
|
|
73
|
+
setup.oft.set_msg_inspector(&Some(inspector_address.clone()), &setup.owner);
|
|
74
74
|
|
|
75
75
|
// Verify inspector is set
|
|
76
76
|
let stored_inspector = setup.oft.msg_inspector();
|
|
@@ -90,11 +90,11 @@ fn test_remove_msg_inspector() {
|
|
|
90
90
|
invoke: &MockAuthInvoke {
|
|
91
91
|
contract: &setup.oft.address,
|
|
92
92
|
fn_name: "set_msg_inspector",
|
|
93
|
-
args: (&Some(inspector_address.clone()),).into_val(&env),
|
|
93
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
94
94
|
sub_invokes: &[],
|
|
95
95
|
},
|
|
96
96
|
}]);
|
|
97
|
-
setup.oft.set_msg_inspector(&Some(inspector_address));
|
|
97
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
98
98
|
|
|
99
99
|
// Verify it's set
|
|
100
100
|
assert!(setup.oft.msg_inspector().is_some());
|
|
@@ -105,11 +105,11 @@ fn test_remove_msg_inspector() {
|
|
|
105
105
|
invoke: &MockAuthInvoke {
|
|
106
106
|
contract: &setup.oft.address,
|
|
107
107
|
fn_name: "set_msg_inspector",
|
|
108
|
-
args: (&None::<Address>,).into_val(&env),
|
|
108
|
+
args: (&None::<Address>, &setup.owner).into_val(&env),
|
|
109
109
|
sub_invokes: &[],
|
|
110
110
|
},
|
|
111
111
|
}]);
|
|
112
|
-
setup.oft.set_msg_inspector(&None);
|
|
112
|
+
setup.oft.set_msg_inspector(&None, &setup.owner);
|
|
113
113
|
|
|
114
114
|
// Verify inspector is removed
|
|
115
115
|
let stored_inspector = setup.oft.msg_inspector();
|
|
@@ -119,7 +119,7 @@ fn test_remove_msg_inspector() {
|
|
|
119
119
|
// ==================== Access Control Tests ====================
|
|
120
120
|
|
|
121
121
|
#[test]
|
|
122
|
-
#[should_panic(expected = "
|
|
122
|
+
#[should_panic(expected = "Error(Contract, #1086)")] // RbacError::Unauthorized
|
|
123
123
|
fn test_set_msg_inspector_requires_owner() {
|
|
124
124
|
let env = Env::default();
|
|
125
125
|
let setup = OFTTestSetup::new(&env);
|
|
@@ -127,20 +127,20 @@ fn test_set_msg_inspector_requires_owner() {
|
|
|
127
127
|
// Deploy a passing inspector
|
|
128
128
|
let inspector_address = env.register(PassingInspector, ());
|
|
129
129
|
|
|
130
|
-
// Non-owner tries to set the inspector
|
|
130
|
+
// Non-owner (without OAPP_ADMIN_ROLE) tries to set the inspector
|
|
131
131
|
let non_owner = Address::generate(&env);
|
|
132
132
|
env.mock_auths(&[MockAuth {
|
|
133
133
|
address: &non_owner,
|
|
134
134
|
invoke: &MockAuthInvoke {
|
|
135
135
|
contract: &setup.oft.address,
|
|
136
136
|
fn_name: "set_msg_inspector",
|
|
137
|
-
args: (&Some(inspector_address.clone()),).into_val(&env),
|
|
137
|
+
args: (&Some(inspector_address.clone()), &non_owner).into_val(&env),
|
|
138
138
|
sub_invokes: &[],
|
|
139
139
|
},
|
|
140
140
|
}]);
|
|
141
141
|
|
|
142
|
-
// This should panic because non_owner
|
|
143
|
-
setup.oft.set_msg_inspector(&Some(inspector_address));
|
|
142
|
+
// This should panic because non_owner does not have OAPP_ADMIN_ROLE
|
|
143
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &non_owner);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// ==================== Integration Tests with Send ====================
|
|
@@ -184,11 +184,11 @@ fn test_send_with_passing_inspector() {
|
|
|
184
184
|
invoke: &MockAuthInvoke {
|
|
185
185
|
contract: &setup.oft.address,
|
|
186
186
|
fn_name: "set_msg_inspector",
|
|
187
|
-
args: (&Some(inspector_address.clone()),).into_val(&env),
|
|
187
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
188
188
|
sub_invokes: &[],
|
|
189
189
|
},
|
|
190
190
|
}]);
|
|
191
|
-
setup.oft.set_msg_inspector(&Some(inspector_address));
|
|
191
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
192
192
|
|
|
193
193
|
let sender = Address::generate(&env);
|
|
194
194
|
|
|
@@ -224,11 +224,11 @@ fn test_send_with_failing_inspector() {
|
|
|
224
224
|
invoke: &MockAuthInvoke {
|
|
225
225
|
contract: &setup.oft.address,
|
|
226
226
|
fn_name: "set_msg_inspector",
|
|
227
|
-
args: (&Some(inspector_address.clone()),).into_val(&env),
|
|
227
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
228
228
|
sub_invokes: &[],
|
|
229
229
|
},
|
|
230
230
|
}]);
|
|
231
|
-
setup.oft.set_msg_inspector(&Some(inspector_address));
|
|
231
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
232
232
|
|
|
233
233
|
let sender = Address::generate(&env);
|
|
234
234
|
|
|
@@ -266,11 +266,11 @@ fn test_quote_send_with_passing_inspector() {
|
|
|
266
266
|
invoke: &MockAuthInvoke {
|
|
267
267
|
contract: &setup.oft.address,
|
|
268
268
|
fn_name: "set_msg_inspector",
|
|
269
|
-
args: (&Some(inspector_address.clone()),).into_val(&env),
|
|
269
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
270
270
|
sub_invokes: &[],
|
|
271
271
|
},
|
|
272
272
|
}]);
|
|
273
|
-
setup.oft.set_msg_inspector(&Some(inspector_address));
|
|
273
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
274
274
|
|
|
275
275
|
let sender = Address::generate(&env);
|
|
276
276
|
|
|
@@ -300,11 +300,11 @@ fn test_quote_send_with_failing_inspector() {
|
|
|
300
300
|
invoke: &MockAuthInvoke {
|
|
301
301
|
contract: &setup.oft.address,
|
|
302
302
|
fn_name: "set_msg_inspector",
|
|
303
|
-
args: (&Some(inspector_address.clone()),).into_val(&env),
|
|
303
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
304
304
|
sub_invokes: &[],
|
|
305
305
|
},
|
|
306
306
|
}]);
|
|
307
|
-
setup.oft.set_msg_inspector(&Some(inspector_address));
|
|
307
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
308
308
|
|
|
309
309
|
let sender = Address::generate(&env);
|
|
310
310
|
|
|
@@ -8,7 +8,7 @@ use crate::{
|
|
|
8
8
|
types::{OFTReceipt, SendParam},
|
|
9
9
|
};
|
|
10
10
|
use endpoint_v2::{LayerZeroReceiverClient, MessagingFee, MessagingParams, MessagingReceipt, Origin};
|
|
11
|
-
use oapp::oapp_core::OAppCoreClient;
|
|
11
|
+
use oapp::oapp_core::{OAppCoreClient, OAPP_ADMIN_ROLE};
|
|
12
12
|
use soroban_sdk::{
|
|
13
13
|
address_payload::AddressPayload,
|
|
14
14
|
bytes, contract, contractimpl, log, symbol_short,
|
|
@@ -16,6 +16,7 @@ use soroban_sdk::{
|
|
|
16
16
|
token::{StellarAssetClient, TokenClient},
|
|
17
17
|
Address, Bytes, BytesN, Env, IntoVal, String, Symbol,
|
|
18
18
|
};
|
|
19
|
+
use utils::rbac::grant_role_no_auth;
|
|
19
20
|
|
|
20
21
|
// ==================== Constants ====================
|
|
21
22
|
|
|
@@ -99,6 +100,20 @@ pub fn create_origin(src_eid: u32, sender: &BytesN<32>, nonce: u64) -> Origin {
|
|
|
99
100
|
Origin { src_eid, sender: sender.clone(), nonce }
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
fn grant_oapp_admin(env: &Env, contract: &Address, owner: &Address) {
|
|
104
|
+
let role = Symbol::new(env, oapp::oapp_core::OAPP_ADMIN_ROLE);
|
|
105
|
+
env.mock_auths(&[MockAuth {
|
|
106
|
+
address: owner,
|
|
107
|
+
invoke: &MockAuthInvoke {
|
|
108
|
+
contract,
|
|
109
|
+
fn_name: "grant_role",
|
|
110
|
+
args: (owner, &role, owner).into_val(env),
|
|
111
|
+
sub_invokes: &[],
|
|
112
|
+
},
|
|
113
|
+
}]);
|
|
114
|
+
utils::rbac::RoleBasedAccessControlClient::new(env, contract).grant_role(owner, &role, owner);
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
// ==================== Test OFT Contracts ====================
|
|
103
118
|
|
|
104
119
|
mod test_mint_burn_oft {
|
|
@@ -596,6 +611,16 @@ impl<'a> OFTTestSetupBuilder<'a> {
|
|
|
596
611
|
OFTTestSetup::mint_to(env, &owner, &native_token, &owner, INITIAL_MINT_AMOUNT);
|
|
597
612
|
OFTTestSetup::mint_to(env, &owner, &zro_token, &owner, INITIAL_MINT_AMOUNT);
|
|
598
613
|
|
|
614
|
+
// Grant OAPP_ADMIN_ROLE to owner so they can call set_peer, set_delegate, set_msg_inspector, etc.
|
|
615
|
+
env.as_contract(&oft_address, || {
|
|
616
|
+
grant_role_no_auth(
|
|
617
|
+
env,
|
|
618
|
+
&owner,
|
|
619
|
+
&Symbol::new(env, OAPP_ADMIN_ROLE),
|
|
620
|
+
&owner,
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
|
|
599
624
|
// Setup based on OFT type
|
|
600
625
|
match oft_type {
|
|
601
626
|
OFTType::MintBurn => {
|
|
@@ -649,16 +674,19 @@ impl<'a> OFTTestSetup<'a> {
|
|
|
649
674
|
}
|
|
650
675
|
|
|
651
676
|
pub fn set_peer(&self, eid: u32, peer: &BytesN<32>) {
|
|
677
|
+
grant_oapp_admin(self.env, &self.oft.address, &self.owner);
|
|
678
|
+
|
|
679
|
+
let peer_option = Some(peer.clone());
|
|
652
680
|
self.env.mock_auths(&[MockAuth {
|
|
653
681
|
address: &self.owner,
|
|
654
682
|
invoke: &MockAuthInvoke {
|
|
655
683
|
contract: &self.oft.address,
|
|
656
684
|
fn_name: "set_peer",
|
|
657
|
-
args: (&eid,
|
|
685
|
+
args: (&eid, &peer_option, &self.owner).into_val(self.env),
|
|
658
686
|
sub_invokes: &[],
|
|
659
687
|
},
|
|
660
688
|
}]);
|
|
661
|
-
OAppCoreClient::new(self.env, &self.oft.address).set_peer(&eid, &
|
|
689
|
+
OAppCoreClient::new(self.env, &self.oft.address).set_peer(&eid, &peer_option, &self.owner);
|
|
662
690
|
}
|
|
663
691
|
|
|
664
692
|
pub fn mint_to(env: &Env, owner: &Address, token: &Address, to: &Address, amount: i128) {
|
|
@@ -2,30 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
//! # Upgrader Contract
|
|
4
4
|
//!
|
|
5
|
-
//! A stateless utility contract for performing atomic upgrade
|
|
6
|
-
//! on contracts
|
|
5
|
+
//! A stateless utility contract for performing atomic upgrade-and-migrate operations
|
|
6
|
+
//! on contracts that implement [`Upgradeable`](utils::upgradeable::Upgradeable) (Auth-based)
|
|
7
|
+
//! or [`UpgradeableRbac`](utils::upgradeable::UpgradeableRbac) (RBAC-based).
|
|
7
8
|
//!
|
|
8
|
-
//! ## Security
|
|
9
|
+
//! ## Security model
|
|
9
10
|
//!
|
|
10
|
-
//! The Upgrader is permissionless
|
|
11
|
-
//!
|
|
12
|
-
//! ensures only its authorizer can
|
|
11
|
+
//! The Upgrader is permissionless: anyone may call it. Security is enforced by the target
|
|
12
|
+
//! contract’s authorization:
|
|
13
|
+
//! - **Auth-based**: the target’s `#[only_auth]` ensures only its authorizer can upgrade/migrate.
|
|
14
|
+
//! - **RBAC-based**: the target’s `#[only_role(operator, UPGRADER_ROLE)]` ensures only an
|
|
15
|
+
//! address with `UPGRADER_ROLE` can upgrade/migrate; that address must be passed as `operator`
|
|
16
|
+
//! and must have signed the transaction.
|
|
13
17
|
//!
|
|
14
18
|
//! ## Usage
|
|
15
19
|
//!
|
|
20
|
+
//! - For **Auth-based** targets, pass `operator: &None`. The transaction must be authorized
|
|
21
|
+
//! by the target contract’s authorizer.
|
|
22
|
+
//! - For **RBAC-based** targets, pass `operator: &Some(upgrader_address)`. The transaction
|
|
23
|
+
//! must be signed by that address, which must hold `UPGRADER_ROLE` on the target.
|
|
24
|
+
//!
|
|
16
25
|
//! ```ignore
|
|
17
26
|
//! let upgrader = UpgraderClient::new(&env, &upgrader_id);
|
|
18
27
|
//! let migration_data = my_data.to_xdr(&env);
|
|
19
|
-
//!
|
|
28
|
+
//! // Auth-based target:
|
|
29
|
+
//! upgrader.upgrade_and_migrate(&target_contract, &new_wasm_hash, &migration_data, &None);
|
|
30
|
+
//! // RBAC-based target:
|
|
31
|
+
//! upgrader.upgrade_and_migrate(&target_contract, &new_wasm_hash, &migration_data, &Some(operator));
|
|
20
32
|
//! ```
|
|
21
33
|
|
|
22
34
|
use soroban_sdk::{contract, contractimpl, xdr::ToXdr, Address, Bytes, BytesN, Env};
|
|
23
|
-
use utils::{
|
|
35
|
+
use utils::{
|
|
36
|
+
auth::AuthClient,
|
|
37
|
+
errors::AuthError,
|
|
38
|
+
option_ext::OptionExt,
|
|
39
|
+
upgradeable::{UpgradeableClient, UpgradeableRbacClient},
|
|
40
|
+
};
|
|
24
41
|
|
|
25
42
|
/// Upgrader contract for managing upgrades of other contracts.
|
|
26
43
|
///
|
|
27
|
-
///
|
|
28
|
-
///
|
|
44
|
+
/// Stateless utility: anyone may call it. Authorization is enforced by the target
|
|
45
|
+
/// contract (Auth or RBAC).
|
|
29
46
|
#[contract]
|
|
30
47
|
pub struct Upgrader;
|
|
31
48
|
|
|
@@ -33,39 +50,59 @@ pub struct Upgrader;
|
|
|
33
50
|
impl Upgrader {
|
|
34
51
|
/// Upgrades a target contract without custom migration data.
|
|
35
52
|
///
|
|
36
|
-
///
|
|
53
|
+
/// Convenience wrapper around [`upgrade_and_migrate`](Self::upgrade_and_migrate) that
|
|
54
|
+
/// passes empty migration data (XDR encoding of `()`). Use only when the target’s
|
|
55
|
+
/// `MigrationData` is `()` or it supports empty migration.
|
|
37
56
|
///
|
|
38
57
|
/// # Arguments
|
|
39
|
-
/// * `contract_address` -
|
|
40
|
-
/// * `wasm_hash` -
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
/// * `contract_address` - Address of the contract to upgrade.
|
|
59
|
+
/// * `wasm_hash` - Hash of the new WASM bytecode.
|
|
60
|
+
/// * `operator` - `None` for Auth-based targets; `Some(addr)` for RBAC-based targets
|
|
61
|
+
pub fn upgrade(env: &Env, contract_address: &Address, wasm_hash: &BytesN<32>, operator: &Option<Address>) {
|
|
62
|
+
Self::upgrade_and_migrate(env, contract_address, wasm_hash, &().to_xdr(env), operator);
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
/// Upgrades a target contract and runs its migration in a single transaction.
|
|
46
66
|
///
|
|
47
|
-
///
|
|
48
|
-
///
|
|
67
|
+
/// Chooses Auth-based or RBAC-based flow from `operator`:
|
|
68
|
+
/// - **`Some(operator)`**: RBAC flow. `operator` must sign the transaction and must have
|
|
69
|
+
/// `UPGRADER_ROLE` on the target. The target must implement [`UpgradeableRbac`](utils::upgradeable::UpgradeableRbac).
|
|
70
|
+
/// - **`None`**: Auth flow. The target’s authorizer must sign the transaction. The target
|
|
71
|
+
/// must implement [`Upgradeable`](utils::upgradeable::Upgradeable).
|
|
49
72
|
///
|
|
50
73
|
/// # Arguments
|
|
51
|
-
/// * `contract_address` -
|
|
52
|
-
/// * `wasm_hash` -
|
|
53
|
-
/// * `migration_data` - XDR-encoded
|
|
54
|
-
///
|
|
74
|
+
/// * `contract_address` - Address of the contract to upgrade.
|
|
75
|
+
/// * `wasm_hash` - Hash of the new WASM bytecode.
|
|
76
|
+
/// * `migration_data` - XDR-encoded migration payload. Use `value.to_xdr(&env)` for the
|
|
77
|
+
/// target contract’s `MigrationData` type; use `().to_xdr(&env)` for no custom data.
|
|
78
|
+
/// * `operator` - `None` for Auth-based target; `Some(operator)` for RBAC-based target.
|
|
55
79
|
///
|
|
56
80
|
/// # Example
|
|
57
81
|
/// ```ignore
|
|
58
82
|
/// let migration_data = my_data.to_xdr(&env);
|
|
59
|
-
/// upgrader.upgrade_and_migrate(&contract_addr, &wasm_hash, &migration_data);
|
|
83
|
+
/// upgrader.upgrade_and_migrate(&contract_addr, &wasm_hash, &migration_data, &None);
|
|
60
84
|
/// ```
|
|
61
|
-
pub fn upgrade_and_migrate(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
pub fn upgrade_and_migrate(
|
|
86
|
+
env: &Env,
|
|
87
|
+
contract_address: &Address,
|
|
88
|
+
wasm_hash: &BytesN<32>,
|
|
89
|
+
migration_data: &Bytes,
|
|
90
|
+
operator: &Option<Address>,
|
|
91
|
+
) {
|
|
92
|
+
if let Some(operator) = operator {
|
|
93
|
+
operator.require_auth();
|
|
94
|
+
let client = UpgradeableRbacClient::new(env, contract_address);
|
|
95
|
+
client.upgrade(wasm_hash, operator);
|
|
96
|
+
client.migrate(migration_data, operator);
|
|
97
|
+
} else {
|
|
98
|
+
AuthClient::new(env, contract_address)
|
|
99
|
+
.authorizer()
|
|
100
|
+
.unwrap_or_panic(env, AuthError::AuthorizerNotFound)
|
|
101
|
+
.require_auth();
|
|
102
|
+
let client = UpgradeableClient::new(env, contract_address);
|
|
103
|
+
client.upgrade(wasm_hash);
|
|
104
|
+
client.migrate(migration_data);
|
|
105
|
+
}
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
108
|
|
|
@@ -16,6 +16,19 @@ trait TestUpgradeableContract2 {
|
|
|
16
16
|
fn counter2(env: &Env) -> u32;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
#[allow(dead_code)]
|
|
20
|
+
#[contractclient(name = "TestRbacUpgradeableContractClient")]
|
|
21
|
+
trait TestRbacUpgradeableContract {
|
|
22
|
+
fn counter(env: &Env) -> u32;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[allow(dead_code)]
|
|
26
|
+
#[contractclient(name = "TestRbacUpgradeableContractClient2")]
|
|
27
|
+
trait TestRbacUpgradeableContract2 {
|
|
28
|
+
fn counter(env: &Env) -> u32;
|
|
29
|
+
fn counter2(env: &Env) -> u32;
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
mod contract_v1 {
|
|
20
33
|
//#![no_std]
|
|
21
34
|
|
|
@@ -100,6 +113,13 @@ mod contract_v2 {
|
|
|
100
113
|
soroban_sdk::contractimport!(file = "./src/tests/test_data/test_upgradeable_contract2.wasm");
|
|
101
114
|
}
|
|
102
115
|
|
|
116
|
+
mod contract_v3 {
|
|
117
|
+
soroban_sdk::contractimport!(file = "./src/tests/test_data/test_upgradeable_contract3.wasm");
|
|
118
|
+
}
|
|
119
|
+
mod contract_v4 {
|
|
120
|
+
soroban_sdk::contractimport!(file = "./src/tests/test_data/test_upgradeable_contract4.wasm");
|
|
121
|
+
}
|
|
122
|
+
|
|
103
123
|
fn install_new_wasm(e: &Env) -> BytesN<32> {
|
|
104
124
|
e.deployer().upload_contract_wasm(contract_v2::WASM)
|
|
105
125
|
}
|
|
@@ -107,7 +127,7 @@ fn install_new_wasm(e: &Env) -> BytesN<32> {
|
|
|
107
127
|
#[test]
|
|
108
128
|
fn test_upgrade_with_upgrader() {
|
|
109
129
|
let e = Env::default();
|
|
110
|
-
e.
|
|
130
|
+
e.mock_all_auths();
|
|
111
131
|
|
|
112
132
|
let owner = Address::generate(&e);
|
|
113
133
|
let contract_id = e.register(contract_v1::WASM, (&owner,));
|
|
@@ -121,7 +141,7 @@ fn test_upgrade_with_upgrader() {
|
|
|
121
141
|
let counter_value = 2_u32;
|
|
122
142
|
// Encode migration data as XDR bytes
|
|
123
143
|
let migration_data = counter_value.to_xdr(&e);
|
|
124
|
-
upgrader_client.upgrade_and_migrate(&contract_id, &new_wasm_hash, &migration_data);
|
|
144
|
+
upgrader_client.upgrade_and_migrate(&contract_id, &new_wasm_hash, &migration_data, &None);
|
|
125
145
|
|
|
126
146
|
let client_v2 = TestUpgradeableContractClient2::new(&e, &contract_id);
|
|
127
147
|
|
|
@@ -131,7 +151,7 @@ fn test_upgrade_with_upgrader() {
|
|
|
131
151
|
#[test]
|
|
132
152
|
fn test_upgrade_without_migration_data_returns_error_for_non_unit_migration() {
|
|
133
153
|
let e = Env::default();
|
|
134
|
-
e.
|
|
154
|
+
e.mock_all_auths();
|
|
135
155
|
|
|
136
156
|
let owner = Address::generate(&e);
|
|
137
157
|
let contract_id = e.register(contract_v1::WASM, (&owner,));
|
|
@@ -142,6 +162,32 @@ fn test_upgrade_without_migration_data_returns_error_for_non_unit_migration() {
|
|
|
142
162
|
let new_wasm_hash = install_new_wasm(&e);
|
|
143
163
|
// The upgradeable WASM fixture requires non-unit migration data (u32).
|
|
144
164
|
// `Upgrader::upgrade` always passes empty `()` migration bytes, so this must fail.
|
|
145
|
-
let res = upgrader_client.try_upgrade(&contract_id, &new_wasm_hash);
|
|
165
|
+
let res = upgrader_client.try_upgrade(&contract_id, &new_wasm_hash, &None);
|
|
146
166
|
assert_eq!(res.err().unwrap().unwrap(), utils::errors::UpgradeableError::InvalidMigrationData.into());
|
|
147
167
|
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn test_upgrade_with_upgrader_rbac() {
|
|
171
|
+
let e = Env::default();
|
|
172
|
+
e.mock_all_auths();
|
|
173
|
+
|
|
174
|
+
let owner = Address::generate(&e);
|
|
175
|
+
let operator = Address::generate(&e);
|
|
176
|
+
// RBAC fixture constructor: (owner, upgrader_operator)
|
|
177
|
+
let contract_id = e.register(contract_v3::WASM, (&owner, &operator));
|
|
178
|
+
let client_v3 = TestRbacUpgradeableContractClient::new(&e, &contract_id);
|
|
179
|
+
assert_eq!(client_v3.counter(), 1);
|
|
180
|
+
|
|
181
|
+
let upgrader = e.register(Upgrader, ());
|
|
182
|
+
let upgrader_client = UpgraderClient::new(&e, &upgrader);
|
|
183
|
+
|
|
184
|
+
let new_wasm_hash = e.deployer().upload_contract_wasm(contract_v4::WASM);
|
|
185
|
+
let counter_value = 42_u32;
|
|
186
|
+
let migration_data = counter_value.to_xdr(&e);
|
|
187
|
+
// Use RBAC path: pass Some(operator) so the upgrader uses UpgradeableRbac and operator must have signed
|
|
188
|
+
upgrader_client.upgrade_and_migrate(&contract_id, &new_wasm_hash, &migration_data, &Some(operator));
|
|
189
|
+
|
|
190
|
+
let client_v4 = TestRbacUpgradeableContractClient2::new(&e, &contract_id);
|
|
191
|
+
assert_eq!(client_v4.counter(), 1);
|
|
192
|
+
assert_eq!(client_v4.counter2(), counter_value);
|
|
193
|
+
}
|
|
@@ -64,7 +64,7 @@ pub enum OwnableStorage {
|
|
|
64
64
|
///
|
|
65
65
|
/// Supports both single-step and two-step ownership transfer:
|
|
66
66
|
/// - Single-step: `transfer_ownership` - Immediate transfer (use with caution)
|
|
67
|
-
/// - Two-step: `
|
|
67
|
+
/// - Two-step: `begin_ownership_transfer` + `accept_ownership` - Safer, requires new owner to accept
|
|
68
68
|
#[contract_trait]
|
|
69
69
|
pub trait Ownable: Auth {
|
|
70
70
|
// ===========================================================================
|
|
@@ -88,7 +88,7 @@ pub trait Ownable: Auth {
|
|
|
88
88
|
/// Transfers ownership immediately to a new address.
|
|
89
89
|
///
|
|
90
90
|
/// Use with caution - if you transfer to a wrong address, ownership is lost forever.
|
|
91
|
-
/// Consider using `
|
|
91
|
+
/// Consider using `begin_ownership_transfer` instead.
|
|
92
92
|
///
|
|
93
93
|
/// # Panics
|
|
94
94
|
/// - `OwnerNotSet` if no owner is currently set
|
|
@@ -105,7 +105,7 @@ pub trait Ownable: Auth {
|
|
|
105
105
|
// Two-step transfer (safer)
|
|
106
106
|
// ===========================================================================
|
|
107
107
|
|
|
108
|
-
///
|
|
108
|
+
/// Begins an ownership transfer to a new address.
|
|
109
109
|
///
|
|
110
110
|
/// The new owner must call `accept_ownership()` within `ttl` ledgers
|
|
111
111
|
/// to complete the transfer. The pending transfer will automatically expire after.
|
|
@@ -120,7 +120,7 @@ pub trait Ownable: Auth {
|
|
|
120
120
|
/// - `NoPendingTransfer` when cancelling and no pending transfer exists
|
|
121
121
|
/// - `InvalidTtl` if ttl exceeds max TTL
|
|
122
122
|
/// - `InvalidPendingOwner` when cancelling with wrong new_owner address
|
|
123
|
-
fn
|
|
123
|
+
fn begin_ownership_transfer(env: &soroban_sdk::Env, new_owner: &soroban_sdk::Address, ttl: u32) {
|
|
124
124
|
let old_owner = enforce_owner_auth::<Self>(env);
|
|
125
125
|
|
|
126
126
|
// Cancel case: ttl == 0
|
|
@@ -158,7 +158,7 @@ pub trait Ownable: Auth {
|
|
|
158
158
|
new_owner.require_auth();
|
|
159
159
|
|
|
160
160
|
// Safe to unwrap: owner must exist if pending_owner exists because:
|
|
161
|
-
// 1. pending_owner can only be set via
|
|
161
|
+
// 1. pending_owner can only be set via begin_ownership_transfer, which requires owner auth
|
|
162
162
|
// 2. renounce_ownership is blocked while a 2-step transfer is in progress
|
|
163
163
|
let old_owner = OwnableStorage::owner(env).unwrap();
|
|
164
164
|
|
|
@@ -189,6 +189,17 @@ pub trait Ownable: Auth {
|
|
|
189
189
|
|
|
190
190
|
/// Trait for initializing the owner of the contract.
|
|
191
191
|
pub trait OwnableInitializer {
|
|
192
|
+
/// Initializes the owner of the contract.
|
|
193
|
+
///
|
|
194
|
+
/// # Critical: constructor-only, never expose as a public entrypoint
|
|
195
|
+
///
|
|
196
|
+
/// `init_owner` must **ONLY** be called from the contract constructor. Do not expose it
|
|
197
|
+
/// as a public function under the assumption that it will "simply fail" after initialization.
|
|
198
|
+
///
|
|
199
|
+
/// After `renounce_ownership`, the owner is removed and `has_owner` returns false. If
|
|
200
|
+
/// `init_owner` were exposed publicly, anyone could call it post-renounce and become the
|
|
201
|
+
/// new owner, effectively undoing the renunciation. Always keep this logic internal to
|
|
202
|
+
/// the constructor.
|
|
192
203
|
fn init_owner(env: &Env, owner: &Address) {
|
|
193
204
|
assert_with_error!(env, !OwnableStorage::has_owner(env), OwnableError::OwnerAlreadySet);
|
|
194
205
|
OwnableStorage::set_owner(env, owner);
|