Skip to content

Commit ef87d78

Browse files
ericnordeloimmrsd
andauthored
Fix pending owner late overwrite issue (#1122)
* fix: pending owner late overwrite issue * feat: add tests * tests: remove old version * feat: update CHANGELOG * Update packages/access/src/tests/test_ownable.cairo Co-authored-by: immrsd <[email protected]> * Update packages/access/src/tests/test_ownable_twostep.cairo Co-authored-by: immrsd <[email protected]> * feat: apply review updates * feat: update CHANGELOG --------- Co-authored-by: immrsd <[email protected]>
1 parent 5ae6b40 commit ef87d78

File tree

5 files changed

+61
-70
lines changed

5 files changed

+61
-70
lines changed

CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Added
1313

14+
- ERC721Enumerable component (#983)
1415
- ERC2981 (NFT Royalty Standard) component (#1091)
1516
- `merkle_tree` package with utilities to verify proofs and multi proofs (#1101)
1617

@@ -22,9 +23,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2223

2324
### Changed (Breaking)
2425

25-
- Changed ABI suffix to Trait in dual case account and eth account modules (#1096).
26+
- Changed ABI suffix to Trait in dual case account and eth account modules (#1096)
2627
- `DualCaseAccountABI` renamed to `DualCaseAccountTrait`
2728
- `DualCaseEthAccountABI` renamed to `DualCaseEthAccountTrait`
29+
- Removed `_accept_ownership` from `OwnableComponent::InternalImpl`
30+
31+
### Fixed
32+
33+
- `OwnableTwoStep` allowing a pending owner to accept ownership after the original owner has renounced ownership (#1119)
2834

2935
## 0.15.1 (2024-08-13)
3036

@@ -37,7 +43,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3743

3844
### Added
3945

40-
- ERC721Enumerable component (#983)
4146
- TimelockController component (#996)
4247
- HashCall implementation (#996)
4348
- Separated package for each submodule (#1065)

docs/modules/ROOT/pages/api/access.adoc

-12
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ This module includes the internal `assert_only_owner` to restrict a function to
8989
* xref:OwnableComponent-assert_only_owner[`++assert_only_owner(self)++`]
9090
* xref:OwnableComponent-_transfer_ownership[`++_transfer_ownership(self, new_owner)++`]
9191
* xref:OwnableComponent-_propose_owner[`++_propose_owner(self, new_owner)++`]
92-
* xref:OwnableComponent-_accept_ownership[`++_accept_ownership(self)++`]
9392
--
9493

9594
[.contract-index]
@@ -239,17 +238,6 @@ Internal function without access restriction.
239238

240239
Emits an xref:OwnableComponent-OwnershipTransferStarted[OwnershipTransferStarted] event.
241240

242-
[.contract-item]
243-
[[OwnableComponent-_accept_ownership]]
244-
==== `[.contract-item-name]#++_accept_ownership++#++(ref self: ContractState)++` [.item-kind]#internal#
245-
246-
Transfers ownership to the pending owner. Resets pending owner to zero address.
247-
Calls xref:OwnableComponent-_transfer_ownership[_transfer_ownership].
248-
249-
Internal function without access restriction.
250-
251-
Emits an xref:OwnableComponent-OwnershipTransferred[OwnershipTransferred] event.
252-
253241
[#OwnableComponent-Events]
254242
==== Events
255243

packages/access/src/ownable/ownable.cairo

+23-13
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,26 @@ pub mod OwnableComponent {
113113

114114
/// Finishes the two-step ownership transfer process by accepting the ownership.
115115
/// Can only be called by the pending owner.
116+
///
117+
/// Requirements:
118+
///
119+
/// - The caller is the pending owner.
120+
///
121+
/// Emits an `OwnershipTransferred` event.
116122
fn accept_ownership(ref self: ComponentState<TContractState>) {
117123
let caller = get_caller_address();
118124
let pending_owner = self.Ownable_pending_owner.read();
119125
assert(caller == pending_owner, Errors::NOT_PENDING_OWNER);
120-
self._accept_ownership();
126+
self._transfer_ownership(pending_owner);
121127
}
122128

123129
/// Starts the two-step ownership transfer process by setting the pending owner.
130+
///
131+
/// Requirements:
132+
///
133+
/// - The caller is the contract owner.
134+
///
135+
/// Emits an `OwnershipTransferStarted` event.
124136
fn transfer_ownership(
125137
ref self: ComponentState<TContractState>, new_owner: ContractAddress
126138
) {
@@ -130,6 +142,12 @@ pub mod OwnableComponent {
130142

131143
/// Leaves the contract without owner. It will not be possible to call `assert_only_owner`
132144
/// functions anymore. Can only be called by the current owner.
145+
///
146+
/// Requirements:
147+
///
148+
/// - The caller is the contract owner.
149+
///
150+
/// Emits an `OwnershipTransferred` event.
133151
fn renounce_ownership(ref self: ComponentState<TContractState>) {
134152
Ownable::renounce_ownership(ref self);
135153
}
@@ -265,14 +283,17 @@ pub mod OwnableComponent {
265283
assert(caller == owner, Errors::NOT_OWNER);
266284
}
267285

268-
/// Transfers ownership of the contract to a new address.
286+
/// Transfers ownership of the contract to a new address and resets
287+
/// the pending owner to the zero address.
269288
///
270289
/// Internal function without access restriction.
271290
///
272291
/// Emits an `OwnershipTransferred` event.
273292
fn _transfer_ownership(
274293
ref self: ComponentState<TContractState>, new_owner: ContractAddress
275294
) {
295+
self.Ownable_pending_owner.write(Zero::zero());
296+
276297
let previous_owner: ContractAddress = self.Ownable_owner.read();
277298
self.Ownable_owner.write(new_owner);
278299
self
@@ -296,16 +317,5 @@ pub mod OwnableComponent {
296317
}
297318
);
298319
}
299-
300-
/// Transfers ownership to the pending owner.
301-
///
302-
/// Internal function without access restriction.
303-
///
304-
/// Emits an `OwnershipTransferred` event.
305-
fn _accept_ownership(ref self: ComponentState<TContractState>) {
306-
let pending_owner = self.Ownable_pending_owner.read();
307-
self.Ownable_pending_owner.write(Zero::zero());
308-
self._transfer_ownership(pending_owner);
309-
}
310320
}
311321
}

packages/access/src/tests/test_ownable.cairo

+15-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::ownable::interface::{IOwnable, IOwnableCamelOnly};
55
use crate::tests::mocks::ownable_mocks::DualCaseOwnableMock;
66

77
use openzeppelin_test_common::ownable::OwnableSpyHelpers;
8-
use openzeppelin_testing::constants::{ZERO, OTHER, OWNER};
8+
use openzeppelin_testing::constants::{ZERO, OTHER, OWNER, RECIPIENT};
99
use snforge_std::{spy_events, test_address, start_cheat_caller_address};
1010

1111
//
@@ -86,6 +86,20 @@ fn test__transfer_ownership() {
8686
assert_eq!(current_owner, OTHER());
8787
}
8888

89+
#[test]
90+
fn test__transfer_ownership_resets_pending_owner() {
91+
let mut state = setup();
92+
93+
state.Ownable_pending_owner.write(OTHER());
94+
let current_pending_owner = state.Ownable_pending_owner.read();
95+
assert_eq!(current_pending_owner, OTHER());
96+
97+
state._transfer_ownership(RECIPIENT());
98+
99+
let current_pending_owner = state.Ownable_pending_owner.read();
100+
assert!(current_pending_owner.is_zero());
101+
}
102+
89103
//
90104
// transfer_ownership & transferOwnership
91105
//

packages/access/src/tests/test_ownable_twostep.cairo

+16-42
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,6 @@ fn test_initializer_owner_pending_owner() {
4444
assert!(state.Ownable_pending_owner.read().is_zero());
4545
}
4646

47-
//
48-
// _accept_ownership
49-
//
50-
51-
#[test]
52-
fn test__accept_ownership() {
53-
let mut state = setup();
54-
let mut spy = spy_events();
55-
state.Ownable_pending_owner.write(OTHER());
56-
57-
state._accept_ownership();
58-
59-
spy.assert_only_event_ownership_transferred(test_address(), OWNER(), OTHER());
60-
assert_eq!(state.owner(), OTHER());
61-
assert!(state.pending_owner().is_zero());
62-
}
63-
6447
//
6548
// _propose_owner
6649
//
@@ -244,6 +227,22 @@ fn test_renounce_ownership() {
244227
assert!(state.owner().is_zero());
245228
}
246229

230+
#[test]
231+
fn test_renounce_ownership_resets_pending_owner() {
232+
let mut state = setup();
233+
let contract_address = test_address();
234+
start_cheat_caller_address(contract_address, OWNER());
235+
236+
state.Ownable_pending_owner.write(OTHER());
237+
let current_pending_owner = state.Ownable_pending_owner.read();
238+
assert_eq!(current_pending_owner, OTHER());
239+
240+
state.renounce_ownership();
241+
242+
let current_pending_owner = state.Ownable_pending_owner.read();
243+
assert!(current_pending_owner.is_zero());
244+
}
245+
247246
#[test]
248247
#[should_panic(expected: ('Caller is the zero address',))]
249248
fn test_renounce_ownership_from_zero_address() {
@@ -307,31 +306,6 @@ fn test_full_two_step_transfer() {
307306
assert!(state.pending_owner().is_zero());
308307
}
309308

310-
#[test]
311-
fn test_pending_accept_after_owner_renounce() {
312-
let mut state = setup();
313-
let mut spy = spy_events();
314-
let contract_address = test_address();
315-
start_cheat_caller_address(contract_address, OWNER());
316-
state.transfer_ownership(OTHER());
317-
318-
spy.assert_event_ownership_transfer_started(contract_address, OWNER(), OTHER());
319-
assert_eq!(state.owner(), OWNER());
320-
assert_eq!(state.pending_owner(), OTHER());
321-
322-
state.renounce_ownership();
323-
324-
spy.assert_only_event_ownership_transferred(contract_address, OWNER(), ZERO());
325-
assert!(state.owner().is_zero());
326-
327-
start_cheat_caller_address(contract_address, OTHER());
328-
state.accept_ownership();
329-
330-
spy.assert_only_event_ownership_transferred(contract_address, ZERO(), OTHER());
331-
assert_eq!(state.owner(), OTHER());
332-
assert!(state.pending_owner().is_zero());
333-
}
334-
335309
//
336310
// Helpers
337311
//

0 commit comments

Comments
 (0)