Skip to content

Commit

Permalink
Adding KODA v2 royalties registry proxy code
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesmorgan committed Dec 14, 2022
1 parent d30907e commit 6cf5eb1
Show file tree
Hide file tree
Showing 5 changed files with 396 additions and 0 deletions.
264 changes: 264 additions & 0 deletions contracts-flat/KODAV2Override.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
// SPDX-License-Identifier: MIT

/// @author: knownorigin.io

pragma solidity ^0.8.0;

/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}

function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}

/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_setOwner(_msgSender());
}

/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}

/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}

/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}

/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}

function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}


/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}


/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}

/**
* @dev Storage based implementation of the {IERC165} interface.
*
* Contracts may inherit from this and call {_registerInterface} to declare
* their support of an interface.
*/
abstract contract ERC165Storage is ERC165 {
/**
* @dev Mapping of interface ids to whether or not it's supported.
*/
mapping(bytes4 => bool) private _supportedInterfaces;

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return super.supportsInterface(interfaceId) || _supportedInterfaces[interfaceId];
}

/**
* @dev Registers the contract as an implementer of the interface defined by
* `interfaceId`. Support of the actual ERC165 interface is automatic and
* registering its interface id is not required.
*
* See {IERC165-supportsInterface}.
*
* Requirements:
*
* - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
*/
function _registerInterface(bytes4 interfaceId) internal virtual {
require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
_supportedInterfaces[interfaceId] = true;
}
}

interface IKODAV2 {
function editionOfTokenId(uint256 _tokenId) external view returns (uint256 _editionNumber);

function artistCommission(uint256 _editionNumber) external view returns (address _artistAccount, uint256 _artistCommission);

function editionOptionalCommission(uint256 _editionNumber) external view returns (uint256 _rate, address _recipient);
}

interface IKODAV2Override {

/// @notice Emitted when the royalties fee changes
event CreatorRoyaltiesFeeUpdated(uint256 _oldCreatorRoyaltiesFee, uint256 _newCreatorRoyaltiesFee);

/// @notice For the given KO NFT and token ID, return the addresses and the amounts to pay
function getKODAV2RoyaltyInfo(address _tokenAddress, uint256 _id, uint256 _amount)
external
view
returns (address payable[] memory, uint256[] memory);

/// @notice Allows the owner() to update the creator royalties
function updateCreatorRoyalties(uint256 _creatorRoyaltiesFee) external;
}

/**
* @dev Implementation of KODA V2 override
* @notice KnownOrigin V2 (KODAV2) records expected commissions in simplistic single digit commission i.e. 100 = 10.0%, we dont store
* a primary vs secondary expected commission amount so we need work this out proportionally
*/
contract KODAV2Override is IKODAV2Override, ERC165Storage, Ownable {

function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Storage) returns (bool) {
return interfaceId == type(IKODAV2Override).interfaceId || super.supportsInterface(interfaceId);
}

/// @notice precision 100.00000%
uint256 public modulo = 100_00000;

/// @notice Secondary sale royalty fee
uint256 public creatorRoyaltiesFee = 10_00000; // 10% by default

function getKODAV2RoyaltyInfo(address _tokenAddress, uint256 _id, uint256 _amount)
external
override
view
returns (address payable[] memory receivers, uint256[] memory amounts) {

// Get the edition the token is part of
uint256 _editionNumber = IKODAV2(_tokenAddress).editionOfTokenId(_id);
require(_editionNumber > 0, "Edition not found for token ID");

// Get existing artist commission
(address artistAccount, uint256 artistCommissionRate) = IKODAV2(_tokenAddress).artistCommission(_editionNumber);

// work out the expected royalty payment
uint256 totalRoyaltyToPay = (_amount / modulo) * creatorRoyaltiesFee;

// Get optional commission set against the edition and work out the expected commission
(uint256 optionalCommissionRate, address optionalCommissionRecipient) = IKODAV2(_tokenAddress).editionOptionalCommission(_editionNumber);
if (optionalCommissionRate > 0) {

receivers = new address payable[](2);
amounts = new uint256[](2);

uint256 totalCommission = artistCommissionRate + optionalCommissionRate;

// Add the artist and commission
receivers[0] = payable(artistAccount);
amounts[0] = (totalRoyaltyToPay / totalCommission) * artistCommissionRate;

// Add optional splits
receivers[1] = payable(optionalCommissionRecipient);
amounts[1] = (totalRoyaltyToPay / totalCommission) * optionalCommissionRate;
} else {
receivers = new address payable[](1);
amounts = new uint256[](1);

// Add the artist and commission
receivers[0] = payable(artistAccount);
amounts[0] = totalRoyaltyToPay;
}

return (receivers, amounts);
}

function updateCreatorRoyalties(uint256 _creatorRoyaltiesFee) external override onlyOwner {
emit CreatorRoyaltiesFeeUpdated(creatorRoyaltiesFee, _creatorRoyaltiesFee);
creatorRoyaltiesFee = _creatorRoyaltiesFee;
}

}
28 changes: 28 additions & 0 deletions contracts/royalties-registry-proxy/IKODAV2Override.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT

