Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Royalties #579

Merged
merged 39 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
74d7330
Manage seller royalties
zajck Mar 2, 2023
c6e4e70
expand offer struct with royalty info
zajck Mar 2, 2023
44f1f52
account related tests
zajck Mar 3, 2023
f597ca4
refactor + natspec
zajck Mar 6, 2023
85f3fc7
domain tests royaltyrecipients, royaltyInfo, offer
zajck Mar 6, 2023
3117f96
migrate from waffle
zajck Mar 7, 2023
dee9a81
adjust old test to work with default royalties
zajck Mar 7, 2023
5a1a797
skip precommit action for empty lists
zajck Mar 7, 2023
388da5e
Offers with royalty recipients
zajck Mar 8, 2023
1b253cb
RR + EIP2981
zajck Mar 10, 2023
5c52815
Merge branch 'main' into royalties-2
zajck Mar 10, 2023
e02b6dd
adjustment to automatic module compilation
zajck Mar 10, 2023
2a8eee8
remove royalty management from voucher
zajck Mar 10, 2023
c1cf149
support royalties for preminted vouchers
zajck Mar 10, 2023
a0a0a50
return 0s instead of revert
zajck Mar 14, 2023
ac7cc92
Merge branch 'main' into royalties-2
zajck Apr 26, 2023
174753b
tidy
zajck Apr 26, 2023
b1957f2
fix failing tests
zajck Apr 26, 2023
16952d8
Merge branch 'main' into royalties-2
zajck Oct 27, 2023
8bfefe7
fix percentage typo
zajck Oct 27, 2023
ebebf34
Merge branch 'main' into royalties-2
zajck Nov 22, 2023
d68e55a
tidy
zajck Nov 22, 2023
04e77c3
Fix failing tests /1
zajck Nov 23, 2023
26a7a39
enable mutable royalties
zajck Nov 23, 2023
49a2eea
domain updated
zajck Nov 24, 2023
b3052d4
adapt old tests
zajck Nov 24, 2023
82fb7e5
Fix failing tests /2
zajck Nov 27, 2023
3ecfb6d
Fix escrow in sequential commit
zajck Nov 27, 2023
116ea0d
rename royalty getters
zajck Nov 27, 2023
99b1d16
getEIP2981Royalties() tests
zajck Nov 27, 2023
fa5432c
getRoyalties() tests
zajck Nov 27, 2023
2dfb80d
additional tests with multiple recipients
zajck Nov 27, 2023
c826ac4
updateOfferRoyaltyRecipients tests
zajck Nov 27, 2023
df1e1d7
tests with external recipients
zajck Nov 28, 2023
a221393
Increase line coverage
zajck Nov 28, 2023
420811e
Apply suggestions from code review
zajck Nov 28, 2023
8582b06
Fix the rest of the typos
zajck Nov 28, 2023
81fc8ea
Fix failing test
zajck Nov 28, 2023
5c4cb54
Update contracts/protocol/libs/FundsLib.sol
zajck Nov 28, 2023
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "submodules/royalty-registry-solidity"]
path = submodules/royalty-registry-solidity
url = [email protected]:manifoldxyz/royalty-registry-solidity.git
[submodule "submodules/seaport"]
path = submodules/seaport
url = [email protected]:ProjectOpenSea/seaport.git
Expand Down
3 changes: 3 additions & 0 deletions contracts/domain/BosonConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ uint256 constant ALL_REGIONS_MASK = (1 << (uint256(type(BosonTypes.PausableRegio
uint256 constant NOT_ENTERED = 1;
uint256 constant ENTERED = 2;

// Seller related
string constant DEFAULT_ROYALTY_RECIPIENT = "Treasury";

// Twin handler
uint256 constant SINGLE_TWIN_RESERVED_GAS = 160000;
uint256 constant MINIMAL_RESIDUAL_GAS = 230000;
Expand Down
16 changes: 16 additions & 0 deletions contracts/domain/BosonErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ interface BosonErrors {
error AgentFeeAmountTooHigh();
// Collection does not exist
error NoSuchCollection();
// Royalty recipient is not allow listed for the seller
error InvalidRoyaltyRecipient();
// Total royality fee exceeds the max allowed
error InvalidRoyaltyPercentage();
// Specified royalty recipient already added
error RecipientNotUnique();
// Trying to access an out of bounds royalty recipient
error InvalidRoyaltyRecipientId();
// Array of royalty recipients is not sorted by id
error RoyaltyRecipientIdsNotSorted();
// Trying to remove the default recipient (treasury)
error CannotRemoveDefaultRecipient();
// Supplying too many Royalty info structs
error InvalidRoyaltyInfo();
// Trying to change the default recipient address (treasury)
error WrongDefaultRecipient();

// Group related
// Group does not exist
Expand Down
13 changes: 13 additions & 0 deletions contracts/domain/BosonTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ contract BosonTypes {
bool voided;
uint256 collectionIndex;
PriceType priceType;
RoyaltyInfo[] royaltyInfo;
}

struct OfferDates {
Expand Down Expand Up @@ -203,6 +204,7 @@ contract BosonTypes {
uint256 price;
uint256 protocolFeeAmount;
uint256 royaltyAmount;
uint256 royaltyInfoIndex;
}

struct Voucher {
Expand Down Expand Up @@ -327,4 +329,15 @@ contract BosonTypes {
Bid,
Wrapper // Side is not relevant from the protocol perspective
}

struct RoyaltyRecipient {
address wallet;
uint256 minRoyaltyPercentage;
string externalId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why an externalId on chain?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No functional reason on the protocol level. It's just it getRoyaltyRecipients returns a nicer output.
It's not that costly (assuming the seller does not update it often), and it's not a required field, so they can even submit an empty string.

We also have similar external identifiers elsewhere (collections, DRfees).

}

struct RoyaltyInfo {
address payable[] recipients;
uint256[] bps;
}
}
26 changes: 2 additions & 24 deletions contracts/interfaces/clients/IBosonVoucher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import { IERC721ReceiverUpgradeable } from "@openzeppelin/contracts-upgradeable/
*
* @notice This is the interface for the Boson Protocol ERC-721 Voucher contract.
*
* The ERC-165 identifier for this interface is: 0x5235dd2b
* The ERC-165 identifier for this interface is: 0x6a474d2c
*/
interface IBosonVoucher is IERC721Upgradeable, IERC721MetadataUpgradeable, IERC721ReceiverUpgradeable {
event ContractURIChanged(string contractURI);
event RoyaltyPercentageChanged(uint256 royaltyPercentage);
event VoucherInitialized(uint256 indexed sellerId, uint256 indexed royaltyPercentage, string indexed contractURI);
event VoucherInitialized(uint256 indexed sellerId, string indexed contractURI);
event RangeReserved(uint256 indexed offerId, Range range);
event VouchersPreMinted(uint256 indexed offerId, uint256 startId, uint256 endId);

Expand Down Expand Up @@ -93,27 +92,6 @@ interface IBosonVoucher is IERC721Upgradeable, IERC721MetadataUpgradeable, IERC7
uint256 _salePrice
) external view returns (address receiver, uint256 royaltyAmount);

/**
* @notice Sets the royalty percentage.
* Can only be called by the owner or during the initialization
*
* Emits RoyaltyPercentageChanged if successful.
*
* Reverts if:
* - Caller is not the owner.
* - `_newRoyaltyPercentage` is greater than max royalty percentage defined in the protocol
*
* @param _newRoyaltyPercentage fee in percentage. e.g. 500 = 5%
*/
function setRoyaltyPercentage(uint256 _newRoyaltyPercentage) external;

/**
* @notice Gets the royalty percentage.
*
* @return royalty percentage
*/
function getRoyaltyPercentage() external view returns (uint256);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: core-components/subgraph currently uses this method to get the royaltyPercentage of a voucher contract
Impact of removal to be assessed


/**
* @notice Reserves a range of vouchers to be associated with an offer
*
Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/events/IBosonAccountEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ interface IBosonAccountEvents {
BosonTypes.AuthToken pendingAuthToken,
address indexed executedBy
);
event RoyaltyRecipientsChanged(
uint256 indexed sellerId,
BosonTypes.RoyaltyRecipient[] royaltyRecipients,
address indexed executedBy
);
event BuyerCreated(uint256 indexed buyerId, BosonTypes.Buyer buyer, address indexed executedBy);
event BuyerUpdated(uint256 indexed buyerId, BosonTypes.Buyer buyer, address indexed executedBy);
event AgentUpdated(uint256 indexed agentId, BosonTypes.Agent agent, address indexed executedBy);
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/events/IBosonConfigEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface IBosonConfigEvents {
address indexed executedBy
);
event MaxTotalOfferFeePercentageChanged(uint16 maxTotalOfferFeePercentage, address indexed executedBy);
event MaxRoyaltyPercentageChanged(uint16 maxRoyaltyPecentage, address indexed executedBy);
event MaxRoyaltyPercentageChanged(uint16 maxRoyaltyPercentage, address indexed executedBy);
event MinResolutionPeriodChanged(uint256 minResolutionPeriod, address indexed executedBy);
event MaxResolutionPeriodChanged(uint256 maxResolutionPeriod, address indexed executedBy);
event MinDisputePeriodChanged(uint256 minDisputePeriod, address indexed executedBy);
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/events/IBosonOfferEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@ interface IBosonOfferEvents {
address owner,
address indexed executedBy
);
event OfferRoyaltyInfoUpdated(
uint256 indexed offerId,
uint256 indexed sellerId,
BosonTypes.RoyaltyInfo royaltyInfo,
address indexed executedBy
);
}
78 changes: 77 additions & 1 deletion contracts/interfaces/handlers/IBosonAccountHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IBosonAccountEvents } from "../events/IBosonAccountEvents.sol";
*
* @notice Handles creation, update, retrieval of accounts within the protocol.
*
* The ERC-165 identifier for this interface is: 0xbc1d7461
* The ERC-165 identifier for this interface is: 0x890d5d20
*/
interface IBosonAccountHandler is IBosonAccountEvents, BosonErrors {
/**
Expand Down Expand Up @@ -147,6 +147,72 @@ interface IBosonAccountHandler is IBosonAccountEvents, BosonErrors {
*/
function optInToSellerUpdate(uint256 _sellerId, BosonTypes.SellerUpdateFields[] calldata _fieldsToUpdate) external;

/**
* @notice Adds royalty recipients to a seller.
*
* Emits a RoyalRecipientsUpdated event if successful.
*
* Reverts if:
* - The sellers region of protocol is paused
* - Seller does not exist
* - Caller is not the seller admin
* - Caller does not own auth token
* - Some recipient is not unique
* - some royalty percentage is above the limit
*
* @param _sellerId - seller id
* @param _royaltyRecipients - list of royalty recipients to add
*/
function addRoyaltyRecipients(
uint256 _sellerId,
BosonTypes.RoyaltyRecipient[] calldata _royaltyRecipients
) external;

/**
* @notice Updates seller's royalty recipients.
*
* Emits a RoyalRecipientsUpdated event if successful.
*
* Reverts if:
* - The sellers region of protocol is paused
* - Seller does not exist
* - Caller is not the seller admin
* - Caller does not own auth token
* - Length of ids to change does not match length of new values
* - Id to update does not exist
* - Seller tries to update the address of default recipient
* - Some recipient is not unique
* - Some royalty percentage is above the limit
*
* @param _sellerId - seller id
* @param _royaltyRecipientIds - list of royalty recipient ids to update
* @param _royaltyRecipients - list of new royalty recipients corresponding to ids
*/
function updateRoyaltyRecipients(
uint256 _sellerId,
uint256[] calldata _royaltyRecipientIds,
BosonTypes.RoyaltyRecipient[] calldata _royaltyRecipients
) external;

/**
* @notice Removes seller's royalty recipients.
*
* Emits a RoyalRecipientsUpdated event if successful.
*
* Reverts if:
* - The sellers region of protocol is paused
* - Seller does not exist
* - Caller is not the seller admin
* - Caller does not own auth token
* - List of ids to remove is not sorted in ascending order
* - Id to remove does not exist
* - Seller tries to remove the default recipient
*
* @param _sellerId - seller id
* @param _royaltyRecipientIds - list of royalty recipient ids to remove
*/
function removeRoyaltyRecipients(uint256 _sellerId, uint256[] calldata _royaltyRecipientIds) external;

/**
* @notice Updates a buyer, with the exception of the active flag.
* All other fields should be filled, even those staying the same.
Expand Down Expand Up @@ -416,6 +482,16 @@ interface IBosonAccountHandler is IBosonAccountEvents, BosonErrors {
bytes32 _collectionSalt
) external view returns (address collectionAddress, bool isAvailable);

/**
* @notice Gets seller's royalty recipients.
*
* @param _sellerId - seller id
* @return royaltyRecipients - list of royalty recipients
*/
function getRoyaltyRecipients(
uint256 _sellerId
) external view returns (BosonTypes.RoyaltyRecipient[] memory royaltyRecipients);

/**
* @notice Gets the details about a buyer.
*
Expand Down
10 changes: 5 additions & 5 deletions contracts/interfaces/handlers/IBosonConfigHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IBosonConfigEvents } from "../events/IBosonConfigEvents.sol";
*
* @notice Handles management of configuration within the protocol.
*
* The ERC-165 identifier for this interface is: 0xbc28d3e6
* The ERC-165 identifier for this interface is: 0x7899c7b9
*/
interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors {
/**
Expand Down Expand Up @@ -232,23 +232,23 @@ interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors {
*
* Reverts if:
* - The _maxRoyaltyPercentage is zero.
* - The _maxRoyaltyPecentage is greater than 10000.
* - The _maxRoyaltyPercentage is greater than 10000.
*
* @dev Caller must have ADMIN role.
*
* @param _maxRoyaltyPecentage - the maximum royalty percentage
* @param _maxRoyaltyPercentage - the maximum royalty percentage
*
* N.B. Represent percentage value as an unsigned int by multiplying the percentage by 100:
* e.g, 1.75% = 175, 100% = 10000
*/
function setMaxRoyaltyPecentage(uint16 _maxRoyaltyPecentage) external;
function setMaxRoyaltyPercentage(uint16 _maxRoyaltyPercentage) external;

/**
* @notice Gets the maximum royalty percentage that can be set by the seller.
*
* @return the maximum royalty percentage
*/
function getMaxRoyaltyPecentage() external view returns (uint16);
function getMaxRoyaltyPercentage() external view returns (uint16);

/**
* @notice Sets the minimum resolution period a seller can specify.
Expand Down
40 changes: 39 additions & 1 deletion contracts/interfaces/handlers/IBosonExchangeHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol";
*
* @notice Handles exchanges associated with offers within the protocol.
*
* The ERC-165 identifier for this interface is: 0x0e1fefcb
* The ERC-165 identifier for this interface is: 0xe480bd0f
*/
interface IBosonExchangeHandler is BosonErrors, IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents {
/**
Expand Down Expand Up @@ -274,6 +274,44 @@ interface IBosonExchangeHandler is BosonErrors, IBosonExchangeEvents, IBosonFund
*/
function getNextExchangeId() external view returns (uint256 nextExchangeId);

/**
* @notice Gets EIP2981 style royalty information for a chosen offer or exchange.
*
* EIP2981 supports only 1 recipient, therefore this method defaults to treasury address.
* This method is not exactly compliant with EIP2981, since it does not accept `salePrice` and does not return `royaltyAmount,
* but it rather returns `royaltyPercentage` which is the sum of all bps (an exchange can have multiple royalty recipients).
*
* This function is meant to be primarly used by boson voucher client, which implements EIP2981.
*
* Reverts if exchange does not exist.
*
* @param _queryId - offer id or exchange id
* @param _isExchangeId - indicates if the query represents the exchange id
* @return receiver - the address of the royalty receiver (seller's treasury address)
* @return royaltyPercentage - the royalty percentage in bps
*/
function getEIP2981Royalties(
uint256 _queryId,
bool _isExchangeId
) external view returns (address receiver, uint256 royaltyPercentage);

/**
* @notice Gets royalty information for a chosen offer or exchange.
*
* Returns a list of royalty recipients and corresponding bps. Format is compatible with Manifold and Foundation royalties
* and can be directly used by royalty registry.
*
* Reverts if exchange does not exist.
*
* @param _queryId - offer id or exchange id
* @param _isExchangeId - indicates if the query represents the exchange id
* @return royaltyInfo - list of royalty recipients and corresponding bps
*/
function getRoyalties(
uint256 _queryId,
bool _isExchangeId
) external view returns (BosonTypes.RoyaltyInfo memory royaltyInfo);

/**
* @notice Gets exchange receipt.
*
Expand Down
Loading