Skip to content

Commit

Permalink
Feat: Add ERC-2981 (#334)
Browse files Browse the repository at this point in the history
* ➕ add ERC2981 lib

* ✨ add contract methods

* ✅ add basic test
  • Loading branch information
tekkac authored Mar 5, 2024
1 parent 7667dcd commit bd67905
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 0 deletions.
1 change: 1 addition & 0 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ alexandria_numeric = { git = "https://github.com/tekkac/alexandria", branch = "f
alexandria_data_structures = { git = "https://github.com/tekkac/alexandria", branch = "feat/interpolation-fast" }
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" }
cairo_erc_3525 = { git = "https://github.com/carbonable-labs/cairo-erc-3525", tag = "2.0.10" }
cairo_erc_2981 = { git = "https://github.com/carbonable-labs/cairo-erc-2981", tag="v1.0.1" }

[[target.starknet-contract]]
98 changes: 98 additions & 0 deletions src/contracts/project.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ mod Project {
IERC721, IERC721Metadata, IERC721CamelOnly, IERC721MetadataCamelOnly
};

// ERC2981
use cairo_erc_2981::components::erc2981::interface::{IERC2981, IERC2981Camel};
use cairo_erc_2981::components::erc2981::module::ERC2981;

// ERC3525
use cairo_erc_3525::interface::IERC3525;
use cairo_erc_3525::extensions::metadata::interface::IERC3525Metadata;
Expand Down Expand Up @@ -674,6 +678,97 @@ mod Project {
}
}

// ERC2981

#[external(v0)]
impl ERC2981Impl of IERC2981<ContractState> {
fn default_royalty(self: @ContractState) -> (ContractAddress, u256, u256) {
let unsafe_state = ERC2981::unsafe_new_contract_state();
ERC2981::ERC2981Impl::default_royalty(@unsafe_state)
}

fn token_royalty(self: @ContractState, token_id: u256) -> (ContractAddress, u256, u256) {
let unsafe_state = ERC2981::unsafe_new_contract_state();
ERC2981::ERC2981Impl::token_royalty(@unsafe_state, token_id)
}

fn royalty_info(
self: @ContractState, token_id: u256, sale_price: u256
) -> (ContractAddress, u256) {
let unsafe_state = ERC2981::unsafe_new_contract_state();
ERC2981::ERC2981Impl::royalty_info(@unsafe_state, token_id, sale_price)
}

fn set_default_royalty(
ref self: ContractState,
receiver: ContractAddress,
fee_numerator: u256,
fee_denominator: u256
) {
// [Check] Only owner
let unsafe_state = Ownable::unsafe_new_contract_state();
Ownable::InternalImpl::assert_only_owner(@unsafe_state);
// [Effect] Set default royalty
let mut unsafe_state = ERC2981::unsafe_new_contract_state();
ERC2981::ERC2981Impl::set_default_royalty(
ref unsafe_state, receiver, fee_numerator, fee_denominator
)
}

fn set_token_royalty(
ref self: ContractState,
token_id: u256,
receiver: ContractAddress,
fee_numerator: u256,
fee_denominator: u256
) {
// [Check] Only owner
let unsafe_state = Ownable::unsafe_new_contract_state();
Ownable::InternalImpl::assert_only_owner(@unsafe_state);
// [Effect] Set token royalty
let mut unsafe_state = ERC2981::unsafe_new_contract_state();
ERC2981::ERC2981Impl::set_token_royalty(
ref unsafe_state, token_id, receiver, fee_numerator, fee_denominator
)
}
}

#[external(v0)]
impl ERC2981CamelImpl of IERC2981Camel<ContractState> {
fn defaultRoyalty(self: @ContractState) -> (ContractAddress, u256, u256) {
self.default_royalty()
}

fn tokenRoyalty(self: @ContractState, tokenId: u256) -> (ContractAddress, u256, u256) {
self.token_royalty(tokenId)
}

fn royaltyInfo(
self: @ContractState, tokenId: u256, salePrice: u256
) -> (ContractAddress, u256) {
self.royalty_info(tokenId, salePrice)
}

fn setDefaultRoyalty(
ref self: ContractState,
receiver: ContractAddress,
feeNumerator: u256,
feeDenominator: u256
) {
self.set_default_royalty(receiver, feeNumerator, feeDenominator)
}

fn setTokenRoyalty(
ref self: ContractState,
tokenId: u256,
receiver: ContractAddress,
feeNumerator: u256,
feeDenominator: u256
) {
self.set_token_royalty(tokenId, receiver, feeNumerator, feeDenominator)
}
}

#[generate_trait]
impl InternalImpl of InternalTrait {
fn initializer(
Expand All @@ -686,6 +781,9 @@ mod Project {
// ERC721 & ERC3525
let mut unsafe_state = ERC3525::unsafe_new_contract_state();
ERC3525::InternalImpl::initializer(ref unsafe_state, name, symbol, value_decimals);
// ERC2981
let mut unsafe_state = ERC2981::unsafe_new_contract_state();
ERC2981::InternalImpl::initializer(ref unsafe_state, owner, 500, 10_000);
// Access control
let mut unsafe_state = Ownable::unsafe_new_contract_state();
Ownable::InternalImpl::initializer(ref unsafe_state, owner);
Expand Down
14 changes: 14 additions & 0 deletions src/tests/test_project.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use openzeppelin::token::erc721::interface::{IERC721_ID, IERC721_METADATA_ID};
use cairo_erc_3525::interface::IERC3525_ID;
use cairo_erc_3525::extensions::metadata::interface::IERC3525_METADATA_ID;
use cairo_erc_3525::extensions::slotenumerable::interface::IERC3525_SLOT_ENUMERABLE_ID;
use cairo_erc_2981::components::erc2981::interface::{IERC2981Dispatcher, IERC2981DispatcherTrait};

// Components

Expand Down Expand Up @@ -249,3 +250,16 @@ fn test_supports_interface_ERC165_backward_compatible() {
assert(project.supports_interface(IERC3525_ID), 'IERC3525 not supported');
assert(project.supports_interface(IERC3525_METADATA_ID), '3525Metadata not supported');
}

#[test]
#[available_gas(20_000_000)]
fn test_royalties_default_setup() {
// [Setup]
let (signers, contracts) = setup();
let project = IERC2981Dispatcher { contract_address: contracts.project };
let (receiver, fee_numerator, fee_denominator) = project.default_royalty();
assert(receiver == signers.owner, 'Invalid receiver');
assert(fee_numerator == 500, 'Invalid fee numerator');
assert(fee_denominator == 10_000, 'Invalid fee denominator');
}

0 comments on commit bd67905

Please sign in to comment.