/// @author: knownorigin.io

pragma solidity ^0.8.0;

interface IKODAV2 {
function editionOfTokenId(uint256 _tokenId) external view returns (uint256 _editionNumber);

function artistCommission(uint256 _editionNumber) external view returns (address _artistAccount, uint256 _artistCommission);

function editionOptionalCommission(uint256 _editionNumber) external view returns (uint256 _rate, address _recipient);
}

interface IKODAV2Override {

/// @notice Emitted when the royalties fee changes
event CreatorRoyaltiesFeeUpdated(uint256 _oldCreatorRoyaltiesFee, uint256 _newCreatorRoyaltiesFee);

/// @notice For the given KO NFT and token ID, return the addresses and the amounts to pay
function getKODAV2RoyaltyInfo(address _tokenAddress, uint256 _id, uint256 _amount)
external
view
returns (address payable[] memory, uint256[] memory);

/// @notice Allows the owner() to update the creator royalties
function updateCreatorRoyalties(uint256 _creatorRoyaltiesFee) external;
}
77 changes: 77 additions & 0 deletions contracts/royalties-registry-proxy/KODAV2Override.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @author: knownorigin.io

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC165Storage} from "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol";
import "./IKODAV2Override.sol";

/**
* @dev Implementation of KODA V2 override
* @notice KnownOrigin V2 (KODAV2) records expected commissions in simplistic single digit commission i.e. 100 = 10.0%, we dont store
* a primary vs secondary expected commission amount so we need work this out proportionally
*/
contract KODAV2Override is IKODAV2Override, ERC165Storage, Ownable {

function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Storage) returns (bool) {
return interfaceId == type(IKODAV2Override).interfaceId || super.supportsInterface(interfaceId);
}

/// @notice precision 100.00000%
uint256 public modulo = 100_00000;

/// @notice Secondary sale royalty fee
uint256 public creatorRoyaltiesFee = 10_00000; // 10% by default

function getKODAV2RoyaltyInfo(address _tokenAddress, uint256 _id, uint256 _amount)
external
override
view
returns (address payable[] memory receivers, uint256[] memory amounts) {

// Get the edition the token is part of
uint256 _editionNumber = IKODAV2(_tokenAddress).editionOfTokenId(_id);
require(_editionNumber > 0, "Edition not found for token ID");

// Get existing artist commission
(address artistAccount, uint256 artistCommissionRate) = IKODAV2(_tokenAddress).artistCommission(_editionNumber);

// work out the expected royalty payment
uint256 totalRoyaltyToPay = (_amount / modulo) * creatorRoyaltiesFee;

// Get optional commission set against the edition and work out the expected commission
(uint256 optionalCommissionRate, address optionalCommissionRecipient) = IKODAV2(_tokenAddress).editionOptionalCommission(_editionNumber);
if (optionalCommissionRate > 0) {

receivers = new address payable[](2);
amounts = new uint256[](2);

uint256 totalCommission = artistCommissionRate + optionalCommissionRate;

// Add the artist and commission
receivers[0] = payable(artistAccount);
amounts[0] = (totalRoyaltyToPay / totalCommission) * artistCommissionRate;

// Add optional splits
receivers[1] = payable(optionalCommissionRecipient);
amounts[1] = (totalRoyaltyToPay / totalCommission) * optionalCommissionRate;
} else {
receivers = new address payable[](1);
amounts = new uint256[](1);

// Add the artist and commission
receivers[0] = payable(artistAccount);
amounts[0] = totalRoyaltyToPay;
}

return (receivers, amounts);
}

function updateCreatorRoyalties(uint256 _creatorRoyaltiesFee) external override onlyOwner {
emit CreatorRoyaltiesFeeUpdated(creatorRoyaltiesFee, _creatorRoyaltiesFee);
creatorRoyaltiesFee = _creatorRoyaltiesFee;
}

}
26 changes: 26 additions & 0 deletions contracts/royalties-registry-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Royalties Registry Proxy for KODA V2

This is the working directory for the sample code for KO V2 royalties registry support.

See: [https://royaltyregistry.xyz](https://royaltyregistry.xyz)

* `RoyaltyEngine` - https://etherscan.io/address/0x0385603ab55642cb4dd5de3ae9e306809991804f
* `RoylatyRegistry` - https://etherscan.io/address/0xad2184fb5dbcfc05d8f056542fb25b04fa32a95d

Methods:

* Method `getKODAV2RoyaltyInfo(address _tokenAddress, uint256 _id, uint256 _amount)`
* This will return the list of accounts and amounts to pay royalties to

* Original PR to add the basic functionality https://github.com/manifoldxyz/royalty-registry-solidity/pull/27

#### Mainnet Deployment

`0x999082546a522eefdc64be8c2a15fdbc94db348d`

You can see it in action here: https://royaltyregistry.xyz/0xfbeef911dc5821886e1dda71586d90ed28174b7d/242853

#### Goerli Deployment

`0xa4ec0c66dbf9ef539524b4183d94c5d33948914b`

Loading

0 comments on commit 6cf5eb1

Please sign in to comment.