Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- Embeddable `ERC2981AdminAccessControlDefaultAdminRulesImpl` implementation providing admin functions for a `ERC2981` token based on `AccessControlDefaultAdminRules` component (#1511)

## 3.0.0-alpha.1 (2025-08-18)

### Added
Expand Down
59 changes: 59 additions & 0 deletions docs/modules/ROOT/pages/api/token_common.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ ERC2981 component extending <<IERC2981,IERC2981>>.
* xref:#ERC2981AdminAccessControlImpl-delete_default_royalty[`++delete_default_royalty(self)++`]
* xref:#ERC2981AdminAccessControlImpl-set_token_royalty[`++set_token_royalty(self, token_id, receiver, fee_numerator)++`]
* xref:#ERC2981AdminAccessControlImpl-reset_token_royalty[`++reset_token_royalty(self, token_id)++`]

[.sub-index#ERC2981Component-Embeddable-Impls-ERC2981AdminAccessControlDefaultAdminRulesImpl]
.ERC2981AdminAccessControlDefaultAdminRulesImpl
* xref:#ERC2981AdminAccessControlDefaultAdminRulesImpl-set_default_royalty[`++set_default_royalty(self, receiver, fee_numerator)++`]
* xref:#ERC2981AdminAccessControlDefaultAdminRulesImpl-delete_default_royalty[`++delete_default_royalty(self)++`]
* xref:#ERC2981AdminAccessControlDefaultAdminRulesImpl-set_token_royalty[`++set_token_royalty(self, token_id, receiver, fee_numerator)++`]
* xref:#ERC2981AdminAccessControlDefaultAdminRulesImpl-reset_token_royalty[`++reset_token_royalty(self, token_id)++`]
--

[.contract-index]
Expand Down Expand Up @@ -374,6 +381,58 @@ Requirements:

- The caller must have `ROYALTY_ADMIN_ROLE` role.

[#ERC2981Component-ERC2981AdminAccessControlDefaultAdminRulesImpl]
==== ERC2981AdminAccessControlDefaultAdminRulesImpl

:accesscontrol-default-admin-rules-component: xref:api/access.adoc#AccessControlDefaultAdminRulesComponent[AccessControlDefaultAdminRulesComponent]

An alternative implementation of xref:#IERC2981Admin[IERC2981Admin]. Provides admin functions for managing royalty settings
that require `ROYALTY_ADMIN_ROLE` to be granted to the caller. Requires the contract to implement {accesscontrol-default-admin-rules-component}.

[.contract-item]
[[ERC2981AdminAccessControlDefaultAdminRulesImpl-set_default_royalty]]
==== `[.contract-item-name]#++set_default_royalty++#++(ref self: ContractState, receiver: ContractAddress, fee_numerator: u128)++` [.item-kind]#external#

Sets the royalty information that all ids in this contract will default to.

Requirements:

- The caller must have `ROYALTY_ADMIN_ROLE` role.
- `receiver` cannot be the zero address.
- `fee_numerator` cannot be greater than the fee denominator.

[.contract-item]
[[ERC2981AdminAccessControlDefaultAdminRulesImpl-delete_default_royalty]]
==== `[.contract-item-name]#++delete_default_royalty++#++(ref self: ContractState)++` [.item-kind]#external#

Sets the default royalty percentage and receiver to zero.

Requirements:

- The caller must have `ROYALTY_ADMIN_ROLE` role.

[.contract-item]
[[ERC2981AdminAccessControlDefaultAdminRulesImpl-set_token_royalty]]
==== `[.contract-item-name]#++set_token_royalty++#++(ref self: ContractState, token_id: u256, receiver: ContractAddress, fee_numerator: u128)++` [.item-kind]#external#

Sets the royalty information for a specific token id that takes precedence over the global default.

Requirements:

- The caller must have `ROYALTY_ADMIN_ROLE` role.
- `receiver` cannot be the zero address.
- `fee_numerator` cannot be greater than the fee denominator.

[.contract-item]
[[ERC2981AdminAccessControlDefaultAdminRulesImpl-reset_token_royalty]]
==== `[.contract-item-name]#++reset_token_royalty++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#external#

Resets royalty information for the token id back to unset.

Requirements:

- The caller must have `ROYALTY_ADMIN_ROLE` role.

[#ERC2981Component-Internal-functions]
==== Internal functions

Expand Down
64 changes: 64 additions & 0 deletions packages/test_common/src/mocks/erc2981.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,67 @@ pub mod ERC2981AccessControlMock {
self.access_control._grant_role(ROYALTY_ADMIN_ROLE, owner);
}
}

#[starknet::contract]
#[with_components(ERC2981, SRC5)]
pub mod ERC2981AccessControlDefaultAdminRulesMock {
use openzeppelin_access::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent::InternalImpl;
use openzeppelin_access::accesscontrol::extensions::{
AccessControlDefaultAdminRulesComponent,
DefaultConfig as AccessControlDefaultAdminRulesDefaultConfig,
};
use openzeppelin_token::common::erc2981::DefaultConfig as ERC2981DefaultConfig;
use openzeppelin_token::common::erc2981::ERC2981Component::ROYALTY_ADMIN_ROLE;
use starknet::ContractAddress;

// ERC2981
#[abi(embed_v0)]
impl ERC2981Impl = ERC2981Component::ERC2981Impl<ContractState>;
#[abi(embed_v0)]
impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl<ContractState>;
#[abi(embed_v0)]
impl ERC2981AdminAccessControlDefaultAdminRulesImpl =
ERC2981Component::ERC2981AdminAccessControlDefaultAdminRulesImpl<ContractState>;

// AccessControl
#[abi(embed_v0)]
impl AccessControlImpl =
AccessControlDefaultAdminRulesComponent::AccessControlImpl<ContractState>;

// SRC5
#[abi(embed_v0)]
impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;

pub const INITIAL_DELAY: u64 = 3600; // 1 hour

component!(
path: AccessControlDefaultAdminRulesComponent,
storage: accesscontrol_dar,
event: AccessControlDAREvent,
);

#[storage]
pub struct Storage {
#[substorage(v0)]
accesscontrol_dar: AccessControlDefaultAdminRulesComponent::Storage,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
AccessControlDAREvent: AccessControlDefaultAdminRulesComponent::Event,
}

#[constructor]
fn constructor(
ref self: ContractState,
owner: ContractAddress,
default_receiver: ContractAddress,
default_royalty_fraction: u128,
) {
self.erc2981.initializer(default_receiver, default_royalty_fraction);
self.accesscontrol_dar.initializer(INITIAL_DELAY, owner);
self.accesscontrol_dar._grant_role(ROYALTY_ADMIN_ROLE, owner);
}
}
73 changes: 73 additions & 0 deletions packages/token/src/common/erc2981/erc2981.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub mod ERC2981Component {
use core::num::traits::Zero;
use openzeppelin_access::accesscontrol::AccessControlComponent;
use openzeppelin_access::accesscontrol::AccessControlComponent::InternalTrait as AccessControlInternalTrait;
use openzeppelin_access::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent;
use openzeppelin_access::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent::InternalTrait as AccessControlDefaultAdminRulesInternalTrait;
use openzeppelin_access::ownable::OwnableComponent;
use openzeppelin_access::ownable::OwnableComponent::InternalTrait as OwnableInternalTrait;
use openzeppelin_interfaces::erc2981 as interface;
Expand Down Expand Up @@ -288,6 +290,77 @@ pub mod ERC2981Component {
}
}

//
// AccessControlDefaultAdminRules-based implementation of IERC2981Admin
//

#[embeddable_as(ERC2981AdminAccessControlDefaultAdminRulesImpl)]
impl ERC2981AdminAccessControlDefaultAdminRules<
TContractState,
+HasComponent<TContractState>,
+ImmutableConfig,
+AccessControlDefaultAdminRulesComponent::ImmutableConfig,
+SRC5Component::HasComponent<TContractState>,
impl AccessControlDAR: AccessControlDefaultAdminRulesComponent::HasComponent<
TContractState,
>,
+Drop<TContractState>,
> of interface::IERC2981Admin<ComponentState<TContractState>> {
/// Sets the royalty information that all ids in this contract will default to.
///
/// Requirements:
///
/// - The caller must have `ROYALTY_ADMIN_ROLE` role.
/// - `receiver` cannot be the zero address.
/// - `fee_numerator` cannot be greater than the fee denominator.
fn set_default_royalty(
ref self: ComponentState<TContractState>,
receiver: ContractAddress,
fee_numerator: u128,
) {
get_dep_component!(@self, AccessControlDAR).assert_only_role(ROYALTY_ADMIN_ROLE);
self._set_default_royalty(receiver, fee_numerator)
}

/// Sets the default royalty percentage and receiver to zero.
///
/// Requirements:
///
/// - The caller must have `ROYALTY_ADMIN_ROLE` role.
fn delete_default_royalty(ref self: ComponentState<TContractState>) {
get_dep_component!(@self, AccessControlDAR).assert_only_role(ROYALTY_ADMIN_ROLE);
self._delete_default_royalty()
}

/// Sets the royalty information for a specific token id that takes precedence over the
/// global default.
///
/// Requirements:
///
/// - The caller must have `ROYALTY_ADMIN_ROLE` role.
/// - `receiver` cannot be the zero address.
/// - `fee_numerator` cannot be greater than the fee denominator.
fn set_token_royalty(
ref self: ComponentState<TContractState>,
token_id: u256,
receiver: ContractAddress,
fee_numerator: u128,
) {
get_dep_component!(@self, AccessControlDAR).assert_only_role(ROYALTY_ADMIN_ROLE);
self._set_token_royalty(token_id, receiver, fee_numerator)
}

/// Resets royalty information for the token id back to unset.
///
/// Requirements:
///
/// - The caller must have `ROYALTY_ADMIN_ROLE` role.
fn reset_token_royalty(ref self: ComponentState<TContractState>, token_id: u256) {
get_dep_component!(@self, AccessControlDAR).assert_only_role(ROYALTY_ADMIN_ROLE);
self._reset_token_royalty(token_id)
}
}

//
// Internal
//
Expand Down
1 change: 1 addition & 0 deletions packages/token/src/tests/erc2981.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod test_erc2981_accesscontrol;
mod test_erc2981_accesscontrol_default_admin_rules;
mod test_erc2981_internal;
mod test_erc2981_ownable;
Loading