Contract Name:
LazyMintERC1155
Contract Source Code:
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
// Interface
import { ILazyMintERC1155 } from "./ILazyMintERC1155.sol";
// Token
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
// Protocol control center.
import { ProtocolControl } from "../../ProtocolControl.sol";
// Royalties
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
// Access Control + security
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
// Meta transactions
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
// Utils
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/utils/Multicall.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
// Helper interfaces
import { IWETH } from "../../interfaces/IWETH.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract LazyMintERC1155 is
ILazyMintERC1155,
ERC1155,
ERC2771Context,
IERC2981,
AccessControlEnumerable,
ReentrancyGuard,
Multicall
{
using Strings for uint256;
/// @dev Only TRANSFER_ROLE holders can have tokens transferred from or to them, during restricted transfers.
bytes32 public constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE");
/// @dev Only MINTER_ROLE holders can lazy mint NFTs (i.e. can call functions prefixed with `lazyMint`).
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
/// @dev The address interpreted as native token of the chain.
address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev The address of the native token wrapper contract.
address public immutable nativeTokenWrapper;
/// @dev The adress that receives all primary sales value.
address public defaultSaleRecipient;
/// @dev The next token ID of the NFT to "lazy mint".
uint256 public nextTokenIdToMint;
/// @dev Contract interprets 10_000 as 100%.
uint64 private constant MAX_BPS = 10_000;
/// @dev The % of secondary sales collected as royalties. See EIP 2981.
uint64 public royaltyBps;
/// @dev The % of primary sales collected by the contract as fees.
uint120 public feeBps;
/// @dev Whether transfers on tokens are restricted.
bool public transfersRestricted;
/// @dev Contract level metadata.
string public contractURI;
/// @dev The protocol control center.
ProtocolControl internal controlCenter;
uint256[] private baseURIIndices;
/// @dev End token Id => URI that overrides `baseURI + tokenId` convention.
mapping(uint256 => string) private baseURI;
/// @dev Token ID => total circulating supply of tokens with that ID.
mapping(uint256 => uint256) public totalSupply;
/// @dev Token ID => public claim conditions for tokens with that ID.
mapping(uint256 => ClaimConditions) public claimConditions;
/// @dev Token ID => the address of the recipient of primary sales.
mapping(uint256 => address) public saleRecipient;
/// @dev Checks whether caller has DEFAULT_ADMIN_ROLE on the protocol control center.
modifier onlyProtocolAdmin() {
require(controlCenter.hasRole(controlCenter.DEFAULT_ADMIN_ROLE(), _msgSender()), "not protocol admin.");
_;
}
/// @dev Checks whether caller has DEFAULT_ADMIN_ROLE.
modifier onlyModuleAdmin() {
require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "not module admin.");
_;
}
/// @dev Checks whether caller has MINTER_ROLE.
modifier onlyMinter() {
require(hasRole(MINTER_ROLE, _msgSender()), "not minter.");
_;
}
constructor(
string memory _contractURI,
address payable _controlCenter,
address _trustedForwarder,
address _nativeTokenWrapper,
address _saleRecipient,
uint128 _royaltyBps,
uint128 _feeBps
) ERC1155("") ERC2771Context(_trustedForwarder) {
controlCenter = ProtocolControl(_controlCenter);
nativeTokenWrapper = _nativeTokenWrapper;
defaultSaleRecipient = _saleRecipient;
contractURI = _contractURI;
royaltyBps = uint64(_royaltyBps);
feeBps = uint120(_feeBps);
address deployer = _msgSender();
_setupRole(DEFAULT_ADMIN_ROLE, deployer);
_setupRole(MINTER_ROLE, deployer);
_setupRole(TRANSFER_ROLE, deployer);
}
/// ===== Public functions =====
/// @dev Returns the URI for a given tokenId.
function uri(uint256 _tokenId) public view override returns (string memory _tokenURI) {
for (uint256 i = 0; i < baseURIIndices.length; i += 1) {
if (_tokenId < baseURIIndices[i]) {
return string(abi.encodePacked(baseURI[baseURIIndices[i]], _tokenId.toString()));
}
}
return "";
}
/// @dev Returns the URI for a given tokenId.
function tokenURI(uint256 _tokenId) public view returns (string memory _tokenURI) {
return uri(_tokenId);
}
/// @dev At any given moment, returns the uid for the active mint condition for a given tokenId.
function getIndexOfActiveCondition(uint256 _tokenId) public view returns (uint256) {
uint256 totalConditionCount = claimConditions[_tokenId].totalConditionCount;
require(totalConditionCount > 0, "no public mint condition.");
for (uint256 i = totalConditionCount; i > 0; i -= 1) {
if (block.timestamp >= claimConditions[_tokenId].claimConditionAtIndex[i - 1].startTimestamp) {
return i - 1;
}
}
revert("no active mint condition.");
}
/// ===== External functions =====
/**
* @dev Lets an account with `MINTER_ROLE` mint tokens of ID from `nextTokenIdToMint`
* to `nextTokenIdToMint + _amount - 1`. The URIs for these tokenIds is baseURI + `${tokenId}`.
*/
function lazyMint(uint256 _amount, string calldata _baseURIForTokens) external onlyMinter {
uint256 startId = nextTokenIdToMint;
uint256 baseURIIndex = startId + _amount;
nextTokenIdToMint = baseURIIndex;
baseURI[baseURIIndex] = _baseURIForTokens;
baseURIIndices.push(baseURIIndex);
emit LazyMintedTokens(startId, startId + _amount - 1, _baseURIForTokens);
}
/// @dev Lets an account claim a given quantity of tokens, of a single tokenId.
function claim(
uint256 _tokenId,
uint256 _quantity,
bytes32[] calldata _proofs
) external payable nonReentrant {
// Get the claim conditions.
uint256 activeConditionIndex = getIndexOfActiveCondition(_tokenId);
ClaimCondition memory condition = claimConditions[_tokenId].claimConditionAtIndex[activeConditionIndex];
// Verify claim validity. If not valid, revert.
verifyClaimIsValid(_tokenId, _quantity, _proofs, activeConditionIndex, condition);
// If there's a price, collect price.
collectClaimPrice(condition, _quantity, _tokenId);
// Mint the relevant tokens to claimer.
transferClaimedTokens(activeConditionIndex, _tokenId, _quantity);
emit ClaimedTokens(activeConditionIndex, _tokenId, _msgSender(), _quantity);
}
// @dev Lets a module admin update mint conditions without resetting the restrictions.
function updateClaimConditions(uint256 _tokenId, ClaimCondition[] calldata _conditions) external onlyModuleAdmin {
resetClaimConditions(_tokenId, _conditions);
emit NewClaimConditions(_tokenId, _conditions);
}
/// @dev Lets a module admin set mint conditions.
function setClaimConditions(uint256 _tokenId, ClaimCondition[] calldata _conditions) external onlyModuleAdmin {
uint256 numOfConditionsSet = resetClaimConditions(_tokenId, _conditions);
resetTimestampRestriction(_tokenId, numOfConditionsSet);
emit NewClaimConditions(_tokenId, _conditions);
}
/// @dev See EIP 2981
function royaltyInfo(uint256, uint256 salePrice)
external
view
virtual
override
returns (address receiver, uint256 royaltyAmount)
{
receiver = controlCenter.getRoyaltyTreasury(address(this));
royaltyAmount = (salePrice * royaltyBps) / MAX_BPS;
}
// ===== Setter functions =====
/// @dev Lets a module admin set the default recipient of all primary sales.
function setDefaultSaleRecipient(address _saleRecipient) external onlyModuleAdmin {
defaultSaleRecipient = _saleRecipient;
emit NewSaleRecipient(_saleRecipient, type(uint256).max, true);
}
/// @dev Lets a module admin set the recipient of all primary sales for a given token ID.
function setSaleRecipient(uint256 _tokenId, address _saleRecipient) external onlyModuleAdmin {
saleRecipient[_tokenId] = _saleRecipient;
emit NewSaleRecipient(_saleRecipient, _tokenId, false);
}
/// @dev Lets a module admin update the royalties paid on secondary token sales.
function setRoyaltyBps(uint256 _royaltyBps) public onlyModuleAdmin {
require(_royaltyBps <= MAX_BPS, "bps <= 10000.");
royaltyBps = uint64(_royaltyBps);
emit RoyaltyUpdated(_royaltyBps);
}
/// @dev Lets a module admin update the fees on primary sales.
function setFeeBps(uint256 _feeBps) public onlyModuleAdmin {
require(_feeBps <= MAX_BPS, "bps <= 10000.");
feeBps = uint120(_feeBps);
emit PrimarySalesFeeUpdates(_feeBps);
}
/// @dev Lets a module admin restrict token transfers.
function setRestrictedTransfer(bool _restrictedTransfer) external onlyModuleAdmin {
transfersRestricted = _restrictedTransfer;
emit TransfersRestricted(_restrictedTransfer);
}
/// @dev Lets a module admin set the URI for contract-level metadata.
function setContractURI(string calldata _uri) external onlyProtocolAdmin {
contractURI = _uri;
}
// ===== Getter functions =====
/// @dev Returns the current active mint condition for a given tokenId.
function getTimestampForNextValidClaim(
uint256 _tokenId,
uint256 _index,
address _claimer
) public view returns (uint256 nextValidTimestampForClaim) {
uint256 timestampIndex = _index + claimConditions[_tokenId].timstampLimitIndex;
uint256 timestampOfLastClaim = claimConditions[_tokenId].timestampOfLastClaim[_claimer][timestampIndex];
unchecked {
nextValidTimestampForClaim =
timestampOfLastClaim +
claimConditions[_tokenId].claimConditionAtIndex[_index].waitTimeInSecondsBetweenClaims;
if (nextValidTimestampForClaim < timestampOfLastClaim) {
nextValidTimestampForClaim = type(uint256).max;
}
}
}
/// @dev Returns the mint condition for a given tokenId, at the given index.
function getClaimConditionAtIndex(uint256 _tokenId, uint256 _index)
external
view
returns (ClaimCondition memory mintCondition)
{
mintCondition = claimConditions[_tokenId].claimConditionAtIndex[_index];
}
// ===== Internal functions =====
/// @dev Lets a module admin set mint conditions for a given tokenId.
function resetClaimConditions(uint256 _tokenId, ClaimCondition[] calldata _conditions)
internal
returns (uint256 indexForCondition)
{
// make sure the conditions are sorted in ascending order
uint256 lastConditionStartTimestamp;
for (uint256 i = 0; i < _conditions.length; i++) {
require(
lastConditionStartTimestamp == 0 || lastConditionStartTimestamp < _conditions[i].startTimestamp,
"startTimestamp must be in ascending order."
);
require(_conditions[i].maxClaimableSupply > 0, "max mint supply cannot be 0.");
require(_conditions[i].quantityLimitPerTransaction > 0, "quantity limit cannot be 0.");
claimConditions[_tokenId].claimConditionAtIndex[indexForCondition] = ClaimCondition({
startTimestamp: _conditions[i].startTimestamp,
maxClaimableSupply: _conditions[i].maxClaimableSupply,
supplyClaimed: 0,
quantityLimitPerTransaction: _conditions[i].quantityLimitPerTransaction,
waitTimeInSecondsBetweenClaims: _conditions[i].waitTimeInSecondsBetweenClaims,
pricePerToken: _conditions[i].pricePerToken,
currency: _conditions[i].currency,
merkleRoot: _conditions[i].merkleRoot
});
indexForCondition += 1;
lastConditionStartTimestamp = _conditions[i].startTimestamp;
}
uint256 totalConditionCount = claimConditions[_tokenId].totalConditionCount;
if (indexForCondition < totalConditionCount) {
for (uint256 j = indexForCondition; j < totalConditionCount; j += 1) {
delete claimConditions[_tokenId].claimConditionAtIndex[j];
}
}
claimConditions[_tokenId].totalConditionCount = indexForCondition;
}
/// @dev Updates the `timstampLimitIndex` to reset the time restriction between claims, for a claim condition.
function resetTimestampRestriction(uint256 _tokenId, uint256 _factor) internal {
claimConditions[_tokenId].timstampLimitIndex += _factor;
}
/// @dev Checks whether a request to claim tokens obeys the active mint condition.
function verifyClaimIsValid(
uint256 _tokenId,
uint256 _quantity,
bytes32[] calldata _proofs,
uint256 _conditionIndex,
ClaimCondition memory _mintCondition
) internal view {
require(_quantity > 0 && _quantity <= _mintCondition.quantityLimitPerTransaction, "invalid quantity claimed.");
require(
_mintCondition.supplyClaimed + _quantity <= _mintCondition.maxClaimableSupply,
"exceed max mint supply."
);
uint256 timestampIndex = _conditionIndex + claimConditions[_tokenId].timstampLimitIndex;
uint256 timestampOfLastClaim = claimConditions[_tokenId].timestampOfLastClaim[_msgSender()][timestampIndex];
uint256 nextValidTimestampForClaim = getTimestampForNextValidClaim(_tokenId, _conditionIndex, _msgSender());
require(timestampOfLastClaim == 0 || block.timestamp >= nextValidTimestampForClaim, "cannot claim yet.");
if (_mintCondition.merkleRoot != bytes32(0)) {
bytes32 leaf = keccak256(abi.encodePacked(_msgSender()));
require(MerkleProof.verify(_proofs, _mintCondition.merkleRoot, leaf), "not in whitelist.");
}
}
/// @dev Collects and distributes the primary sale value of tokens being claimed.
function collectClaimPrice(
ClaimCondition memory _mintCondition,
uint256 _quantityToClaim,
uint256 _tokenId
) internal {
if (_mintCondition.pricePerToken <= 0) {
return;
}
uint256 totalPrice = _quantityToClaim * _mintCondition.pricePerToken;
uint256 fees = (totalPrice * feeBps) / MAX_BPS;
if (_mintCondition.currency == NATIVE_TOKEN) {
require(msg.value == totalPrice, "must send total price.");
} else {
validateERC20BalAndAllowance(_msgSender(), _mintCondition.currency, totalPrice);
}
transferCurrency(_mintCondition.currency, _msgSender(), controlCenter.getRoyaltyTreasury(address(this)), fees);
address recipient = saleRecipient[_tokenId];
transferCurrency(
_mintCondition.currency,
_msgSender(),
recipient == address(0) ? defaultSaleRecipient : recipient,
totalPrice - fees
);
}
/// @dev Transfers the tokens being claimed.
function transferClaimedTokens(
uint256 _claimConditionIndex,
uint256 _tokenId,
uint256 _quantityBeingClaimed
) internal {
// Update the supply minted under mint condition.
claimConditions[_tokenId].claimConditionAtIndex[_claimConditionIndex].supplyClaimed += _quantityBeingClaimed;
// Update the claimer's next valid timestamp to mint. If next mint timestamp overflows, cap it to max uint256.
uint256 timestampIndex = _claimConditionIndex + claimConditions[_tokenId].timstampLimitIndex;
claimConditions[_tokenId].timestampOfLastClaim[_msgSender()][timestampIndex] = block.timestamp;
_mint(_msgSender(), _tokenId, _quantityBeingClaimed, "");
}
/// @dev Transfers a given amount of currency.
function transferCurrency(
address _currency,
address _from,
address _to,
uint256 _amount
) internal {
if (_amount == 0 || _from == _to) {
return;
}
if (_currency == NATIVE_TOKEN) {
if (_from == address(this)) {
IWETH(nativeTokenWrapper).withdraw(_amount);
safeTransferNativeToken(_to, _amount);
} else if (_to == address(this)) {
require(_amount == msg.value, "native token value does not match bid amount.");
IWETH(nativeTokenWrapper).deposit{ value: _amount }();
} else {
safeTransferNativeToken(_to, _amount);
}
} else {
safeTransferERC20(_currency, _from, _to, _amount);
}
}
/// @dev Validates that `_addrToCheck` owns and has approved contract to transfer the appropriate amount of currency
function validateERC20BalAndAllowance(
address _addrToCheck,
address _currency,
uint256 _currencyAmountToCheckAgainst
) internal view {
require(
IERC20(_currency).balanceOf(_addrToCheck) >= _currencyAmountToCheckAgainst &&
IERC20(_currency).allowance(_addrToCheck, address(this)) >= _currencyAmountToCheckAgainst,
"insufficient currency balance or allowance."
);
}
/// @dev Transfers `amount` of native token to `to`.
function safeTransferNativeToken(address to, uint256 value) internal {
(bool success, ) = to.call{ value: value }("");
if (!success) {
IWETH(nativeTokenWrapper).deposit{ value: value }();
safeTransferERC20(nativeTokenWrapper, address(this), to, value);
}
}
/// @dev Transfer `amount` of ERC20 token from `from` to `to`.
function safeTransferERC20(
address _currency,
address _from,
address _to,
uint256 _amount
) internal {
uint256 balBefore = IERC20(_currency).balanceOf(_to);
bool success = IERC20(_currency).transferFrom(_from, _to, _amount);
uint256 balAfter = IERC20(_currency).balanceOf(_to);
require(success && balAfter == balBefore + _amount, "failed to transfer currency.");
}
/// ===== ERC 1155 functions =====
/// @dev Lets a token owner burn the tokens they own (i.e. destroy for good)
function burn(
address account,
uint256 id,
uint256 value
) public virtual {
require(
account == _msgSender() || isApprovedForAll(account, _msgSender()),
"ERC1155: caller is not owner nor approved."
);
_burn(account, id, value);
}
/// @dev Lets a token owner burn multiple tokens they own at once (i.e. destroy for good)
function burnBatch(
address account,
uint256[] memory ids,
uint256[] memory values
) public virtual {
require(
account == _msgSender() || isApprovedForAll(account, _msgSender()),
"ERC1155: caller is not owner nor approved."
);
_burnBatch(account, ids, values);
}
/**
* @dev See {ERC1155-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override {
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
// if transfer is restricted on the contract, we still want to allow burning and minting
if (transfersRestricted && from != address(0) && to != address(0)) {
require(hasRole(TRANSFER_ROLE, from) || hasRole(TRANSFER_ROLE, to), "restricted to TRANSFER_ROLE holders.");
}
if (from == address(0)) {
for (uint256 i = 0; i < ids.length; ++i) {
totalSupply[ids[i]] += amounts[i];
}
}
if (to == address(0)) {
for (uint256 i = 0; i < ids.length; ++i) {
totalSupply[ids[i]] -= amounts[i];
}
}
}
/// ===== Low level overrides =====
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC1155, AccessControlEnumerable, IERC165)
returns (bool)
{
return interfaceId == type(IERC1155).interfaceId || interfaceId == type(IERC2981).interfaceId;
}
function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) {
return ERC2771Context._msgSender();
}
function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/**
* `LazyMintERC1155` is an ERC 1155 contract. It takes in a base URI in its
* constructor (e.g. "ipsf://Qmece.../"), and the URI for each token of ID
* `tokenId` is baseURI + `${tokenId}` (e.g. "ipsf://Qmece.../1").
*
* For each token with a unique ID, the module admin (account with `DEFAULT_ADMIN ROLE`)
* can create mint conditions with non-overlapping time windows, and accounts can claim
* the NFTs, in a given time window, according to that time window's mint conditions.
*/
interface ILazyMintERC1155 {
/**
* @notice The mint conditions for a given tokenId x time window.
*
* @param startTimestamp The unix timestamp after which the mint conditions last.
* The same mint conditions last until the `startTimestamp`
* of the next mint condition.
*
* @param maxClaimableSupply The maximum number of tokens of the same `tokenId` that can
* be claimed under the mint condition.
*
* @param supplyClaimed At any given point, the number of tokens of the same `tokenId`
* that have been claimed.
*
* @param quantityLimitPerTransaction The maximum number of tokens a single account can
* claim in a single transaction.
*
* @param waitTimeInSecondsBetweenClaims The least number of seconds an account must wait
* after claiming tokens, to be able to claim again.
*
* @param merkleRoot Only accounts whose address is a leaf of `merkleRoot` can claim tokens
* under the mint condition.
*
* @param pricePerToken The price per token that can be claimed.
*
* @param currency The currency in which `pricePerToken` must be paid.
*/
struct ClaimCondition {
uint256 startTimestamp;
uint256 maxClaimableSupply;
uint256 supplyClaimed;
uint256 quantityLimitPerTransaction;
uint256 waitTimeInSecondsBetweenClaims;
bytes32 merkleRoot;
uint256 pricePerToken;
address currency;
}
/**
* @notice The set of all mint conditions for a given tokenId.
*
* @dev In the contract, we use this in a mapping: tokenId => mint conditions i.e.
* mapping(uint256 => PublicMintConditions) public mintConditions;
*
* @param totalConditionCount The uid for each mint condition. Incremented
* by one every time a mint condition is created.
*
* @param claimConditionAtIndex The mint conditions at a given uid. Mint conditions
* are ordered in an ascending order by their `startTimestamp`.
*
* @param nextValidTimestampForClaim Account => uid for a mint condition => timestamp after
* which the account can claim tokens again.
*/
struct ClaimConditions {
uint256 totalConditionCount;
uint256 timstampLimitIndex;
mapping(uint256 => ClaimCondition) claimConditionAtIndex;
mapping(address => mapping(uint256 => uint256)) timestampOfLastClaim;
}
/// @dev Emitted when tokens are lazy minted.
event LazyMintedTokens(uint256 startTokenId, uint256 endTokenId, string baseURI);
/// @dev Emitted when tokens are claimed.
event ClaimedTokens(
uint256 indexed claimConditionIndex,
uint256 indexed tokenId,
address indexed claimer,
uint256 quantityClaimed
);
/// @dev Emitted when new mint conditions are set for a token.
event NewClaimConditions(uint256 indexed tokenId, ClaimCondition[] claimConditions);
/// @dev Emitted when a new sale recipient is set.
event NewSaleRecipient(address indexed recipient, uint256 indexed _tokenId, bool isDefaultRecipient);
/// @dev Emitted when the royalty fee bps is updated
event RoyaltyUpdated(uint256 newRoyaltyBps);
/// @dev Emitted when fee on primary sales is updated.
event PrimarySalesFeeUpdates(uint256 newFeeBps);
/// @dev Emitted when transfers are set as restricted / not-restricted.
event TransfersRestricted(bool restricted);
/// @dev The next token ID of the NFT to "lazy mint".
function nextTokenIdToMint() external returns (uint256);
/**
* @notice Lets an account with `MINTER_ROLE` mint tokens of ID from `nextTokenIdToMint`
* to `nextTokenIdToMint + _amount - 1`. The URIs for these tokenIds is baseURI + `${tokenId}`.
*
* @param _amount The amount of tokens (each with a unique tokenId) to lazy mint.
*/
function lazyMint(uint256 _amount, string calldata _baseURIForTokens) external;
/**
* @notice Lets an account claim a given quantity of tokens, of a single tokenId.
*
* @param _tokenId The unique ID of the token to claim.
* @param _quantity The quantity of tokens to claim.
* @param _proofs The proof required to prove the account's inclusion in the merkle root whitelist
* of the mint conditions that apply.
*/
function claim(
uint256 _tokenId,
uint256 _quantity,
bytes32[] calldata _proofs
) external payable;
/**
* @notice Lets a module admin (account with `DEFAULT_ADMIN_ROLE`) set mint conditions for a given token ID.
*
* @param _tokenId The token ID for which to set mint conditions.
* @param _conditions Mint conditions in ascending order by `startTimestamp`.
*/
function setClaimConditions(uint256 _tokenId, ClaimCondition[] calldata _conditions) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/ERC1155.sol)
pragma solidity ^0.8.0;
import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of the basic standard multi-token.
* See https://eips.ethereum.org/EIPS/eip-1155
* Originally based on code by Enjin: https://github.com/enjin/erc-1155
*
* _Available since v3.1._
*/
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
using Address for address;
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;
// Mapping from account to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
string private _uri;
/**
* @dev See {_setURI}.
*/
constructor(string memory uri_) {
_setURI(uri_);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC1155).interfaceId ||
interfaceId == type(IERC1155MetadataURI).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC1155MetadataURI-uri}.
*
* This implementation returns the same URI for *all* token types. It relies
* on the token type ID substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
*
* Clients calling this function must replace the `\{id\}` substring with the
* actual token type ID.
*/
function uri(uint256) public view virtual override returns (string memory) {
return _uri;
}
/**
* @dev See {IERC1155-balanceOf}.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
require(account != address(0), "ERC1155: balance query for the zero address");
return _balances[id][account];
}
/**
* @dev See {IERC1155-balanceOfBatch}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
public
view
virtual
override
returns (uint256[] memory)
{
require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}
return batchBalances;
}
/**
* @dev See {IERC1155-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC1155-isApprovedForAll}.
*/
function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
return _operatorApprovals[account][operator];
}
/**
* @dev See {IERC1155-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not owner nor approved"
);
_safeTransferFrom(from, to, id, amount, data);
}
/**
* @dev See {IERC1155-safeBatchTransferFrom}.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: transfer caller is not owner nor approved"
);
_safeBatchTransferFrom(from, to, ids, amounts, data);
}
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data);
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
emit TransferSingle(operator, from, to, id, amount);
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
}
emit TransferBatch(operator, from, to, ids, amounts);
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}
/**
* @dev Sets a new URI for all token types, by relying on the token type ID
* substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
*
* By this mechanism, any occurrence of the `\{id\}` substring in either the
* URI or any of the amounts in the JSON file at said URI will be replaced by
* clients with the token type ID.
*
* For example, the `https://token-cdn-domain/\{id\}.json` URI would be
* interpreted by clients as
* `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
* for token type ID 0x4cce0.
*
* See {uri}.
*
* Because these URIs cannot be meaningfully represented by the {URI} event,
* this function emits no events.
*/
function _setURI(string memory newuri) internal virtual {
_uri = newuri;
}
/**
* @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), to, _asSingletonArray(id), _asSingletonArray(amount), data);
_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);
_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; i++) {
_balances[ids[i]][to] += amounts[i];
}
emit TransferBatch(operator, address(0), to, ids, amounts);
_doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}
/**
* @dev Destroys `amount` tokens of token type `id` from `from`
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `from` must have at least `amount` tokens of token type `id`.
*/
function _burn(
address from,
uint256 id,
uint256 amount
) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, address(0), _asSingletonArray(id), _asSingletonArray(amount), "");
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
emit TransferSingle(operator, from, address(0), id, amount);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
*/
function _burnBatch(
address from,
uint256[] memory ids,
uint256[] memory amounts
) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
}
emit TransferBatch(operator, from, address(0), ids, amounts);
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits a {ApprovalForAll} event.
*/
function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC1155: setting approval status for self");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `id` and `amount` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
* - `ids` and `amounts` have the same, non-zero length.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {}
function _doSafeTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non ERC1155Receiver implementer");
}
}
}
function _doSafeBatchTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
bytes4 response
) {
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non ERC1155Receiver implementer");
}
}
}
function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
uint256[] memory array = new uint256[](1);
array[0] = element;
return array;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
// Access Control
import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
// Registry
import { Registry } from "./Registry.sol";
import { Royalty } from "./Royalty.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ProtocolControl is AccessControlEnumerable {
/// @dev MAX_BPS for the contract: 10_000 == 100%
uint128 public constant MAX_BPS = 10000;
/// @dev Module ID => Module address.
mapping(bytes32 => address) public modules;
/// @dev Module type => Num of modules of that type.
mapping(uint256 => uint256) public numOfModuleType;
/// @dev module address => royalty address
mapping(address => address) private moduleRoyalty;
/// @dev The top level app registry.
address public registry;
/// @dev Deployer's treasury
address public royaltyTreasury;
/// @dev The Forwarder for this app's modules.
address private _forwarder;
/// @dev Contract level metadata.
string private _contractURI;
/// @dev Events.
event ModuleUpdated(bytes32 indexed moduleId, address indexed module);
event TreasuryUpdated(address _newTreasury);
event ForwarderUpdated(address _newForwarder);
event FundsWithdrawn(address indexed to, address indexed currency, uint256 amount, uint256 fee);
event EtherReceived(address from, uint256 amount);
event RoyaltyTreasuryUpdated(
address indexed protocolControlAddress,
address indexed moduleAddress,
address treasury
);
/// @dev Check whether the caller is a protocol admin
modifier onlyProtocolAdmin() {
require(
hasRole(DEFAULT_ADMIN_ROLE, msg.sender),
"ProtocolControl: Only protocol admins can call this function."
);
_;
}
constructor(
address _registry,
address _admin,
string memory _uri
) {
// Set contract URI
_contractURI = _uri;
// Set top level ap registry
registry = _registry;
// Set default royalty treasury address
royaltyTreasury = address(this);
// Set access control roles
_setupRole(DEFAULT_ADMIN_ROLE, _admin);
}
/// @dev Lets the contract receive ether.
receive() external payable {
emit EtherReceived(msg.sender, msg.value);
}
/// @dev Initialize treasury payment royalty splitting pool
function setRoyaltyTreasury(address payable _treasury) external onlyProtocolAdmin {
require(_isRoyaltyTreasuryValid(_treasury), "ProtocolControl: provider shares too low.");
royaltyTreasury = _treasury;
emit RoyaltyTreasuryUpdated(address(this), address(0), _treasury);
}
/// @dev _treasury must be PaymentSplitter compatible interface.
function setModuleRoyaltyTreasury(address moduleAddress, address payable _treasury) external onlyProtocolAdmin {
require(_isRoyaltyTreasuryValid(_treasury), "ProtocolControl: provider shares too low.");
moduleRoyalty[moduleAddress] = _treasury;
emit RoyaltyTreasuryUpdated(address(this), moduleAddress, _treasury);
}
/// @dev validate to make sure protocol provider (the registry) gets enough fees.
function _isRoyaltyTreasuryValid(address payable _treasury) private view returns (bool) {
// Get `Royalty` and `Registry` instances
Royalty royalty = Royalty(_treasury);
Registry _registry = Registry(registry);
// Calculate the protocol provider's shares.
uint256 royaltyRegistryShares = royalty.shares(_registry.treasury());
uint256 royaltyTotalShares = royalty.totalShares();
uint256 registryCutBps = (royaltyRegistryShares * MAX_BPS) / royaltyTotalShares;
// 10 bps (0.10%) tolerance in case of precision loss
// making sure registry treasury gets at least the fee's worth of shares.
uint256 feeBpsTolerance = 10;
return registryCutBps >= (_registry.getFeeBps(address(this)) - feeBpsTolerance);
}
/// @dev Returns the Royalty payment splitter for a particular module.
function getRoyaltyTreasury(address moduleAddress) external view returns (address) {
address moduleRoyaltyTreasury = moduleRoyalty[moduleAddress];
if (moduleRoyaltyTreasury == address(0)) {
return royaltyTreasury;
}
return moduleRoyaltyTreasury;
}
/// @dev Lets a protocol admin add a module to the protocol.
function addModule(address _newModuleAddress, uint256 _moduleType)
external
onlyProtocolAdmin
returns (bytes32 moduleId)
{
// `moduleId` is collision resitant -- unique `_moduleType` and incrementing `numOfModuleType`
moduleId = keccak256(abi.encodePacked(numOfModuleType[_moduleType], _moduleType));
numOfModuleType[_moduleType] += 1;
modules[moduleId] = _newModuleAddress;
emit ModuleUpdated(moduleId, _newModuleAddress);
}
/// @dev Lets a protocol admin change the address of a module of the protocol.
function updateModule(bytes32 _moduleId, address _newModuleAddress) external onlyProtocolAdmin {
require(modules[_moduleId] != address(0), "ProtocolControl: a module with this ID does not exist.");
modules[_moduleId] = _newModuleAddress;
emit ModuleUpdated(_moduleId, _newModuleAddress);
}
/// @dev Sets contract URI for the contract-level metadata of the contract.
function setContractURI(string calldata _URI) external onlyProtocolAdmin {
_contractURI = _URI;
}
/// @dev Lets the admin set a new Forwarder address [NOTE: for off-chain convenience only.]
function setForwarder(address forwarder) external onlyProtocolAdmin {
_forwarder = forwarder;
emit ForwarderUpdated(forwarder);
}
/// @dev Returns the URI for the contract-level metadata of the contract.
function contractURI() public view returns (string memory) {
return _contractURI;
}
/// @dev Returns all addresses for a module type
function getAllModulesOfType(uint256 _moduleType) external view returns (address[] memory allModules) {
uint256 numOfModules = numOfModuleType[_moduleType];
allModules = new address[](numOfModules);
for (uint256 i = 0; i < numOfModules; i += 1) {
bytes32 moduleId = keccak256(abi.encodePacked(i, _moduleType));
allModules[i] = modules[moduleId];
}
}
/// @dev Returns the forwarder address stored on the contract.
function getForwarder() public view returns (address) {
if (_forwarder == address(0)) {
return Registry(registry).forwarder();
}
return _forwarder;
}
function withdrawFunds(address to, address currency) external onlyProtocolAdmin {
Registry _registry = Registry(registry);
IERC20 _currency = IERC20(currency);
address registryTreasury = _registry.treasury();
uint256 registryTreasuryFee = 0;
uint256 amount = 0;
if (currency == address(0)) {
amount = address(this).balance;
} else {
amount = _currency.balanceOf(address(this));
}
registryTreasuryFee = (amount * _registry.getFeeBps(address(this))) / MAX_BPS;
amount = amount - registryTreasuryFee;
if (currency == address(0)) {
(bool sent, ) = payable(to).call{ value: amount }("");
require(sent, "failed to withdraw funds");
(bool sentRegistry, ) = payable(registryTreasury).call{ value: registryTreasuryFee }("");
require(sentRegistry, "failed to withdraw funds to registry");
emit FundsWithdrawn(to, currency, amount, registryTreasuryFee);
} else {
require(_currency.transferFrom(_msgSender(), to, amount), "failed to transfer payment");
require(
_currency.transferFrom(_msgSender(), registryTreasury, registryTreasuryFee),
"failed to transfer payment to registry"
);
emit FundsWithdrawn(to, currency, amount, registryTreasuryFee);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC2981.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard
*/
interface IERC2981 is IERC165 {
/**
* @dev Called with the sale price to determine how much royalty is owed and to whom.
* @param tokenId - the NFT asset queried for royalty information
* @param salePrice - the sale price of the NFT asset specified by `tokenId`
* @return receiver - address of who should be sent the royalty payment
* @return royaltyAmount - the royalty payment amount for `salePrice`
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControlEnumerable.sol";
import "./AccessControl.sol";
import "../utils/structs/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view override returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_roleMembers[role].add(account);
}
/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
super._revokeRole(role, account);
_roleMembers[role].remove(account);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (metatx/ERC2771Context.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Context variant with ERC2771 support.
*/
abstract contract ERC2771Context is Context {
address private _trustedForwarder;
constructor(address trustedForwarder) {
_trustedForwarder = trustedForwarder;
}
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
return forwarder == _trustedForwarder;
}
function _msgSender() internal view virtual override returns (address sender) {
if (isTrustedForwarder(msg.sender)) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return super._msgSender();
}
}
function _msgData() internal view virtual override returns (bytes calldata) {
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return super._msgData();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Trees proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merklee tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
// Hash(current element of the proof + current computed hash)
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Multicall.sol)
pragma solidity ^0.8.0;
import "./Address.sol";
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* _Available since v4.1._
*/
abstract contract Multicall {
/**
* @dev Receives and executes a batch of function calls on this contract.
*/
function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = Address.functionDelegateCall(address(this), data[i]);
}
return results;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
interface IWETH {
function deposit() external payable;
function withdraw(uint256 amount) external;
function transfer(address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/IERC1155.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC1155 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-1155[EIP].
*
* _Available since v3.1._
*/
interface IERC1155 is IERC165 {
/**
* @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
*/
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
/**
* @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
* transfers.
*/
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
/**
* @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
* `approved`.
*/
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
/**
* @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
*
* If an {URI} event was emitted for `id`, the standard
* https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
* returned by {IERC1155MetadataURI-uri}.
*/
event URI(string value, uint256 indexed id);
/**
* @dev Returns the amount of tokens of token type `id` owned by `account`.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function balanceOf(address account, uint256 id) external view returns (uint256);
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
external
view
returns (uint256[] memory);
/**
* @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
*
* Emits an {ApprovalForAll} event.
*
* Requirements:
*
* - `operator` cannot be the caller.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address account, address operator) external view returns (bool);
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/IERC1155Receiver.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev _Available since v3.1._
*/
interface IERC1155Receiver is IERC165 {
/**
@dev Handles the receipt of a single ERC1155 token type. This function is
called at the end of a `safeTransferFrom` after the balance has been updated.
To accept the transfer, this must return
`bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
(i.e. 0xf23a6e61, or its own function selector).
@param operator The address which initiated the transfer (i.e. msg.sender)
@param from The address which previously owned the token
@param id The ID of the token being transferred
@param value The amount of tokens being transferred
@param data Additional data with no specified format
@return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
@dev Handles the receipt of a multiple ERC1155 token types. This function
is called at the end of a `safeBatchTransferFrom` after the balances have
been updated. To accept the transfer(s), this must return
`bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
(i.e. 0xbc197c81, or its own function selector).
@param operator The address which initiated the batch transfer (i.e. msg.sender)
@param from The address which previously owned the token
@param ids An array containing ids of each token being transferred (order and length must match values array)
@param values An array containing amounts of each token being transferred (order and length must match ids array)
@param data Additional data with no specified format
@return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)
pragma solidity ^0.8.0;
import "../IERC1155.sol";
/**
* @dev Interface of the optional ERC1155MetadataExtension interface, as defined
* in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
*
* _Available since v3.1._
*/
interface IERC1155MetadataURI is IERC1155 {
/**
* @dev Returns the URI for token type `id`.
*
* If the `\{id\}` substring is present in the URI, it must be replaced by
* clients with the actual token type ID.
*/
function uri(uint256 id) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
pragma solidity ^0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @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);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
// CREATE2 -- contract deployment.
import "@openzeppelin/contracts/utils/Create2.sol";
// Access Control
import "@openzeppelin/contracts/access/Ownable.sol";
// Protocol Components
import { IControlDeployer } from "./interfaces/IControlDeployer.sol";
import { Forwarder } from "./Forwarder.sol";
import { ProtocolControl } from "./ProtocolControl.sol";
contract Registry is Ownable {
uint256 public constant MAX_PROVIDER_FEE_BPS = 1000; // 10%
uint256 public defaultFeeBps = 500; // 5%
/// @dev service provider / admin treasury
address public treasury;
/// @dev `Forwarder` for meta-transacitons
address public forwarder;
/// @dev The Create2 `ProtocolControl` contract factory.
IControlDeployer public deployer;
struct ProtocolControls {
// E.g. if `latestVersion == 2`, there are 2 `ProtocolControl` contracts deployed.
uint256 latestVersion;
// Mapping from version => contract address.
mapping(uint256 => address) protocolControlAddress;
}
/// @dev Mapping from app deployer => versions + app addresses.
mapping(address => ProtocolControls) private _protocolControls;
/// @dev Mapping from app (protocol control) => protocol provider fees for the app.
mapping(address => uint256) private protocolControlFeeBps;
/// @dev Emitted when the treasury is updated.
event TreasuryUpdated(address newTreasury);
/// @dev Emitted when a new deployer is set.
event DeployerUpdated(address newDeployer);
/// @dev Emitted when the default protocol provider fees bps is updated.
event DefaultFeeBpsUpdated(uint256 defaultFeeBps);
/// @dev Emitted when the protocol provider fees bps for a particular `ProtocolControl` is updated.
event ProtocolControlFeeBpsUpdated(address indexed control, uint256 feeBps);
/// @dev Emitted when an instance of `ProtocolControl` is migrated to this registry.
event MigratedProtocolControl(address indexed deployer, uint256 indexed version, address indexed controlAddress);
/// @dev Emitted when an instance of `ProtocolControl` is deployed.
event NewProtocolControl(
address indexed deployer,
uint256 indexed version,
address indexed controlAddress,
address controlDeployer
);
constructor(
address _treasury,
address _forwarder,
address _deployer
) {
treasury = _treasury;
forwarder = _forwarder;
deployer = IControlDeployer(_deployer);
}
/// @dev Deploys `ProtocolControl` with `_msgSender()` as admin.
function deployProtocol(string memory uri) external {
// Get deployer
address caller = _msgSender();
// Get version for deployment
uint256 version = getNextVersion(caller);
// Deploy contract and get deployment address.
address controlAddress = deployer.deployControl(version, caller, uri);
_protocolControls[caller].protocolControlAddress[version] = controlAddress;
emit NewProtocolControl(caller, version, controlAddress, address(deployer));
}
/// @dev Returns the latest version of protocol control.
function getProtocolControlCount(address _deployer) external view returns (uint256) {
return _protocolControls[_deployer].latestVersion;
}
/// @dev Returns the protocol control address for the given version.
function getProtocolControl(address _deployer, uint256 index) external view returns (address) {
return _protocolControls[_deployer].protocolControlAddress[index];
}
/// @dev Lets the owner migrate `ProtocolControl` instances from a previous registry.
function addProtocolControl(address _deployer, address _protocolControl) external onlyOwner {
// Get version for protocolControl
uint256 version = getNextVersion(_deployer);
_protocolControls[_deployer].protocolControlAddress[version] = _protocolControl;
emit MigratedProtocolControl(_deployer, version, _protocolControl);
}
/// @dev Sets a new `ProtocolControl` deployer in case `ProtocolControl` is upgraded.
function setDeployer(address _newDeployer) external onlyOwner {
deployer = IControlDeployer(_newDeployer);
emit DeployerUpdated(_newDeployer);
}
/// @dev Sets a new protocol provider treasury address.
function setTreasury(address _newTreasury) external onlyOwner {
treasury = _newTreasury;
emit TreasuryUpdated(_newTreasury);
}
/// @dev Sets a new `defaultFeeBps` for protocol provider fees.
function setDefaultFeeBps(uint256 _newFeeBps) external onlyOwner {
require(_newFeeBps <= MAX_PROVIDER_FEE_BPS, "Registry: provider fee cannot be greater than 10%");
defaultFeeBps = _newFeeBps;
emit DefaultFeeBpsUpdated(_newFeeBps);
}
/// @dev Sets the protocol provider fee for a particular instance of `ProtocolControl`.
function setProtocolControlFeeBps(address protocolControl, uint256 _newFeeBps) external onlyOwner {
require(_newFeeBps <= MAX_PROVIDER_FEE_BPS, "Registry: provider fee cannot be greater than 10%");
protocolControlFeeBps[protocolControl] = _newFeeBps;
emit ProtocolControlFeeBpsUpdated(protocolControl, _newFeeBps);
}
/// @dev Returns the protocol provider fee for a particular instance of `ProtocolControl`.
function getFeeBps(address protocolControl) external view returns (uint256) {
uint256 fees = protocolControlFeeBps[protocolControl];
if (fees == 0) {
return defaultFeeBps;
}
return fees;
}
/// @dev Returns the next version of `ProtocolControl` for the given `_deployer`.
function getNextVersion(address _deployer) internal returns (uint256) {
// Increment version
_protocolControls[_deployer].latestVersion += 1;
return _protocolControls[_deployer].latestVersion;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
// Base
import "./openzeppelin-presets/finance/PaymentSplitter.sol";
// Meta transactions
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts/utils/Multicall.sol";
import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import { Registry } from "./Registry.sol";
import { ProtocolControl } from "./ProtocolControl.sol";
/**
* Royalty automatically adds protocol provider (the registry) of protocol control to the payees
* and shares that represent the fees.
*/
contract Royalty is PaymentSplitter, AccessControlEnumerable, ERC2771Context, Multicall {
/// @dev The protocol control center.
ProtocolControl private controlCenter;
/// @dev Contract level metadata.
string private _contractURI;
modifier onlyModuleAdmin() {
require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "only module admin role");
_;
}
/// @dev shares_ are scaled by 10,000 to prevent precision loss when including fees
constructor(
address payable _controlCenter,
address _trustedForwarder,
string memory _uri,
address[] memory payees,
uint256[] memory shares_
) PaymentSplitter() ERC2771Context(_trustedForwarder) {
require(payees.length == shares_.length, "Royalty: unequal number of payees and shares provided.");
require(payees.length > 0, "Royalty: no payees provided.");
// Set contract metadata
_contractURI = _uri;
// Set the protocol's control center.
controlCenter = ProtocolControl(_controlCenter);
Registry registry = Registry(controlCenter.registry());
uint256 feeBps = registry.getFeeBps(_controlCenter);
uint256 totalScaledShares = 0;
uint256 totalScaledSharesMinusFee = 0;
// Scaling the share, so we don't lose precision on division
for (uint256 i = 0; i < payees.length; i++) {
uint256 scaledShares = shares_[i] * 10000;
totalScaledShares += scaledShares;
uint256 feeFromScaledShares = (scaledShares * feeBps) / 10000;
uint256 scaledSharesMinusFee = scaledShares - feeFromScaledShares;
totalScaledSharesMinusFee += scaledSharesMinusFee;
// WARNING: Do not call _addPayee outside of this constructor.
_addPayee(payees[i], scaledSharesMinusFee);
}
// WARNING: Do not call _addPayee outside of this constructor.
uint256 totalFeeShares = totalScaledShares - totalScaledSharesMinusFee;
_addPayee(registry.treasury(), totalFeeShares);
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
}
/// @dev See ERC2771
function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) {
return ERC2771Context._msgSender();
}
/// @dev See ERC2771
function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
/// @dev Sets contract URI for the contract-level metadata of the contract.
function setContractURI(string calldata _URI) external onlyModuleAdmin {
_contractURI = _URI;
}
/// @dev Returns the URI for the contract-level metadata of the contract.
function contractURI() public view returns (string memory) {
return _contractURI;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role, _msgSender());
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastvalue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Update the index for the moved value
set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
return _values(set._inner);
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Create2.sol)
pragma solidity ^0.8.0;
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(
uint256 amount,
bytes32 salt,
bytes memory bytecode
) internal returns (address) {
address addr;
require(address(this).balance >= amount, "Create2: insufficient balance");
require(bytecode.length != 0, "Create2: bytecode length is zero");
assembly {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
}
require(addr != address(0), "Create2: Failed on deploy");
return addr;
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(
bytes32 salt,
bytes32 bytecodeHash,
address deployer
) internal pure returns (address) {
bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash));
return address(uint160(uint256(_data)));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @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() {
_transferOwnership(_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 {
_transferOwnership(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");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
interface IControlDeployer {
/// @dev Emitted when an instance of `ProtocolControl` is deployed.
event DeployedControl(address indexed registry, address indexed deployer, address indexed control);
/// @dev Deploys an instance of `ProtocolControl`
function deployControl(
uint256 nonce,
address deployer,
string memory uri
) external returns (address);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
/*
* @dev Minimal forwarder for GSNv2
*/
contract Forwarder is EIP712 {
using ECDSA for bytes32;
struct ForwardRequest {
address from;
address to;
uint256 value;
uint256 gas;
uint256 nonce;
bytes data;
}
bytes32 private constant TYPEHASH =
keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)");
mapping(address => uint256) private _nonces;
constructor() EIP712("GSNv2 Forwarder", "0.0.1") {}
function getNonce(address from) public view returns (uint256) {
return _nonces[from];
}
function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
address signer = _hashTypedDataV4(
keccak256(abi.encode(TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data)))
).recover(signature);
return _nonces[req.from] == req.nonce && signer == req.from;
}
function execute(ForwardRequest calldata req, bytes calldata signature)
public
payable
returns (bool, bytes memory)
{
require(verify(req, signature), "MinimalForwarder: signature does not match request");
_nonces[req.from] = req.nonce + 1;
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = req.to.call{ gas: req.gas, value: req.value }(
abi.encodePacked(req.data, req.from)
);
if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert("Transaction reverted silently");
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}
// Check gas: https://ronan.eth.link/blog/ethereum-gas-dangers/
assert(gasleft() > req.gas / 63);
return (success, result);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
} else if (error == RecoverError.InvalidSignatureV) {
revert("ECDSA: invalid signature 'v' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
// Check the signature length
// - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else if (signature.length == 64) {
bytes32 r;
bytes32 vs;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly {
r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40))
}
return tryRecover(hash, r, vs);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s;
uint8 v;
assembly {
s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
v := add(shr(255, vs), 27)
}
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
if (v != 27 && v != 28) {
return (address(0), RecoverError.InvalidSignatureV);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol)
pragma solidity ^0.8.0;
import "./ECDSA.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
* thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
* they need in their contracts using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* _Available since v3.4._
*/
abstract contract EIP712 {
/* solhint-disable var-name-mixedcase */
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;
address private immutable _CACHED_THIS;
bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
/* solhint-enable var-name-mixedcase */
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
bytes32 hashedName = keccak256(bytes(name));
bytes32 hashedVersion = keccak256(bytes(version));
bytes32 typeHash = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_CACHED_CHAIN_ID = block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
_CACHED_THIS = address(this);
_TYPE_HASH = typeHash;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
}
}
function _buildDomainSeparator(
bytes32 typeHash,
bytes32 nameHash,
bytes32 versionHash
) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Context.sol";
/**
* Changelog:
* 1. Remove add payees and shares in the constructor, so inherited class is responsible for adding.
* 2. Change _addPayee(...) visibility to internal. DANGEROUS: Make sure it is not called outside from constructor
* initialization.
* 3. Add distribute(...) to distribute all owed amount to all payees.
* 4. Add payeeCount() view to returns the number of payees.
*/
/**
* @title PaymentSplitter
* @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware
* that the Ether will be split in this way, since it is handled transparently by the contract.
*
* The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each
* account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim
* an amount proportional to the percentage of total shares they were assigned.
*
* `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the
* accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}
* function.
*
* NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and
* tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you
* to run tests before sending real value to this contract.
*/
contract PaymentSplitter is Context {
event PayeeAdded(address account, uint256 shares);
event PaymentReleased(address to, uint256 amount);
event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
event PaymentReceived(address from, uint256 amount);
uint256 private _totalShares;
uint256 private _totalReleased;
mapping(address => uint256) private _shares;
mapping(address => uint256) private _released;
address[] private _payees;
mapping(IERC20 => uint256) private _erc20TotalReleased;
mapping(IERC20 => mapping(address => uint256)) private _erc20Released;
/**
* @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at
* the matching position in the `shares` array.
*
* All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no
* duplicates in `payees`.
*/
constructor() payable {}
/**
* @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully
* reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the
* reliability of the events, and not the actual splitting of Ether.
*
* To learn more about this see the Solidity documentation for
* https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback
* functions].
*/
receive() external payable virtual {
emit PaymentReceived(_msgSender(), msg.value);
}
/**
* @dev Getter for the total shares held by payees.
*/
function totalShares() public view returns (uint256) {
return _totalShares;
}
/**
* @dev Getter for the total amount of Ether already released.
*/
function totalReleased() public view returns (uint256) {
return _totalReleased;
}
/**
* @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20
* contract.
*/
function totalReleased(IERC20 token) public view returns (uint256) {
return _erc20TotalReleased[token];
}
/**
* @dev Getter for the amount of shares held by an account.
*/
function shares(address account) public view returns (uint256) {
return _shares[account];
}
/**
* @dev Getter for the amount of Ether already released to a payee.
*/
function released(address account) public view returns (uint256) {
return _released[account];
}
/**
* @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an
* IERC20 contract.
*/
function released(IERC20 token, address account) public view returns (uint256) {
return _erc20Released[token][account];
}
/**
* @dev Getter for the address of the payee number `index`.
*/
function payee(uint256 index) public view returns (address) {
return _payees[index];
}
/**
* @dev Getter for getting the number of payee
*/
function payeeCount() public view returns (uint256) {
return _payees.length;
}
/**
* @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
* total shares and their previous withdrawals.
*/
function release(address payable account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");
uint256 totalReceived = address(this).balance + totalReleased();
uint256 payment = _pendingPayment(account, totalReceived, released(account));
require(payment != 0, "PaymentSplitter: account is not due payment");
_released[account] += payment;
_totalReleased += payment;
Address.sendValue(account, payment);
emit PaymentReleased(account, payment);
}
/**
* @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their
* percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20
* contract.
*/
function release(IERC20 token, address account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");
uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
uint256 payment = _pendingPayment(account, totalReceived, released(token, account));
require(payment != 0, "PaymentSplitter: account is not due payment");
_erc20Released[token][account] += payment;
_erc20TotalReleased[token] += payment;
SafeERC20.safeTransfer(token, account, payment);
emit ERC20PaymentReleased(token, account, payment);
}
/**
* @dev Release the owed amount of token to all of the payees.
*/
function distribute() public virtual {
for (uint256 i = 0; i < _payees.length; i++) {
release(payable(_payees[i]));
}
}
/**
* @dev Release owed amount of the `token` to all of the payees.
*/
function distribute(IERC20 token) public virtual {
for (uint256 i = 0; i < _payees.length; i++) {
release(token, _payees[i]);
}
}
/**
* @dev internal logic for computing the pending payment of an `account` given the token historical balances and
* already released amounts.
*/
function _pendingPayment(
address account,
uint256 totalReceived,
uint256 alreadyReleased
) private view returns (uint256) {
return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;
}
/**
* @dev Add a new payee to the contract.
* @param account The address of the payee to add.
* @param shares_ The number of shares owned by the payee.
*/
function _addPayee(address account, uint256 shares_) internal {
require(account != address(0), "PaymentSplitter: account is the zero address");
require(shares_ > 0, "PaymentSplitter: shares are 0");
require(_shares[account] == 0, "PaymentSplitter: account already has shares");
_payees.push(account);
_shares[account] = shares_;
_totalShares = _totalShares + shares_;
emit PayeeAdded(account, shares_);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol)
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165.sol";